mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
fdbd2b515f
Two distinct bugs were causing OPTIONS-black hard freezes after 12K-65K frames: 1. interrupts.c + traps.c: exception_jmpbuf is a single global. setjmp captures the running fiber, but longjmp may be requested from a different fiber via psx_exception_longjmp / psx_restore_state_escape during chain-handler dispatch. Cross-fiber longjmp restores RSP to the wrong stack while Win32 still tracks the calling fiber, corrupting fiber state until SwitchToFiber eventually hangs. Fix: track g_exception_owner_fiber at setjmp time. New deferred_exception_longjmp checks the current fiber; if it's not the owner, sets a pending flag and SwitchToFiber's back to the owner. psx_change_thread_fiber's post-SwitchToFiber path observes the flag and executes the longjmp on the correct stack. 2. gpu.c: gpu_read_gpustat fired gpu_vblank_tick() after 1000 polls, which invoked sdl_vblank_present (SDL_Delay -> Sleep) from inside an MMIO read code path. That meant recompiled MIPS spent wall time in Sleep deep inside the fiber call tree -- confusing to reason about and a contributor to fiber/longjmp interactions. Fix: synthetic VBLANK from a poll-storm only updates emulation state (LCF + I_STAT). The host-side render+sleep happens only at the proper cycle-paced VBLANK trigger in psx_check_interrupts. Also adds: dirty_block ring slice to the freeze auto-dump, Win32 main- thread stack capture for wedge_kind=1 hard freezes (SuspendThread + StackWalk64 + SymFromAddr), and dbghelp link. Validated: TombaRecomp psx-runtime ran 30+ minutes / 105K+ frames at OPTIONS with no hard freeze. Previous freezes consistently hit at 11900, 20493, and 64596 frames. Diagnostic tooling under tools/_options_session/ that produced this diagnosis is included. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
3.7 KiB
Python
97 lines
3.7 KiB
Python
"""Properly split the SIO byte stream into sector-sized groups.
|
|
|
|
Each sector read fills the buffer at 0xBE48..0xBEC7 (128 bytes). k0 resets
|
|
when a new sector starts. Group by addr-decrease, then compare each
|
|
captured sector against the real card1.mcd content.
|
|
"""
|
|
import json, os, sys
|
|
|
|
path = sys.argv[1] if len(sys.argv) > 1 else \
|
|
'F:/Projects/TombaRecomp/psx_freeze_dump_psx-runtime_1778800465.json'
|
|
|
|
with open(path) as f:
|
|
d = json.load(f)
|
|
|
|
STORE_PCS = {'0xBFC16024', '0x00006524'}
|
|
hits = [e for e in d['wtrace_all']
|
|
if e['pc'].upper() in {p.upper() for p in STORE_PCS}
|
|
and e.get('w', 1) == 1]
|
|
hits.sort(key=lambda e: int(e['seq']))
|
|
print(f'{len(hits)} byte-store events captured')
|
|
|
|
# Group by sector: a new sector starts when the destination address
|
|
# decreases (or, more reliably, when it equals the lowest seen base).
|
|
addrs = [int(e['addr'], 16) for e in hits]
|
|
base = min(addrs)
|
|
print(f'Buffer base: 0x{base:04X}')
|
|
|
|
sectors = []
|
|
cur = []
|
|
prev_addr = -1
|
|
for e in hits:
|
|
a = int(e['addr'], 16)
|
|
if a < prev_addr or (a == base and cur):
|
|
sectors.append(cur)
|
|
cur = []
|
|
cur.append(e)
|
|
prev_addr = a
|
|
if cur:
|
|
sectors.append(cur)
|
|
|
|
print(f'Sectors identified: {len(sectors)}')
|
|
for i, s in enumerate(sectors):
|
|
addrs = [int(e['addr'], 16) for e in s]
|
|
print(f' Sector {i}: {len(s)} bytes, addr range 0x{min(addrs):04X}..0x{max(addrs):04X}, '
|
|
f'frames {s[0]["frame"]}..{s[-1]["frame"]}')
|
|
|
|
def sector_bytes(s):
|
|
"""Reconstruct sector buffer from the stores (last write wins per addr)."""
|
|
buf = {}
|
|
for e in s:
|
|
buf[int(e['addr'], 16)] = int(e['new'], 16) & 0xFF
|
|
base = min(buf)
|
|
return bytes(buf[base + i] if (base + i) in buf else 0
|
|
for i in range(max(buf) - base + 1))
|
|
|
|
# Pull real card data for comparison.
|
|
with open('F:/Projects/TombaRecomp/saves/card1.mcd', 'rb') as fh:
|
|
card1 = fh.read()
|
|
|
|
# A PS1 memcard read typically starts at the directory (sector 0), then
|
|
# reads sectors 1..15. Each "frame" in the .mcd is 128 bytes.
|
|
for i, s in enumerate(sectors):
|
|
recv = sector_bytes(s)
|
|
print(f'\n=== Sector {i}: {len(recv)} bytes ===')
|
|
print('Received hex (full):')
|
|
for off in range(0, len(recv), 32):
|
|
print(f' {off:03x}: ' + ' '.join(f'{b:02x}' for b in recv[off:off+32]))
|
|
|
|
# Try to find a window in card1.mcd that this matches.
|
|
if len(recv) >= 8:
|
|
for fr in range(0, 16):
|
|
real = card1[fr*128:(fr+1)*128]
|
|
if recv[:len(recv)] == real[:len(recv)]:
|
|
print(f' EXACT match: card1.mcd frame {fr}')
|
|
break
|
|
# Try matching the prefix
|
|
common = 0
|
|
for j in range(min(len(recv), len(real))):
|
|
if recv[j] == real[j]:
|
|
common += 1
|
|
else:
|
|
break
|
|
if common >= 8:
|
|
print(f' Prefix match with card1.mcd frame {fr}: {common}/{len(recv)} bytes')
|
|
# If mismatch starts mid-stream, show the divergence
|
|
if common < len(recv):
|
|
div = common
|
|
print(f' Divergence at offset 0x{div:02X}:')
|
|
print(f' received[{div}..{div+16}]: ' + ' '.join(f'{b:02x}' for b in recv[div:div+16]))
|
|
print(f' real [{div}..{div+16}]: ' + ' '.join(f'{b:02x}' for b in real[div:div+16]))
|
|
# See if received[div:] matches real[div+1:] (shifted by 1)
|
|
if div+1 < len(real):
|
|
shifted = real[div+1:div+1+min(16, len(recv)-div)]
|
|
match_shift = recv[div:div+len(shifted)] == shifted
|
|
print(f' SHIFT-BY-1 match: {match_shift}')
|
|
break
|