joeylib2/examples/spacetaxi/assets/depackSpeechRam.py

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()