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>
152 lines
6.1 KiB
Diff
152 lines
6.1 KiB
Diff
## 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",
|
|
]
|
|
)
|
|
```
|