re(challenge): the cleared-stage mask is CONFIRMED on the running game
Booted the title and read the two gate words live:
0x828F40C0 = 0x00000002 word A
0x828F4814 = 0x00000000 word B
Word A = 2 = bit 1. The profile's save is Stage 02 "At Standby" -- stage 01
cleared -- so the mask is exactly one bit, at the index of the one cleared
stage, 1-BASED. Reproduced across two cold boots. That confirms against a known
progress state, on the real game:
- the singleton is the static object at 0x828F4070, as derived statically;
- word A is a cleared-stage bitmask (not achievements, not a stage number);
- bit index = stage id, 1-based, so TimeAttack's REQUIREMENT 16 means "clear
stage 16" -- the last story mission;
- word B is the challenge half and is 0 on a story-only profile.
New tools: gpoke.py (live guest-memory WRITE, companion to gmem.py, prints
before/after for every word), pad.py (drives the new --hid=file pad; replaces
vgamepad, which leaked to the host through /dev/uinput), challenge_probe.sh
(one blocking session: boot, wait for title, drive in, poke, screenshot).
Poking both words did NOT surface a challenge entry in EXTRAS -- and that menu
was built 26 s after the poke, so it is not staleness. Entering MISSION SELECT
then failed, but the log names the real cause and it is not the gate:
MmAllocatePhysicalMemoryEx could not satisfy a 128 MB request (parent free
30633/131072 pages), the guest threw a C++ exception, and Xenia surfaced its
generic "Disc Read Error". It is preceded by "BaseHeap::Release failed because
address is not a region start" -- a failed release leaking the range. Recorded
as an emulator heap problem, with the control run (same navigation, no poke)
named as the next step.
This commit is contained in:
70
tools/re-capture/pad.py
Executable file
70
tools/re-capture/pad.py
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Drive Xenia Canary's `--hid=file` pad — the container-safe controller.
|
||||
|
||||
Replaces the old `vgamepad` path, which created its device through `/dev/uinput`.
|
||||
Input devices are not namespaced, so that device registered with the HOST's input
|
||||
stack and every scripted press leaked to the user's desktop. This one writes a
|
||||
text file the emulator polls; nothing leaves the container.
|
||||
|
||||
pad.py set "press=A" set the pad state and leave it held
|
||||
pad.py clear release everything
|
||||
pad.py tap A [secs] press, hold `secs` (default 0.10), release
|
||||
pad.py dpad down [secs] one menu step (default 0.06 — longer
|
||||
auto-repeats and overshoots)
|
||||
pad.py hold "lt=255" secs hold an arbitrary state for `secs`
|
||||
|
||||
Buttons: UP DOWN LEFT RIGHT START BACK LS RS LB RB A B X Y.
|
||||
Path from $XENIA_PAD_FILE, default /tmp/xenia_pad.txt (matches --pad_file).
|
||||
|
||||
Menu conventions in this game: A = OK, B = Back, Y = Gallery/extra, X = Delete.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
PAD = os.environ.get("XENIA_PAD_FILE", "/tmp/xenia_pad.txt")
|
||||
|
||||
|
||||
def write(state: str):
|
||||
# The driver re-parses on any (mtime-ns, size) change, so a plain rewrite is
|
||||
# enough — but write through a temp + rename so a poll can never observe a
|
||||
# half-written file.
|
||||
tmp = PAD + ".tmp"
|
||||
with open(tmp, "w") as f:
|
||||
f.write(state)
|
||||
os.replace(tmp, PAD)
|
||||
|
||||
|
||||
def main():
|
||||
a = sys.argv[1:]
|
||||
if not a:
|
||||
print(__doc__)
|
||||
return 1
|
||||
cmd = a[0]
|
||||
if cmd == "set":
|
||||
write(a[1])
|
||||
elif cmd == "clear":
|
||||
write("")
|
||||
elif cmd == "tap":
|
||||
secs = float(a[2]) if len(a) > 2 else 0.10
|
||||
write(f"press={a[1]}")
|
||||
time.sleep(secs)
|
||||
write("")
|
||||
elif cmd == "dpad":
|
||||
secs = float(a[2]) if len(a) > 2 else 0.06
|
||||
write(f"press={a[1].upper()}")
|
||||
time.sleep(secs)
|
||||
write("")
|
||||
elif cmd == "hold":
|
||||
write(a[1])
|
||||
time.sleep(float(a[2]))
|
||||
write("")
|
||||
else:
|
||||
print(f"unknown command {cmd!r}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user