#!/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"