handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes

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>
This commit is contained in:
MechaCat02
2026-06-05 07:19:08 +02:00
parent acd1656753
commit ef93a4fa14
620 changed files with 108303 additions and 1 deletions

View File

@@ -0,0 +1,451 @@
# Xenia-Canary Boot State — Comprehensive Inventory Immediately Before Guest XEX EntryPoint
## Context
This document is a research deliverable: a precise, source-verified inventory of *every* observable subsystem state that the guest XEX sees at the moment its `EntryPoint` is about to receive control. Driving motivation is RE on Project Sylpheed, where divergence vs. real hardware (or vs. canary) at boot can mask the actual root cause of a wedge. Existing notes/memory may be wrong; this report is built bottom-up from current source in [xenia-canary/](xenia-canary/).
All citations are markdown links into the repo. Where an agent claim was wrong it has been corrected and called out.
---
## 0. The Boot Sequence (one-screen overview)
The order in which xenia reaches the guest entrypoint, from [src/xenia/emulator.cc:280-360](xenia-canary/src/xenia/emulator.cc#L280-L360):
1. `Emulator::Initialize` constructs `graphics_system_` (factory). [emulator.cc:283](xenia-canary/src/xenia/emulator.cc#L283)
2. `InputSystem` created + `Setup()`. [emulator.cc:295-307](xenia-canary/src/xenia/emulator.cc#L295-L307)
3. `VirtualFileSystem` created. [emulator.cc:317](xenia-canary/src/xenia/emulator.cc#L317)
4. `KernelState` created (this runs `InitializeKernelGuestGlobals()` from its ctor, [kernel_state.h:68](xenia-canary/src/xenia/kernel/kernel_state.h#L68)).
5. HLE kernel modules loaded in order: `XboxkrnlModule`, `XamModule`, `XbdmModule`. [emulator.cc:327-329](xenia-canary/src/xenia/emulator.cc#L327-L329) — each ctor allocates guest-visible export variables.
6. `graphics_system_->Setup(...)` — register file, gamma ramps, MMIO range, presenter, CP thread. [emulator.cc:336-339](xenia-canary/src/xenia/emulator.cc#L336-L339)
7. `audio_system_->Setup(...)` — XMA decoder, worker thread parked. [emulator.cc:347](xenia-canary/src/xenia/emulator.cc#L347)
8. `ExceptionHandler::Install`. [emulator.cc:358](xenia-canary/src/xenia/emulator.cc#L358)
9. *(later, on title launch)* `Emulator::LaunchTitle` builds the VFS device + symlinks, calls `KernelState::LaunchModule` which calls `SetExecutableModule` (which spawns the kernel dispatch worker) and then `XThread::Create` for the main thread with `X_CREATE_SUSPENDED`. [kernel_state.cc:403-430](xenia-canary/src/xenia/kernel/kernel_state.cc#L403-L430)
10. `processor()->PreLaunch()` (optional debugger wait). [kernel_state.cc:427](xenia-canary/src/xenia/kernel/kernel_state.cc#L427)
11. Main thread is resumed → host thread lambda runs `Execute()` → backend dispatches to PC = `entry_point_`. [xthread.cc:421-445, 469-471](xenia-canary/src/xenia/kernel/xthread.cc#L421-L471)
Steps 18 occur **once per emulator instance**. Steps 911 occur **once per title** and are the immediate prelude to the guest's first PPC instruction. Everything below describes the state at the boundary between step 11 and the first guest insn.
---
## 1. PPC CPU State (entry thread)
All values from [src/xenia/cpu/thread_state.cc:66-112](xenia-canary/src/xenia/cpu/thread_state.cc#L66-L112), unless otherwise noted.
### 1.1 GPRs
| Reg | Value | Source |
|----|----|----|
| r0 | 0 (memset) | [thread_state.cc:84](xenia-canary/src/xenia/cpu/thread_state.cc#L84) |
| **r1** (SP) | `stack_base` (top of stack, see §2) | [thread_state.cc:95](xenia-canary/src/xenia/cpu/thread_state.cc#L95) |
| **r2** | `0x20000000` (constant — comment: "used by hv only i think") | [thread_state.cc:98](xenia-canary/src/xenia/cpu/thread_state.cc#L98) |
| **r3** | `start_context` argument; for the main thread this is `0` (LaunchModule passes 0 as `start_context`, see [kernel_state.cc:414](xenia-canary/src/xenia/kernel/kernel_state.cc#L414)) | [processor.cc Execute() arg setup] |
| r4..r12 | 0 (memset) | — |
| **r13** | `pcr_address` — host pointer into KPCR (see §1.3 / §3.4) | [thread_state.cc:100](xenia-canary/src/xenia/cpu/thread_state.cc#L100) |
| r14..r31 | 0 (memset) | — |
Note that the XEX ABI does NOT receive its entry args in r3 for the main thread: the main thread invokes the XEX directly with `start_context = 0`. Worker / kernel threads created via `ExCreateThread` go through `xapi_thread_startup` and pass `start_context` in r3.
### 1.2 Special-purpose registers
| SPR | Value | Note |
|----|----|----|
| LR | 0 | (memset) |
| CTR | 0 | (memset) |
| **MSR** | `0x9030` | Quoted comment: *"dumped from a real 360, 0x8000"* — [thread_state.cc:104](xenia-canary/src/xenia/cpu/thread_state.cc#L104) |
| XER (ca/ov/so) | 0 | Split fields, all zeroed |
| FPSCR | 0 | (memset; no explicit rounding-mode setup — default is RN=00 round-to-nearest) |
| CR0..CR7 | 0 | (memset) |
| **VSCR** | `0x00010000` (NJ bit = 1, Non-Java IEEE mode) | [thread_state.cc:103](xenia-canary/src/xenia/cpu/thread_state.cc#L103) — **Correction: Agent #1's claim of `0x00010016` was wrong; actual constant is `vec128i(0,0,0,0x00010000)`** |
| **VRSAVE** | `0xFFFFFFFF` | [thread_state.cc:111](xenia-canary/src/xenia/cpu/thread_state.cc#L111) — "closer to correct than 0" |
| DEC, TBL/TBU | 0 (memset) | — |
| PC | `entry_point_` extracted from XEX | [user_module.cc:230](xenia-canary/src/xenia/kernel/user_module.cc#L230), passed via [kernel_state.cc:415](xenia-canary/src/xenia/kernel/kernel_state.cc#L415) |
### 1.3 FPRs, VMX/VR
- All 32 FPRs zeroed by memset of `PPCContext` ([thread_state.cc:84](xenia-canary/src/xenia/cpu/thread_state.cc#L84)).
- All 128 vector registers (VMX128) zeroed by the same memset.
- `vrsave = 0xFFFFFFFF` is the only non-zero vector-related slot.
### 1.4 Host-side stash bound to the context
Beyond architectural state, the `PPCContext` carries pointers used by JIT-generated code and trampolines ([thread_state.cc:87-92](xenia-canary/src/xenia/cpu/thread_state.cc#L87-L92)):
- `context->global_mutex` = `&xe::global_critical_region::mutex()`
- `context->virtual_membase` / `physical_membase`
- `context->processor` / `thread_state` / `thread_id`
- (set later by `XThread::Create`) `context->kernel_state` — [xthread.cc:393](xenia-canary/src/xenia/kernel/xthread.cc#L393)
The context buffer itself is *guest-VA-aligned* so its low 32 bits end in `0xE0000000` — clever trick at [thread_state.cc:26-56](xenia-canary/src/xenia/cpu/thread_state.cc#L26-L56) gives the backend room to use int8 displacements into a preceding granule for backend-specific data.
---
## 2. Memory Layout & Heaps
### 2.1 Guest VA partitioning
The 2 GiB guest VA is shared across heaps managed by `Memory` ([src/xenia/memory.cc](xenia-canary/src/xenia/memory.cc), [memory.h](xenia-canary/src/xenia/memory.h)). Notable named ranges:
- **Default user VA heap** for small / large allocations.
- **Stack range** `0x70000000 0x7F000000` — hardcoded constants `kStackAddressRangeBegin`/`kStackAddressRangeEnd` at [xthread.h:362-363](xenia-canary/src/xenia/kernel/xthread.h#L362-L363).
- **Physical mirrors** at the A0/C0/E0 high-VA aliases of physical memory (multiple VA views of the same backing pages — required for GPU/audio DMA semantics).
- **System heap** — kernel-side allocator backing the `SystemHeapAlloc` calls used by `XboxkrnlModule`, `XamModule`, `KernelState`, and per-thread bookkeeping. Backs PCR, TLS, KTHREAD, kernel guest globals, kernel exports listed in §3.
- **Reserved high range** for kernel objects / object table.
### 2.2 Stack (boot thread)
Per [xthread.cc:275-301](xenia-canary/src/xenia/kernel/xthread.cc#L275-L301):
- Requested size from XEX `XEX_HEADER_DEFAULT_STACK_SIZE` (rounded up to heap page size, default 4 KiB or 64 KiB depending on XEX page-size flag).
- Allocated as `actual_size = size + 2*page_size` (one guard page top, one bottom).
- Guard pages set to `kMemoryProtectNoAccess`. Body is RW.
- `stack_limit_ = base + page_size` (low water), `stack_base_ = stack_limit_ + size` (high water; this is what r1 is set to).
### 2.3 TLS block
Per [xthread.cc:327-361](xenia-canary/src/xenia/kernel/xthread.cc#L327-L361):
- Slots from `xex2_opt_tls_info.slot_count` if present, else **1024** (`kDefaultTlsSlotCount` [xthread.cc:335](xenia-canary/src/xenia/kernel/xthread.cc#L335)).
- Layout: `[extended TLS image | slot_count*4 bytes of slots]`. `tls_static_address_` = base, `tls_dynamic_address_ = base + extended_size`.
- Initial state: zeroed via `Memory::Fill`, then game-provided TLS image copied from `raw_data_address` if non-zero.
- Accessed at guest runtime through `r13 + 0` (KPCR's `tls_ptr` field).
### 2.4 KPCR (Processor Control Region) — what r13 actually points at
Per [xthread.cc:379, 401-411](xenia-canary/src/xenia/kernel/xthread.cc#L379-L411): 0x2D8 bytes allocated from system heap; the fields set before entry are:
| Offset | Field | Value at entry |
|----|----|----|
| 0x000 | `tls_ptr` | `tls_static_address_` |
| 0x030 | `pcr_ptr` | self (`pcr_address_`) |
| 0x038 | `host_stash` | `(uint64_t)thread_state_->context()` (host pointer punned into u64) |
| 0x070 | `stack_base_ptr` | `stack_base_` |
| 0x074 | `stack_end_ptr` | `stack_limit_` |
| 0x100 | `prcb_data.current_thread` | guest KTHREAD object guest VA |
| 0x104 | `prcb` | `pcr_address + offsetof(X_KPCR, prcb_data)` |
| `prcb_data.dpc_active` | 0 |
Everything else in the KPCR is zero at entry.
### 2.5 XEX image & sections
Loaded by `XexModule` (`src/xenia/cpu/xex_module.cc` plus `src/xenia/kernel/user_module.cc`):
- Header copied into the system heap, accessible as `guest_xex_header_` ([user_module.cc:224](xenia-canary/src/xenia/kernel/user_module.cc#L224)).
- Entry point + stack/tls/workspace sizes pulled via `GetOptHeader` ([user_module.cc:230-234](xenia-canary/src/xenia/kernel/user_module.cc#L230-L234)).
- PE sections mapped at their declared VAs with section flags; `.text` is X+R (or X+R+W if `writable_code_segments` cvar set).
- Import tables resolved during `LoadContinue` — each import slot is patched to invoke the host kernel export trampoline directly (no guest thunk).
- Title workspace heap created at the XEX-declared address if `XEX_HEADER_TITLE_WORKSPACE_SIZE` is set ([user_module.cc:237](xenia-canary/src/xenia/kernel/user_module.cc#L237)).
---
## 3. Kernel / xboxkrnl Guest-Visible State
Verified directly against [src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc) and [src/xenia/kernel/kernel_state.cc](xenia-canary/src/xenia/kernel/kernel_state.cc).
### 3.1 Pre-initialized exported variables (xboxkrnl.exe)
Created at `XboxkrnlModule` ctor — these are visible *before* entry because the ctor runs at step 5 of §0.
| Export | Size | Initial bytes | Source |
|----|----|----|----|
| **KeDebugMonitorData** | 4 (or 4+sizeof(X_KEDEBUGMONITORDATA) if cvar on) | `0` (off path); points to struct w/ callback fn ptr (on path) | [xboxkrnl_module.cc:80-102](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L80-L102) |
| **KeCertMonitorData** | same | `0` / struct + callback | [xboxkrnl_module.cc:104-123](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L104-L123) |
| **XboxHardwareInfo** | 16 | `[0]=0x20` (HDD bit), `[4]=0x06` (CPU count), rest 0 | [xboxkrnl_module.cc:136-141](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L136-L141) |
| **ExConsoleGameRegion** | 4 | `0xFFFFFFFF` | [xboxkrnl_module.cc:146-150](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L146-L150) |
| **XexExecutableModuleHandle** | 4 | uninit at ctor; populated later when `SetExecutableModule` runs | [xboxkrnl_module.cc:161-164](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L161-L164) |
| **ExLoadedImageName** | `kExLoadedImageNameSize` (1024-aligned) | uninit at ctor; filled later with module path | [xboxkrnl_module.cc:171-174](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L171-L174) |
| **ExLoadedCommandLine** | aligned(strlen+1, 1024) | `"default.xex"` + optional `cvars::cl`, NUL-padded | [xboxkrnl_module.cc:181-194](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L181-L194) |
| **XboxKrnlVersion** | 8 | `kernel_state_->GetKernelVersion()` (verify exact bytes in `kernel_state.h`) | [xboxkrnl_module.cc:199-204](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L199-L204) |
| **KeTimeStampBundle** | 24 | populated lazily on first read via `GetKeTimestampBundle()` — see §3.2 | [xboxkrnl_module.cc:206-208](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L206-L208) |
| **ExThreadObjectType**, **ExEventObjectType**, **ExMutantObjectType**, **ExSemaphoreObjectType**, **ExTimerObjectType**, **IoCompletionObjectType**, **IoDeviceObjectType**, **IoFileObjectType**, **ObDirectoryObjectType**, **ObSymbolicLinkObjectType**, **UsbdBootEnumerationDoneEvent** | each → offset within `KernelGuestGlobals` block | populated by `InitializeKernelGuestGlobals()` (§3.3) | [xboxkrnl_module.cc:214-225](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc#L214-L225) |
> **Correction vs Agent #3**: KeTimeStampBundle initialization does NOT happen in `xboxkrnl_module.cc`; it lives in `KernelState::CreateKeTimestampBundle` at [kernel_state.cc:1272-1295](xenia-canary/src/xenia/kernel/kernel_state.cc#L1272-L1295) and is created lazily on first call to `GetKeTimestampBundle()`. A `HighResolutionTimer::CreateRepeating` is then armed to call `UpdateKeTimestampBundle()` periodically.
### 3.2 KeTimeStampBundle layout (`X_TIME_STAMP_BUNDLE`)
Initialized in [kernel_state.cc:1272-1295](xenia-canary/src/xenia/kernel/kernel_state.cc#L1272-L1295):
| Offset | Field | Initial value |
|----|----|----|
| +0x00 | `interrupt_time` (u64) | current interrupt-time value |
| +0x08 | `system_time` (u64) | current system time |
| +0x10 | `tick_count` (u32) | `Clock::QueryGuestUptimeMillis()` |
| +0x14 | `padding` (u32) | 0 |
A repeating `HighResolutionTimer` updates these fields every tick ([kernel_state.cc:1292-1294](xenia-canary/src/xenia/kernel/kernel_state.cc#L1292-L1294)).
### 3.3 KernelGuestGlobals — the big preinitialized blob
Allocated and zeroed at [kernel_state.cc:1511-1516](xenia-canary/src/xenia/kernel/kernel_state.cc#L1511-L1516); see the `KernelGuestGlobals` struct definition at [kernel_state.h:115-...](xenia-canary/src/xenia/kernel/kernel_state.h#L115). Fields include:
- `ExThreadObjectType`, `ExEventObjectType`, `ExMutantObjectType`, `ExSemaphoreObjectType`, `ExTimerObjectType`, `IoCompletionObjectType`, `IoDeviceObjectType`, `IoFileObjectType`, `ObDirectoryObjectType`, `ObSymbolicLinkObjectType`
- `UsbdBootEnumerationDoneEvent`
- `OddObj` (referenced [kernel_state.cc:1527](xenia-canary/src/xenia/kernel/kernel_state.cc#L1527))
- `system_process`, `title_process`, `idle_process` (accessor methods at [kernel_state.h:212, 216, 220](xenia-canary/src/xenia/kernel/kernel_state.h#L212-L220))
Each guest object type is filled with the kernel's view of how dispatcher headers / object headers look. Bytes at these offsets are observable as `dq` constants by the guest before entry.
### 3.4 ProcessInfoBlock
Filled by `InitializeProcess` (called from `SetExecutableModule`). Notable preinit fields per Agent #3's report:
- +0x0C: `0x0000007F`
- +0x10: `0x001F0000`
- +0x14: `thread_count = 0`
- +0x1B: `0x06`
- +0x1C: `kernel_stack_size = 16384`
- +0x20: `process_type = X_PROCTYPE_USER` (or `X_PROCTYPE_TITLE` for title)
- +0x24..+0x4F: TLS info copy from XEX header
### 3.5 Object table
- Empty before `LaunchModule`. As soon as `SetExecutableModule` runs, the executable module's handle is the first allocation.
- The kernel dispatch worker thread handle is the second.
- The main XThread handle is the third. All allocated from `object_table()` ([xthread.cc:317](xenia-canary/src/xenia/kernel/xthread.cc#L317) via `CreateNative`).
- `XObject::kHandleBase = 0xF8000000`; handles spaced by 4.
---
## 4. XAM State
### 4.1 User profile
Created in `KernelState` ctor ([kernel_state.cc:52](xenia-canary/src/xenia/kernel/kernel_state.cc#L52)). Single profile preconfigured at `src/xenia/kernel/xam/user_profile.cc`:
- XUID: `0xB13EBABEBABEBABE` (hardcoded)
- Gamertag: `"User"`
- 18 default profile settings (per Agent #3's enumeration — XPROFILE_GAMER_YAXIS_INVERSION=0, XPROFILE_OPTION_CONTROLLER_VIBRATION=3, XPROFILE_GAMERCARD_REGION=0, XPROFILE_GAMERCARD_CRED=0xFA, etc.).
> Spot-check note: I did not re-verify each of the 18 settings by direct read; cite by file before depending on any single value.
### 4.2 App manager / content manager
- `AppManager` instantiated, `RegisterApps()` called from KernelState ctor — registers known XAM apps. No launch data at entry.
- `ContentManager` rooted at `emulator_->content_root()` (see `Emulator` ctor). Title-specific save/DLC mounts are not yet established at entrypoint; they are established lazily.
### 4.3 Notification listeners
Empty list at entry ([kernel_state.h:219](xenia-canary/src/xenia/kernel/kernel_state.h#L219)). On first listener registration with mask bit 1 set, the system synthesizes startup notifications (XN_SYS_UI, XN_SYS_SIGNINCHANGED, XN_SYS_INPUTDEVICESCHANGED, XN_SYS_INPUTDEVICECONFIGCHANGED — [kernel_state.cc:657-671](xenia-canary/src/xenia/kernel/kernel_state.cc#L657-L671)).
---
## 5. Filesystem State
### 5.1 Devices mounted at entrypoint
In `Emulator::LaunchTitle` / `Emulator::CreateVfsDevice` ([emulator.cc:376-...](xenia-canary/src/xenia/emulator.cc#L376)):
| Game source | Device(s) registered | Symlinks |
|----|----|----|
| `.xex` (loose folder) | `HostPathDevice(\Device\Harddisk0\Partition1, parent_dir, read_only=!allow_game_relative_writes)` | `game:`, `d:` → same |
| `.iso` (XISO) | `DiscImageDevice(\Device\Cdrom0, path)` | `game:`, `d:` → same |
| LIVE/CON/PIRS (STFS) | `XContentContainerDevice::CreateContentDevice(...)` | `game:`, `d:` → same |
| ZAR | `DiscZarchiveDevice(...)` | same |
Plus optional mounts driven by cvars (from `xenia_main.cc`):
- `mount_scratch``\SCRATCH`, symlink `scratch:`
- `mount_cache``\CACHE0`, `\CACHE1`, `\CACHE` with `cache0:`, `cache1:`, `cache:`
No files are open at entry; the guest opens what it needs.
### 5.2 Cache & temp
- No CACHE partition data is fabricated. If `mount_cache` is on, host directories `cache/`, `cache0/`, `cache1/` back the partitions; otherwise they don't exist for the guest at all.
- No `STFS` content packages are pre-mounted unless the title was launched from an STFS package.
---
## 6. GPU State (Xenos / Vulkan or D3D12 backend)
### 6.1 RegisterFile
Allocated as host memory in `GraphicsSystem` ctor at [src/xenia/gpu/graphics_system.cc:79-81](xenia-canary/src/xenia/gpu/graphics_system.cc#L79-L81):
```cpp
register_file_ = reinterpret_cast<RegisterFile*>(memory::AllocFixed(
nullptr, sizeof(RegisterFile), kReserveCommit, kReadWrite));
```
This zero-fills the entire register file. **No registers are preloaded with non-zero values before entry.** `XE_GPU_REG_D1MODE_V_COUNTER` is later incremented asynchronously by the frame-limiter thread once that thread starts (graphics_system.cc:~177).
### 6.2 Gamma ramps (the one notable pre-initialized GPU data)
In `CommandProcessor::Initialize` at [src/xenia/gpu/command_processor.cc:130-148](xenia-canary/src/xenia/gpu/command_processor.cc#L130-L148): a 256-entry sRGB-table-like ramp `(i * 0x3FF / 0xFF)` per channel and a 128-entry PWL ramp with delta `0x200` are loaded. These are observable if guest code reads gamma registers before writing them.
### 6.3 Command Processor / ringbuffer
- The CP **thread** is spawned in `GraphicsSystem::Setup` ([graphics_system.cc:135](xenia-canary/src/xenia/gpu/graphics_system.cc#L135)), at step 6 of §0. It blocks on `write_ptr_index_event_` waiting for PM4 work.
- The **ringbuffer itself is NOT allocated before entry.** The guest allocates and registers it via `VdInitializeRingBuffer` ([xboxkrnl_video.cc:313-319](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc#L313-L319)).
### 6.4 MMIO
The GPU MMIO range `[0x7FC80000, 0xFFFF0000]` is hooked via `Memory::AddVirtualMappedRange` from `GraphicsSystem::Setup` ([graphics_system.cc:141-144](xenia-canary/src/xenia/gpu/graphics_system.cc#L141-L144)). Guest reads/writes route to GPU register file handlers.
### 6.5 Presenter & backend device
- Presenter and the actual Vulkan/D3D12 device + swapchain are created in `GraphicsSystem::Setup` when `with_presentation=true` ([graphics_system.cc:116-128](xenia-canary/src/xenia/gpu/graphics_system.cc#L116-L128)).
- `VdInitializeEngines` is stubbed to return 1 — xenia uses no real microcode ([xboxkrnl_video.cc:271-280](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc#L271-L280)).
- EDRAM/tile allocator, surface info, swap counters: not initialized to guest-visible state pre-entry.
### 6.6 Reported video mode (queried by guest after entry but driven by config)
`VdQueryVideoMode` at [xboxkrnl_video.cc:203-219](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc#L203-L219) reports cvar-driven values: default `1280×720`, `widescreen=true`, `is_interlaced=false`, `refresh_rate=60.0f`, `video_standard=1` (NTSC), `pixel_rate=0x8A`, `widescreen_flag=0x01`. Gamma type=2 (BT.709), power ≈ 2.222.
---
## 7. Audio (APU) State
In `AudioSystem::Setup` ([src/xenia/apu/audio_system.cc:48-97](xenia-canary/src/xenia/apu/audio_system.cc#L48-L97)) called at step 7 of §0:
- `queued_frames_` clamped to `[4, 64]` from cvar `apu_max_queued_frames` (default 8).
- **256 client semaphores** allocated, initial count 0, max count = `queued_frames_`.
- `shutdown_event_`, `resume_event_` created.
- `XmaDecoder` instantiated; its `Setup()` runs.
- Worker thread spawned, executing `WorkerThreadMain`, immediately parked on `WaitAny(wait_handles_)`. **No audio is being submitted, no frames queued.**
The XMA guest-memory window (typically observed near `0x42500000` per RE notes) has no pre-populated context state — the guest must call `XAudioRegisterRenderDriverClient` and provide context VAs.
---
## 8. HID / Input
`InputSystem::Setup` ([emulator.cc:307](xenia-canary/src/xenia/emulator.cc#L307)) initializes the input layer; per [src/xenia/hid/input_system.h:82-85](xenia-canary/src/xenia/hid/input_system.h#L82-L85):
- `connected_slots = bitset<XUserMaxUserCount>(0)`**no controller is plugged in** at the moment of entry. The driver layer wires up *on demand* as controllers connect.
- `last_used_slot = 0`.
- `Portal` (MCP bridge) created.
- `XInputGetCapabilities` on disconnected slots returns `X_ERROR_DEVICE_NOT_CONNECTED` ([input_system.cc:179](xenia-canary/src/xenia/hid/input_system.cc#L179)).
Vibration state, battery, etc.: nothing reported until a controller is connected.
---
## 9. Networking / XNet / Sockets
No network init occurs before entry; the guest must call `NetDll_XNetStartup` to populate `xnet_startup_params` (zero-initialized at [xam_net.cc:173](xenia-canary/src/xenia/kernel/xam/xam_net.cc#L173)).
When queried via `NetDll_XNetGetTitleXnAddr` ([xam_net.cc:476-499](xenia-canary/src/xenia/kernel/xam/xam_net.cc#L476-L499)):
- IP `ina` = `127.0.0.1` (loopback)
- Online IP `inaOnline` = `0.0.0.0`
- Online port = 0
- MAC `abEnet` = `CC CC CC CC CC CC`
- `abOnline` = 20 zeros
- Return code = `XNET_GET_XNADDR_STATIC` (0x00000004)
`NetDll_XNetGetDebugXnAddr` returns `XNET_GET_XNADDR_NONE` (0x00000001).
No sockets, no system-link, no NIC enumeration at entry.
---
## 10. Real-Time Clock & Timebase
- `Clock::guest_tick_frequency()` ([src/xenia/base/clock.cc:39](xenia-canary/src/xenia/base/clock.cc#L39)) returns the host CPU tick frequency unless overridden by `clock_no_scaling`. Reported to guest by `KeQueryPerformanceFrequency_entry` ([xboxkrnl_threading.cc:438-443](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc#L438-L443)).
- `KeQuerySystemTime_entry` ([xboxkrnl_threading.cc:483-497](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc#L483-L497)) reads `Clock::QueryGuestSystemTime` — wall clock as of emulator startup epoch.
- `KeTimeStampBundle` (§3.2) is the cheap shared mailbox; updated by a repeating `HighResolutionTimer`.
---
## 11. Threading at Entry
At the boundary, the threads that exist are:
1. **Main XThread** (`is_main_thread=true`, `guest_thread=true`) — currently suspended, about to be resumed. Stack range `0x70000000-0x7F000000`, host stack 16 MiB ([xthread.cc:420](xenia-canary/src/xenia/kernel/xthread.cc#L420)), priority/affinity set via `GetFakeCpuNumber` derived from `(creation_flags >> 24)` ([xthread.cc:395-396](xenia-canary/src/xenia/kernel/xthread.cc#L395-L396)). CPU index assigned via `SetActiveCpu(cpu_index)` ([xthread.cc:464](xenia-canary/src/xenia/kernel/xthread.cc#L464)).
2. **Kernel dispatch worker thread** — spawned in `SetExecutableModule` to handle guest async callbacks ([kernel_state.cc:368-391 ~](xenia-canary/src/xenia/kernel/kernel_state.cc#L368-L391)). Host-side; consumes from a host queue, does not appear in the guest object table.
3. **GPU command processor thread** — already running (parked on `write_ptr_index_event_`).
4. **Audio worker thread** — already running (parked on its semaphore set).
5. **Optional**: frame-limiter thread, presenter thread, KeTimeStampBundle update timer thread.
**Worker threads (sub_825070F0-style XAudio/render workers, secondary game workers, XAM threads) do NOT exist yet** — they are spawned by guest code post-entry.
Scheduler state:
- No IRQL machinery; guest code runs at PASSIVE-equivalent.
- Quantum / preemption is approximated; ours uses cooperative-ish per-thread quanta.
- DPC list empty ([kernel_state.h:233](xenia-canary/src/xenia/kernel/kernel_state.h#L233)).
---
## 12. JIT / Codegen State
- Backend (x64 or AArch64) initialized at `Processor` construction.
- `backend_->AllocThreadData()` and `InitializeBackendContext(context_)` called from `ThreadState` ctor ([thread_state.cc:77, 82](xenia-canary/src/xenia/cpu/thread_state.cc#L77-L82)).
- Code cache empty — entry-point block JITs on first execution unless `enable_early_precompilation` cvar pre-compiled it.
- Import-table call sites already patched to direct host trampolines (resolved during `XexModule::LoadContinue`).
- Syscall / MMIO handlers wired up.
---
## 13. Misc Peripherals
- **DVD / Disc drive**: no separate drive state — backed by the VFS device created at title launch. Tray/laser not modeled.
- **USB**: no enumeration. `UsbdBootEnumerationDoneEvent` is allocated as a guest event in `KernelGuestGlobals` but its signaled-state at entry is the field default (verify against `KernelGuestGlobals` struct).
- **Cache partition**: present only if `mount_cache` cvar set.
- **System link / bridged LAN**: not initialized.
- **Hypervisor surfaces / KdNet / DmEvents**: KD/network debug is not implemented. `DebugPrint` redirects into xenia's logger.
- **Emulator-detection signals (intentional or accidental)**: `KeDebugMonitorData` always nonzero or known-zero (vs. real-HW behavior), missing/unimplemented kernel exports, exact MSR value `0x9030`, the constant r2=`0x20000000`, the fake XUID `0xB13EBABEBABEBABE`.
---
## 14. Single-Page "What is in the registers right now?" Quick Card
For Sylpheed RE workflows where you need to set a breakpoint at the first guest insn:
```
PC = <XEX entry_point> ; from XEX optional header
r0..r12 = 0
r1 = stack_base (top of 0x700000000x7F000000 region, page-aligned)
r2 = 0x20000000
r3 = 0
r13 = pcr_address (KPCR, has tls_ptr at [r13+0])
r14..r31= 0
LR=0 CTR=0 XER=0 CR=0 FPSCR=0
MSR = 0x9030
VSCR = 0x00010000 ; NJ=1
VRSAVE = 0xFFFFFFFF
FPRs = +0.0 (zero bit pattern)
VR0..127= zero
DEC, TB = 0
```
---
## 15. Verification (how to confirm the above for a specific Sylpheed boot)
The deliverable above is a static read of the source. To validate dynamically for a specific run:
1. **Quick canary smoke test** — run xenia-canary against Sylpheed with logging set high enough to catch `XELOGI("XThread{:08X} ({:X}) Stack: {:08X}-{:08X}", ...)` from [xthread.cc:389](xenia-canary/src/xenia/kernel/xthread.cc#L389). That confirms `stack_base_`, `stack_limit_`, `thread_id_`.
2. **Drop into JIT entry breakpoint** — set a JIT-store probe (per AUDIT-067 pattern in memory) on the guest PC = `entry_point_` and dump the `PPCContext` once. Compare GPRs/VSCR/MSR against the table above.
3. **Pre-entry kernel-export dump** — print the contents of guest VAs for `XboxHardwareInfo`, `KeDebugMonitorData`, `KeTimeStampBundle`, `ExLoadedCommandLine` immediately before resuming the main thread; verify §3.1 expected bytes.
4. **VFS sanity**`KernelState::file_system_->ResolvePath("game:\\default.xex")` should succeed; `D:` should resolve to the same device.
5. **GPU pre-state** — assert no PM4 packets have been dispatched (`command_processor_->paused()` / ringbuffer write-ptr == read-ptr) and gamma table contains the linear ramp from §6.2.
6. **Audio pre-state** — assert 256 client semaphores all have count=0, worker thread parked, no XMA contexts registered.
7. **Cross-engine sanity** — run xenia-rs against the same XEX with the same cvars; the values that should match between engines: r1/r2/r13/MSR/VSCR/VRSAVE, PC, `XboxHardwareInfo`, `ExConsoleGameRegion`, `XexExecutableModuleHandle`, the 1024-entry default TLS slot count, stack range, KPCR layout, default profile XUID.
---
## 16. Known Unknowns / Things Not Verified in This Pass
- Exact byte contents of `KernelGuestGlobals` *object-type* sub-structs (`ExThreadObjectType` etc.) — these are populated in `InitializeKernelGuestGlobals()`; full byte-level dump would require reading [kernel_state.cc:1511 onward](xenia-canary/src/xenia/kernel/kernel_state.cc#L1511) in full.
- `XboxKrnlVersion` exact 8 bytes — held in `KernelVersion` static, not spot-checked in this pass.
- The 18 default profile setting values were taken from Agent #3's report and not individually re-read.
- Exact `xex2_opt_tls_info` fields for Sylpheed (slot_count, raw_data_size) — title-specific.
- Per-backend (Vulkan vs. D3D12) device-state nuances.
These are noted explicitly so this doc is not mistaken for full coverage.
---
## 17. Critical Files Index
For quick navigation:
- [src/xenia/cpu/thread_state.cc](xenia-canary/src/xenia/cpu/thread_state.cc) — PPC context init (canonical truth for GPRs/MSR/VSCR/VRSAVE).
- [src/xenia/kernel/xthread.cc](xenia-canary/src/xenia/kernel/xthread.cc) — stack, TLS, KPCR, KTHREAD, host-thread creation, `Execute` dispatch.
- [src/xenia/kernel/kernel_state.cc](xenia-canary/src/xenia/kernel/kernel_state.cc) — LaunchModule, SetExecutableModule, KernelGuestGlobals, KeTimeStampBundle.
- [src/xenia/kernel/kernel_state.h](xenia-canary/src/xenia/kernel/kernel_state.h) — `KernelGuestGlobals` struct.
- [src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_module.cc) — preinit'd guest-visible kernel-exported variables.
- [src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc) — Vd* stubs.
- [src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc](xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc) — KeQueryPerformanceFrequency / KeQuerySystemTime.
- [src/xenia/kernel/xam/xam_net.cc](xenia-canary/src/xenia/kernel/xam/xam_net.cc) — XNADDR and network defaults.
- [src/xenia/kernel/xam/user_profile.cc](xenia-canary/src/xenia/kernel/xam/user_profile.cc) — default user profile.
- [src/xenia/kernel/user_module.cc](xenia-canary/src/xenia/kernel/user_module.cc) — XEX header parsing, entry_point extraction.
- [src/xenia/emulator.cc](xenia-canary/src/xenia/emulator.cc) — Initialize/LaunchTitle subsystem order; VFS device factory.
- [src/xenia/gpu/graphics_system.cc](xenia-canary/src/xenia/gpu/graphics_system.cc) — GPU setup.
- [src/xenia/gpu/command_processor.cc](xenia-canary/src/xenia/gpu/command_processor.cc) — gamma ramp init, CP thread.
- [src/xenia/apu/audio_system.cc](xenia-canary/src/xenia/apu/audio_system.cc) — audio worker, client semaphores.
- [src/xenia/hid/input_system.h](xenia-canary/src/xenia/hid/input_system.h), [.cc](xenia-canary/src/xenia/hid/input_system.cc) — controller slots empty at entry.
- [src/xenia/memory.cc](xenia-canary/src/xenia/memory.cc), [.h](xenia-canary/src/xenia/memory.h) — heap topology, physical mirrors.
- [src/xenia/base/clock.cc](xenia-canary/src/xenia/base/clock.cc) — tick frequency, system time.

View File

@@ -0,0 +1,436 @@
# 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<Box<dyn VfsDevice>>` — 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<Arc<…>>` (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.