Files
psxrecomp/tools/_options_session/_diff_at_options.py
T
2026-05-17 11:29:26 -07:00

86 lines
3.1 KiB
Python

"""Compare runtime (4470) vs DuckStation (4371) at OPTIONS.
Handles scratchpad correctly on both sides.
Samples values over 3 seconds to spot static-vs-dynamic differences."""
import socket, json, time, struct, sys
def call(port, cmd, **kw):
try:
s = socket.create_connection(('127.0.0.1', port), timeout=3)
s.sendall((json.dumps({'id':1,'cmd':cmd, **kw})+'\n').encode())
buf = b''
while not buf.endswith(b'\n'):
c = s.recv(65536)
if not c: break
buf += c
s.close()
return json.loads(buf.decode())
except Exception as e:
return {'error': str(e)}
def read_word(port, addr):
"""Read a 32-bit LE word, handling scratchpad correctly on DuckStation."""
is_scratchpad = (addr & 0xFFFFE000) == 0x1F800000
if port == 4371 and is_scratchpad:
r = call(port, 'read_scratch', addr=f'0x{(addr & 0x3FF):04X}', len=4)
else:
r = call(port, 'read_ram', addr=f'0x{addr:08X}', len=4)
if 'error' in r or not r.get('ok'):
return None
return int.from_bytes(bytes.fromhex(r['hex']), 'little')
def ping(port):
return call(port, 'ping')
# Verify both alive
rt = ping(4470)
duck = ping(4371)
print(f"runtime: {rt}")
print(f"duckstation: {duck}")
print()
if 'error' in rt or 'error' in duck:
print("Both backends must be running & at OPTIONS. Re-launch any that errored.")
sys.exit(1)
# Static comparison — values that should NOT change frame to frame
print("=== STATIC values (these should match if both at same state) ===")
print(f"{'addr':>14} {'runtime':>12} {'duckstation':>12} {'match':>6}")
for addr, label in [
(0x1F8001D4, "scratchpad[0x1D4] state struct ptr"),
(0x80097520, "fn ptr table+0"),
(0x80097524, "fn ptr table+4 (state-0 helper)"),
(0x80097528, "fn ptr table+8"),
(0x8009752C, "fn ptr table+C"),
(0x80000108, "kernel TCB list ptr"),
(0x80000110, "kernel TCBH"),
(0x80000120, "kernel EvCB ptr"),
]:
rt_v = read_word(4470, addr)
du_v = read_word(4371, addr)
if rt_v is None or du_v is None:
print(f" 0x{addr:08X} err err ? {label}")
continue
m = "OK" if rt_v == du_v else "DIFF"
print(f" 0x{addr:08X} 0x{rt_v:08X} 0x{du_v:08X} {m:>6} {label}")
print()
# Dynamic comparison — sample 10 times, see which values CHANGE
print("=== DYNAMIC values (sample 10 frames; see who's animating) ===")
addrs = [
(0x8009B3D4, "state-0 helper struct +0x34"),
(0x8009B3A0, "state-0 helper struct base"),
]
for addr, label in addrs:
print(f"\n Address 0x{addr:08X} ({label}):")
rt_vals = []
du_vals = []
for i in range(10):
rt_vals.append(read_word(4470, addr))
du_vals.append(read_word(4371, addr))
time.sleep(0.05)
rt_unique = len(set(rt_vals))
du_unique = len(set(du_vals))
print(f" runtime values: {[hex(v) if v is not None else '?' for v in rt_vals]} ({rt_unique} unique --> {'FROZEN' if rt_unique == 1 else 'animating'})")
print(f" duck values: {[hex(v) if v is not None else '?' for v in du_vals]} ({du_unique} unique --> {'FROZEN' if du_unique == 1 else 'animating'})")