# Xenia-rs Boot State Inventory + Canary Comparison Companion to [`inventory.md`](inventory.md). Inventories xenia-rs's state immediately before guest XEX `EntryPoint`, then compares element-by-element against canary, calling out **missing** / **wrong** / **structurally different** initialization. All citations are paths relative to project root `/home/fabi/RE - Project Sylpheed/`. The comparison output is the most important part — that's §B at the bottom. §A is the inventory itself. --- # §A — Xenia-rs Inventory ## 0. The Boot Sequence End-to-end path through [xenia-rs/crates/xenia-app/src/main.rs](xenia-rs/crates/xenia-app/src/main.rs): 1. Parse XEX header (`parse_xex2_header`) — extract image base, entry point, imports, TLS info. 2. Decompress + decrypt image (`load_image`) and write into guest memory. 3. Resolve imports → build `thunk_map`. 4. **Allocate stack** at fixed VA `0x70000000`, size `0x100000` (1 MiB). [main.rs:933-936](xenia-rs/crates/xenia-app/src/main.rs#L933-L936) 5. **Allocate PCR** at fixed VA `0x7FFF_0000`, size `0x1000` (4 KiB); **TLS** at fixed `0x7FFE_0000`, size `0x1000`. [main.rs:939-942](xenia-rs/crates/xenia-app/src/main.rs#L939-L942) 6. **Write 3 PCR fields**: `[+0x00]=tls_addr`, `[+0x100]=0x1000` (fake), `[+0x150]=0`. [main.rs:945-947](xenia-rs/crates/xenia-app/src/main.rs#L945-L947) 7. **Build CPU context**: `PpcContext::new()` + override `pc`, `gpr[1]`, `gpr[2]`, `gpr[3..=7]`, `gpr[13]`, `msr`. [main.rs:953-966](xenia-rs/crates/xenia-app/src/main.rs#L953-L966) 8. Construct `KernelState` with GPU backend, register thunks. [main.rs:1014-1028](xenia-rs/crates/xenia-app/src/main.rs#L1014-L1028) 9. Install MMIO region (GPU). [main.rs:1441](xenia-rs/crates/xenia-app/src/main.rs#L1441) 10. **Allocate `main_handle`** = `0x1000` and install the initial thread on HW slot 0. [main.rs:1446-1467](xenia-rs/crates/xenia-app/src/main.rs#L1446-L1467) Write `pcr[+0x2C]=0` (processor number). 11. `kernel.retain_handle(main_handle)` — mirrors canary's `XThread::Create`→`RetainHandle()`. [main.rs:1476](xenia-rs/crates/xenia-app/src/main.rs#L1476) 12. If XISO, mount `d:` device. [main.rs:1480-1485](xenia-rs/crates/xenia-app/src/main.rs#L1480-L1485) 13. **Patch variable imports** by ordinal — only the ones the XEX imports get non-zero values. [main.rs:1496-1588](xenia-rs/crates/xenia-app/src/main.rs#L1496-L1588) 14. (optional) DB writer + analysis. 15. **Begin execution**: interpreter loop dispatches at `ctx.pc = entry`. There is **no `XThread::Create` / `X_CREATE_SUSPENDED` / `PreLaunch` / Resume** sequence — execution begins as soon as the run loop is entered. --- ## 1. PPC CPU State at entry From [xenia-cpu/src/context.rs:148-182](xenia-rs/crates/xenia-cpu/src/context.rs#L148-L182) (`PpcContext::new`) + [main.rs:953-966](xenia-rs/crates/xenia-app/src/main.rs#L953-L966) (overrides). | Reg | Value | Source | |----|----|----| | r0 | 0 | `PpcContext::new` | | **r1** (SP) | `((stack_base + stack_size) - 0x100) & ~0xF` = **`0x700F_FF00`** | [main.rs:958-959](xenia-rs/crates/xenia-app/src/main.rs#L958-L959) | | **r2** | `0x2000_0000` | [main.rs:960](xenia-rs/crates/xenia-app/src/main.rs#L960) | | **r3..r7** | explicitly 0 (loop) | [main.rs:964](xenia-rs/crates/xenia-app/src/main.rs#L964) | | r8..r12 | 0 | `PpcContext::new` | | **r13** | `0x7FFF_0000` (fixed VA, no allocation) | [main.rs:965](xenia-rs/crates/xenia-app/src/main.rs#L965) | | r14..r31 | 0 | `PpcContext::new` | | **LR** | **`0xBCBC_BCBC`** (halt sentinel — `bclr` exits the interpreter) | [context.rs:55, 155](xenia-rs/crates/xenia-cpu/src/context.rs#L55) | | CTR | 0 | `PpcContext::new` | | **MSR** | `0x9030` | [main.rs:966](xenia-rs/crates/xenia-app/src/main.rs#L966) | | FPSCR | 0 | `PpcContext::new` | | XER (ca/ov/so/tbc) | 0/0/0/0 | `PpcContext::new` | | CR0..CR7 | `CrField::default()` (=0) | `PpcContext::new` | | FPRs | `0.0` × 32 | `PpcContext::new` | | VRs | `Vec128::ZERO` × 128 | `PpcContext::new` | | **VSCR** | `Vec128::from_u32x4(0, 0, 0, 0x0001_0000)` (NJ=1 only) | [context.rs:167](xenia-rs/crates/xenia-cpu/src/context.rs#L167) | | **VRSAVE** | `0xFFFF_FFFF` | [context.rs:168](xenia-rs/crates/xenia-cpu/src/context.rs#L168) | | DEC | 0 | `PpcContext::new` | | timebase, cycle_count | 0 | `PpcContext::new` | | reservation\_\* | unset / `None` (M3.7 table optional) | [context.rs:170-176](xenia-rs/crates/xenia-cpu/src/context.rs#L170-L176) | | PC | `entry_point_` from XEX header | [main.rs:954](xenia-rs/crates/xenia-app/src/main.rs#L954) | PpcContext is `#[repr(C, align(64))]` — same alignment expectation as canary's host-allocated context. --- ## 2. Memory Layout | Region | xenia-rs VA | Size | Source | |----|----|----|----| | XEX image | from header (`base`) | `alloc_size` from XEX | [main.rs:905-907](xenia-rs/crates/xenia-app/src/main.rs#L905-L907) | | Stack | `0x7000_0000 .. 0x7010_0000` | 1 MiB **fixed** | [main.rs:933-936](xenia-rs/crates/xenia-app/src/main.rs#L933-L936) | | **PCR** | `0x7FFF_0000` | 4 KiB **fixed** | [main.rs:939-941](xenia-rs/crates/xenia-app/src/main.rs#L939-L941) | | **TLS** | `0x7FFE_0000` | 4 KiB **fixed** | [main.rs:940-942](xenia-rs/crates/xenia-app/src/main.rs#L940-L942) | | User heap (bump alloc) | `0x4000_0000+` | — | [state.rs:550](xenia-rs/crates/xenia-kernel/src/state.rs#L550) | | Aux kernel stack alloc cursor | `0x7100_0000+` | — | [state.rs:551](xenia-rs/crates/xenia-kernel/src/state.rs#L551) | **No guard pages** are allocated around the stack. **No physical-mirror aliases** (A0/C0/E0). **No XEX `LoadContinue`-style page-protection split** for `.text` (image is allocated as RW). --- ## 3. PCR layout in xenia-rs Only **4 fields** are written ([main.rs:945-947, 1457](xenia-rs/crates/xenia-app/src/main.rs#L945-L947)): | Offset | Field | Value | Note | |----|----|----|----| | +0x00 | tls_ptr | `0x7FFE_0000` | matches canary | | +0x2C | processor_number | 0 | matches canary semantically | | +0x100 | current_thread | `0x1000` (the main_handle, NOT a guest KTHREAD VA) | divergence — see §B | | +0x150 | dpc_active | 0 | matches canary | The rest of the 4 KiB is zero. No `pcr_ptr` self-reference at +0x30, no `host_stash` at +0x38, no `stack_base_ptr`/`stack_end_ptr` at +0x70/+0x74, no `prcb` pointer at +0x104. --- ## 4. TLS - `kernel.next_tls_index = AtomicU32::new(0)` at [state.rs:546](xenia-rs/crates/xenia-kernel/src/state.rs#L546). Slots grow on first `ExAllocateTls` ([state.rs:1539](xenia-rs/crates/xenia-kernel/src/state.rs#L1539)). - `scheduler.tls_slot_count = 0` at [scheduler.rs:360, 396](xenia-rs/crates/xenia-cpu/src/scheduler.rs#L360); main thread receives `tls_values = vec![0; 0]` ([scheduler.rs:682](xenia-rs/crates/xenia-cpu/src/scheduler.rs#L682)). - TLS *block* in guest memory at `0x7FFE_0000` is 4 KiB zeroed; not parsed from XEX TLS info either. --- ## 5. Threading - 6 HW slots (`HW_THREAD_COUNT = 6`), all empty except slot 0 (`HwSlot::Ready`, contains main thread tid=`INITIAL_GUEST_TID`=1). - `next_thread_id` starts at `INITIAL_GUEST_TID + 1 = 2`. - No background scheduler tick, no kernel dispatch worker, no GPU command-processor thread *parked on guest semaphores*. GPU runs on its own host worker (M1.9 default) but doesn't appear in the guest scheduler. - Quantum = `QUANTUM_DEFAULT = 500_000` insns ([scheduler.rs:795](xenia-rs/crates/xenia-cpu/src/scheduler.rs#L795)). --- ## 6. Kernel guest-visible variable exports Done **lazily, per-import-record** in `main.rs:1496-1588`. Only ordinals the XEX imports get matched; everything else falls through to `_ => mem.write_u32(addr, 0)`. Handled ordinals (xboxkrnl.exe): | Ord | Name | xenia-rs init | |----|----|----| | 0x001B | `ExThreadObjectType` | ptr to 0x40 zero block | | 0x0059 | `KeDebugMonitorData` | ptr to 0x40 zero block | | 0x00AD | `KeTimeStampBundle` | ptr to 0x18 block: `[+0]=FILETIME` `[+0x10]=FILETIME` (both = `132_500_000_000_000_000`) | | 0x0158 | `XboxKrnlVersion` | u16×4: `{2, 0, 20000, 0}` written directly at import slot (no indirection) | | 0x0193 | `XexExecutableModuleHandle` | `addr = base`; raw XEX header bytes copied to a separate heap allocation stashed in `kernel.xex_header_guest_ptr` | | 0x01AE | `ExLoadedCommandLine` | ptr to 0x10 zero block (empty string) | | 0x01BE | `VdGlobalDevice` | 0 | | 0x01C0 | `VdGpuClockInMHz` | `500` | | 0x01C1 | `VdHSIOCalibrationLock` | 0 | | 0x0266 | `KeCertMonitorData` | ptr to 0x100 zero block | | all others | — | 0 | **Critically: no `XboxHardwareInfo` (0x017A), no `ExConsoleGameRegion` (0x015F), no `ExLoadedImageName` (0x01AD), no `KernelGuestGlobals` block, and `KeTimeStampBundle` has a structurally different layout from canary's.** --- ## 7. Object table / handles - Empty at boot; `next_handle = 0x1000`, increments by 4. [state.rs:540-547](xenia-rs/crates/xenia-kernel/src/state.rs#L540-L547) - First handle = `0x1000` = main thread (via `alloc_handle_for(KernelObject::Thread {...})`). - No event/semaphore/mutex pre-creation. --- ## 8. XAM Largely stub-only: - No `UserProfile` struct ever instantiated. Queries return `ERROR_NOT_FOUND` / 0 / sentinel values. - `XamUserGetXUID` → 0, `XamUserGetName` → empty string, `XamUserGetSigninState` → 1 for user_index==0 else 0. [xam.rs:354-381](xenia-rs/crates/xenia-kernel/src/xam.rs#L354-L381) - Notification state: `has_notified_startup=false`, `has_notified_live_startup=false`. No listeners pre-registered. - No 18-setting default profile. --- ## 9. Filesystem `kernel.vfs: Option>` — at most ONE device. If `.iso`/`.xiso`, `DiscImageDevice::open("d", path)` is installed under `kernel.vfs`. Otherwise `None`. [main.rs:1480-1485](xenia-rs/crates/xenia-app/src/main.rs#L1480-L1485) No `game:`/`d:` symbolic-link distinction, no `\Device\Harddisk0\Partition1`, no STFS/CON/PIRS/UDF, no `system:`/`hdd:`/`mu:`. Cache is a host tmpdir wiped on startup ([state.rs:611-636](xenia-rs/crates/xenia-kernel/src/state.rs#L611-L636)). --- ## 10. GPU - `GpuSystem::new` → register file `vec![0u32; 0x6000]`, all zero. [register_file.rs:7-9](xenia-rs/crates/xenia-gpu/src/register_file.rs#L7-L9) - **No gamma-ramp preload** — neither the 256-entry sRGB ramp nor the 128-entry PWL ramp from canary's `CommandProcessor::Initialize`. - Ring buffer base/size = 0 until guest calls `VdInitializeRingBuffer`. - MMIO region at `0x7FC8_0000` (mask `0xFFFF_0000`, 64 KiB window), registers `CP_RB_WPTR`, `CP_RB_RPTR`, `CP_INT_STATUS`, `CP_INT_ACK`, `D1MODE_VBLANK_VLINE_STATUS` hooked. [mmio_region.rs:23-27](xenia-rs/crates/xenia-gpu/src/mmio_region.rs#L23-L27) - Backend: threaded (M1.9 default), `Arc`-shared `GpuSystem`. Vulkan/D3D12 device is **not** created at boot — it stays in the "no-presenter" mode unless `--ui` is used. --- ## 11. Audio (APU) Largely a stub: - `xenia-apu` is described as stub-only ([lib.rs:1-16](xenia-rs/crates/xenia-apu/src/lib.rs#L1-L16)). No XMA decoder. - The functional audio path is `xenia-kernel/src/xaudio.rs`: 8 client slots (all `None`), synthetic park-handle base `0xF000_0000`, empty pending-fire FIFO, no worker threads. - A periodic `xaudio_tick_enabled = true` will fire buffer-complete callbacks every 48,000 instructions (≈5.33 ms wall) **but only after the guest calls `XAudioRegisterRenderDriverClient`**. --- ## 12. HID, Network, Display, Clock - HID: single `GamepadState` zeroed, all 4 slots disconnected. - Network: `WSAStartup`/`WSACleanup` are no-ops; no IP/MAC value pre-written. No XNet stack. - Video mode: hardcoded HDMI 1280×720 widescreen via `XGetVideoMode`/`XGetAVPack` exports ([xam.rs:631-654](xenia-rs/crates/xenia-kernel/src/xam.rs#L631-L654)). - Clock: `KeQuerySystemTime` returns fixed FILETIME `132_500_000_000_000_000`; `KeQueryInterruptTime` returns fixed `0x0000_0001_0000_0000` ([exports.rs:869-883](xenia-rs/crates/xenia-kernel/src/exports.rs#L869-L883)). No `HighResolutionTimer` repeating update. --- ## 13. Interpreter / codegen Pure interpreter (no JIT). Code blocks decoded on first execution. Reservation table is `Option>` (gated by `--reservations-table` / `XENIA_RESERVATIONS_TABLE=1`). Import thunks not pre-patched into guest code; instead, the interpreter intercepts at the thunk PC and dispatches to a host `call_export(module, ordinal)`. --- # §B — Comparison: where xenia-rs is missing or wrong Tagged each row with: - **= match** — bit-equivalent or semantically equivalent - **≈ semantic** — different mechanism, same observable result - **✗ missing** — guest-visible state canary provides that xenia-rs doesn't - **!= wrong** — both engines set it but the values/layout/timing diverge - **+ extra** — xenia-rs sets something canary doesn't (impact unknown) ## B.1 PPC CPU registers | Element | Canary | xenia-rs | Status | |----|----|----|----| | r0 | 0 (memset) | 0 | **=** | | r1 (SP) | `stack_base_` (top of allocated region from `0x70000000-0x7F000000`, page-aligned, sits between two guard pages) | `0x700F_FF00` (1 MiB stack ends at `0x7010_0000`, SP = top − 0x100, 16B aligned) | **!= wrong** — value differs because stack size is hardcoded 1 MiB ignoring `XEX_HEADER_DEFAULT_STACK_SIZE`; no guard pages | | r2 | `0x2000_0000` | `0x2000_0000` | **=** | | r3 | `start_context` = 0 for main | 0 | **=** | | r4..r7 | 0 (memset) | 0 (explicit) | **=** | | r13 | `pcr_address_` = system-heap VA (variable, in `0x80000000+` range) | `0x7FFF_0000` (fixed) | **!= wrong** — guest code that walks PCR via r13 cannot assume a fixed VA, but most reads via `lwz rX, off(r13)` work fine; *however* canary places r13 in 0x80000000+ system-heap, ours in 0x7FFF_0000 user heap — any code that does `if (r13 >= 0x80000000) …` would diverge | | r14..r31 | 0 (memset) | 0 | **=** | | LR | 0 (memset) | **`0xBCBC_BCBC`** (halt sentinel) | **+ extra (and != wrong)** — canary's main thread enters with LR=0; ours uses a sentinel so `bclr` from the entry frame exits the interpreter. A guest function that reads LR before saving it (very rare) would see a different value. | | CTR, XER, FPSCR, CR | 0 | 0 | **=** | | MSR | `0x9030` | `0x9030` | **=** | | VSCR | `0x0001_0000` (NJ=1) | `0x0001_0000` (NJ=1) | **=** | | VRSAVE | `0xFFFF_FFFF` | `0xFFFF_FFFF` | **=** | | FPRs | 0.0 × 32 (memset of bit pattern) | 0.0 × 32 | **=** | | VRs | 0 × 128 | 0 × 128 | **=** | | DEC | 0 (memset) | 0 | **=** | | PC | `entry_point_` | `entry` | **=** | **Net**: register-level state is essentially equivalent. The two *real* divergences are SP value (because stack-size is wrong) and LR (intentional design choice but observable). Everything else matches. ## B.2 Stack | Element | Canary | xenia-rs | Status | |----|----|----|----| | Range | `0x70000000-0x7F000000` (240 MiB pool, multiple stacks bump-allocated) | `0x70000000-0x70100000` (1 MiB hardcoded) | **!= wrong** | | Size | `XEX_HEADER_DEFAULT_STACK_SIZE` rounded to heap page size | `0x10_0000` (1 MiB) — XEX header ignored | **!= wrong** | | Guard pages | 2× `page_size` (one above, one below), `kMemoryProtectNoAccess` | none | **✗ missing** — stack overflow on ours will silently corrupt adjacent VA; canary would fault on the guard page | | stack_limit / stack_base recorded in KPCR | yes (+0x70 / +0x74) | not written | **✗ missing** (see B.4) | If the title declares a stack size larger than 1 MiB (some larger XEXes do), xenia-rs will violate that contract. Worth checking Sylpheed's `XEX_HEADER_DEFAULT_STACK_SIZE`. ## B.3 TLS | Element | Canary | xenia-rs | Status | |----|----|----|----| | Slot count when XEX has no TLS info | **1024** (`kDefaultTlsSlotCount`, `xthread.cc:335`) | **0** (`next_tls_index = AtomicU32::new(0)`, grows on `ExAllocateTls`) | **!= wrong** | | Slot zeroing | `Memory::Fill(tls_static_address_, tls_total_size_, 0)` (4 KiB for default) | block at `0x7FFE_0000` is 4 KiB zero (allocation default), but the *runtime* `tls_values` vector starts empty | **!= wrong** for guest semantics — canary returns 0 from any of 1024 slots; ours returns 0 by lazy resize but if guest reads slot N via `lwz r3, (4*N)(r13)` it actually reads the **guest-memory TLS block at 0x7FFE_0000**, not the host-side `tls_values` Vec. Whether these stay coherent depends on which kernel API is used. | | Extended TLS image (from `xex2_opt_tls_info.raw_data_address`) | copied into TLS block if `raw_data_size > 0` | **not parsed, never copied** | **✗ missing** — if Sylpheed has any `__declspec(thread)` static data, xenia-rs starts with all zeros. | Highest-impact divergence in this section. ## B.4 PCR / KPCR | Offset | Field | Canary writes | xenia-rs writes | Status | |----|----|----|----|----| | +0x000 | tls_ptr | yes | yes | **=** | | +0x02C | processor_number | implicit (via SetActiveCpu) | yes (0) | **=** | | +0x030 | pcr_ptr (self-ref) | yes (`pcr_ptr = pcr_address_`) | **no** | **✗ missing** | | +0x038 | host_stash (`uint64` host pointer) | yes | **no** | **✗ missing** (host-side stash; xenia-rs doesn't need it because it has the `PpcContext` Rust struct out-of-band, but anything reading `[r13+0x38]` would see 0 in ours and a host pointer in canary) | | +0x070 | stack_base_ptr | yes | **no** | **✗ missing** | | +0x074 | stack_end_ptr | yes | **no** | **✗ missing** | | +0x100 | prcb_data.current_thread (real guest KTHREAD VA) | yes (a VA in the system-heap KTHREAD allocation) | yes, but `0x1000` (the *handle*, not a guest KTHREAD VA) | **!= wrong** — any guest code that dereferences this expecting to read KTHREAD fields will read garbage from `0x1000` (probably zero memory or invalid). Multiple ntdll-style helpers in xboxkrnl walk this. | | +0x104 | prcb (=`pcr+offsetof(prcb_data)`) | yes | **no** | **✗ missing** | | +0x150 | dpc_active | implicit (init writes `prcb_data.dpc_active=0`) | yes (0) | **=** | | Size | 0x2D8 | 0x1000 (over-allocated) | **+ extra** (no functional impact) | | Base VA | dynamic from system heap | fixed `0x7FFF_0000` | **!= wrong** value but compatible layout | This is the **most consequential structural divergence**: any guest code path that touches PCR fields beyond `[r13+0]` and `[r13+0x100]` will diverge. ## B.5 Kernel variable exports | Export (ord) | Canary | xenia-rs | Status | |----|----|----|----| | `XboxHardwareInfo` (0x017A) | 16B: `[0]=0x20` HDD bit, `[4]=0x06` CPU count, rest 0 | **falls through to default 0** — not handled | **✗ missing** — games that probe HDD-present bit or CPU-count will see 0/0. | | `XboxKrnlVersion` (0x0158) | 8B from `kernel_state_->GetKernelVersion()` (=`{2, 0xFFFF, 0xFFFF, 0x80}`-ish per canary `KernelVersion`) | 8B inline `{2, 0, 20000, 0}` — *written at the import-slot address directly, not via pointer indirection* | **!= wrong** — canary writes a *pointer* to the version struct into the import slot; xenia-rs writes the version bytes directly into the import slot. If the XEX import declares it as `ptr-to-data` (which is how canary's `SetVariableMapping` semantics work) and the guest dereferences it, ours will deref `0x00020000` and crash. The XEX import record type 0 is "data" but the canonical pattern is still indirection. Worth verifying which side the game expects. | | `XexExecutableModuleHandle` (0x0193) | pointer-to-pointer chain that ultimately leads to the XEX header base | direct write of `base` at the import slot, with header bytes stashed separately at `kernel.xex_header_guest_ptr` for `RtlImageXexHeaderField` to consume | **!= wrong** but per the in-source comment ([main.rs:1532-1556](xenia-rs/crates/xenia-app/src/main.rs#L1532-L1556)) the previous "proper" indirection caused divergence at idx=0; current direct-write workaround is intentional. | | `ExLoadedImageName` (0x01AD) | 1024-aligned buffer filled with module path after `SetExecutableModule` | **not handled** — falls through to default 0 | **✗ missing** | | `ExLoadedCommandLine` (0x01AE) | 1024-aligned buffer containing `"default.xex"` (with literal quotes) + cvar `cl` | 0x10 zero block (empty string) | **!= wrong** — empty string vs `"default.xex"`. Could cascade if anything parses it. | | `ExConsoleGameRegion` (0x015F) | `0xFFFF_FFFF` (region-free) | **not handled** — falls through to default 0 | **✗ missing** — region check will fail in any title that branches on this. | | `KeDebugMonitorData` (0x0059) | 4B `0` (or 4B + struct when cvar on) | 0x40 zero block pointer | **≈ semantic** — both effectively zero; size differs harmlessly | | `KeCertMonitorData` (0x0266) | 4B `0` (or struct when cvar on) | 0x100 zero block pointer | **≈ semantic** | | `KeTimeStampBundle` (0x00AD) | 0x18 block: `+0x00=interrupt_time u64`, `+0x08=system_time u64`, `+0x10=tick_count u32` (uptime ms), `+0x14=padding u32`; updated every tick by `HighResolutionTimer::CreateRepeating` | 0x18 block: `+0x00=FILETIME hi/lo u32×2`, `+0x10=FILETIME hi/lo u32×2` again; `+0x08` is **never written** (stays 0); no repeating timer | **!= wrong** — (a) `[+0x08]` (canary's `system_time u64`) is 0 in ours, should be the time. (b) `[+0x10]` should be `tick_count u32` (ms since boot) — ours writes the high half of a 64-bit FILETIME there instead. (c) values are static; any code that polls this expecting forward progress (game loops do) will see a frozen tick-count. **High-impact.** | | `ExThreadObjectType` (0x001B) | pointer into `KernelGuestGlobals` block, with object-type bytes populated by `InitializeKernelGuestGlobals` | 0x40 zero block pointer | **!= wrong** — object-type sub-struct bytes (header, pool tag, vtable-ish) are *not* zero in canary. Any guest code that compares `*hType` against expected magic bytes will diverge. | | `ExEventObjectType`, `ExMutantObjectType`, `ExSemaphoreObjectType`, `ExTimerObjectType`, `IoCompletionObjectType`, `IoDeviceObjectType`, `IoFileObjectType`, `ObDirectoryObjectType`, `ObSymbolicLinkObjectType`, `UsbdBootEnumerationDoneEvent` | all populated as part of `KernelGuestGlobals` | **all fall through to default 0** | **✗ missing** — same class of bug as ExThreadObjectType, multiplied across all type tags. | | `VdGpuClockInMHz` (0x01C0) | 500 (`xenia_main.cc:661`) | 500 | **=** | | `VdGlobalDevice` (0x01BE) | 0 | 0 | **=** | | `VdHSIOCalibrationLock` (0x01C1) | 0 | 0 | **=** | ## B.6 Object table / sync primitives | Element | Canary | xenia-rs | Status | |----|----|----|----| | Pre-created kernel objects | none (the executable module + dispatch worker thread + main thread are created during LaunchModule) | none (main thread handle 0x1000 created in `install_initial_thread`) | **=** | | Main thread refcount | 2 (creator + self via `RetainHandle()`) | 2 (creator + `retain_handle`) | **=** | | **Kernel dispatch worker thread** (canary `SetExecutableModule` creates one to dispatch guest async callbacks) | **yes** | **no** | **✗ missing** — guest async callback paths may behave differently | | `kHandleBase` / handle spacing | `0xF8000000`, step 4 | `0x1000`, step 4 | **!= wrong** value but compatible if the guest doesn't hard-compare handle values | ## B.7 XAM | Element | Canary | xenia-rs | Status | |----|----|----|----| | User profile | XUID `0xB13E_BABE_BABE_BABE`, gamertag `"User"`, 18 default settings (per `user_profile.cc:32-92`) | no profile; `XamUserGetXUID` returns 0; `XamUserGetName` returns "" | **!= wrong** — any title that branches on `XamUserGetSigninState(0) == eSignedInLive`/`eSignedInLocally` will likely treat user as signed-out in ours, signed-in (locally) in canary. | | `XamUserGetSigninState` | returns 1 (signed-in locally) for slot 0 | returns 1 for slot 0 | **=** | | Notification listeners | empty until first registration; first registration triggers synthesized `XN_SYS_UI` / `SIGNINCHANGED` / etc. burst | empty; no synthesized burst | **!= wrong** — guests subscribing to startup events will not receive them | | `XamGetExecutionId` | implemented (returns title-info struct) | stub (returns 0) | **!= wrong** | ## B.8 Filesystem | Element | Canary | xenia-rs | Status | |----|----|----|----| | `game:` symlink → DiscImageDevice / StfsContainerDevice / HostPathDevice | always mounted from input | mounted only if input is `.iso`/`.xiso`, under device name `d` (not `game`) | **!= wrong** — guests opening `game:\\…` paths against a non-ISO input will fail in ours. | | `d:` symlink | always present pointing to same device as `game:` | identical to `game:` in xenia-rs (device name is `"d"`) | **≈ semantic** | | `cache:` / `cache0:` / `cache1:` | optional via cvar (`mount_cache`) | optional via env (tmpdir default, wiped on boot) | **≈ semantic** but content differs (canary persists, ours wipes) | | `system:`, `hdd:`, `mu:`, `udf` | optional | absent | **✗ missing** | | `\Device\Harddisk0\Partition1` / `\Device\Cdrom0` device path | yes | no | **✗ missing** — code that opens by NT device path won't work | ## B.9 GPU | Element | Canary | xenia-rs | Status | |----|----|----|----| | Register file zeroed | yes | yes | **=** | | **Gamma ramps preloaded** (256-entry sRGB + 128-entry PWL) at `CommandProcessor::Initialize` | **yes** | **no** | **✗ missing** — games that read gamma registers before writing them will see linear-zero, not the canary sRGB ramp. Likely cosmetic, unless gamma is queried during init. | | MMIO range hooked | `[0x7FC8_0000, 0xFFFF_0000]` | same range | **=** | | CP thread parked | parked on `write_ptr_index_event_` | xenia-rs GPU worker is on the host side, not modeled as a guest scheduler thread | **≈ semantic** | | Ringbuffer pre-allocated | no (guest does it via `VdInitializeRingBuffer`) | no | **=** | | Vsync / interrupts | parked until callback registered | parked until callback registered | **=** | ## B.10 Audio (APU) | Element | Canary | xenia-rs | Status | |----|----|----|----| | `XmaDecoder` instantiated | yes | no (apu crate is stub) | **✗ missing** | | Worker thread spawned | yes, parked on semaphores | not at boot; xaudio worker(s) spawn on `XAudioRegisterRenderDriverClient` | **!= wrong** but covered by lazy mechanism | | 256 client semaphores | yes, count=0 each | 8 client slots (None), synthetic handle base `0xF000_0000` | **!= wrong** — different architecture (host workers + 256 sems vs. guest worker park-on-synthetic-handle) but same observable effect at entry | | Periodic buffer-complete tick | driven by host audio device callback | driven by `xaudio_tick_enabled` every 48,000 insns | **≈ semantic** | ## B.11 HID | Element | Canary | xenia-rs | Status | |----|----|----|----| | `connected_slots` = 0 (all 4 disconnected) | yes | yes | **=** | | `XInputGetCapabilities` returns `ERROR_DEVICE_NOT_CONNECTED` | yes | yes (when no UI) | **=** | | Rumble stub | yes | yes | **=** | ## B.12 Network | Element | Canary | xenia-rs | Status | |----|----|----|----| | `WSAStartup` etc. | full kernel exports | stub returning success | **!= wrong** semantically but at entry both = uninitialized | | `XNetGetTitleXnAddr` | returns IP=127.0.0.1, MAC=`CC CC CC CC CC CC`, `XNET_GET_XNADDR_STATIC` | not exported (returns 0/error) | **✗ missing** — any code that probes XNet status will see different result | | Sockets pre-created | none | none | **=** | ## B.13 Clock | Element | Canary | xenia-rs | Status | |----|----|----|----| | `KeQueryPerformanceFrequency` | host CPU tick frequency | fixed | **!= wrong** for any title that uses this for real-time timing | | `KeQuerySystemTime` | wall clock since emulator startup | fixed `132_500_000_000_000_000` | **!= wrong** for save-game timestamps, anything time-dependent | | `KeQueryInterruptTime` | host-derived | fixed `0x0000_0001_0000_0000` | **!= wrong** | | `KeTimeStampBundle` updates | repeating `HighResolutionTimer` every 1 ms | static | **✗ missing** — main loop polling for forward progress will hang | | `KeTimeStampBundle.tick_count` location | `[+0x10]` u32 | not at `[+0x10]`; ours writes FILETIME hi there | **!= wrong** layout | | `KeTimeStampBundle.system_time` | `[+0x08]` u64 | `[+0x08]` is **never written** (0) | **✗ missing** | ## B.14 Threading | Element | Canary | xenia-rs | Status | |----|----|----|----| | Main thread starts `X_CREATE_SUSPENDED` then resumes after `PreLaunch` | yes | no (interpreter loop starts running immediately) | **≈ semantic** (no debugger attach hook) | | Kernel dispatch worker thread | yes, host-side | no | **✗ missing** | | GPU command processor thread | yes, host-side, parked | yes (M1.9 default), but no guest visibility | **=** | | Audio worker | yes, parked on 256 sems | xaudio fires via guest workers parked on synthetic handles | **!= wrong** architecture | --- # §C — Highest-impact divergences (ranked) These are the items most likely to cause guest behavior divergence. Sorted by likely blast radius: 1. **`KeTimeStampBundle` layout + static values + missing repeating-timer update.** Games poll this. xenia-rs writes the wrong fields, never updates them, and `[+0x08]` (canary's `system_time`) is always 0. If anything games polls `[+0x10]` (the `tick_count` slot) expecting forward progress, it sees the upper half of a fake FILETIME, not a monotonically-increasing tick count. 2. **`KernelGuestGlobals` object-type sub-structs are all-zero in xenia-rs.** Canary's `InitializeKernelGuestGlobals` (`kernel_state.cc:1511+`) populates `ExEventObjectType`, `ExMutantObjectType`, `ExSemaphoreObjectType`, `ExTimerObjectType`, `IoCompletionObjectType`, `IoDeviceObjectType`, `IoFileObjectType`, `ObDirectoryObjectType`, `ObSymbolicLinkObjectType`, `UsbdBootEnumerationDoneEvent` with real bytes (pool tag, vtable-ish, etc.). xenia-rs returns either nullptr (for unhandled ordinals) or a zero block (for `ExThreadObjectType` only). Any guest code that reads object-type fields will see 0 in ours. 3. **`XboxHardwareInfo` not initialized.** Canary writes `[0]=0x20` (HDD bit) and `[4]=0x06` (CPU count). xenia-rs writes 0. Games that branch on HDD-present or CPU-count will diverge. 4. **`ExConsoleGameRegion = 0xFFFFFFFF` not set.** Canary returns "region-free". xenia-rs returns 0 (no region). Could trip region-check codepaths. 5. **TLS slot count = 0 vs 1024 default**, and **no extended TLS image copy**. If Sylpheed has `__declspec(thread)` data, xenia-rs starts with all zeros instead of the XEX-provided initial values. 6. **PCR field gaps**: missing `pcr_ptr` (+0x30), `host_stash` (+0x38), `stack_base_ptr` (+0x70), `stack_end_ptr` (+0x74), `prcb` (+0x104); and `[+0x100]` holds the *handle* `0x1000` rather than the guest KTHREAD VA. Any kernel-thunk or HLE-helper that walks PCR will see garbage. 7. **Stack size hardcoded to 1 MiB**, XEX `XEX_HEADER_DEFAULT_STACK_SIZE` ignored. No guard pages. Stack overflow goes undetected. 8. **`ExLoadedCommandLine` empty** instead of canary's `"default.xex"`. Probably low-impact (rarely parsed), but observably different. 9. **No `ExLoadedImageName`** (canary fills with module path). 10. **GPU gamma ramps not preloaded.** Cosmetic at worst, but a real divergence. 11. **User profile**: canary has a populated profile (XUID, gamertag, 18 settings); xenia-rs has none. Title-side branches on signed-in state are equivalent (both return 1), but any code reading `XamUserGetXUID` or profile settings will diverge. 12. **Filesystem mount**: canary always mounts `game:` whatever the input format; xenia-rs only mounts if `.iso`/`.xiso`, and under `d` (no `game:` symlink). Title code opening `game:\\…` paths will fail on non-ISO inputs. 13. **Kernel dispatch worker thread absent.** Guest async callbacks routed differently. # §D — Things xenia-rs gets right For completeness, these are bit-equivalent / verified-matching: - All CPU registers except r1 value, r13 base, and LR. - MSR (`0x9030`), VSCR (`0x00010000` NJ-only), VRSAVE (`0xFFFFFFFF`). - Stack VA base (`0x70000000`), TLS VA base (`0x7FFE_0000`), PCR VA base (`0x7FFF_0000`). - GPU register file zero-init; MMIO range; ring-buffer NOT pre-allocated. - HID: 4 disconnected slots. - Handle allocator starts at `0x1000`, step 4 (canary uses `0xF8000000` but spacing matches). - Main thread refcount = 2 (creator + self). - No pre-created sync primitives. # §E — Recommended verification & remediation order Cheap, high-value first: 1. **Fix `KeTimeStampBundle` layout + repeating update**. ~30 LOC: change init to write `interrupt_time u64` at `[+0]`, `system_time u64` at `[+8]`, `tick_count u32` at `[+0x10]`, padding at `[+0x14]`; add a `tokio`/`std::thread` repeating tick at 1 ms to update tick_count (or per-host-tick on emulator wallclock). High likely impact. 2. **Add `XboxHardwareInfo` (0x017A)** handler: 16B with `[0]=0x20` `[4]=0x06`. ~5 LOC. 3. **Add `ExConsoleGameRegion` (0x015F)** handler: 4B = `0xFFFF_FFFF`. ~3 LOC. 4. **Add `ExLoadedImageName` (0x01AD)** handler: 1024B containing module path. ~10 LOC. 5. **Fill `KernelGuestGlobals` object-type bytes** for the 10 ordinals listed in B.5 (Ev/Mu/Sem/Tim/IoCompl/IoDev/IoFile/ObDir/ObSym/Usbd). Sizing + bytes need to be read from canary `kernel_state.cc:1511+`. ~50-100 LOC. 6. **Honor `XEX_HEADER_DEFAULT_STACK_SIZE`** instead of hardcoded 1 MiB; allocate guard pages above and below the stack body. ~20-30 LOC. 7. **Add `XEX_HEADER_TLS_INFO` parsing** in the boot-CPU path: set initial `tls_slot_count = max(1024, header.slot_count)`, copy `raw_data_address` into the TLS block. ~30 LOC. 8. **Fix PCR field initialization** to include `pcr_ptr` (+0x30), `stack_base_ptr` (+0x70), `stack_end_ptr` (+0x74), `prcb` (+0x104). Resize PCR to `0x2D8`. Write a real guest KTHREAD VA into `[+0x100]` (allocate guest memory for X_KTHREAD struct, init with `KTHREAD` dispatcher header + minimum required fields, store its VA). ~50-80 LOC. 9. **Preload GPU gamma ramps** in `GpuSystem::new()` (or first init). ~20 LOC translating from canary's `command_processor.cc:130-148`. 10. **Mount `game:` symlink for any input type**, not just XISO. Add `HostPathDevice`/`StfsContainerDevice` cases. Larger change in VFS layer. Each step is independently testable via `--phase-b-snapshot` (the existing kernel-state dump hook). --- # §F — Source-of-truth files xenia-rs: - [xenia-rs/crates/xenia-cpu/src/context.rs](xenia-rs/crates/xenia-cpu/src/context.rs) — `PpcContext::new`, `LR_HALT_SENTINEL`, `VSCR_NJ_MASK`. - [xenia-rs/crates/xenia-app/src/main.rs](xenia-rs/crates/xenia-app/src/main.rs) — stack/PCR/TLS alloc, CPU context override, variable-export patching. - [xenia-rs/crates/xenia-kernel/src/state.rs](xenia-rs/crates/xenia-kernel/src/state.rs) — `KernelState` defaults, TLS index, handle allocator. - [xenia-rs/crates/xenia-kernel/src/xam.rs](xenia-rs/crates/xenia-kernel/src/xam.rs) — XAM stubs. - [xenia-rs/crates/xenia-kernel/src/xaudio.rs](xenia-rs/crates/xenia-kernel/src/xaudio.rs) — XAudio worker model. - [xenia-rs/crates/xenia-gpu/src/register_file.rs](xenia-rs/crates/xenia-gpu/src/register_file.rs) — GPU register file. - [xenia-rs/crates/xenia-gpu/src/mmio_region.rs](xenia-rs/crates/xenia-gpu/src/mmio_region.rs) — MMIO range. - [xenia-rs/crates/xenia-cpu/src/scheduler.rs](xenia-rs/crates/xenia-cpu/src/scheduler.rs) — scheduler / TLS slot count / hw threads. canary (reference): - See [inventory.md](inventory.md) §17 for the canary file list.