#!/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()