joeylib2/examples/spacetaxi/assets/sidCapture.py

57 lines
2.1 KiB
Python

#!/usr/bin/env python3
"""Run the C64 Space Taxi music player and log every SID write per frame.
The player lives in the TITLE memory image at $CB00-$CFFF (its own relocated
copy; the $7500 copy is gameplay-side). Rather than hand-decode its byte
code, drive the real routines under cpu6502.py -- the same trick that
extracted the speech:
$CB00 select song ($CB01 = song number) and init (hooks the IRQ vector)
$CF52 the IRQ body: first frame calls start ($CC28), then ticks the three
voices via $CC6C, then JMPs to the KERNAL IRQ ($EA31) which is what
advances the jiffy clock $A2. We poke an RTS at $EA31 and bump $A2
ourselves, once per frame.
A song is over when all three voice stream pointers' high bytes are zero
(that is exactly what the game's play-and-wait loop at $44D5 spins on).
"""
import json, sys
sys.path.insert(0, "/home/scott/claude/joeylib/stuff/spacetaxi")
from cpu6502 import Cpu6502
RAM = bytearray(open(sys.argv[1], "rb").read()[:65536])
OUT = sys.argv[2]
MAX_FRAMES = 4000
class SidTrap(Cpu6502):
def __init__(self, ram):
super().__init__(ram)
self.log = []
self.frame = 0
def write8(self, addr, val):
a = addr & 0xFFFF
if 0xD400 <= a <= 0xD418:
self.log.append((self.frame, a - 0xD400, val & 0xFF))
super().write8(addr, val)
songs = {}
for song in range(0, 9):
ram = bytearray(RAM)
ram[0xEA31] = 0x60 # KERNAL IRQ tail -> RTS
cpu = SidTrap(ram)
cpu.call(0xCC00) # full reset first
ram[0xCB01] = song
cpu.call(0xCB00) # select + arm
for frame in range(MAX_FRAMES):
cpu.frame = frame
ram[0xA2] = (ram[0xA2] + 1) & 0xFF
cpu.call(0xCF52)
if ram[0xCB81] == 0 and ram[0xCB88] == 0 and ram[0xCB8F] == 0:
break
songs[song] = {"frames": frame + 1, "writes": cpu.log,
"ptr": ram[0xCB40 + song * 2] | (ram[0xCB41 + song * 2] << 8)}
print(f"song {song}: ptr=${songs[song]['ptr']:04X} frames={frame+1} "
f"({(frame+1)/50:.1f}s PAL) sidWrites={len(cpu.log)}")
json.dump(songs, open(OUT, "w"))