Found the routines in the disassembly DB rather than guessing from data: sub_82447DF0 IDXD tag hash (lbz+extsb, modulus 0x00FFFFDF, magic 0x2101) sub_82447E70 IXUD tag hash (lhz, 64-bit, modulus 0xFFFFFF67 then 0x00FFFFDF) Both transcribed instruction-for-instruction into Python and Rust. IXUD SOLVED. It defeated every single-modulus search because it chains TWO exact moduli -- the loop reduces mod 2^32-153 in 64-bit arithmetic and only the result is folded mod 2^24-33. A polynomial mod M1 folded through M2 is not a polynomial mod anything, which is exactly why the gcd test returned 1. Verified independently: 86/86 record keys and 108,261/108,261 field tags in GP_MAIN_GAME_E.pak, and NoRecord -> 0x1c6d9c96. CORRECTION 1: tag_hash must SIGN-EXTEND each byte (extsb). My reconstruction used unsigned bytes and matched all 1.27M disc names -- every one is ASCII -- while disagreeing on ~90% of random inputs with a byte >= 0x80 (verified: 18096/20000). The disc could never have caught this; only the disassembly did. CORRECTION 2: name_hash's reduction is EXACT, not lossy. The module doc claimed the missing conditional subtract made it something other than %. rlwinm r6,r6, 9,23,31 is just hi>>23, and with RECIP = floor(2^55/M)+1 that is Granlund- Montgomery magic division -- 0 wrong at every quotient boundary across the full 32-bit domain. Retracted. cargo test -p sylpheed-formats --lib hash: 10/10.
136 lines
6.9 KiB
Bash
Executable File
136 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Boot Canary and drive it into the Stage 02 mission from save slot 01, then
|
|
# LEAVE IT RUNNING (unlike grab_tutorial.sh, which snapshots and exits) so a
|
|
# live control loop can attach to /dev/shm.
|
|
#
|
|
# Nav is the verified route: title -> LOAD GAME -> slot 01 -> YES -> READY ROOM
|
|
# -> TAKE OFF. With `--hangar` it stops in the HANGAR instead so a loadout can
|
|
# be picked first.
|
|
set -u
|
|
MODE="${1:-fly}"
|
|
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
|
SD="$(cd "$(dirname "$0")" && pwd)"
|
|
SHOTS=/sylph-home/re/shots
|
|
|
|
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
|
|
|
# DO NOT `setsid` THE DISPLAY OR THE EMULATOR. They used to be detached into
|
|
# their own sessions so they would outlive the shell that started them. What
|
|
# that actually bought was the opposite: a process nothing owns is a process
|
|
# nothing keeps alive, and both were being reaped a couple of minutes in — the
|
|
# long-standing "Xvfb and the emulator die on their own every few minutes" note.
|
|
# MEASURED WRONG 2026-08-10: a harness-tracked BACKGROUND task does not protect
|
|
# them either — the display was lost 11 s in, at the turn boundary. Run this
|
|
# whole script as ONE BLOCKING FOREGROUND call and leave Xvfb, openbox
|
|
# and xenia as its children: they then live exactly as long as the session does.
|
|
# `nohup` still shields them from a stray HUP; the exit-status wrapper means a
|
|
# death is reported with the server's own account instead of being inferred.
|
|
ensure_display(){
|
|
if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
|
|
rm -f "/tmp/.X${DISPLAY#:}-lock" 2>/dev/null || true
|
|
nohup bash -c 'Xvfb "$0" -screen 0 1280x720x24 -ac -nolisten tcp \
|
|
+extension GLX +extension RANDR >/tmp/xvfb98.log 2>&1
|
|
echo "$(date +%T) XVFB EXIT $? (128+N means signal N)" >>/tmp/xvfb-exit.log' \
|
|
"$DISPLAY" </dev/null >/dev/null 2>&1 &
|
|
for _ in $(seq 1 50); do xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 && break; sleep 0.2; done
|
|
nohup env DISPLAY="$DISPLAY" HOME=/sylph-home openbox </dev/null >/tmp/openbox98.log 2>&1 &
|
|
sleep 1
|
|
fi
|
|
xdpyinfo -display "$DISPLAY" >/dev/null 2>&1 || { echo "DISPLAY UNAVAILABLE"; exit 1; }
|
|
}
|
|
# pad.py, NOT `vgamepad`: that command no longer exists (the uinput pad was
|
|
# replaced by the --hid=file driver) and every call here failed silently, so the
|
|
# whole scripted route pressed nothing. See canary-scripted-input-traps.md.
|
|
step(){ python3 "$SD/pad.py" dpad "$1" 0.08; sleep 0.9; }
|
|
tap(){ python3 "$SD/pad.py" tap "$1" 0.3 || { echo "PAD PRESS FAILED"; exit 6; }; }
|
|
shot(){ screenshot "$SHOTS/$1" >/dev/null 2>&1; }
|
|
|
|
pkill -x xenia_canary 2>/dev/null; sleep 2
|
|
[ -n "$(alive)" ] && { kill -9 $(alive) 2>/dev/null; sleep 2; }
|
|
rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
|
|
ensure_display
|
|
|
|
cd /sylph-home/re
|
|
# Sign in a profile that EXISTS. A XUID with nothing behind it opens a sign-in
|
|
# dialog and IsUIActive() then swallows every keystroke for the whole run — 8.4
|
|
# million of them, measured. And KEEP the log: /dev/null is what hid that.
|
|
XUID="${SYLPH_XUID:-$(ls "${XENIA_CONTENT:-$HOME/.local/share/Xenia/content}" 2>/dev/null | head -1)}"
|
|
[ -n "$XUID" ] || { echo "NO PROFILE on disc"; exit 2; }
|
|
LOG="${LAUNCH_MISSION_LOG:-$SHOTS/launch_mission-canary.stdout}"
|
|
echo "signing in profile $XUID; emulator log: $LOG"
|
|
# EXTRA_FLAGS is for one-off diagnostics that should not become defaults --
|
|
# e.g. --log_high_frequency_kernel_calls=true, which is the only way to see
|
|
# KeWaitForSingleObject at all (it is declared kHighFrequency and is therefore
|
|
# silent even at LOG_LEVEL=3).
|
|
# shellcheck disable=SC2086
|
|
nohup run-canary --apu=sdl --log_mask="${LOG_MASK:-13}" --log_level="${LOG_LEVEL:-2}" \
|
|
--logged_profile_slot_0_xuid="$XUID" ${EXTRA_FLAGS:-} </dev/null >"$LOG" 2>&1 &
|
|
sleep 5
|
|
# The boot budget is a knob because one diagnostic blows straight through the
|
|
# default: --log_high_frequency_kernel_calls=true writes ~157 MB in ten minutes
|
|
# and the title had still not appeared, so skip_intro timed out and the run was
|
|
# scored a BOOT FAILED when it was only slow.
|
|
"$SD/skip_intro.sh" "${SKIP_INTRO_TIMEOUT:-600}" \
|
|
|| { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }
|
|
sleep 14 # main menu is not input-ready before this
|
|
|
|
step down # NEW GAME -> LOAD GAME
|
|
tap A; sleep 8 # save list, slot 01 preselected
|
|
# Open "Load game?" and answer YES, VERIFYING the dialog is actually up first.
|
|
# A fixed sleep here desynchronised the whole route: if the dialog had not
|
|
# opened yet, `step up` moved the SAVE CURSOR instead of selecting YES, and the
|
|
# blind `--tap A` below then oscillated the dialog for 300 s. See dialog_up.py.
|
|
opened=0
|
|
for _ in 1 2 3; do
|
|
tap A; sleep 4
|
|
shot "lm-loaddialog.png"
|
|
if python3 "$SD/dialog_up.py" "$SHOTS/lm-loaddialog.png" >/dev/null 2>&1; then
|
|
step up # cursor starts on NO
|
|
tap A; opened=1; break
|
|
fi
|
|
echo "--- load dialog not up yet, retrying"
|
|
done
|
|
[ $opened -eq 1 ] || { echo "LOAD DIALOG NEVER OPENED"; exit 5; }
|
|
# NOT a fixed sleep. LOAD -> READY ROOM took longer than 28 s in both runs on
|
|
# 2026-08-23, so the next press was eaten by the transition and the run ended up
|
|
# in OPTIONS (once) and BRIEFINGS (once); and a freshly restored profile inserts
|
|
# an extra "Auto-Save is active. OK?" dialog here, which the `--tap A` clears
|
|
# without the script having to know whether it is there.
|
|
# KNOWN LIMIT, stated rather than hidden: those blind A taps are safe only
|
|
# because everything before this point still lands. If the save-list presses
|
|
# ever drift and the run is still sitting on the MAIN MENU here, a blind A would
|
|
# start a NEW GAME. The real fix is to wait for each screen on the route rather
|
|
# than only for this one; only this transition has actually been measured to
|
|
# overrun, so only this one is waited for.
|
|
"$SD/wait_screen.sh" readyroom 300 --tap-if-dialog A \
|
|
|| { echo "NEVER REACHED READY ROOM"; exit 1; }
|
|
# ...and the READY ROOM is DRAWN before it is usable: it comes up with a
|
|
# "Preparing to Sortie" spinner and TAKE OFF greyed out, for tens of seconds.
|
|
# Pressing during that does nothing, and the press after it then lands on the
|
|
# wrong item. The two states are the same image to within 1.7 units of blue, so
|
|
# they need the label test rather than screen_id.py.
|
|
armed=0
|
|
for _ in $(seq 1 60); do
|
|
shot "lm-readyroom.png"
|
|
if python3 "$SD/take_off_armed.py" "$SHOTS/lm-readyroom.png" >/dev/null; then
|
|
armed=1; break
|
|
fi
|
|
sleep 3
|
|
done
|
|
[ $armed -eq 1 ] || { echo "TAKE OFF NEVER ARMED"; exit 1; }
|
|
|
|
if [ "$MODE" = "--hangar" ]; then
|
|
step down; step down # BRIEFINGS -> HANGAR
|
|
tap A; sleep 12
|
|
shot "lm-hangar.png"
|
|
echo "STOPPED IN HANGAR"; exit 0
|
|
fi
|
|
|
|
step up # BRIEFINGS -> TAKE OFF
|
|
tap A
|
|
# The launch cinematic + stage load + objective card is NOT a fixed 75 s under
|
|
# lavapipe; wait for the flight HUD itself.
|
|
"$SD/wait_flight.sh" 300 || { echo "NEVER REACHED FLIGHT"; exit 1; }
|
|
shot "lm-flight.png"
|
|
echo "IN FLIGHT (emulator left running)"
|