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.
76 lines
3.2 KiB
Bash
Executable File
76 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Wait until screen_id.py reports <class>, instead of sleeping a guessed number
|
|
# of seconds.
|
|
#
|
|
# Written because `launch_mission.sh` allowed a fixed 28 s for LOAD -> READY
|
|
# ROOM and neither run on 2026-08-23 was ready in time: the next press was
|
|
# swallowed by the transition, which left one run in OPTIONS and the next in
|
|
# BRIEFINGS, and `wait_flight.sh` then tapped A into the wrong screen for five
|
|
# minutes. See docs/re/dynamic-re-state-restore.md.
|
|
#
|
|
# `--tap BTN` presses BTN every 6 s *until the class is first seen* — that is
|
|
# what clears a dialog the caller cannot know about, such as the
|
|
# "Auto-Save is active. OK?" that a freshly restored profile puts up. Tapping
|
|
# STOPS at the first match, because on most screens the button that dismisses a
|
|
# dialog also leaves the screen you are waiting for.
|
|
#
|
|
# ⚠️ `--tap` is BLIND and that is dangerous on a YES/NO dialog: this game starts
|
|
# those with the cursor on **NO**, so on the LOAD GAME screen a blind A answers
|
|
# NO, falls back to the save list, and the next tap reopens the dialog — a
|
|
# stable oscillation that burned three consecutive 300 s boots as
|
|
# "NO readyroom" while the pad was working perfectly.
|
|
#
|
|
# Prefer **`--tap-if-dialog BTN`**: same thing, but only presses while a modal
|
|
# is actually up (dialog_up.py, which detects the dim the game draws behind a
|
|
# modal). With a dialog up, A is the OK/affirmative button and pressing it is
|
|
# safe; with no dialog up it does nothing, so it cannot oscillate.
|
|
#
|
|
# Two consecutive matches are required, so a single frame caught mid-fade does
|
|
# not count as arrival.
|
|
#
|
|
# Usage: wait_screen.sh <class> [timeout_s] [--tap BTN]
|
|
set -u
|
|
CLASS="${1:?usage: wait_screen.sh <class> [timeout_s] [--tap BTN]}"
|
|
TIMEOUT="${2:-180}"
|
|
TAP=""; TAP_ONLY_IF_DIALOG=0
|
|
case "${3:-}" in
|
|
--tap) TAP="${4:-A}" ;;
|
|
--tap-if-dialog) TAP="${4:-A}"; TAP_ONLY_IF_DIALOG=1 ;;
|
|
esac
|
|
export HOME=/sylph-home/re
|
|
DISP="${DISPLAY:-:98}"
|
|
SD="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Same liveness rule as skip_intro.sh/wait_flight.sh: a failed screenshot must
|
|
# not be indistinguishable from "the screen is not there yet".
|
|
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
|
|
|
DEADLINE=$(( SECONDS + TIMEOUT ))
|
|
hits=0; seen=0; last_tap=0
|
|
while [ $SECONDS -lt $DEADLINE ]; do
|
|
rm -f /tmp/ws.png
|
|
if ! screenshot /tmp/ws.png >/dev/null 2>&1 || [ ! -s /tmp/ws.png ]; then
|
|
xdpyinfo -display "$DISP" >/dev/null 2>&1 \
|
|
|| { echo "DISPLAY LOST at ${SECONDS}s"; exit 3; }
|
|
sleep 2; continue
|
|
fi
|
|
[ -n "$(alive)" ] || { echo "EMULATOR GONE at ${SECONDS}s"; exit 4; }
|
|
got="$(python3 "$SD/screen_id.py" /tmp/ws.png | awk '{print $1}')"
|
|
if [ "$got" = "$CLASS" ]; then
|
|
seen=1; hits=$(( hits + 1 ))
|
|
[ $hits -ge 2 ] && { echo "$CLASS at ${SECONDS}s"; exit 0; }
|
|
else
|
|
hits=0
|
|
if [ -n "$TAP" ] && [ $seen -eq 0 ] && [ $(( SECONDS - last_tap )) -ge 6 ]; then
|
|
if [ "$TAP_ONLY_IF_DIALOG" = 1 ] && ! python3 "$SD/dialog_up.py" /tmp/ws.png >/dev/null 2>&1; then
|
|
: # no modal up -- pressing would be blind
|
|
else
|
|
python3 "$SD/pad.py" tap "$TAP" 0.3
|
|
last_tap=$SECONDS
|
|
fi
|
|
fi
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "NO $CLASS within ${TIMEOUT}s (last seen: ${got:-none})"; exit 1
|