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.
127 lines
7.3 KiB
Markdown
127 lines
7.3 KiB
Markdown
# 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`](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`](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`](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`](title-crash-stl-tree.md):
|
|
|
|
* the access violation is a **consequence** of the guest's own `throw` returning
|
|
(this build does not unwind guest EH), so the event to study is the throw;
|
|
* the throw is `std::out_of_range` from 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`](title-crash-stl-tree.md).
|
|
|
|
**Next step, for whoever picks this up.** What is left:
|
|
|
|
1. 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_0000000C` is consistent with either;
|
|
2. 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.
|