Source changes (dormant parity infra, retained from iterate 2.AI/2.AO): - xenia-kernel/exports.rs: nt_create_event manual_reset polarity + related event wiring - xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps (.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as regenerable local artifacts — see memory + HANDOFF for the running findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.5 KiB
Phase C+9 — XamContentCreateEnumerator investigation
Phase A oracle (predecessor C+8 baseline)
canary tid=6 → ours tid=1 main chain, tid_event_idx = 102197:
canary: kernel.return XamContentCreateEnumerator return_value=1317 status=0x00000525 side_effects=[]
ours: kernel.return XamContentCreateEnumerator return_value=0 status=0x00000000 side_effects=[]
args and args_resolved are empty per the current Phase A emitter
scope (kernel.call args are not yet wired for dword-only signatures).
Framing verification (reading-error #28 — canary source supersedes
NT-doc semantics or value-name guesses)
The plan's framing was wrong on the constant identity:
0x525 = 1317 is X_ERROR_NO_SUCH_USER, NOT X_ERROR_NO_CONTENT
(xbox.h:113):
#define X_ERROR_NO_SUCH_USER X_RESULT_FROM_WIN32(0x00000525L)
X_ERROR_NO_CONTENT is not even defined in canary's xbox.h. Always
verify constants against canary source, never the plan's inline
annotations.
Canary body (xenia-canary/src/xenia/kernel/xam/xam_content.cc:129-220)
dword_result_t XamContentCreateEnumerator_entry(
dword_t user_index, dword_t device_id, dword_t content_type,
dword_t content_flags, dword_t items_per_enumerate,
lpdword_t buffer_size_ptr, lpdword_t handle_out) {
assert_not_null(handle_out);
auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
if ((device_id && device_info == nullptr) || !handle_out) {
if (buffer_size_ptr) *buffer_size_ptr = 0;
return X_E_INVALIDARG; // 0x80070057
}
if (buffer_size_ptr) {
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
} // sizeof(XCONTENT_DATA) = 0x134
uint64_t xuid = 0;
if (user_index != XUserIndexNone /* 0xFE */) {
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
if (!user) return X_ERROR_NO_SUCH_USER; // 0x525 ← THIS BRANCH
xuid = user->xuid();
}
// ... enumerator init, ListContent over HDD/ODD, fill items ...
*handle_out = e->handle();
return X_ERROR_SUCCESS; // 0
}
Only ONE control-flow path returns 0x525:
user_index != 0xFE(i.e. a specific signed-in user is being queried)XamState::GetUserProfile(user_index)returnsnullptr— i.e. no profile is installed at that slot.
Under canary's default --mute=true config (no --profile_slot_*
flags), ProfileManager has zero profiles, so GetProfile(N) returns
nullptr for every N.
Side-effects check (rule out classification C)
Canary's kernel.return payload has side_effects=[]. Combined with
the body shape: the *handle_out write and the *buffer_size_ptr
write happen inside the function but are not handle-creation
side effects (no Phase A handle.create emit because no XEnumerator
was minted). The empty side_effects confirms canary reached the
X_ERROR_NO_SUCH_USER early-return at line 153-155, before the
new ContentEnumerator(...) allocation at line 160-161. So:
- No handle-table mutation to mirror.
- The
*buffer_size_ptrwrite happens BEFORE the user check (line 145-147), so it does fire on the 0x525 path. Ours must mirror.
Classification
Class A — Unconditional (under ours's state model).
Ours has no profile manager; all user_index < 4 queries return
"no such user". This is effectively unconditional from Sylpheed's
perspective. Mirror canary's early-return.
NOT class B (state-dependent over real disk content) — we don't
reach the ListContent() enumeration path, so we don't need a
filesystem scan. No escalation.
NOT class C (side-effect mismatch) — both engines have empty
side_effects for this idx (above).
NOT class D (Phase A coverage asymmetry) — both engines emit the same 3-event sequence (import.call, kernel.call, kernel.return).
Ours pre-fix state
crates/xenia-kernel/src/xam.rs:59:
state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success);
stub_success sets r3 = 0. Hence return_value=0, status=0x00000000,
no buffer_size_ptr write, no handle_out write.
Fix shape
Replace the stub_success registration with a dedicated handler that:
- Reads r3..r9 (user_index, device_id, content_type, content_flags, items_per_enumerate, buffer_size_ptr, handle_out).
- Returns
X_E_INVALIDARG(0x80070057) when device_id is unknown (>2; canary's GetDummyDeviceInfo accepts HDD=1, ODD=2) or handle_out is NULL; writes*buffer_size_ptr = 0in that arm. - Otherwise writes
*buffer_size_ptr = 0x134 * items_per_enumerate(the sizeof(XCONTENT_DATA) computation). - If
user_index != 0xFE: returnX_ERROR_NO_SUCH_USER(0x525) — ours has no profile manager. - Otherwise (user_index == 0xFE, XUserIndexNone): proceed to a
stubbed enumerator-success path — write
*handle_out = 0and return 0. Defensive; not exercised by current Phase A.
LOC: ~85 body + ~125 tests = ~210 additive in xam.rs only.
Internal-consistency caveat (deferred)
Ours's xam_user_get_signin_state (xam.rs:380-384) returns 1 for
user_index == 0, claiming user 0 is signed in. This contradicts the
"no profile manager" model used by the C+9 fix. The conflict surfaces
at Phase A idx 107996 (canary returns 0 for XamUserGetSigninState)
and will be a separate fix in a future session. Single-fix discipline:
do not widen scope here.
Internal-consistency caveat #2 (cosmetic)
The doc comment on xam_user_get_signin_state says "user0 signed in
locally" but canary returns 0 here. That's evidence of an older fix
that needs reconciliation. Out of scope for C+9.