mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
1751c6973b
Per ring save, the section CRCs (and small sections in hex) of the boot_state blob, marked live or replay. tools/rb_snap_audit_diff.py checks a seat's replay against its own live save and seats against each other, naming the first differing section and bytes. Off unless set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.6 KiB
Python
Executable File
70 lines
2.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Replay-determinism diff for PSX_RB_SNAP_AUDIT logs.
|
|
|
|
usage: rb_snap_audit_diff.py <dir> (dir holds snap-audit-p<N>.txt per seat)
|
|
|
|
1. Same seat, replay vs live: every R save at tick T must equal the seat's
|
|
live L save at T section by section (same snap + same rows => same machine).
|
|
2. Across seats: the last save of each tick (after any rollback) must agree.
|
|
Prints the first differing section/byte offsets; exit 1 on any difference.
|
|
"""
|
|
import collections, glob, re, sys
|
|
|
|
NAMES = {1: 'CPU', 2: 'RAM', 3: 'SPAD', 4: 'IRQ', 5: 'TIMER', 6: 'CLOCK', 7: 'GPU',
|
|
8: 'VRAM', 9: 'SPU', 0xa: 'SPURAM', 0xb: 'CDROM', 0xc: 'DMA', 0xd: 'SIO',
|
|
0xe: 'DIRTY', 0xf: 'MDEC', 0x10: 'ICACHE', 0x11: 'MODMEM', 0x13: 'GCS'}
|
|
|
|
|
|
def parse(path):
|
|
live, replay = {}, collections.defaultdict(list)
|
|
for line in open(path):
|
|
p = line.split()
|
|
if len(p) < 3:
|
|
continue
|
|
secs = {}
|
|
for x in p[2:]:
|
|
tag, rest = x.split(':', 1)
|
|
crc, _, hexd = rest.partition('=')
|
|
secs[int(tag, 16)] = (crc, hexd)
|
|
(live.__setitem__(int(p[1]), secs) if p[0] == 'L'
|
|
else replay[int(p[1])].append(secs))
|
|
return live, replay
|
|
|
|
|
|
def bytes_diff(a, b):
|
|
if not a or not b:
|
|
return []
|
|
x, y = bytes.fromhex(a), bytes.fromhex(b)
|
|
return [(i, x[i], y[i]) for i in range(min(len(x), len(y))) if x[i] != y[i]][:8]
|
|
|
|
|
|
def main(d):
|
|
seats = {int(re.search(r'p(-?\d+)', f)[1]): parse(f)
|
|
for f in glob.glob(f'{d}/snap-audit-p*.txt')}
|
|
bad = 0
|
|
for s, (live, rep) in sorted(seats.items()):
|
|
for t, rows in sorted(rep.items()):
|
|
for r in rows:
|
|
for tag, (crc, h) in r.items():
|
|
if t in live and tag in live[t] and live[t][tag][0] != crc:
|
|
bad += 1
|
|
if bad <= 10:
|
|
print(f'seat {s} tick {t} replay!=live {NAMES.get(tag, hex(tag))}',
|
|
bytes_diff(live[t][tag][1], h))
|
|
if len(seats) > 1:
|
|
common = set.intersection(*(set(v[0]) for v in seats.values()))
|
|
for t in sorted(common):
|
|
ref = min(seats)
|
|
for tag in seats[ref][0][t]:
|
|
vals = {s: v[0][t].get(tag, ('', ''))[0] for s, v in seats.items()}
|
|
if len(set(vals.values())) > 1:
|
|
bad += 1
|
|
if bad <= 20:
|
|
print(f'tick {t} seats differ {NAMES.get(tag, hex(tag))} {vals}')
|
|
print('differences:', bad)
|
|
return 1 if bad else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1]))
|