This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/tools/re-capture/phase_probe.py

63 lines
2.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Watch the mission's phase ordinal change, live.
The static work (docs/re/mission-phase-advance.md) found two 1/2/3 fields:
[ScriptMission+40] the counter itself; incremented at 0x82260A00
[*(0x828F35F8) + 236] a mirror, written by CScriptInterpreter::
ChangePhase (opcode 995) after the fact
The mirror is the one reachable without a debugger: 0x828F35F8 is a static
singleton pointer in guest memory, so this is two reads through /dev/shm and
needs no gdb -- which matters, because booting under gdb costs ~300s.
This is the first DIRECT observation of a phase advance. Everything about phases
so far is either static (route names, disassembly) or inferred; nobody has
watched the number change.
Usage: phase_probe.py [secs] [every_s]
"""
import sys, time
sys.path.insert(0, __file__.rsplit('/', 1)[0])
import gmem
SINGLETON_PTR = 0x828F35F8 # static pointer to the mission-manager singleton
PHASE_OFF = 236 # the mirror ChangePhase writes
_FD = None
def _fd():
"""gmem exposes va_to_off/mem_path but no reader, so open the image once."""
global _FD
if _FD is None:
_FD = open(gmem.mem_path(), 'rb', buffering=0)
return _FD
def u32(va):
off = gmem.va_to_off(va)
if off is None:
return None
f = _fd()
f.seek(off)
b = f.read(4)
return int.from_bytes(b, 'big') if len(b) == 4 else None
def sample():
base = u32(SINGLETON_PTR)
if not base or not (0x10000 <= base < 0xFFFFFFFF):
return None, base
return u32(base + PHASE_OFF), base
if __name__ == '__main__':
secs = float(sys.argv[1]) if len(sys.argv) > 1 else 600
every = float(sys.argv[2]) if len(sys.argv) > 2 else 5
t0 = time.time(); last = object()
print('singleton ptr 0x%08X, phase at +%d' % (SINGLETON_PTR, PHASE_OFF), flush=True)
while time.time() - t0 < secs:
ph, base = sample()
if ph != last:
print(' [%6.1fs] singleton=%s phase=%s' % (
time.time() - t0, ('0x%08X' % base) if base else base, ph), flush=True)
last = ph
time.sleep(every)