Chasing the title-screen crash into the emulator's own diagnostics turned it from a mystery into a controlled experiment, and knocked over two things this corpus said yesterday. Withdrawn #1: "the fault address 0x1_0000000C is a pointer with a stale high word". The crash dump prints r25 = 0x0000000C, clean. Xenia maps the guest's 4 GiB at host 0x1_00000000, so that IS guest address 12. The guest dereferenced the small integer 12. Withdrawn #2: "with --mem_watch=false the crash does not happen at all", which named the crash-oracle handoff's suspect #1 as measured. It was confounded — every --mem_watch=false run had also had a warm cache. Held cold, the throw happens with the probe off (2 437 crash dumps). mem_watch is eliminated for this crash. What it actually is: the access violation is the guest's own `throw` RETURNING, because this build logs guest C++ exceptions and continues rather than unwinding. So the event is the throw, and with --cache_throw_diag=true the guest names it: std::out_of_range, from the cache-manager flush, with a deque of 38 entries (37 distinct, one duplicated) against a 37-key map, every one of them absent from the flush's snapshot but present in the live map — the TOCTOU race the logger's own message describes. And the new, useful part: the trigger is the on-disc cache. Complete cache, no throw (2 runs). Directory moved aside or half-rebuilt, throw ~100 s into the boot (3 runs, including one that threw with NO access violation behind it — which is why crash dialogs are the wrong thing to count). A suspect in that bisection plan now costs a `mv` and two minutes instead of a mission.
7.3 KiB
Getting past the title screen in the container — three traps and one blocker
Status: ✅ CONFIRMED for the three traps (each reproduced, and two of them
fixed in the toolkit). 🔴 blocked on reaching the main menu: the guest reads
Ⓐ at the title, and the step behind Ⓐ either crashes it or stalls it.
Written down because each of these presents as "the pad is dead" or "the game ignores input", and each has a completely different cause.
1. F10 opens the emulator's menu bar, and that swallows every guest keystroke
F10 is the RE hotkey (ship capture, and now log_ui_draws). It is also the
toolkit key that opens the application menu bar, and it does both. With that menu
open, XamState::IsUIActive() is true, so XamInputGetKeystrokeEx returns
X_ERROR_SUCCESS with a zeroed keystroke before the driver is ever asked
(xam_input.cc). The guest polls, gets an empty event, and does nothing —
forever, and with no error anywhere.
This cost most of an iteration. Symptom: a run presses F10 to arm a capture,
then every later Ⓐ does nothing; the title sits there while the pad log happily
prints [file-pad] keystroke vk=5800 down. Screenshot
captures/title-with-emulator-menu-open.png
shows the giveaway — Open… / Open Recent / Install Content… / Exit over the
title art. It is easy to miss because the menu is at the far left of the window
and the game is still animating behind it.
Fix: after any F10, click once on the game surface to dismiss the menu.
Escape does not close it. ui_draw_capture.sh now does this.
2. The pad is fine — the driver was never the problem
Worth stating because the file-pad's own history (--hid=file, GetKeystroke
implemented specifically so "PRESS Ⓐ BUTTON" would work) makes it the natural
suspect. It delivers: the log shows vk=5800 down/up per tap, and Ⓐ during a
movie skips the movie, every time. When the title ignores Ⓐ, the pad is not why.
3. Ⓐ at the title needs a signed-in profile — and this container cannot type one in
With no profile, Ⓐ is handled: the guest calls XamShowSigninUI and Xenia
pops its Sign In dialog (captures/title-signin-dialog.png),
ProfileManager: Found 0 Profiles. That dialog is the proof the guest reads the
button.
The dialog cannot be completed from here: "Create Profile" opens an ImGui text
field for a gamertag, and synthetic X key events do not reach it. Tried, all
with the window focused (xdotool getwindowfocus = the game window):
xdotool type --window, xdotool type via XTEST, single xdotool key presses,
clicking the field first. The field stays empty; Create with an empty gamertag
is refused. Mouse clicks do work (they opened the dialog and the sub-panel), so
it is text entry specifically.
Xenia's own Profile ▸ Show Profile Menu panel does have a working Create Profile button, and mouse clicks reach it — but the gamertag it then asks for is
the same ImGui field, so the UI route dead-ends in the same place.
Fix, landed in Canary (auto/re-ui-draw-order): a --create_profile_if_none=<gamertag>
cvar that calls ProfileManager::CreateProfile(tag, autologin, default_xuid) at
startup when no profile exists. default_xuid is fixed at B13EBABEBABEBABE, so
a script can then pass --logged_profile_slot_0_xuid=B13EBABEBABEBABE and be
deterministic. Verified: ProfileManager: RE bootstrap profile 'SylphRE' -> created, then on the next boot Found 1 Profiles / Loaded SylphRE (GUID: B13EBABEBABEBABE) to slot 0, and the Profiles Menu panel shows the user
(captures/xenia-profile-panel-signed-in.png).
The profile persists on disk, so later runs need only
--logged_profile_slot_0_xuid=B13EBABEBABEBABE.
The blocker: what happens after Ⓐ
With the profile created and signed in — Xenia's own Profile ▸ Show Profile
Menu confirms it, User: SylphRE / XUID: B13EBABEBABEBABE / Assigned to slot: 1
(the panel counts from 1; the log says slot 0) — Ⓐ at the title still does not
advance the screen. Measured across four boots, including one that was allowed
to play the whole intro untouched, with holds of 10 s and bursts of five taps.
What is not the explanation, each checked:
- the emulator menu bar being open (trap 1) — the grabs are clean;
- the pad (trap 2) — Ⓐ skips movies in the same runs;
- the profile being absent (trap 3) — it is present, signed in, and the Sign In dialog no longer appears;
- an ImGui window being open — but note this generalises trap 1: any Xenia UI
window makes
IsUIActive()true, the Profiles Menu included, so a panel left open silently disables the guest's input for as long as it is up.
One observation that did not reproduce, recorded rather than dropped. In the
run where the Profiles Menu had just been closed, a single Ⓐ crashed the
guest — PC 0x824578A0, guest thread 6, distinct from the boot-path crash
below. Two later attempts to reproduce it (open the panel, close it, press Ⓐ)
produced neither a crash nor a transition. So it is one data point that Ⓐ can
reach code the title does not otherwise run, and nothing more.
The reproducible crash is the cache flush, and it is not what it looked like.
With the default --mem_watch=true, in two runs, the guest died with an access
violation at PC 0x82307128 right after RtlRaiseException(E06D7363). Chasing
that produced a correction and a cheap trigger, both in
title-crash-stl-tree.md:
- the access violation is a consequence of the guest's own
throwreturning (this build does not unwind guest EH), so the event to study is the throw; - the throw is
std::out_of_rangefrom the game's cache-manager flush, and an incomplete on-disc cache triggers it ~100 s into a boot; - the observation that started this — "crash with
--mem_watch=true, none with--mem_watch=false" — was confounded by cache warmth, and is withdrawn: cold cache with the probe off throws just the same.
The first of those next steps is done, and it moves the crash out of this
entry into its own: 0x82307128 is inside an MSVC std::map/set erase —
the function carries the string 'invalid map/set<T> iterator' and the node
offsets to prove it — which makes this a 40-second reproduction of the crash the
Canary handoff blames on the mem_watch probe. See
title-crash-stl-tree.md.
Next step, for whoever picks this up. What is left:
- dump the tree nodes from guest memory at the throw, to separate "the guest's
tree is corrupt" from "the emulator left a stale high word in a register" —
the fault address
0x1_0000000Cis consistent with either; - the content exports this title imports —
XamContentCreateEnumerator,XamContentGetDeviceState,XamUserReadProfileSettings— logged around the press, to see whether the guest is handed a bad enumerator or a device state it does not accept.\aab216c3\5\…is the content directory in question.
What this blocks: every dynamic experiment past the title — a second UI screen's paint order, the main menu, mission captures. Everything up to and including the title screen is unaffected, which is why the paint-order result above could still be measured.