re(challenge): the part id is never persisted -- differential search says stack only
No literal 26 exists anywhere, so the GamePart id is computed. That does not stop it being found: the id is KNOWN at each screen from the GamePart table (EXTRAS = 5, MISSION SELECT = 7), so snapshot both and intersect. New tool diff_words.py does the classic differential search over the sparse guest image, and find_partslot.sh drives the two screens and runs it. Result: 171 MB scanned, exactly 4 addresses read 5 then 7 -- 0x708FFBEC, 0x708FFCBC, 0x708FFDAC, 0x708FFE20 -- and all four are guest STACK (the same run's log puts thread stacks at 0x709...). So the requested part id exists only as a stack argument in flight; there is no persistent field, which is consistent with finding no literal store, and means there is nothing stable to poke. That closes the last memory-and-menu route to the challenge missions. Reaching them needs either the genuine in-game unlock (an in-mission attainment, per AVSCRIPT_COMMAND_ATTAINMENT_CHALLENGE_MISSION_CARGO_SCORE) or an emulator-side hook that forces the transition -- a code change, not a poke. diff_words.py is worth keeping well beyond this question: it locates any field whose address is unknown but whose value is known at two moments.
This commit is contained in:
69
tools/re-capture/find_partslot.sh
Executable file
69
tools/re-capture/find_partslot.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Locate the GamePart "slot" in guest memory by DIFFERENTIAL SEARCH.
|
||||
#
|
||||
# sub_821749C0 creates a part from slot+12 (the requested part id) and stores the
|
||||
# result at *(slot+16). The id is computed from a menu selection -- no literal 26
|
||||
# exists anywhere -- so it cannot be found statically. But the id is KNOWN at each
|
||||
# screen from the GamePart table (docs/re/challenge-mission-gate.md section 3):
|
||||
#
|
||||
# GP_EXTRAS = 5 GP_MISSION_SELECT = 7
|
||||
#
|
||||
# so the slot is simply the word that reads 5 on the EXTRAS screen and 7 on the
|
||||
# MISSION SELECT screen. Snapshot both, intersect, and the candidates are few.
|
||||
#
|
||||
# Usage: find_partslot.sh [boot_timeout_s]
|
||||
set -u
|
||||
export HOME=/sylph-home/re DISPLAY=:99 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" "$@"; }
|
||||
BOOT_TIMEOUT="${1:-400}"
|
||||
say() { echo "[$(date +%H:%M:%S)] $*"; }
|
||||
px() { convert /tmp/nav-probe.png -format \
|
||||
"%[fx:int(255*p{$1}.r)] %[fx:int(255*p{$1}.g)] %[fx:int(255*p{$1}.b)]" info: 2>/dev/null; }
|
||||
at_menu() {
|
||||
screenshot /tmp/nav-probe.png >/dev/null 2>&1 || return 1
|
||||
read -r r g b < <(px "648,221"); [ -n "${r:-}" ] || return 1
|
||||
[ "$r" -gt 230 ] && [ "$g" -gt 230 ] && [ "$b" -gt 230 ] || return 1
|
||||
read -r r2 g2 b2 < <(px "560,300"); [ -n "${r2:-}" ] || return 1
|
||||
[ "$r2" -lt 120 ] && [ "$b2" -gt "$r2" ]
|
||||
}
|
||||
at_title() {
|
||||
screenshot /tmp/nav-probe.png >/dev/null 2>&1 || return 1
|
||||
read -r r g b < <(px "625,618"); [ -n "${g:-}" ] || return 1
|
||||
[ "$g" -gt 130 ] && [ $((g - r)) -gt 45 ] && [ $((g - b)) -gt 45 ]
|
||||
}
|
||||
snap() { SHM=$(ls /dev/shm/xenia_memory_* 2>/dev/null | head -1); cp --sparse=always "$SHM" "$1"; }
|
||||
|
||||
pkill -9 -x xenia_canary 2>/dev/null; sleep 1
|
||||
rm -f /dev/shm/xenia_* 2>/dev/null; : > "$XENIA_PAD_FILE"
|
||||
say "launching"
|
||||
run-canary --audio --apu=sdl --log_mask=13 \
|
||||
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 \
|
||||
--hid=file --pad_file="$XENIA_PAD_FILE" &
|
||||
xsetroot -solid black 2>/dev/null || true
|
||||
|
||||
MENU=0
|
||||
for i in $(seq 1 "$BOOT_TIMEOUT"); do
|
||||
[ "$i" -lt 40 ] && { sleep 1; continue; }
|
||||
if at_menu && sleep 1 && at_menu; then say "MAIN MENU after ${i}s"; MENU=1; break; fi
|
||||
at_title && { say " title — tapping A"; pad tap A 0.25; sleep 2; }
|
||||
sleep 1
|
||||
done
|
||||
[ "$MENU" = 1 ] || { say "TIMEOUT"; exit 1; }
|
||||
|
||||
say "-> EXTRAS (GamePart 5)"
|
||||
for _ in 1 2 3 4; do pad dpad down 0.06; sleep 0.35; done
|
||||
sleep 0.5; pad tap A 0.15; sleep 4
|
||||
screenshot "$HOME/shots/slot-01-extras.png" >/dev/null
|
||||
snap /sylph-home/re/snap-extras.bin; say "snapshot A (EXTRAS) taken"
|
||||
|
||||
say "-> MISSION SELECT (GamePart 7)"
|
||||
pad tap A 0.15; sleep 5
|
||||
screenshot "$HOME/shots/slot-02-missionselect.png" >/dev/null
|
||||
snap /sylph-home/re/snap-msel.bin; say "snapshot B (MISSION SELECT) taken"
|
||||
|
||||
say "intersecting: word == 5 in A and == 7 in B"
|
||||
python3 "$HERE/diff_words.py" /sylph-home/re/snap-extras.bin 5 /sylph-home/re/snap-msel.bin 7
|
||||
say "done — emulator left running"
|
||||
Reference in New Issue
Block a user