# 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`): ```cpp #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`) ```cpp 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`: 1. `user_index != 0xFE` (i.e. a specific signed-in user is being queried) 2. `XamState::GetUserProfile(user_index)` returns `nullptr` — 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_ptr` write 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`: ```rust 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: 1. Reads r3..r9 (user_index, device_id, content_type, content_flags, items_per_enumerate, buffer_size_ptr, handle_out). 2. 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 = 0` in that arm. 3. Otherwise writes `*buffer_size_ptr = 0x134 * items_per_enumerate` (the sizeof(XCONTENT_DATA) computation). 4. If `user_index != 0xFE`: return `X_ERROR_NO_SUCH_USER` (0x525) — ours has no profile manager. 5. Otherwise (user_index == 0xFE, XUserIndexNone): proceed to a stubbed enumerator-success path — write `*handle_out = 0` and 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.