diff --git a/docs/CROSS_BUILD_SETUP.md b/docs/CROSS_BUILD_SETUP.md index 4c5d562df..ecbdf9b9a 100644 --- a/docs/CROSS_BUILD_SETUP.md +++ b/docs/CROSS_BUILD_SETUP.md @@ -1,100 +1,1289 @@ -# Building and Running Xenia Canary Windows Debug from Linux + -## What we built and why +> ⚠️ **The audience line below predates a decision.** It assumes the `xenia-rs` +> Rust emulator, which is **retired** — Canary *is* the emulator and the oracle +> now, and this guide's value is unchanged: it is how you get a Windows-MSVC +> debug build running under Wine. Read "audit oracle for the Rust port" as +> "instrumented build for RE". -A Windows-MSVC debug `xenia_canary.exe` cross-compiled on Linux, runnable under Wine. This serves as the audit oracle for the xenia-rs Rust port: the Linux-native canary build stalls before the front-end UI, so all audit comparisons (memory entries audit_044–audit_059) need the Wine-targeted debug binary as the ground-truth reference engine. +# Building xenia-canary as a Windows Debug Binary on Linux — Complete Setup Guide -## Phase 1 — Host setup (run in your terminal) +> **Audience**: A fresh Linux machine with the `xenia-rs` Rust project cloned but no `xenia-canary` checkout yet, and no cross-compile toolchain. By the end of this guide you will have an instrumentation-ready Windows-MSVC debug build of xenia-canary running under Wine, suitable as the audit oracle for the Rust port. - sudo apt install -y clang-18 lld-18 llvm-18 # clang-cl driver mode + lld-link + binutils - sudo ln -s /usr/bin/clang-18 /usr/bin/clang-cl # activates MSVC-compatible driver mode - xwin --accept-license --arch x86_64 splat --include-debug-libs --output ~/.xwin/splat - # ~807 MB Win10 SDK + MSVC CRT sysroot - curl ... linkid=2361406 -o ~/winsdk.iso # Win10 SDK ISO for real fxc.exe (SM 5_1) - winetricks -q vkd3d # vkd3d-proton d3d12core.dll (5.96 MB) - winetricks -q dxvk # DXVK dxgi.dll (2.95 MB) +--- -Ubuntu's clang-18 / lld-18 apt packages ship binutils suffixed (llvm-lib-18, lld-link-18, etc.); the toolchain expects unsuffixed names on PATH. Create 4 symlinks in `~/.local/bin/` (no sudo needed since it was already on PATH). +## Preface — context for a fresh receiver -## Phase 2 — In-tree edits +### Why this build exists -### 8 build-fix patches (compile-time only; zero runtime semantics changes) +The receiver's Rust project (`xenia-rs`) is a partial reimplementation of [xenia-canary](https://github.com/xenia-canary/xenia-canary), the community-maintained fork of the Xenia Xbox 360 emulator. Audits compare `xenia-rs` execution traces against canary execution traces on the same guest binary (the Project Sylpheed XEX, embedded in the project ISO) to identify divergences. For those comparisons to be meaningful: -| § | File | What changed | +1. **Canary must be debug-mode** so XELOG channels (`i> Setup: ...`, `K> XThread::Execute ...`, etc.) emit verbosely. +2. **Canary must run on the same host as `xenia-rs`** to remove machine-state confounds. +3. **Canary must accept custom audit instrumentation** (new `cvars::audit_XX_*` flags and `XELOGI("AUDIT-XX-…")` trace lines) that can be toggled per run. + +A **Linux-native** canary build exists (`xb build` in the upstream tree) but in practice doesn't render past the splash screen on this stack — the host XCB / Vulkan pipeline gets stuck before the front-end UI advances. The **Windows-targeted** canary build runs the game cleanly when launched under Wine with the right runtime DLLs (`vkd3d-proton` + `DXVK`). So the workable oracle is: cross-compile a Windows debug build on Linux, run it under Wine. + +### What you'll produce + +| Artifact | Path | Approx size | |---|---|---| -| 7.1 | `src/xenia/base/mapped_memory_win.cc:30` | `constexpr` → `const` for `kFileHandleInvalid` (clang-cl rejects `reinterpret_cast` in constant expressions) | -| 7.2 | `src/xenia/app/main_resources.rc:3` | `..\\..\\..\\assets\\icon\\icon.ico` → forward slashes (llvm-rc backslash literalism) | -| 7.3 | `third_party/snappy/snappy-stubs-public.h` | Already in place via the `fabi/sylpheed-crossbuild` snappy fork submodule | -| 7.4 | `third_party/zlib-ng/zconf-ng.h:113` | `#if 1` → `#if !defined(_WIN32)` for `Z_HAVE_UNISTD_H` (header was pre-generated on Linux) | -| 7.5 | `third_party/CMakeLists.txt:335` | Extended `if(NOT MSVC)` → `if(NOT MSVC OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")` for zlib-ng AVX flags | -| 7.6a | `CMakeLists.txt` | Wrapped `/RTCsu` appends in `CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC" AND NOT … "Clang"` | -| 7.6b | `CMakeLists.txt:85-101` | Inserted `execute_process` block invoking `xenia-build.py:generate_version_h()` so CMake-direct flow produces `version.h` | -| 7.7 | `cmake/XeniaHelpers.cmake` | Added `_env_prefix` to wrap per-shader commands in `cmake -E env FXC_PATH=…` (`cmake set(ENV{…})` doesn't survive ninja's subprocess spawn) | -| 7.8 | `tools/build/compile_shader_dxbc.py:74` | Added `_wineify()` helper translating unix paths via `winepath -w` before fxc sees them (fxc parses `/home/…` as a `/h` switch) | +| Windows PE32+ executable | `xenia-canary/build-cross/bin/Windows/Debug/xenia_canary.exe` | 27 MB | +| Matching CodeView PDB | `xenia-canary/build-cross/bin/Windows/Debug/xenia_canary.pdb` | 114 MB | -### 3 new cross-compile config files +### Time and disk budget -| File | Purpose | +| Resource | First-time | Incremental | +|---|---|---| +| Wall time (decent box: 16+ cores, 30 Mbps net) | ~30 minutes total | < 60 seconds per code change | +| Network download | ~2.3 GB (xwin ~600 MB + Win10 SDK ISO ~1.1 GB + Wine bits ~150 MB + xenia source ~400 MB) | 0 | +| Free disk required | ~10 GB | ~6 GB (build dir keeps growing as you instrument) | + +### Workflow at a glance + +The setup is a 12-phase pipeline. Each phase has a **[CHECKPOINT]** verification command — run it before moving on. Failure-mode lookups are in Appendix A. + +``` +Phase 0 → Workspace baseline + path conventions +Phase 1 → System packages (apt + cargo) +Phase 2 → clang-cl symlink +Phase 3 → xwin: download + splat the Win10 SDK & MSVC CRT +Phase 4 → Win10 SDK fxc.exe: ISO download + CAB extraction +Phase 5 → Wine runtime DLL trio (vkd3d-proton + DXVK + system Wine) +Phase 6 → Clone xenia-canary +Phase 7 → Apply 8 build-fix source patches +Phase 8 → Add 3 cross-compile config files +Phase 9 → Configure (cmake --preset) +Phase 10 → Build (cmake --build) +Phase 11 → Smoke test under Wine (no error dialogs!) +Phase 12 → Wire into the xenia-rs audit workflow +``` + +Phases 3 and 4 can run in parallel (both are bandwidth-bound and independent). Everything else is sequential. + +--- + +## Phase 0 — Workspace baseline & path conventions + +This guide uses the following placeholders. Bind them in your shell once and re-use them throughout — every command below assumes they are set. + +```bash +export XENIA_RS_ROOT="/home/$(whoami)/RE - Project Sylpheed" # adjust if you keep things elsewhere +export XENIA_CANARY_SRC="$XENIA_RS_ROOT/xenia-canary" +export XWIN_DIR="$HOME/.xwin/splat" +export FXC_CROSS_DIR="$HOME/.local/share/xenia-cross/fxc" +export ISO_PATH="$XENIA_RS_ROOT/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso" +``` + +> **[CHECKPOINT P0]** Confirm `xenia-rs/` exists at the expected location and the ISO is readable: +> ```bash +> ls -d "$XENIA_RS_ROOT/xenia-rs" && test -r "$ISO_PATH" && echo OK +> ``` +> Expected output: a directory listing and `OK`. If the path is different on your system, re-export the variables accordingly. + +The workspace layout you're targeting: + +``` +$XENIA_RS_ROOT/ + ├── xenia-rs/ ← already present (your Rust port + audit-runs) + ├── sylpheed-reborn/ ← already present (asset-side Rust tools, optional) + ├── xenia-canary/ ← will be cloned in Phase 6 (THIS GUIDE'S PRODUCT) + │ └── build-cross/bin/Windows/Debug/ ← will be built in Phase 10 + │ ├── xenia_canary.exe ← the binary you'll run + │ └── xenia_canary.pdb ← debug symbols for crash backtraces + └── Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso ← already present +``` + +--- + +## Phase 1 — System packages + +The package list is one apt command. Cargo is needed for `xwin` and isn't shipped with most distros. + +### 1.1 apt packages + +```bash +sudo apt update +sudo apt install -y \ + clang-18 lld-18 llvm-18 \ + cmake ninja-build \ + python3 \ + wine64 winetricks \ + p7zip-full \ + curl wget \ + git \ + pkg-config \ + build-essential +``` + +**Tested distros**: Ubuntu 24.04 LTS, Linux Mint 22.1 (Xia, Ubuntu-24.04-base). Other Debian/Ubuntu derivatives ≥ 24.04 should work identically. On older LTS (22.04) the clang ≥ 16 floor for xwin's MSVC STL may not be met from the default repos — add LLVM's official apt source if so. + +### 1.2 Rust toolchain + +If you don't already have `cargo`: +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source "$HOME/.cargo/env" +``` + +The receiver may already have a Rust toolchain because `xenia-rs` is Rust. Confirm: +```bash +cargo --version # ≥ 1.70 +rustc --version # ≥ 1.70 +``` + +> **[CHECKPOINT P1]** All required tools resolve and report a sane version: +> ```bash +> for t in clang-18 lld-18 ninja cmake python3 wine winetricks 7z cargo; do +> command -v $t > /dev/null && echo "OK $t" || echo "MISSING $t" +> done +> ``` + +--- + +## Phase 2 — `clang-cl` symlink + +**Why this is its own phase**: Ubuntu's `clang-18` package ships `clang-18`, `clang++-18`, and `clang-cpp-18` but **not** `clang-cl`. clang only activates its MSVC-driver mode when `argv[0] == "clang-cl"` or `--driver-mode=cl` is passed. The cleanest fix is one symlink. + +```bash +sudo ln -s /usr/bin/clang-18 /usr/bin/clang-cl +``` + +Verify both that the symlink exists and that clang reports the MSVC target when invoked through it: +```bash +ls -la /usr/bin/clang-cl +clang-cl --version +# Expected: third line says Target: x86_64-pc-windows-msvc +``` + +`lld-link`, `llvm-rc`, `llvm-lib`, `llvm-mt` are already on PATH from the apt packages. Confirm: +```bash +for t in lld-link llvm-rc llvm-lib llvm-mt; do + command -v $t > /dev/null && echo "OK $t" +done +``` + +> **[CHECKPOINT P2]** `clang-cl --version` reports `Target: x86_64-pc-windows-msvc`. All four LLVM utilities are on PATH. + +--- + +## Phase 3 — `xwin`: download & splat the Windows SDK + MSVC CRT + +`xwin` (by Jake Shadle) downloads Microsoft's redistributable CRT + Windows SDK directly from Microsoft's update servers and arranges them into a usable cross-compile sysroot. ~600 MB download, ~807 MB on disk. + +```bash +cargo install --locked xwin + +xwin --accept-license --arch x86_64 splat \ + --include-debug-libs \ + --output "$XWIN_DIR" +``` + +Notes & caveats: + +- `--include-debug-libs` is **mandatory** — even though we'll force the release CRT for runtime (Phase 8.3), headers reference `_ITERATOR_DEBUG_LEVEL` library symbols at link time. +- The default splat layout is **flat** (`crt/include/`, `sdk/include/{ucrt,um,shared,winrt}/`). **Do not** pass `--use-winsysroot-style` — our toolchain uses explicit `-imsvc` flags against the flat layout. If you change this, rewrite the toolchain accordingly. +- xwin auto-creates **lowercase symlinks** for headers that ship with uppercase names (e.g. `windows.h → Windows.h`). It does **not** create the reverse. Phase 9 includes a helper script that patches the reverse-case aliases that xenia needs (e.g. `` against a SDK that ships `objbase.h` only). + +> **[CHECKPOINT P3]** Splat directory is populated: +> ```bash +> test -d "$XWIN_DIR/crt/include" \ +> && test -d "$XWIN_DIR/sdk/include/um" \ +> && du -sh "$XWIN_DIR" # ≈ 807 MB +> ``` + +--- + +## Phase 4 — Real Win10 SDK fxc.exe + +**Why this can't be skipped**: xenia's D3D12 backend uses Shader Model 5.1 (`cs_5_1`, `ps_5_1`, …). The legacy DirectX SDK June 2010 fxc (commonly distributed via `winetricks dxsdk_jun2010`) caps at SM 5.0 — it cannot compile current xenia shaders. **No standalone NuGet package contains just `fxc.exe`** (the `Microsoft.Windows.SDK.BuildTools` NuGet does **not** contain fxc despite the name). The reliable path is to fetch the full SDK ISO and extract two files surgically. + +This phase can run in parallel with Phase 3 — both are bandwidth-bound and independent. + +### 4.1 Download the SDK ISO (~1.1 GB) + +```bash +mkdir -p "$FXC_CROSS_DIR" +curl -L -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \ + -o ~/winsdk.iso \ + "https://go.microsoft.com/fwlink/?linkid=2361406" + +# Verify +file ~/winsdk.iso +# Expected: "UDF filesystem data (version 1.5) 'KSDKWIN_…'" +# NOT a PDF — see caveat below +``` + +**Caveat ⚠️ Microsoft's `go.microsoft.com/fwlink/?linkid=…` URLs rotate over time**, and they occasionally serve an unrelated PDF if the request lacks a browser User-Agent. The `-A` flag avoids the PDF redirect. If the link returns a PDF (`file` reports `PDF document`) or 404s, get the current "Installer"/"ISO" link from . Any 10.0.22000+ release contains an SM 5.1-capable fxc; pick the latest stable. + +### 4.2 Extract the right MSI's CABs + +The fxc-bearing MSI is `Windows SDK for Windows Store Apps Tools-x86_en-us.msi`. It references 10 CABs by hash name. + +```bash +# Pull only Tools MSIs out of the ISO +mkdir -p /tmp/winsdk-extract /tmp/sdk-cabs/extracted +7z x ~/winsdk.iso 'Installers/*Tools*.msi' -o/tmp/winsdk-extract -aoa >/dev/null + +# Confirm the right MSI +MSI="/tmp/winsdk-extract/Installers/Windows SDK for Windows Store Apps Tools-x86_en-us.msi" +strings "$MSI" 2>/dev/null | grep -q 'fxc\.exe' || \ + { echo "FAIL: fxc not referenced in expected MSI"; exit 1; } + +# Pull the 10 CABs the MSI references +CABS=$(strings "$MSI" | grep -oE '[0-9a-f]{32}\.cab' | sort -u) +mkdir -p /tmp/sdk-cabs +for c in $CABS; do + 7z x ~/winsdk.iso "Installers/$c" -o/tmp/sdk-cabs -aoa >/dev/null +done +ls /tmp/sdk-cabs/Installers/*.cab | wc -l # → 10 + +# Extract every CAB into a flat dir of content-hashed payload files +for c in /tmp/sdk-cabs/Installers/*.cab; do + 7z x "$c" -o/tmp/sdk-cabs/extracted -aoa >/dev/null +done +``` + +### 4.3 Identify and copy the x64 fxc.exe + d3dcompiler_47.dll + +CAB payload files use opaque hashed names. We identify the right ones by PE architecture (`file`) + content strings. + +```bash +# fxc.exe: PE32+ x86-64 console executable containing "Direct3D Shader Compiler" in resources (~183 KB) +for f in /tmp/sdk-cabs/extracted/*; do + if file "$f" | grep -q 'PE32+ executable (console) x86-64' \ + && strings -n 8 "$f" 2>/dev/null | grep -q 'Direct3D Shader Compiler'; then + cp "$f" "$FXC_CROSS_DIR/fxc.exe" + echo "fxc.exe installed from: $f" + break + fi +done +test -s "$FXC_CROSS_DIR/fxc.exe" || \ + { echo "ERROR: fxc.exe not found in extracted CABs" >&2; exit 1; } + +# d3dcompiler_47.dll: PE32+ x86-64 DLL, ~4.75 MB, containing "D3DCompile_47" export string +for f in /tmp/sdk-cabs/extracted/*; do + sz=$(stat -c %s "$f") + if [ "$sz" -gt 4500000 ] && [ "$sz" -lt 5000000 ] \ + && file "$f" | grep -q 'PE32+ executable (DLL).*x86-64' \ + && strings -n 8 "$f" 2>/dev/null | grep -qE '^D3DCompile_47$'; then + cp "$f" "$FXC_CROSS_DIR/d3dcompiler_47.dll" + echo "d3dcompiler_47.dll installed from: $f" + break + fi +done +test -s "$FXC_CROSS_DIR/d3dcompiler_47.dll" || \ + { echo "ERROR: d3dcompiler_47.dll not found in extracted CABs" >&2; exit 1; } +``` + +### 4.4 Verify under Wine + +```bash +wine "$FXC_CROSS_DIR/fxc.exe" /? 2>&1 | head -3 +# Expected first line: "Microsoft (R) Direct3D Shader Compiler 10.0.…" + +wine "$FXC_CROSS_DIR/fxc.exe" /? 2>&1 | grep -E 'cs_5_1|ds_5_1' | head -1 +# Expected: a line listing cs_5_1 among supported profiles +``` + +> **[CHECKPOINT P4]** Both files installed; fxc reports a 10.0.x version and lists `cs_5_1`. Cleanup optional but recommended: +> ```bash +> rm -rf /tmp/winsdk-extract /tmp/sdk-cabs ~/winsdk.iso +> ``` + +--- + +## Phase 5 — Wine runtime DLL trio + +xenia under Wine needs **three matched DLL overrides** in the active prefix. **All three pieces are required** — a partial install is worse than nothing because it segfaults on swapchain creation. + +| File(s) | Source | Provides | +|---|---|---| +| `d3d12.dll`, `d3d12core.dll` | **vkd3d-proton** ≥ 3.0 | D3D12 → Vulkan translation | +| `dxgi.dll` | **DXVK** ≥ 2.4 | DXGI factory/swapchain (shared D3D9/10/11/12) | +| host Wine | **system Wine 9.0** | NT API surface, JIT-friendly DLL address layout | + +The combo MUST be **vkd3d-proton + DXVK**, not just vkd3d-proton alone — Wine's *builtin* `dxgi` and vkd3d-proton's `d3d12` pass each other malformed swapchain handles and crash in `vkd3d_instance_get_vk_instance` (see Appendix A). + +### 5.1 Install both into the default prefix + +```bash +WINEDEBUG=-all WINETRICKS_DOWNLOADER=wget winetricks -q vkd3d +WINEDEBUG=-all WINETRICKS_DOWNLOADER=wget winetricks -q dxvk +``` + +Each verb downloads its tarball to `~/.cache/winetricks/`, extracts it into a temp prefix, and `ln -s`'s the DLLs into `~/.wine/drive_c/windows/system32/`. Takes ~3 minutes each. + +### 5.2 Verify the trio + +```bash +# Size fingerprints +stat -c '%n = %s bytes' \ + "$HOME/.wine/drive_c/windows/system32/d3d12core.dll" \ + "$HOME/.wine/drive_c/windows/system32/dxgi.dll" +# Expected: +# d3d12core.dll ≈ 6 MB (vkd3d-proton) — Wine builtin would be ~66 KB +# dxgi.dll ≈ 3 MB (DXVK) — Wine builtin would be ~1.5 MB + +# String fingerprints +strings "$HOME/.wine/drive_c/windows/system32/d3d12core.dll" | grep -i 'vkd3d-proton' | head -1 +# Must hit a vkd3d-proton path string + +strings "$HOME/.wine/drive_c/windows/system32/dxgi.dll" | grep -i 'DXVK' | head -1 +# Must hit DxvkAdap, DXVK:, or similar +``` + +> **[CHECKPOINT P5]** Both fingerprint checks pass. + +### 5.3 What NOT to use + +- **Wine GE / Glorious Eggroll**: has esync/gamemode shims loaded in `0xA0000000-0xAFFFFFFF` — collides with xenia's hardcoded JIT generated-code region (see Appendix C). +- **`winetricks d3dcompiler_47`** alone: that's the *runtime* DLL, irrelevant here. +- **`winetricks dxsdk_jun2010`** alone: that's the SM 5.0 legacy fxc, fails on current shaders (see Phase 4). +- **Suppressing `WINEDEBUG=-all`** when debugging a crash: masks the SEH stderr you need. + +--- + +## Phase 6 — Clone xenia-canary + +### 6.1 Decision: Plan A (vanilla upstream) or Plan B (continue prior audit work) + +| | Plan A | Plan B | +|---|---|---| +| Source | Fresh clone from upstream | Copy / rsync from a peer machine that has prior audit work | +| Pros | 100% reproducible, minimal | Picks up `audit_NN_*` / `phase_b_*` accumulated instrumentation | +| Cons | Loses any in-tree audit instrumentation | Less reproducible across machines | +| Recommendation | Use this for first-time setup | Switch to this only if you're picking up a specific audit family | + +This guide assumes **Plan A**. If you go Plan B, skip ahead to Phase 9 — the eight build-fix patches in Phase 7 are already applied on the peer machine. + +### 6.2 Clone + +```bash +cd "$XENIA_RS_ROOT" +git clone --recurse-submodules \ + https://github.com/xenia-canary/xenia-canary.git \ + "$XENIA_CANARY_SRC" +cd "$XENIA_CANARY_SRC" +git submodule update --init --recursive +git rev-parse HEAD +``` + +**Pinned commit for this recipe**: `6de80dffe261b368ecefee36c9b2b337335228c0` (early 2026 mainline). If upstream has moved beyond, the patches in Phase 7 may need fuzzy-applying: +```bash +git checkout 6de80dffe261b368ecefee36c9b2b337335228c0 +git submodule update --init --recursive +``` + +> **[CHECKPOINT P6]** `xenia-canary/` exists with submodules populated: +> ```bash +> test -f "$XENIA_CANARY_SRC/CMakeLists.txt" \ +> && test -d "$XENIA_CANARY_SRC/third_party/SDL2/src" \ +> && test -d "$XENIA_CANARY_SRC/third_party/DirectXShaderCompiler/include" \ +> && echo "OK xenia-canary source ready" +> ``` + +--- + +## Phase 7 — Build-fix source patches (eight required) + +These patches **only fix build-time compilation issues** under clang-cl + xwin. None of them changes runtime behavior. They are required for both Plans A and B — apply each, then proceed. + +For each patch below: the `OLD` snippet is unique enough to anchor with an Edit-tool-style replacement. Apply them in any order. + +### 7.1 `src/xenia/base/mapped_memory_win.cc` — constexpr → const + +clang-cl rejects `reinterpret_cast` in constant expressions (per the C++ standard); MSVC accepts as an extension. `INVALID_HANDLE_VALUE` expands to `((HANDLE)(LONG_PTR)-1)` — a chained int-to-pointer cast. + +```diff +- // chrispy: made inline const to get around clang error +- static inline constexpr HANDLE kFileHandleInvalid = INVALID_HANDLE_VALUE; ++ // INVALID_HANDLE_VALUE expands to a reinterpret_cast which MSVC accepts ++ // in constexpr as an extension; clang-cl rejects it per C++ standard. ++ static inline const HANDLE kFileHandleInvalid = INVALID_HANDLE_VALUE; +``` + +### 7.2 `src/xenia/app/main_resources.rc` — backslash → forward slash + +llvm-rc on Linux treats `\` as a literal filename character, so `..\\..\\..\\assets\\icon\\icon.ico` becomes a single literal name and the file isn't found. Forward slashes work on both rc.exe and llvm-rc. + +```diff +- MAINICON ICON "..\\..\\..\\assets\\icon\\icon.ico" ++ MAINICON ICON "../../../assets/icon/icon.ico" +``` + +### 7.3 `third_party/snappy/snappy-stubs-public.h` — POSIX gate on `!_WIN32` + +This header is checked-in pre-generated; whoever originally ran cmake on snappy did so on a Linux host, so `HAVE_SYS_UIO_H` was baked in as `1`. Make the gate target-platform-aware. + +Apply two edits in the same file: + +```diff +- #if 1 // HAVE_SYS_UIO_H ++ // Pre-generated as 1 on the original Linux host; gate on platform so cross ++ // compiles to Windows fall back to the iovec definition below. ++ #if !defined(_WIN32) // HAVE_SYS_UIO_H + #include + #endif // HAVE_SYS_UIO_H +``` + +```diff +- #if !1 // !HAVE_SYS_UIO_H ++ #if defined(_WIN32) // !HAVE_SYS_UIO_H +``` + +### 7.4 `third_party/zlib-ng/zconf-ng.h` — gate `Z_HAVE_UNISTD_H` on `!_WIN32` + +Same root cause as 7.3. + +```diff +- #if 1 /* was set to #if 1 by configure/cmake/etc */ ++ /* Pre-generated as 1 on the Linux host; gate on platform. */ ++ #if !defined(_WIN32) /* was set to #if 1 by configure/cmake/etc */ + # define Z_HAVE_UNISTD_H + #endif +``` + +### 7.5 `third_party/CMakeLists.txt` — apply zlib-ng AVX flags under clang-cl too + +Real `cl.exe` exposes AVX-512 intrinsics unconditionally; clang-cl follows standard clang and requires `-mavx512*` per file. Extend the existing gate. + +```diff +- if(NOT MSVC) ++ if(NOT MSVC OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_definitions(zlib-ng PRIVATE HAVE_BUILTIN_CTZ HAVE_BUILTIN_CTZLL) + set_source_files_properties( + zlib-ng/arch/x86/adler32_avx2.c + zlib-ng/arch/x86/chunkset_avx2.c + zlib-ng/arch/x86/compare256_avx2.c + zlib-ng/arch/x86/slide_hash_avx2.c + PROPERTIES COMPILE_OPTIONS "-mavx2;-mbmi2" + ) + set_source_files_properties( + zlib-ng/arch/x86/adler32_avx512.c + zlib-ng/arch/x86/chunkset_avx512.c + PROPERTIES COMPILE_OPTIONS "-mavx512f;-mavx512dq;-mavx512vl;-mavx512bw;-mbmi2" + ) + set_source_files_properties( + zlib-ng/arch/x86/adler32_avx512_vnni.c + PROPERTIES COMPILE_OPTIONS "-mavx512f;-mavx512dq;-mavx512vl;-mavx512bw;-mavx512vnni;-mbmi2" + ) + endif() +``` + +### 7.6 `CMakeLists.txt` (top-level) — gate `/RTCsu` + auto-generate `version.h` + +Two edits in the same file. + +**7.6.a** — `/RTCsu` is MSVC-only; clang-cl warns per-TU. Gate it. Around line 145: +```diff +- string(APPEND CMAKE_C_FLAGS_CHECKED " /RTCsu /fsanitize=address") +- string(APPEND CMAKE_CXX_FLAGS_CHECKED " /RTCsu /fsanitize=address") ++ if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC" ++ AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") ++ string(APPEND CMAKE_C_FLAGS_CHECKED " /RTCsu /fsanitize=address") ++ string(APPEND CMAKE_CXX_FLAGS_CHECKED " /RTCsu /fsanitize=address") ++ else() ++ string(APPEND CMAKE_C_FLAGS_CHECKED " /fsanitize=address") ++ string(APPEND CMAKE_CXX_FLAGS_CHECKED " /fsanitize=address") ++ endif() +``` + +**7.6.b** — `xenia-build.py:generate_version_h()` normally runs as part of `xb setup`; CMake-direct flows need it called explicitly. After the existing `find_package(Python3 REQUIRED COMPONENTS Interpreter)` line, add: + +```cmake +# Generate build-tree version.h via xenia-build.py's generate_version_h() +# (normally invoked by `xb premake`/`xb build`; CMake-direct flows need it too). +execute_process( + COMMAND ${Python3_EXECUTABLE} -c + "import importlib.util,sys; \ + spec=importlib.util.spec_from_file_location('xb',r'${PROJECT_SOURCE_DIR}/xenia-build.py'); \ + m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m); \ + m.generate_version_h(r'${CMAKE_BINARY_DIR}')" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + RESULT_VARIABLE _xenia_version_rc +) +if(NOT _xenia_version_rc EQUAL 0) + message(WARNING "version.h generation failed (rc=${_xenia_version_rc}); writing stub.") + file(WRITE "${CMAKE_BINARY_DIR}/version.h" + "#ifndef GENERATED_VERSION_H_\n#define GENERATED_VERSION_H_\n#define XE_BUILD_BRANCH \"unknown\"\n#define XE_BUILD_COMMIT \"unknown\"\n#define XE_BUILD_COMMIT_SHORT \"unknown\"\n#define XE_BUILD_DATE __DATE__\n#endif\n") +endif() +``` + +### 7.7 `cmake/XeniaHelpers.cmake` — propagate `FXC_PATH` to ninja-spawned subprocesses + +CMake's `set(ENV{X} …)` persists only for configure-time `execute_process`, not for ninja's runtime. Wrap each shader-compile command in `cmake -E env`. + +In the function `xe_shader_rules_dxbc`, two edits: + +**Add this block right after `set(_bytecode_dir …)`:** +```cmake +# Propagate FXC_PATH from the configure-time env so ninja-spawned python +# subprocesses can find fxc.exe (CMake `set(ENV{...})` doesn't reach build). +set(_env_prefix "") +if(DEFINED ENV{FXC_PATH}) + set(_env_prefix ${CMAKE_COMMAND} -E env "FXC_PATH=$ENV{FXC_PATH}") +endif() +``` + +**Modify the per-shader command emission inside the `foreach`:** +```diff +- list(APPEND _commands COMMAND ${Python3_EXECUTABLE} "${_script}" "${src}" "${_bytecode_dir}/${_id}.h") ++ list(APPEND _commands COMMAND ${_env_prefix} ${Python3_EXECUTABLE} "${_script}" "${src}" "${_bytecode_dir}/${_id}.h") +``` + +### 7.8 `tools/build/compile_shader_dxbc.py` — winepath-translate paths + +When fxc.exe runs under Wine, unix paths like `/home//…` are parsed by fxc as switches (it sees `/h` as the help switch and bails). Convert input/output/include paths to Windows form first via `winepath -w`. + +After the `is_dxc = "dxc" in os.path.basename(fxc).lower()` line and **before** building `compiler_args`: +```diff +- # Start with base command — use wine on non-Windows platforms. + if sys.platform != "win32": ++ def _wineify(p): ++ try: ++ out = subprocess.check_output(["winepath", "-w", p], ++ stderr=subprocess.DEVNULL) ++ return out.decode("utf-8", "replace").strip() ++ except (OSError, subprocess.CalledProcessError): ++ return p ++ input_path = _wineify(input_path) ++ output_path = _wineify(output_path) ++ src_dir = _wineify(src_dir) + compiler_args = ["wine", fxc] + else: + compiler_args = [fxc] +``` + +> **[CHECKPOINT P7]** All eight patches applied. Sanity-check at the top of each file: +> ```bash +> cd "$XENIA_CANARY_SRC" +> grep -q 'inline const HANDLE kFileHandleInvalid' src/xenia/base/mapped_memory_win.cc && echo "OK 7.1" +> grep -q '"../../../assets/icon/icon.ico"' src/xenia/app/main_resources.rc && echo "OK 7.2" +> grep -q '!defined(_WIN32) // HAVE_SYS_UIO_H' third_party/snappy/snappy-stubs-public.h && echo "OK 7.3" +> grep -q '!defined(_WIN32) /\* was set to #if 1' third_party/zlib-ng/zconf-ng.h && echo "OK 7.4" +> grep -q 'NOT MSVC OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang"' third_party/CMakeLists.txt && echo "OK 7.5" +> grep -q 'CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"' CMakeLists.txt && echo "OK 7.6a" +> grep -q 'generate_version_h(r' CMakeLists.txt && echo "OK 7.6b" +> grep -q '_env_prefix' cmake/XeniaHelpers.cmake && echo "OK 7.7" +> grep -q '_wineify' tools/build/compile_shader_dxbc.py && echo "OK 7.8" +> ``` +> Expected: 9 × `OK`. + +--- + +## Phase 8 — Add the three cross-compile config files + +These add a new `cross-win-clangcl` preset alongside existing `default`/`vs`/`vs-arm64` presets without disturbing them. + +### 8.1 New file: `cmake/toolchains/linux-to-win-msvc.cmake` + +Create the directory and file. **Copy this verbatim** — reformatting line-continuations inside CMake `set(... "string")` would break `/I` flag parsing. The toolchain is gnarly because it has to bridge multiple incompatibilities at once; comments inline explain. + +```bash +mkdir -p "$XENIA_CANARY_SRC/cmake/toolchains" +``` + +```cmake +# cmake/toolchains/linux-to-win-msvc.cmake +# Linux host -> Windows MSVC-ABI cross toolchain using clang-cl + lld-link +# + xwin-supplied Win10 SDK/CRT. Driven by Ninja Multi-Config. + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +if(NOT DEFINED XWIN_DIR) + if(DEFINED ENV{XWIN_DIR}) + set(XWIN_DIR "$ENV{XWIN_DIR}") + else() + set(XWIN_DIR "$ENV{HOME}/.xwin/splat") + endif() +endif() +set(XWIN_DIR "${XWIN_DIR}" CACHE PATH "xwin splat root (contains crt/ and sdk/)") + +if(NOT EXISTS "${XWIN_DIR}/crt/include") + message(FATAL_ERROR "XWIN_DIR=${XWIN_DIR} missing crt/include - run xwin splat.") +endif() + +set(CMAKE_C_COMPILER clang-cl) +set(CMAKE_CXX_COMPILER clang-cl) +set(CMAKE_LINKER lld-link) +set(CMAKE_RC_COMPILER llvm-rc) +set(CMAKE_AR llvm-lib) +set(CMAKE_MT llvm-mt) + +set(CMAKE_C_COMPILER_TARGET x86_64-pc-windows-msvc) +set(CMAKE_CXX_COMPILER_TARGET x86_64-pc-windows-msvc) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +# Force /MD (release CRT) for every config. xenia's CMakeLists.txt does a +# `string(REPLACE "/MDd" "/MD" ...)` on CMAKE_CXX_FLAGS_DEBUG, but with +# clang-cl, the runtime selection comes from CMAKE_MSVC_RUNTIME_LIBRARY +# (which expands to -MDd in dash form), so the substitution misses. +# Pinning the policy here avoids the need for non-redistributable debug +# CRT DLLs (MSVCP140D.dll etc) at runtime, which xwin doesn't ship. +cmake_policy(SET CMP0091 NEW) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + +# xwin's default splat is a flat layout (crt/include, sdk/include/{ucrt,um,shared,...}), +# which clang-cl's /winsysroot does NOT understand. Use explicit -imsvc + -libpath: +# instead. Re-running `xwin splat --use-winsysroot-style` would also work but +# requires re-downloading ~600 MB. +# Use SHELL: prefix so CMake doesn't deduplicate repeated -imsvc tokens. +add_compile_options( + "SHELL:-imsvc \"${XWIN_DIR}/crt/include\"" + "SHELL:-imsvc \"${XWIN_DIR}/sdk/include/ucrt\"" + "SHELL:-imsvc \"${XWIN_DIR}/sdk/include/um\"" + "SHELL:-imsvc \"${XWIN_DIR}/sdk/include/shared\"" + "SHELL:-imsvc \"${XWIN_DIR}/sdk/include/winrt\"" +) +add_link_options( + "/libpath:${XWIN_DIR}/crt/lib/x86_64" + "/libpath:${XWIN_DIR}/sdk/lib/ucrt/x86_64" + "/libpath:${XWIN_DIR}/sdk/lib/um/x86_64" +) +# xwin pulls the latest MSVC STL which now hard-asserts Clang >= 19. Our host +# has Clang 18, which works fine in practice — opt out of the version check. +# (See yvals_core.h STL1000 in $XWIN_DIR/crt/include.) +add_compile_definitions(_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH) +# llvm-rc needs SDK headers explicitly + the resource file's own dir so the +# RC's relative ICON path (../../../assets/icon/icon.ico) resolves. +get_filename_component(_toolchain_dir "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) +get_filename_component(_xenia_root "${_toolchain_dir}/../.." ABSOLUTE) +set(CMAKE_RC_FLAGS_INIT + "/I \"${XWIN_DIR}/sdk/include/um\" /I \"${XWIN_DIR}/sdk/include/shared\" /I \"${_xenia_root}/src/xenia/app\"") + +# Quiet MSVC-STL false positives and xenia-specific warnings that clang-cl +# emits but cl.exe doesn't (treated as errors under /WX). +add_compile_options( + -Wno-microsoft-include + -Wno-unused-command-line-argument + -Wno-ignored-pragma-intrinsic + -Wno-nonportable-include-path + -Wno-pragma-pack + -Wno-tautological-pointer-compare + -Wno-microsoft-cast + -Wno-deprecated-declarations + # These are silenced for native Linux Clang in CMakeLists.txt's else() branch, + # but the if(MSVC) branch fires under clang-cl and skips them — so re-add. + -Wno-switch + -Wno-attributes + -Wno-deprecated-register + -Wno-deprecated-volatile + -Wno-deprecated-enum-enum-conversion + # cl.exe accepts __pragma(optimize("s",on)); clang-cl only knows the empty + # argument form. xenia gates XE_MSVC_OPTIMIZE_SMALL on _MSC_VER so the + # rejected pragma still emits from the clang-cl path. Treat as no-op. + -Wno-ignored-pragmas + # xenia decorates several `virtual` methods with XE_FORCEINLINE; clang-cl + # then complains that the inline body isn't visible in includer TUs + # (definitions live in command_processor.cc). cl.exe accepts this silently. + -Wno-undefined-inline + -Wno-sizeof-pointer-memaccess + # `'ZM'` (PE magic, stored little-endian as "MZ" in the binary) — cl.exe + # accepts the multi-char constant silently; clang-cl errors under /WX. + -Wno-multichar +) +# _mm_cvtsi64x_si128 is an MSVC-only alias for the standard _mm_cvtsi64_si128. +# Used (gated on XE_PLATFORM_WIN32) in xenia/gpu/draw_util.cc. +add_compile_definitions(_mm_cvtsi64x_si128=_mm_cvtsi64_si128) + +# Plumb FXC for tools/build/compile_shader_dxbc.py (wine fxc auto-prepend). +# Use real Win10 SDK fxc.exe 10.x (supports SM 5_1, produces vkd3d-proton- +# acceptable DXBC). +if(DEFINED ENV{FXC_PATH}) + set(ENV_FXC "$ENV{FXC_PATH}") +else() + set(ENV_FXC "$ENV{HOME}/.local/share/xenia-cross/fxc/fxc.exe") +endif() +set(ENV{FXC_PATH} "${ENV_FXC}") + +set(CMAKE_FIND_ROOT_PATH "${XWIN_DIR}") +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +``` + +### 8.2 New file: `cmake/toolchains/xwin-case-symlinks.py` + +xwin auto-creates lowercase aliases for SDK headers shipped with uppercase names (`windows.h → Windows.h`) but doesn't create the reverse aliases. xenia includes some headers using mixed-case spellings (``, ``, …) that match SDK files only on a case-insensitive filesystem. This helper scans xenia's `#include <...>` directives and creates the missing case-symlinks. Idempotent. + +```python +#!/usr/bin/env python3 +"""Scan xenia source for #include <...> directives and ensure case-aliases +exist in xwin's flat SDK/CRT include dirs. Idempotent — safe to re-run.""" +from __future__ import annotations +import os, re, sys +from pathlib import Path + +INCLUDE_RE = re.compile(r'^\s*#\s*include\s*<([A-Za-z][A-Za-z0-9_/.\-]*\.h)>', + re.MULTILINE) +SOURCE_ROOTS = ["src", "third_party/SDL2/src", "third_party/SDL2/include", + "third_party/discord-rpc/src", "third_party/fmt", + "third_party/imgui"] +SOURCE_EXTS = {".h", ".hpp", ".inc", ".c", ".cc", ".cpp", ".cxx"} + +def collect_includes(repo_root: Path) -> set[str]: + seen: set[str] = set() + for root in SOURCE_ROOTS: + base = repo_root / root + if not base.exists(): continue + for path in base.rglob("*"): + if path.suffix.lower() not in SOURCE_EXTS: continue + try: txt = path.read_text(encoding="utf-8", errors="ignore") + except OSError: continue + for m in INCLUDE_RE.finditer(txt): + seen.add(m.group(1)) + return seen + +def fix_dir(target_dir: Path, want: set[str]) -> int: + if not target_dir.is_dir(): return 0 + idx = {e.name.lower(): e.name for e in target_dir.iterdir()} + created = 0 + for name in want: + if "/" in name: continue + actual = idx.get(name.lower()) + if actual is None or actual == name: continue + link = target_dir / name + if link.exists() or link.is_symlink(): continue + try: + link.symlink_to(actual) + created += 1 + except OSError: pass + return created + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.exit("usage: xwin-case-symlinks.py ") + repo = Path(sys.argv[1]).resolve() + xwin = Path(sys.argv[2]).resolve() + want = collect_includes(repo) + total = 0 + for d in (xwin/"crt/include", xwin/"sdk/include/ucrt", + xwin/"sdk/include/um", xwin/"sdk/include/shared", + xwin/"sdk/include/winrt"): + total += fix_dir(d, want) + print(f"Created {total} case-symlinks across {len(want)} unique includes.") +``` + +### 8.3 Edit: `CMakePresets.json` + +Add **one configure preset** and **three build presets** alongside the existing entries. The full set of `existing` arrays is unchanged; only the additions matter: + +In the `configurePresets` array, after the last existing entry, add: +```json +{ + "name": "cross-win-clangcl", + "displayName": "Cross (Linux→Win MSVC) clang-cl + xwin", + "generator": "Ninja Multi-Config", + "binaryDir": "${sourceDir}/build-cross", + "toolchainFile": "${sourceDir}/cmake/toolchains/linux-to-win-msvc.cmake", + "condition": { + "type": "notEquals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + }, + "cacheVariables": { + "XWIN_DIR": "$env{HOME}/.xwin/splat", + "FXC_PATH": "$env{HOME}/.local/share/xenia-cross/fxc/fxc.exe" + } +} +``` + +In the `buildPresets` array, append: +```json +{ "name": "cross-debug", "configurePreset": "cross-win-clangcl", "configuration": "Debug" }, +{ "name": "cross-release", "configurePreset": "cross-win-clangcl", "configuration": "Release" }, +{ "name": "cross-checked", "configurePreset": "cross-win-clangcl", "configuration": "Checked" } +``` + +> **[CHECKPOINT P8]** All three config files exist: +> ```bash +> test -f "$XENIA_CANARY_SRC/cmake/toolchains/linux-to-win-msvc.cmake" && echo "OK toolchain" +> test -f "$XENIA_CANARY_SRC/cmake/toolchains/xwin-case-symlinks.py" && echo "OK case-symlinks" +> grep -q 'cross-win-clangcl' "$XENIA_CANARY_SRC/CMakePresets.json" && echo "OK presets" +> ``` + +--- + +## Phase 9 — Configure cmake + +### 9.1 Run the case-symlink helper (one-time) + +```bash +cd "$XENIA_CANARY_SRC" +python3 cmake/toolchains/xwin-case-symlinks.py "$XENIA_CANARY_SRC" "$XWIN_DIR" +# Expected: "Created N case-symlinks across M unique includes." +# Typical first-run N ≈ 5 (e.g. ObjBase.h, Psapi.h, …). +``` + +### 9.2 Configure with the new preset + +```bash +cmake --preset cross-win-clangcl +``` + +**Expected tail of output**: +``` +-- The C compiler identification is Clang 18.1.3 with MSVC-like command-line +-- The CXX compiler identification is Clang 18.1.3 with MSVC-like command-line +-- Target architecture: XE_TARGET_AARCH64=FALSE XE_TARGET_X86_64=TRUE (CMAKE_SYSTEM_PROCESSOR=x86_64) +-- Found Python3: /usr/bin/python3 (found version "3.x.x") found components: Interpreter +-- Configuring done (0.5s) +-- Generating done (0.1s) +-- Build files have been written to: /build-cross +``` + +A trailing `CMake Warning: Manually-specified variables were not used by the project: FXC_PATH` is **harmless** — the toolchain reads `FXC_PATH` via `$ENV{...}`, not via CMake cache. + +> **[CHECKPOINT P9]** Configure step generates `build-cross/`: +> ```bash +> test -f "$XENIA_CANARY_SRC/build-cross/build-Debug.ninja" && echo "OK configure" +> test -f "$XENIA_CANARY_SRC/build-cross/version.h" && echo "OK version.h" +> ``` + +If configure fails: see Appendix A, especially rows tagged **Configure**. + +--- + +## Phase 10 — Build `xenia-app` + +```bash +cmake --build "$XENIA_CANARY_SRC/build-cross" \ + --preset cross-debug \ + --target xenia-app \ + -j$(nproc) +``` + +The first build is ~927 ninja targets and takes **8–20 minutes** depending on cores. The heaviest phases are FFmpeg, DirectXShaderCompiler (vendored), glslang. Incremental builds after small source edits typically complete in 10–60 seconds. + +**Expected final three lines on success**: +``` +[925/927] Linking CXX static library obj/Windows/Debug/xenia-ui-d3d12.lib +[926/927] Linking CXX static library obj/Windows/Debug/xenia-gpu-d3d12.lib +[927/927] Linking CXX executable bin/Windows/Debug/xenia_canary.exe +``` + +Verify: +```bash +ls -lh "$XENIA_CANARY_SRC/build-cross/bin/Windows/Debug/"xenia_canary.{exe,pdb} +file "$XENIA_CANARY_SRC/build-cross/bin/Windows/Debug/xenia_canary.exe" +# Expected: PE32+ executable (GUI) x86-64, for MS Windows, 9 sections +``` + +> **[CHECKPOINT P10]** Both `.exe` and `.pdb` are present, exe is PE32+ x86-64. + +If build fails: see Appendix A, rows tagged **Build**. + +--- + +## Phase 11 — Smoke test under Wine + +This is the make-or-break verification. The previous phases all could succeed and you still get a runtime crash — **especially if Phase 5 (DLL trio) is not solid**. + +### 11.1 The test + +```bash +cd "$XENIA_CANARY_SRC/build-cross/bin/Windows/Debug" +rm -f xenia.log +WINEDEBUG=-all wine ./xenia_canary.exe --mute=true "$ISO_PATH" & +WINE_PID=$! +sleep 30 +wineserver -k # kill xenia; this is expected — we just want startup behavior +echo "==== Smoke test results ====" +echo "errors: $(grep -c '^!>' xenia.log)" +echo "fatals: $(grep -c '^x>' xenia.log) # MUST be 0" +echo "lines: $(wc -l < xenia.log) # ≥ 1000 = healthy" +echo "adapter: $(grep DXGI xenia.log | head -1)" +echo "deepest: $(grep XThread::Execute xenia.log | tail -1)" +``` + +### 11.2 Pass / fail criteria + +| Check | Pass | |---|---| -| `cmake/toolchains/linux-to-win-msvc.cmake` | clang-cl + lld-link + xwin sysroot driver. Forces `MultiThreadedDLL` runtime (no debug-CRT DLLs needed), feeds `-imsvc` flags via `SHELL:` prefix, suppresses 15+ clang-cl-specific warnings, defines `_mm_cvtsi64x_si128` alias, opts out of MSVC STL's Clang≥19 assertion, plumbs `FXC_PATH` | -| `cmake/toolchains/xwin-case-symlinks.py` | Scans xenia includes for mixed-case Windows headers and creates the missing case-aliases in xwin's flat splat (5 needed: `ObjBase.h`, `Psapi.h`, etc.) | -| `CMakePresets.json` | Added `cross-win-clangcl` configure preset + `cross-debug` / `cross-release` / `cross-checked` build presets, gated on `${hostSystemName}` ≠ Windows | +| **No `Unhandled Exception in Xenia` GUI dialog popped during the 30 s window** | required | +| `^x>` (fatal) line count is **0** | required | +| `xenia.log` ≥ 1000 lines | required | +| Log shows `i> Setup: Initializing Memory/Exports/Processor/Audio/Graphics/HID/VFS/Kernel` | required | +| Log shows `K> XThread::Execute thid N (handle=…, '…', native=…)` for ≥ 5 threads | required | +| `DXGI adapter:` line shows your real GPU (not WARP) | required for renderer audits, optional otherwise | +| Log shows `i> Skipping draw - pipeline not ready: …` repeating | normal (async pipeline compile) | +| Log shows `!> Failed to create graphics pipeline …` rarely | non-fatal (occasional shader miss) | -## Phase 3 — Configure + build +If a GUI dialog appeared or `^x>` count > 0: go to Appendix A, **Runtime** rows. - python3 cmake/toolchains/xwin-case-symlinks.py "$PWD" "$HOME/.xwin/splat" - # Created 5 case-symlinks across 556 unique includes +### 11.3 Inspect the binary in a debugger (optional) - cmake --preset cross-win-clangcl - # Generates build-cross/ with build-Debug.ninja + auto-generated version.h +Wine ships `winedbg`. For most audit work you don't need it, but if a crash needs traceback: +```bash +WINEDEBUG=err+seh,err+exception wine ./xenia_canary.exe "$ISO_PATH" 2>&1 \ + | tee /tmp/wine-trace.log +grep -E 'Unhandled exception|=>0|=>1|=>2' /tmp/wine-trace.log +``` - cmake --build build-cross --preset cross-debug --target xenia-app -j6 - # 936 ninja targets. -j6 not -j12 because host RAM is tight (~3.4 GiB free at start) - # Wall time ~25 min total +> **[CHECKPOINT P11]** Smoke test passes: 0 fatals, ≥ 1000 log lines, no error dialog. -Final output: `xenia_canary.exe` (27 MB PE32+ GUI x86-64) + `xenia_canary.pdb` (116 MB CodeView). +--- -## Phase 4 — Running the game +## Phase 12 — Wire into the xenia-rs audit workflow - cd "/home/fabi/RE Project Sylpheed/xenia-canary/build-cross/bin/Windows/Debug" - WINEDEBUG=-all wine ./xenia_canary.exe --mute=true \ - "/home/fabi/RE Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso" +The Windows debug build's role in the audit workflow: -Kill after the boot phase completes (the audit workflow only needs ~30 s of trajectory): +1. **It is the sole canary oracle going forward.** The Linux-native canary build at `xenia-canary/build/bin/Linux/Debug/xenia_canary` is **deprecated** for audits — it doesn't reach the same boot trajectory. Use the cross-build at `xenia-canary/build-cross/bin/Windows/Debug/xenia_canary.exe`. - sleep 30 - wineserver -k +2. **All canary launches MUST include `--mute=true`** (project policy established 2026-05-12). `--mute=true` is semantically transparent — XAudio2 still initializes, the same code paths fire, the output is silent. Do **NOT** use `--apu=nop` as a substitute; it switches the audio backend entirely and can shift the boot trajectory in non-obvious ways, invalidating cross-engine comparisons. -### Expected boot signals in `xenia.log` +3. **Logs default to `/xenia.log`**. For audit runs, point `--log_file=` somewhere predictable, e.g. `$XENIA_RS_ROOT/xenia-rs/audit-runs/audit_XX/canary.log`. -| Signal | Value | Meaning | +### 12.1 A reusable launcher + +Place this somewhere on PATH or in `$XENIA_RS_ROOT/`: + +```bash +#!/bin/bash +# launch-canary.sh — invoke the Windows debug build under Wine, audit-style. +set -euo pipefail +XENIA_CROSS_EXE="${XENIA_CANARY_SRC:-$HOME/RE - Project Sylpheed/xenia-canary}/build-cross/bin/Windows/Debug/xenia_canary.exe" +LOG="${LOG:-$(pwd)/canary.log}" + +WINEDEBUG=-all wine "$XENIA_CROSS_EXE" \ + --mute=true \ + --log_file="$LOG" \ + "$@" +``` + +Usage: +```bash +LOG="$XENIA_RS_ROOT/xenia-rs/audit-runs/audit_71/canary.log" \ + ./launch-canary.sh "$ISO_PATH" --audit_71_my_probe=true +``` + +### 12.2 Coordinating with xenia-rs + +In the typical audit workflow: +1. Both engines (canary and xenia-rs) are run against the same ISO. +2. Both engines emit a log file. +3. The audit script diffs the two for divergences (timing, register state, kernel calls, memory ops, …). + +Where to put canary's log relative to xenia-rs's expectations is project-specific; check the audit-runs/ directory in `xenia-rs` for the established convention. + +--- + +## Adding instrumentation — the audit recipe inline + +You'll add a new `XELOGI("AUDIT-XX-…")` trace site for almost every audit. The pattern is three steps. **Add instrumentation, do not change runtime behavior** — see "Golden rule" below. + +### Step 1: Declare the cvar in `src/xenia/cpu/cpu_flags.h` + +(or a topically-better-suited `*_flags.h`: `apu_flags.h`, `gpu_flags.h`, `hid_flags.h`, `kernel_flags.h`) + +```cpp +DECLARE_bool(audit_XX_my_probe); +// or: +DECLARE_uint64(audit_XX_focus_pc); +DECLARE_string(audit_XX_filter); +``` + +Macros available: `DECLARE_bool / int32 / uint32 / int64 / uint64 / double / string / path`. See `src/xenia/base/cvar.h:349-419` for the full list. + +### Step 2: Define the cvar in the matching `*_flags.cc` + +```cpp +DEFINE_bool(audit_XX_my_probe, false, + "Audit-XX: short human-readable description.", + "Audit"); // category — keep "Audit" +``` + +Default **false** (off until explicitly enabled per run). Default `true` only for permanent always-on markers like `AUDIT-DEMO`. + +### Step 3: Add the trace site + +```cpp +#include "xenia/cpu/cpu_flags.h" // if not already included + +// at the call site: +if (cvars::audit_XX_my_probe) { + XELOGI("AUDIT-XX-EVENT field1={:#x} field2={} ...", field1, field2); +} +``` + +Use a `AUDIT-XX-` prefix in the log string — makes grepping trivial. + +### Step 4: Rebuild and run + +```bash +cmake --build build-cross --preset cross-debug --target xenia-app -j$(nproc) +WINEDEBUG=-all wine ./build-cross/bin/Windows/Debug/xenia_canary.exe \ + --mute=true --audit_XX_my_probe=true "$ISO_PATH" & +sleep 30 ; wineserver -k +grep AUDIT-XX ./build-cross/bin/Windows/Debug/xenia.log +``` + +### Log channels reference + +From `src/xenia/base/logging.h`: + +| Macro | Prefix | Use for | |---|---|---| -| Total log lines | 1091 | Healthy boot (≥1000 = passes) | -| `^x>` fatals | 0 | No `xe::FatalError()` triggered | -| `^!>` errors | 28 | Non-fatal (occasional pipeline misses — normal) | -| `^w>` warnings | 28 | Normal startup | -| `i> Setup: Initializing chain` | Memory → Exports → Processor → Audio → Graphics → HID → VFS → Kernel | All eight subsystems came up | -| `i> Setup: Starting graphics_system + Starting audio_system` | present | Threads spawned cleanly | -| DXGI adapter | NVIDIA GeForce GTX 1070 Ti | vkd3d-proton + DXVK loaded | -| `K> XThread::Execute thid N count` | 29 | Guest threads spawned (game is past `XexLoadImage`) | -| `Skipping draw - pipeline not ready: VS … PS …` repeating | yes | D3D12 backend is async-compiling pipelines — expected on cold boot | -| `F>` channel | 321 lines | VFS is mounting and walking the disc image | -| `K>` channel | 29 lines | Kernel dispatching XThread creation | +| `XELOGI` | `i>` | Info — most audit traces | +| `XELOGW` | `w>` | Warnings — non-fatal anomalies | +| `XELOGE` | `!>` | Errors — failed paths | +| `XELOGD` | `d>` | Debug-level (verbose) | +| `XELOGCPU` | `C>` | PPC JIT, register state | +| `XELOGAPU` | `A>` | Audio system | +| `XELOGGPU` | `G>` | Graphics / D3D12 / Vulkan | +| `XELOGKERNEL` | `K>` | Kernel calls, handle/object table | +| `XELOGFS` | `F>` | VFS, file I/O | -## Audit-workflow integration +`x>` (fatal) only comes from `xe::FatalError()` — **don't** use it for instrumentation; it `exit()`s the process. -The cross-build is now the sole canary oracle. The Linux-native `xenia-canary/build/bin/Linux/Debug/xenia_canary` is deprecated for audits. +### Golden rule -All canary launches must include `--mute=true` (project policy 2026-05-12). XAudio2 still initializes — same code paths, silent output. Do not substitute `--apu=nop`; that switches backend entirely. +**Permitted edits**: new `XELOG*()` calls, new cvars, new `#if`-gated diagnostic blocks, new `audit_NN_*.cc` files (separate translation units that only observe). -Point logs at `audit-runs` with `--log_file=`: +**Forbidden edits**: changing control flow, return values, struct layouts, timing, or anything else that influences runtime semantics. The whole point of this build is to *observe* the same code path the official binary runs; silently changing logic invalidates every audit downstream. - LOG="$XENIA_RS_ROOT/xenia-rs/audit-runs/audit_NN/canary.log" \ - WINEDEBUG=-all wine xenia_canary.exe \ - --mute=true --log_file="$LOG" \ - --audit_NN_my_probe=true \ - "$ISO_PATH" +If you think a behavior change is needed for instrumentation, ask first. -Add new audit instrumentation as cvars in `cpu_flags.h` / `gpu_flags.h` / `kernel_flags.h`, guard with `if (cvars::audit_NN_my_probe)`, emit `XELOGI("AUDIT-NN-EVENT …")`. Rebuild incrementally — only the touched TU recompiles, ≤60 s. +--- -The build is observation-only: cvars + `XELOG*()` + `#if`-gated diagnostic blocks are permitted; changing control flow, return values, struct layouts, or timing is forbidden, since that would invalidate every downstream audit comparison. +## Quick verification: am I in steady state? + +When picking the project up again later, run this one block to verify all phases still pass: + +```bash +echo "=== P1 system packages ===" +for t in clang-18 lld-18 ninja cmake python3 wine winetricks 7z cargo; do + command -v $t > /dev/null && echo "OK $t" || echo "MISSING $t" +done + +echo "=== P2 clang-cl ===" +clang-cl --version 2>&1 | grep -q 'Target: x86_64-pc-windows-msvc' && echo "OK clang-cl" + +echo "=== P3 xwin splat ===" +test -d "$HOME/.xwin/splat/crt/include" && echo "OK splat present" + +echo "=== P4 fxc.exe ===" +test -s "$HOME/.local/share/xenia-cross/fxc/fxc.exe" \ + && wine "$HOME/.local/share/xenia-cross/fxc/fxc.exe" /? 2>&1 | grep -q 'cs_5_1' \ + && echo "OK fxc with SM 5_1" + +echo "=== P5 Wine DLL trio ===" +sz12=$(stat -c %s "$HOME/.wine/drive_c/windows/system32/d3d12core.dll" 2>/dev/null || echo 0) +szDX=$(stat -c %s "$HOME/.wine/drive_c/windows/system32/dxgi.dll" 2>/dev/null || echo 0) +[ "$sz12" -gt 1000000 ] && echo "OK d3d12core ($sz12 bytes, vkd3d-proton)" \ + || echo "FAIL d3d12core ($sz12 bytes — re-run winetricks vkd3d)" +[ "$szDX" -gt 2000000 ] && echo "OK dxgi ($szDX bytes, DXVK)" \ + || echo "FAIL dxgi ($szDX bytes — re-run winetricks dxvk)" + +echo "=== P6-8 patched source ===" +cd "$XENIA_CANARY_SRC" 2>/dev/null && { + grep -q 'inline const HANDLE kFileHandleInvalid' src/xenia/base/mapped_memory_win.cc && echo "OK 7.1" + grep -q '"../../../assets/icon/icon.ico"' src/xenia/app/main_resources.rc && echo "OK 7.2" + grep -q '!defined(_WIN32)' third_party/snappy/snappy-stubs-public.h && echo "OK 7.3" + grep -q '!defined(_WIN32)' third_party/zlib-ng/zconf-ng.h && echo "OK 7.4" + grep -q 'NOT MSVC OR CMAKE_CXX_COMPILER_ID' third_party/CMakeLists.txt && echo "OK 7.5" + grep -q 'CMAKE_CXX_COMPILER_FRONTEND_VARIANT' CMakeLists.txt && echo "OK 7.6a" + grep -q 'generate_version_h' CMakeLists.txt && echo "OK 7.6b" + grep -q '_env_prefix' cmake/XeniaHelpers.cmake && echo "OK 7.7" + grep -q '_wineify' tools/build/compile_shader_dxbc.py && echo "OK 7.8" + test -f cmake/toolchains/linux-to-win-msvc.cmake && echo "OK 8.1" + test -f cmake/toolchains/xwin-case-symlinks.py && echo "OK 8.2" + grep -q 'cross-win-clangcl' CMakePresets.json && echo "OK 8.3" +} + +echo "=== P10 build artifact ===" +test -f "$XENIA_CANARY_SRC/build-cross/bin/Windows/Debug/xenia_canary.exe" \ + && echo "OK build artifact" +``` + +A clean steady state prints ~20 `OK` lines. + +--- + +## Appendix A — Failure-mode matrix (extended) + +| Phase | Symptom | Probable cause | Fix | +|---|---|---|---| +| Configure | `fatal error: 'stdio.h' file not found` (or `cstdint`, `algorithm`, etc.) | xwin splat layout incompatible with `/winsysroot` | Use the toolchain in §8.1 verbatim — explicit `-imsvc` with `SHELL:` prefix | +| Configure | `Manually-specified variables were not used by the project: FXC_PATH` warning | Harmless — toolchain reads from `$ENV{FXC_PATH}`, not cache | Ignore | +| Configure | `Could NOT find Python3` | python3 not on PATH | `apt install python3` | +| Configure | `Manually-specified variables were not used by the project: CMAKE_TOOLCHAIN_FILE` | Toolchain file path wrong | Verify §9.2's `cmake --preset` command and that the toolchain exists at the expected path | +| Build | `static assertion failed: STL1000: Unexpected compiler version, expected Clang 19.0.0 or newer` | xwin's MSVC STL version-pinned | Verify `add_compile_definitions(_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH)` is in the toolchain (§8.1) | +| Build | `fatal error: 'ObjBase.h' file not found` (or other mixed-case Windows header) | xwin missed a case-alias | Re-run `cmake/toolchains/xwin-case-symlinks.py` (§9.1); add the new dir to SOURCE_ROOTS if it's under a non-standard third_party path | +| Build | `'fxc.exe' is not recognized` or `Unknown or invalid option '/foo/bar'` | `FXC_PATH` env not propagated, or unix path passed to fxc | Confirm §7.7 (XeniaHelpers.cmake `_env_prefix`) and §7.8 (winepath) patches are applied | +| Build | `error X3506: unrecognized compiler target 'cs_5_1'` | Using legacy DX SDK Jun 2010 fxc 9.29 (SM 5.0 max) | Install Win10 SDK fxc per §4 | +| Build | `Unknown or invalid option '/Qstrip_priv'` | Same as above — legacy fxc | Same fix | +| Build | `'INVALID_HANDLE_VALUE' must be initialized by a constant expression` | clang-cl strict constexpr check | Apply §7.1 patch | +| Build | `'cstdint' file not found` inside ``, or `'sys/uio.h' file not found` from snappy | clang-cl pulling Linux-pre-generated POSIX headers | Apply §7.3 + §7.4 patches | +| Build | `call to undeclared function '_mm256_broadcastsi128_si256'` etc. | clang-cl needs per-file `-mavx512*` flags | Apply §7.5 patch | +| Build | `Error in ICON statement (ID MAINICON): file not found ..\\..\\..\\assets\\icon\\icon.ico` | llvm-rc backslash literalism | Apply §7.2 patch | +| Build | missing `version.h` | xenia-build.py wasn't invoked | Apply §7.6b (the `execute_process` block) | +| Build | `-Wignored-pragmas: argument 's' to '#pragma optimize'; expected ""` treated as error | clang-cl strictness | Add `-Wno-ignored-pragmas` to toolchain (already in §8.1) | +| Build | clang-cl complaints `multi-character character constant` for `'ZM'` etc. | clang-cl errors under `/WX` | `-Wno-multichar` in toolchain (already in §8.1) | +| Build | `inline function 'X::Y' is not defined` | `XE_FORCEINLINE virtual` methods | `-Wno-undefined-inline` in toolchain (already in §8.1) | +| Build | LNK errors about missing debug-CRT symbols | Inconsistent CRT runtime selection | Verify `CMAKE_MSVC_RUNTIME_LIBRARY = MultiThreadedDLL` (§8.1) | +| Runtime | Dialog: `Unhandled Exception in Xenia / 0x578 Invalid window handle / NTSTATUS 0xC0010578` | vkd3d-proton + Wine builtin dxgi mismatch — crash in `vkd3d_instance_get_vk_instance` at swapchain init | Install BOTH `winetricks vkd3d` AND `winetricks dxvk` per §5 | +| Runtime | `D3D12RenderTargetCache: Failed to create Resolve Copy Fast 32bpp 1x/2xMSAA …` then `x> Unable to setup command processor internal state` | vkd3d-proton ≤ 2.14 (too old for current xenia) | Re-run `winetricks -q vkd3d` to fetch current release; confirm `d3d12core.dll` is ~6 MB | +| Runtime | `!> Unable to allocate code cache generated code storage` (range 0xA0000000-0xAFFFFFFF) | Wine GE / Proton variant has shims loaded in xenia's hardcoded JIT region | Use `/usr/bin/wine` (system Wine 9.0); see Appendix C | +| Runtime | `i> DXGI adapter: Radeon RX 6800/6800 XT / 6900 XT (vendor 0x1002 …)` with no `(RADV NAVI21)` or vkd3d-proton suffix | Wine builtin vkd3d is loaded, not vkd3d-proton | Re-check §5 trio install; the DXVK-flavored dxgi must also be in place | +| Runtime | Log file empty / no `xenia.log` created | Working dir wrong, or wine prefix not properly set | `cd` into the exe's directory before invoking wine; ensure `WINEPREFIX` is unset or points to a primed prefix | +| Runtime | `i> Skipping draw - pipeline not ready: VS …` repeats forever | Normal — pipelines compile asynchronously | Not an error; wait or trigger gameplay input | +| Runtime | Random GUI dialog at startup with "Visual C++ runtime not installed" | `CMAKE_MSVC_RUNTIME_LIBRARY` wasn't set to `MultiThreadedDLL`, debug CRT DLLs missing | Verify §8.1 — toolchain must force `/MD`, not `/MDd` | + +--- + +## Appendix B — Why each patch exists (one-liner per) + +| § | Patch | Root cause | +|---|---|---| +| 7.1 | `mapped_memory_win.cc` constexpr→const | clang-cl rejects reinterpret_cast in constant expressions; MSVC accepts as extension | +| 7.2 | `main_resources.rc` backslash→forward slash | llvm-rc on Linux treats `\` as literal filename char; can't resolve relative path | +| 7.3 | `snappy-stubs-public.h` `!_WIN32` gate | Header was pre-generated on a Linux host, hardcoded `HAVE_SYS_UIO_H=1` | +| 7.4 | `zconf-ng.h` `!_WIN32` gate | Same — `Z_HAVE_UNISTD_H` baked in on Linux | +| 7.5 | `third_party/CMakeLists.txt` add `Clang` to AVX flag gate | cl.exe exposes intrinsics unconditionally; clang-cl needs `-mavx512*` per file | +| 7.6a | `CMakeLists.txt` `/RTCsu` gate | `/RTCsu` is MSVC-only (warns per TU under clang-cl) | +| 7.6b | `CMakeLists.txt` `version.h` execute_process | `version.h` normally generated by `xb setup`; CMake-direct flow needs explicit call | +| 7.7 | `XeniaHelpers.cmake` env propagation | CMake `set(ENV{...})` doesn't survive ninja's subprocess spawn | +| 7.8 | `compile_shader_dxbc.py` winepath | fxc.exe parses `/path/to/foo` as a switch (`/p`) instead of a path | + +All eight are **build-fix patches**. None changes runtime semantics. + +Clang-cl-specific runtime warnings silenced in toolchain (§8.1's `-Wno-*` list): + +| Flag | Why | +|---|---| +| `-Wno-microsoft-include` | xenia uses MSVC-style relative paths in some `#include`s | +| `-Wno-unused-command-line-argument` | `/MP` etc. silently accepted by clang-cl | +| `-Wno-ignored-pragma-intrinsic` | `#pragma intrinsic(...)` from MSVC headers | +| `-Wno-nonportable-include-path` | Case mismatches caught by clang on Linux but accepted by MSVC | +| `-Wno-pragma-pack` | `#pragma pack` in third_party deps | +| `-Wno-tautological-pointer-compare` | `&name == nullptr` in xenia's threading code | +| `-Wno-microsoft-cast` | `static_cast`s clang considers questionable | +| `-Wno-deprecated-declarations` | Win32 API surface | +| `-Wno-switch` | xenia uses non-exhaustive switches | +| `-Wno-attributes` | various MSVC attribute syntax | +| `-Wno-deprecated-{register,volatile,enum-enum-conversion}` | C++ standard tightenings | +| `-Wno-ignored-pragmas` | `__pragma(optimize("s",on))` — clang-cl only knows the `""` arg | +| `-Wno-undefined-inline` | `XE_FORCEINLINE virtual` methods defined in a separate TU | +| `-Wno-sizeof-pointer-memaccess` | one real-bug case in `winkey_input_driver.cc` | +| `-Wno-multichar` | `'ZM'` PE magic literal in `windowed_app_main_win.cc` | + +Plus `_mm_cvtsi64x_si128=_mm_cvtsi64_si128` macro (MSVC-only intrinsic alias). + +--- + +## Appendix C — Alternative Wine runtimes + +| Setup | Works? | Notes | +|---|---|---| +| **System Wine 9.0 + winetricks vkd3d + winetricks dxvk** | ✅ **recommended** | This recipe | +| Lutris with system Wine + Lutris's runtime DLLs | ✅ | Works if `~/.local/share/lutris/runtime/vkd3d/v<3.0+>/` and `runtime/dxvk/v<2.4+>/` are present. Lutris's bundled vkd3d-proton 2.14 is too old for current xenia — manually drop in 3.0+ via the same approach | +| Steam Proton | ✅ | Proton ships its own vkd3d-proton + DXVK. Run from inside a Proton-managed prefix (the receiver may have done this previously — Steam-origin DLLs in `/some/path/drive_c/windows/system32/` won't hurt) | +| **Wine GE (Glorious Eggroll)** | ❌ **DO NOT USE** | Wine GE's esync/gamemode shims load in `0xA0000000-0xAFFFFFFF`, colliding with xenia's hardcoded JIT generated-code region. Symptom: `!> Unable to allocate code cache generated code storage`. The older May 2025 xenia release tolerated this; current does not. | +| Wine Staging | Untested | Should work — has more permissive vkd3d-proton hooks | +| Vanilla Wine without vkd3d/dxvk | ❌ | Wine builtin vkd3d ≠ vkd3d-proton; pipeline init fails | + +--- + +## Appendix D — Useful debugging commands + +```bash +# Inspect which d3d12.dll Wine is using (right now) +stat -c '%n is %s bytes' "$WINEPREFIX/drive_c/windows/system32/d3d12core.dll" +# 6 MB = vkd3d-proton, 66 KB = Wine builtin + +# Confirm dxgi.dll is DXVK +strings "$WINEPREFIX/drive_c/windows/system32/dxgi.dll" | grep -i 'DXVK' | head -1 +# DxvkAdap / DXVK: … = DXVK; nothing = Wine builtin + +# Capture a Wine SEH backtrace from a crash +WINEDEBUG=err+seh,err+exception wine ./xenia_canary.exe "$ISO_PATH" 2>&1 \ + | tee /tmp/wine-trace.log +grep -E 'Unhandled exception|=>0|=>1|=>2' /tmp/wine-trace.log + +# Force a specific DLL override for one run (without modifying registry) +WINEDLLOVERRIDES="d3d12,d3d12core,dxgi=n,b" wine ./xenia_canary.exe … +# n,b = native, then builtin fallback + +# Inspect xenia.log live as the binary runs +tail -f "$XENIA_CANARY_SRC/build-cross/bin/Windows/Debug/xenia.log" & +TAIL_PID=$! +WINEDEBUG=-all wine ./xenia_canary.exe --mute=true "$ISO_PATH" +kill $TAIL_PID 2>/dev/null + +# Find a specific symbol in the produced binary via the PDB +# (requires Wine-runnable WinDbg or x64dbg; alternatively use llvm-pdbutil) +llvm-pdbutil dump --symbols build-cross/bin/Windows/Debug/xenia_canary.pdb \ + | grep -i AUDIT_DEMO_SETUP_TRACE +``` + +--- + +## Appendix E — Locked version pin table + +For long-term reproducibility. All versions tested 2026-05-12; substitute current stables if needed. + +``` +Distro: Linux Mint 22.1 (Ubuntu 24.04 LTS base) +clang/lld/llvm-rc: 18.1.3-1ubuntu1 +ninja: 1.11.1 +cmake: 3.28.3 +python3: 3.12.3 +wine: 9.0~repack-4build3 +winetricks: 20240105 +p7zip-full: 16.02 +xwin: 0.9.0 +vkd3d-proton: 3.0.1 (current upstream stable, via winetricks) +DXVK: 2.x (current upstream stable, via winetricks) +Win10 SDK fxc: 10.0.28000.1839 (April 2026; any 10.0.22000+ works) +xenia-canary commit: 6de80dffe261b368ecefee36c9b2b337335228c0 +``` + +--- + +## Appendix F — Recovery: sanity-rebuild from scratch + +If something gets weird in `build-cross/` and you want a true clean rebuild without re-running Phases 1-9: + +```bash +cd "$XENIA_CANARY_SRC" +rm -rf build-cross +cmake --preset cross-win-clangcl +cmake --build build-cross --preset cross-debug --target xenia-app -j$(nproc) +``` + +This re-runs only Phases 9-10. Phases 1-8 (toolchain, xwin, fxc, Wine, source patches, config files) are persistent. + +If the source tree itself gets corrupted but Phases 1-5 are still intact: +```bash +cd "$XENIA_CANARY_SRC" +git stash # save any uncommitted instrumentation +git reset --hard 6de80dffe261b368ecefee36c9b2b337335228c0 +git submodule update --recursive --force +# Re-apply Phase 7 patches and Phase 8 config files +# (or `git stash pop` if you had committed them) +``` + +If you suspect the Wine prefix is what's broken: +```bash +mv "$HOME/.wine" "$HOME/.wine.broken.$(date +%s)" +wineboot # creates a fresh ~/.wine +winetricks -q vkd3d +winetricks -q dxvk +``` + +A fresh prefix loses any winetricks state (corefonts, DLL overrides for other Wine apps you had configured) — only do this if you specifically suspect prefix corruption. + +--- + +## Closing notes + +If you reached this line and the Phase 11 smoke test passes, you have a working cross-build. Hand it to your audit work; the instrumentation recipe in this doc (above) is identical to the one in [memory/project_xenia_canary_cross_build.md](../.claude/projects/-home-fabi-RE---Project-Sylpheed/memory/project_xenia_canary_cross_build.md) (which is the live quick-reference for routine work). + +For non-routine investigations (a crash that doesn't match Appendix A, a new patch needed because upstream xenia-canary moved, etc.) — capture both the failing command and the relevant log/trace excerpt, then update Appendix A with the new symptom → fix pair so the next agent doesn't have to rediscover it.