# Phase C — fix patch The fix is in the **diff/snapshot infrastructure**, not in either engine's XEX loader. No engine bug was found; the Phase B STOP invariant was over-strict. ## Files modified 1. `xenia-rs/tools/diff-state/diff_state.py` — relaxed STOP invariant. When `--xex-json` is supplied AND both snapshots have `image.bin`, compute `image_canonical_sha256` (XEX import slots masked) and check that as the STOP key. The raw `image_loaded_sha256` is reported but informational. 2. `xenia-rs/crates/xenia-kernel/src/phase_b_snapshot.rs` — when `phase_b_dump_section_content` is set, also write `image.bin` with raw bytes of the XEX-image region. Default-off; inert when cvar OFF (cvar-OFF digest byte-identical to pre-Phase-C baseline). 3. `xenia-canary/src/xenia/kernel/phase_b_snapshot.cc` — same. ## Diff (relative to pre-Phase-C state) Generated via `git diff --no-index` against an unmodified baseline. The full unified diffs are below; see also re-validation.md for proof both engines still build and all gates pass. --- /dev/fd/63 2026-05-13 22:41:06.597568277 +0200 +++ /dev/fd/62 2026-05-13 22:41:06.596568265 +0200 @@ -1,2 +1,25 @@ let _ = write_file(&engine_dir.join("manifest.json"), &body); + + // Phase C: when dump_section_content is on, write raw bytes of the + // XEX image region to /image.bin. This is the only + // region positionally matched between canary and ours, so it's the + // only one suitable for byte-level diff. + if state.phase_b_dump_section_content && state.image_base != 0 { + let mut sz: u32 = 0; + let mut a = state.image_base; + while mem.is_mapped(a) { + sz = sz.wrapping_add(4096); + let next = a.wrapping_add(4096); + if next < a { + break; + } + a = next; + } + if sz > 0 { + let bytes = read_bytes(mem, state.image_base, sz); + if let Err(e) = std::fs::write(engine_dir.join("image.bin"), &bytes) { + tracing::warn!("phase_b_snapshot: image.bin write failed: {}", e); + } + } + } } ---canary phase_b_snapshot.cc change (only the appended block): // Phase C: when dump_section_content is on, write raw bytes of the // XEX image region to /image.bin. This is the only // region positionally matched between canary and ours, so it's the // only one suitable for byte-level diff. if (cvars::phase_b_dump_section_content) { auto exec_module = kstate->GetExecutableModule(); if (exec_module) { uint32_t image_base = exec_module->xex_module()->base_address(); uint32_t image_size = exec_module->xex_module()->image_size(); uint8_t* host = kstate->memory()->TranslateVirtual(image_base); if (host && image_size > 0) { std::filesystem::path ip = engine_dir / "image.bin"; std::FILE* bf = std::fopen(ip.string().c_str(), "wb"); if (bf) { std::fwrite(host, 1, image_size, bf); std::fflush(bf); std::fclose(bf); } } } } }