## Phase C+3 fix — RtlImageXexHeaderField Three files changed; ~80 LOC net. ### `xenia-rs/crates/xenia-kernel/src/state.rs` (+13 LOC) Add `xex_header_guest_ptr: u32` field to `KernelState`. Initialized to 0; populated once at startup by `xenia-app` after copying raw XEX header bytes into guest memory. ```diff @@ -103,6 +103,17 @@ /// Image base of the loaded XEX (for XexExecutableModuleHandle etc.) pub image_base: u32, + /// Guest VA of the raw XEX header bytes copied into guest memory at + /// startup (mirrors canary's `UserModule::guest_xex_header_`, + /// allocated in `user_module.cc:224`). Used by `RtlImageXexHeaderField` + /// to compute return values that are offsets into the in-guest header + /// copy (canary's `xboxkrnl_rtl.cc:501-514` calls `UserModule::Get + /// OptHeader(memory, header, key, &field_value)` which iterates + /// `header->headers[]` and returns `HostToGuestVirtual(header) + + /// opt_header.offset` for "else"-class keys, key low byte != 0/1). Zero + /// when the executable hasn't been installed yet. Set once by + /// `xenia-app` after `mem.write_bulk(base, &image_data)`. + pub xex_header_guest_ptr: u32, /// `XEX_HEADER_SYSTEM_FLAGS` (key `0x00030000`) parsed from the loaded /// XEX header. ... @@ -330,6 +331,7 @@ image_base: 0, + xex_header_guest_ptr: 0, xex_system_flags: 0, ``` ### `xenia-rs/crates/xenia-kernel/src/exports.rs` (~50 LOC) Replace stub `rtl_image_xex_header_field` (always returned 0) with a proper implementation mirroring canary's `UserModule::GetOptHeader` (`user_module.cc:335-369`). Walks the in-guest XEX header byte array to find the matching key entry and returns the appropriate value per the key's low-byte class (0x00 inline, 0x01 ptr-to-value, else header-base+offset). Falls back to `state.xex_header_guest_ptr` when the caller passes a NULL `xex_header` arg (the common ours-side case because ours's `*XexExecutableModuleHandle = image_base` doesn't resolve through a proper LDR_DATA_TABLE_ENTRY — see investigation.md for why fixing that breaks Phase A alignment). ```diff -fn rtl_image_xex_header_field(ctx: &mut PpcContext, _mem: &GuestMemory, _state: &mut KernelState) { - // r3 = xex_header_ptr, r4 = field_id - // Return 0 for all fields - ctx.gpr[3] = 0; -} +fn rtl_image_xex_header_field(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) { + // r3 = xex_header_guest_ptr (may be NULL — game's CRT often passes 0 + // because ours's `*XexExecutableModuleHandle = image_base` doesn't + // resolve to a real LDR_DATA_TABLE_ENTRY ...). When NULL, fall back + // to KernelState's recorded `xex_header_guest_ptr`. + // r4 = field_key (xex2_header_keys). + // + // Mirror of canary's `xboxkrnl_rtl.cc:501-514` → + // `UserModule::GetOptHeader(memory, header, key, &field_value)`. + let mut xex_header_ptr = ctx.gpr[3] as u32; + let field_key = ctx.gpr[4] as u32; + if xex_header_ptr == 0 { + xex_header_ptr = state.xex_header_guest_ptr; + } + if xex_header_ptr == 0 { + ctx.gpr[3] = 0; + return; + } + let header_count = mem.read_u32(xex_header_ptr.wrapping_add(0x14)); + let entries_base = xex_header_ptr.wrapping_add(0x18); + let mut field_value: u32 = 0; + let mut found = false; + for i in 0..header_count { + let entry_addr = entries_base.wrapping_add(i.wrapping_mul(8)); + let entry_key = mem.read_u32(entry_addr); + if entry_key != field_key { + continue; + } + found = true; + let entry_value_addr = entry_addr.wrapping_add(4); + match entry_key & 0xFF { + 0x00 => { field_value = mem.read_u32(entry_value_addr); } + 0x01 => { field_value = entry_value_addr; } + _ => { + let offset = mem.read_u32(entry_value_addr); + field_value = xex_header_ptr.wrapping_add(offset); + } + } + break; + } + if !found { + ctx.gpr[3] = 0; + return; + } + ctx.gpr[3] = field_value as u64; +} ``` ### `xenia-rs/crates/xenia-app/src/main.rs` (+15 LOC) In the variable-export patcher for ordinal `0x0193` (`XexExecutableModuleHandle`), keep `*XexExecutableModuleHandle = base` (don't disturb the CRT's early branch) but additionally allocate guest memory for the raw XEX header bytes, copy them in via `mem.write_bulk`, and record the guest VA in `kernel.xex_header_guest_ptr` for the new `rtl_image_xex_header_field` implementation to use as a fallback. ```diff ("xboxkrnl.exe", 0x0193) => { - // XexExecutableModuleHandle -> image base - mem.write_u32(addr, base); + // (long comment block) + let header_size = header.header_size as usize; + if header_size > 0 && header_size <= data.len() { + let xex_va = alloc_zero(header.header_size, &mut mem, &mut kernel); + if xex_va != 0 { + mem.write_bulk(xex_va, &data[0..header_size]); + kernel.xex_header_guest_ptr = xex_va; + } + } + mem.write_u32(addr, base); } ``` ### `xenia-rs/tools/diff-events/diff_events.py` (+13 LOC) Add `RtlImageXexHeaderField` to the `ALLOCATOR_RETURN_FNS` canonicalization set. The function's return value for "else"-class keys is a guest VA inside the engine's in-guest XEX header copy, which is allocated at host-allocator-dependent addresses (canary's `SystemHeapAlloc` lands in `0x30xxxxxx`; ours's `KernelState::heap_alloc` lands in `0x4xxxxxxx`). Per-(tid, name) ordinal sentinels mask this VA divergence (same pattern as Phase C+2's allocator canonicalization). ```diff ALLOCATOR_RETURN_FNS = frozenset( [ "MmAllocatePhysicalMemoryEx", "MmAllocatePhysicalMemory", "NtAllocateVirtualMemory", "RtlAllocateHeap", "MmCreateKernelStack", + # Phase C+3: `RtlImageXexHeaderField` returns ... (see source). + "RtlImageXexHeaderField", ] ) ```