tools: two dead flags that made every emulator boot script fail silently

Trying to read a third paint order off the running game turned up two bugs in
the capture harness, both of which fail in ways that look like the game
misbehaving rather than the script being wrong.

1. `--audio` is not a cvar in this tree, and eight boot scripts passed it.
   Xenia calls ShowSimpleMessageBox from ParseLaunchArguments, BEFORE logging is
   initialised, so the symptom is a 10x10 window, no log, no guest memory and a
   dialog that blocks on XIfEvent forever - i.e. a hang deep in the emulator.
   run-canary`s own header documents this exact trap; the scripts predate it.
   Removed from all eight.

2. `vgamepad` no longer exists - the uinput pad was replaced by the --hid=file
   driver and pad.py - but skip_intro.sh still called it. The script runs
   without `set -e`, so the call failed silently and the title branch pressed
   nothing while still exiting 0. A caller was told "TITLE -> A" with the game
   sitting on the title screen. It now presses through pad.py and exits 6 if
   that fails.

The first bug is fixed and verified: the boot now reaches the title screen with
PRESS (A) BUTTON. The second is fixed but does NOT unblock the title - see the
next commit.
This commit is contained in:
Sylpheed RE agent
2026-08-19 07:48:16 +00:00
parent 71d9b73afe
commit 01580d294e
11 changed files with 122 additions and 10 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 KiB

View File

@@ -37,7 +37,7 @@ ensure_display(){
rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
ensure_display ensure_display
cd /sylph-home/re cd /sylph-home/re
nohup run-canary --audio --apu=sdl --log_mask=13 \ nohup run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 & --logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 &
sleep 5 sleep 5
"$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; } "$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }

View File

@@ -46,7 +46,7 @@ LOG="$HOME/canary.stdout"
# --- launch ------------------------------------------------------------------ # --- launch ------------------------------------------------------------------
say "launching canary (lavapipe, file pad) — POKE=$POKE tag=$TAG" say "launching canary (lavapipe, file pad) — POKE=$POKE tag=$TAG"
run-canary --audio --apu=sdl --log_mask=13 \ run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 \ --logged_profile_slot_0_xuid=E0300000EFBEA3D4 \
--hid=file --pad_file="$XENIA_PAD_FILE" & --hid=file --pad_file="$XENIA_PAD_FILE" &

View File

@@ -39,7 +39,7 @@ snap() { SHM=$(ls /dev/shm/xenia_memory_* 2>/dev/null | head -1); cp --sparse=al
pkill -9 -x xenia_canary 2>/dev/null; sleep 1 pkill -9 -x xenia_canary 2>/dev/null; sleep 1
rm -f /dev/shm/xenia_* 2>/dev/null; : > "$XENIA_PAD_FILE" rm -f /dev/shm/xenia_* 2>/dev/null; : > "$XENIA_PAD_FILE"
say "launching" say "launching"
run-canary --audio --apu=sdl --log_mask=13 \ run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 \ --logged_profile_slot_0_xuid=E0300000EFBEA3D4 \
--hid=file --pad_file="$XENIA_PAD_FILE" & --hid=file --pad_file="$XENIA_PAD_FILE" &
xsetroot -solid black 2>/dev/null || true xsetroot -solid black 2>/dev/null || true

View File

@@ -26,7 +26,7 @@ say "launching canary for stage $STAGE"
# CANARY_EXTRA lets a caller add cvars (e.g. --frame_probe_log=...) without # CANARY_EXTRA lets a caller add cvars (e.g. --frame_probe_log=...) without
# forking this script; unquoted on purpose so it can carry several. # forking this script; unquoted on purpose so it can carry several.
# shellcheck disable=SC2086 # shellcheck disable=SC2086
run-canary --audio --apu=sdl --log_mask=13 \ run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 \ --logged_profile_slot_0_xuid=E0300000EFBEA3D4 \
--hid=file --pad_file="$XENIA_PAD_FILE" ${CANARY_EXTRA:-} & --hid=file --pad_file="$XENIA_PAD_FILE" ${CANARY_EXTRA:-} &
# Xvfb keeps the previous instance's framebuffer until the new one draws. # Xvfb keeps the previous instance's framebuffer until the new one draws.

View File

@@ -42,7 +42,7 @@ rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
ensure_display ensure_display
cd /sylph-home/re cd /sylph-home/re
setsid nohup run-canary --audio --apu=sdl --log_mask=13 \ setsid nohup run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 & --logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 &
sleep 5 sleep 5
"$RC/skip_intro.sh" 600 || { echo "BOOT FAILED"; exit 1; } "$RC/skip_intro.sh" 600 || { echo "BOOT FAILED"; exit 1; }

View File

@@ -47,7 +47,7 @@ rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
ensure_display ensure_display
cd /sylph-home/re cd /sylph-home/re
nohup run-canary --audio --apu=sdl --log_mask=13 \ nohup run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 & --logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 &
sleep 5 sleep 5
"$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; } "$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }

View File

@@ -39,7 +39,7 @@ pkill -x xenia_canary 2>/dev/null; sleep 2
rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null
ensure_display ensure_display
cd /sylph-home/re cd /sylph-home/re
nohup run-canary --audio --apu=sdl --log_mask=13 \ nohup run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 & --logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 &
sleep 5 sleep 5
"$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; } "$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }

View File

@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""Dump the PAINT ORDER of every live UI screen object, off a running Canary.
A screen object (vtable `0x820b30b4`) holds its elements at `+0x08` as
`{ptr, count, capacity}` over 48-byte records in **declaration** order, and a
second list at `+0x30` — pointers to the same records, in the order the screen
is **painted**. `docs/re/structures/ui-screen-runtime.md` establishes both.
This walks every resident screen object and prints, per object, the permutation
and each element's pivot, which is what identifies the build in the file (a
declaration entry's pivot is exactly half the decoded sprite).
screen_children.py [/dev/shm/xenia_memory_XXX]
Point `$GMEM_FILE` at a snapshot to work offline:
cp --sparse=always /dev/shm/xenia_memory_* /tmp/snap.bin
"""
import os
import struct
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gmem
VTABLE = 0x820B30B4
ELEMENTS_AT = 0x08 # {ptr, count, capacity} -> 48-byte element records
CHILDREN_AT = 0x30 # {ptr, count, capacity} -> pointers into those records
RECORD = 48
def main():
path = gmem.mem_path()
size = os.path.getsize(path)
with open(path, "rb") as f:
def rd(va, n):
f.seek(gmem.va_to_off(va))
return f.read(n)
def u32(va):
return struct.unpack(">I", rd(va, 4))[0]
def f32(va):
return struct.unpack(">f", rd(va, 4))[0]
# every object whose word 0 is the screen vtable
objs = []
pat = struct.pack(">I", VTABLE)
CHUNK = 1 << 24
for start, end in gmem.extents(f.fileno(), size):
pos = start
while pos < end:
f.seek(pos)
buf = f.read(min(CHUNK, end - pos))
if not buf:
break
i = buf.find(pat)
while i != -1:
va = gmem.primary_va(pos + i)
if va is not None:
objs.append(va)
i = buf.find(pat, i + 1)
pos += len(buf)
print(f"{len(objs)} object(s) with vtable {VTABLE:#010x}")
for obj in objs:
try:
eptr, ecount = u32(obj + ELEMENTS_AT), u32(obj + ELEMENTS_AT + 4)
cptr, ccount = u32(obj + CHILDREN_AT), u32(obj + CHILDREN_AT + 4)
except ValueError:
continue
if not (0 < ecount <= 4096) or eptr == 0:
continue
# element record pointer -> declaration index
index_of, pivots = {}, []
ok = True
for i in range(ecount):
rec = eptr + i * RECORD
try:
index_of[u32(rec)] = i
pivots.append((f32(rec + 0x10), f32(rec + 0x14)))
except ValueError:
ok = False
break
if not ok:
continue
print(f"\n== object {obj:#010x}: {ecount} elements, {ccount} children")
print(" pivots: " + " ".join(f"{i}:({x:g},{y:g})" for i, (x, y) in enumerate(pivots)))
if not (0 < ccount <= 4096) or cptr == 0:
print(" (no child array)")
continue
order, unknown = [], 0
for i in range(ccount):
p = u32(cptr + i * 4)
if p in index_of:
order.append(index_of[p])
else:
order.append(None)
unknown += 1
print(f" paint order: {order}")
if unknown:
print(f" ({unknown} child pointer(s) did not match an element record)")
if __name__ == "__main__":
main()

View File

@@ -15,6 +15,13 @@ set -u
export HOME=/sylph-home/re export HOME=/sylph-home/re
DISP="${DISPLAY:-:98}" DISP="${DISPLAY:-:98}"
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; } alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
# Press through pad.py, NOT `vgamepad`. That command no longer exists — the
# uinput pad was replaced by the `--hid=file` driver — and because this script
# runs without `set -e`, calling it failed silently: the title branch pressed
# nothing and still `exit 0`, so a caller was told "TITLE -> A" while the game
# sat on the title screen forever. Fail loudly instead.
SD_SI="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
tapA(){ python3 "$SD_SI/pad.py" tap A 0.25 || { echo "PAD PRESS FAILED"; exit 6; }; }
# The X root keeps the DEAD session's last frame, so a fresh launch would be # The X root keeps the DEAD session's last frame, so a fresh launch would be
# detected as "already at the title". Blank it, and wait for the new window. # detected as "already at the title". Blank it, and wait for the new window.
xsetroot -solid black 2>/dev/null || true xsetroot -solid black 2>/dev/null || true
@@ -38,10 +45,10 @@ while [ $SECONDS -lt $deadline ]; do
d=${d:-0} d=${d:-0}
read -r r g b < <(convert /tmp/f2.png -format "%[fx:int(255*p{625,618}.r)] %[fx:int(255*p{625,618}.g)] %[fx:int(255*p{625,618}.b)]" info:) read -r r g b < <(convert /tmp/f2.png -format "%[fx:int(255*p{625,618}.r)] %[fx:int(255*p{625,618}.g)] %[fx:int(255*p{625,618}.b)]" info:)
if [ "$g" -gt 130 ] && [ $((g - r)) -gt 45 ] && [ $((g - b)) -gt 45 ]; then if [ "$g" -gt 130 ] && [ $((g - r)) -gt 45 ] && [ $((g - b)) -gt 45 ]; then
echo "TITLE at ${SECONDS}s -> A"; vgamepad tap A 250; exit 0 echo "TITLE at ${SECONDS}s -> A"; tapA; exit 0
fi fi
if [ "$d" -gt 1500 ]; then if [ "$d" -gt 1500 ]; then
echo "movie (rmse $d) at ${SECONDS}s -> skip A"; vgamepad tap A 250; sleep 3 echo "movie (rmse $d) at ${SECONDS}s -> skip A"; tapA; sleep 3
fi fi
sleep 1 sleep 1
done done

View File

@@ -36,7 +36,7 @@ if ! xdpyinfo -display "$DISPLAY" >/dev/null 2>&1; then
fi fi
cd /sylph-home/re cd /sylph-home/re
nohup run-canary --audio --apu=sdl --log_mask=13 \ nohup run-canary --apu=sdl --log_mask=13 \
--logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 & --logged_profile_slot_0_xuid=E0300000EFBEA3D4 </dev/null >/dev/null 2>&1 &
sleep 5 sleep 5
"$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; } "$SD/skip_intro.sh" 600 || { echo "BOOT FAILED (skip_intro exit $?)"; exit 1; }