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.
88 lines
3.3 KiB
Bash
Executable File
88 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Probe the challenge-mission gate on the running game.
|
|
#
|
|
# The gate (docs/re/challenge-mission-gate.md): GamePart_ChallengeMission tests a
|
|
# CLEARED-STAGE bitmask on a static singleton at guest 0x828F4070 —
|
|
# word A 0x828F40C0 bit = stage id, for ids < 24 (story 1-16, tutorial 18-23)
|
|
# word B 0x828F4814 bit = stage id - 24 (challenge 24-29)
|
|
# so setting every bit should make all six challenge missions available without
|
|
# playing the campaign. This boots, reaches the title, pokes both words, and
|
|
# screenshots the menus so the result can be seen.
|
|
#
|
|
# Runs as ONE blocking foreground call on purpose: setsid'd processes are reaped
|
|
# at turn boundaries, so a session split across calls loses its emulator.
|
|
#
|
|
# Usage: challenge_probe.sh [boot_timeout_s]
|
|
set -u
|
|
export HOME=/sylph-home/re
|
|
export DISPLAY=:99
|
|
export SDL_AUDIODRIVER=dummy
|
|
export XENIA_PAD_FILE=/tmp/xenia_pad.txt
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
pad() { python3 "$HERE/pad.py" "$@"; }
|
|
poke() { python3 "$HERE/gpoke.py" "$@"; }
|
|
SHOTS="$HOME/shots"
|
|
BOOT_TIMEOUT="${1:-420}"
|
|
mkdir -p "$SHOTS"
|
|
|
|
say() { echo "[$(date +%H:%M:%S)] $*"; }
|
|
|
|
# --- clean slate -------------------------------------------------------------
|
|
pkill -9 -x xenia_canary 2>/dev/null
|
|
sleep 1
|
|
rm -f /dev/shm/xenia_* 2>/dev/null
|
|
: > "$XENIA_PAD_FILE"
|
|
|
|
# --- launch ------------------------------------------------------------------
|
|
say "launching canary (lavapipe, file pad)"
|
|
run-canary --audio --apu=sdl --log_mask=13 \
|
|
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 \
|
|
--hid=file --pad_file="$XENIA_PAD_FILE" &
|
|
CANARY_PID=$!
|
|
trap 'pkill -9 -x xenia_canary 2>/dev/null' EXIT
|
|
|
|
# --- wait for the title ------------------------------------------------------
|
|
# Oracle: the green "PRESS (A) BUTTON" glyph at (625,618).
|
|
say "waiting for the title (up to ${BOOT_TIMEOUT}s)"
|
|
TITLE=0
|
|
for _ in $(seq 1 "$BOOT_TIMEOUT"); do
|
|
if screenshot /tmp/title-probe.png >/dev/null 2>&1; then
|
|
read -r r g b < <(convert /tmp/title-probe.png -format \
|
|
"%[fx:int(255*p{625,618}.r)] %[fx:int(255*p{625,618}.g)] %[fx:int(255*p{625,618}.b)]" info: 2>/dev/null)
|
|
if [ -n "${g:-}" ] && [ "$g" -gt 130 ] && [ $((g - r)) -gt 45 ] && [ $((g - b)) -gt 45 ]; then
|
|
say "TITLE detected (rgb $r,$g,$b)"
|
|
TITLE=1
|
|
break
|
|
fi
|
|
fi
|
|
sleep 1
|
|
done
|
|
[ "$TITLE" = 1 ] || { say "TIMEOUT: no title"; screenshot "$SHOTS/chal-00-timeout.png"; exit 1; }
|
|
|
|
# --- prove the file pad works before trusting anything else -------------------
|
|
say "pad check: tapping A at the title"
|
|
pad tap A 0.25
|
|
sleep 3
|
|
screenshot "$SHOTS/chal-01-after-A.png" >/dev/null
|
|
say "file-pad log lines so far:"
|
|
grep -c 'file-pad' "$HOME/canary.stdout" 2>/dev/null || true
|
|
grep 'file-pad' "$HOME/canary.stdout" 2>/dev/null | tail -3
|
|
|
|
# --- read the gate words BEFORE poking ---------------------------------------
|
|
say "gate words before poke:"
|
|
poke r32 0x828F40C0 1
|
|
poke r32 0x828F4814 1
|
|
|
|
# --- poke --------------------------------------------------------------------
|
|
say "poking word A = 0xFFFFFFFF, word B = 0x3F"
|
|
poke w32 0x828F40C0 0xFFFFFFFF
|
|
poke w32 0x828F4814 0x0000003F
|
|
|
|
# --- look at the menu --------------------------------------------------------
|
|
sleep 2
|
|
screenshot "$SHOTS/chal-02-mainmenu.png" >/dev/null
|
|
say "screenshots in $SHOTS: chal-01-after-A.png chal-02-mainmenu.png"
|
|
say "done — leaving the emulator running for follow-up"
|
|
trap - EXIT
|