81 lines
2.9 KiB
Python
Executable file
81 lines
2.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Run Space Taxi's depacker to produce a FLAT 64K RAM image.
|
|
|
|
Why this exists: the disk's "SPACE TAXI+" is a crunched single file (it
|
|
matches the running game's memory in 0.6% of bytes), so the speech
|
|
samples cannot be read out of it statically. And a VICE memory dump is
|
|
no good either -- it captures the banked-IN view, so $A000-$BFFF comes
|
|
back as BASIC ROM and 13 of the 15 utterances are missing. Every
|
|
mem0000*.bin in this tree has that problem.
|
|
|
|
The way out needs no emulator install: stuff/spacetaxi/cpu6502.py is a
|
|
plain 6502 core over a flat 64K bytearray with no banking and no I/O,
|
|
which is all a depacker needs. Load the .prg, run from the BASIC SYS
|
|
entry, and the depacked image IS the RAM -- $A000-$BFFF included.
|
|
|
|
Feed the result to extractSpeech.py.
|
|
|
|
Usage: depackSpeechRam.py [spaceTaxiPlus.prg] [out.bin]
|
|
Defaults resolve inside stuff/spacetaxi/ (gitignored research
|
|
area, same as genC64Data.py's inputs).
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
STUFF = os.path.join(HERE, "..", "..", "..", "stuff", "spacetaxi")
|
|
|
|
# The BASIC stub is `0B08 D307 9E "2059" 00` -- SYS 2059.
|
|
ENTRY = 0x080B
|
|
MAX_STEPS = 60000000
|
|
CHECK_MASK = 0xFFFF # how often to test for completion
|
|
|
|
# Completion test: the $9B00 index table is populated and the first
|
|
# utterance's $FE lead-in is in place.
|
|
TABLE_ADDR = 0x9B00
|
|
FIRST_UTT = 0x9B40
|
|
|
|
|
|
def main():
|
|
prgPath = sys.argv[1] if len(sys.argv) > 1 else os.path.join(STUFF, "extract", "SPACE TAXI+.prg")
|
|
outPath = sys.argv[2] if len(sys.argv) > 2 else os.path.join(STUFF, "staxi-ram.bin")
|
|
|
|
sys.path.insert(0, STUFF)
|
|
try:
|
|
from cpu6502 import Cpu6502
|
|
except ImportError:
|
|
sys.exit("depackSpeechRam: need cpu6502.py in %s" % STUFF)
|
|
|
|
prg = open(prgPath, "rb").read()
|
|
load = prg[0] | (prg[1] << 8)
|
|
ram = bytearray(65536)
|
|
ram[load:load + len(prg) - 2] = prg[2:]
|
|
print("loaded %s at $%04X..$%04X" % (os.path.basename(prgPath), load, load + len(prg) - 3))
|
|
|
|
cpu = Cpu6502(ram)
|
|
cpu.pc = ENTRY
|
|
steps = 0
|
|
done = False
|
|
try:
|
|
while steps < MAX_STEPS:
|
|
cpu.step()
|
|
steps += 1
|
|
if (steps & CHECK_MASK) == 0:
|
|
if ram[TABLE_ADDR] != 0 and ram[FIRST_UTT] == 0xFE:
|
|
done = True
|
|
break
|
|
except Exception as exc:
|
|
# A depacker that runs off into I/O or an undocumented opcode
|
|
# still usually gets the data down first, so report and carry on
|
|
# to the write rather than throwing the work away.
|
|
print("6502 stopped after %d steps at PC=$%04X: %s" % (steps, cpu.pc, exc))
|
|
|
|
open(outPath, "wb").write(bytes(ram))
|
|
print("%d steps, depack %s -> %s" % (steps, "detected" if done else "NOT detected", outPath))
|
|
if not done:
|
|
print("WARNING: completion check never fired; the image may be partial.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|