Phase C+9 — XamContentCreateEnumerator canary-parity X_ERROR_NO_SUCH_USER ========================================================================= Scope: `crates/xenia-kernel/src/xam.rs` LOC: ~85 body + ~125 test = ~210 net additive Registration change — xam.rs:59: -------------------------------- - state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success); + state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", xam_content_create_enumerator); Body — new function after `xam_user_get_signin_state` (xam.rs:384): ------------------------------------------------------------------- + // ===== Content ===== + + /// `XamContentCreateEnumerator(user_index, device_id, content_type, + /// content_flags, items_per_enumerate, buffer_size_ptr, handle_out)`. + /// Mirrors xenia-canary `XamContentCreateEnumerator_entry` + /// (xam_content.cc:129-220). Reading-error #28 discipline applied: body + /// shape verified against canary source. + /// + /// Canary's normal-path success returns `X_ERROR_SUCCESS` (0) with a + /// fresh enumerator handle in `*handle_out`. The Phase A oracle at + /// `tid_event_idx=102197` shows canary returning `X_ERROR_NO_SUCH_USER` + /// (`0x525`, 1317) with empty side_effects — the call hit the + /// `if (!user) return X_ERROR_NO_SUCH_USER;` early-return at + /// xam_content.cc:153-155 because no profile is installed in canary's + /// default `--mute=true` config (no `--profile_slot_*` flags). + /// + /// Ours has no profile-manager state, so all `user_index != 0xFE` + /// queries miss. Mirror the early-return: write `*buffer_size_ptr` per + /// canary line 145-147 (which executes *before* the user check) and + /// return `X_ERROR_NO_SUCH_USER`. Implementing real content enumeration + /// is an XAM-content-subsystem session (escalation-tier scope), not + /// this fix. + /// + /// Side note on internal consistency: ours's `xam_user_get_signin_state` + /// returns 1 for `user_index == 0`, conflicting with the "no profile" + /// model used here. That divergence surfaces later in the Phase A trace + /// (idx 107996+) and is a separate fix — deferred per single-fix + /// discipline. + fn xam_content_create_enumerator( + ctx: &mut PpcContext, + mem: &GuestMemory, + _state: &mut KernelState, + ) { + const X_USER_INDEX_NONE: u32 = 0xFE; + const X_USER_INDEX_LATEST: u32 = 0xFD; + const X_USER_MAX_USER_COUNT: u32 = 4; + const X_E_INVALIDARG: u32 = 0x8007_0057; + const X_ERROR_NO_SUCH_USER: u32 = 0x0000_0525; + const X_ERROR_SUCCESS: u32 = 0; + const X_CONTENT_DATA_SIZE: u32 = 0x134; + + let user_index = ctx.gpr[3] as u32; + let device_id = ctx.gpr[4] as u32; + let _content_type = ctx.gpr[5] as u32; + let _content_flags = ctx.gpr[6] as u32; + let items_per_enumerate = ctx.gpr[7] as u32; + let buffer_size_ptr = ctx.gpr[8] as u32; + let handle_out = ctx.gpr[9] as u32; + + let device_unknown = device_id != 0 && device_id > 2; + if device_unknown || handle_out == 0 { + if buffer_size_ptr != 0 { + mem.write_u32(buffer_size_ptr, 0); + } + ctx.gpr[3] = X_E_INVALIDARG as u64; + return; + } + + if buffer_size_ptr != 0 { + mem.write_u32(buffer_size_ptr, X_CONTENT_DATA_SIZE.wrapping_mul(items_per_enumerate)); + } + + if user_index != X_USER_INDEX_NONE { + let out_of_range = user_index >= X_USER_MAX_USER_COUNT + && user_index != X_USER_INDEX_LATEST; + let _ = out_of_range; // documentation only — both branches → no user + ctx.gpr[3] = X_ERROR_NO_SUCH_USER as u64; + return; + } + + if handle_out != 0 { + mem.write_u32(handle_out, 0); + } + ctx.gpr[3] = X_ERROR_SUCCESS as u64; + } Tests — 5 new in `mod tests`: ----------------------------- + xam_content_create_enumerator_returns_no_such_user_for_user0 + xam_content_create_enumerator_invalid_handle_out_returns_invalidarg + xam_content_create_enumerator_unknown_device_returns_invalidarg + xam_content_create_enumerator_user_none_returns_success + xam_content_create_enumerator_out_of_range_user_returns_no_such_user The first test is the THE Phase A oracle case: user_index=0, device_id=1 (HDD), items_per_enumerate=4 → asserts ret=0x525 and buffer_size_ptr=0x134*4 (canary writes size BEFORE the user check).