The project root carried a 59 KB version of this guide, untracked, on one disk. This repository carried 6.8 KB of it. Consolidation Phase 6 in the Sylpheed repo. Its audience line predates the decision to retire xenia-rs, so a note at the top says to read 'audit oracle for the Rust port' as 'instrumented build for RE'. The instructions themselves are unaffected -- clang-cl, xwin, lld-link and Wine do not care why you want the binary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1290 lines
58 KiB
Markdown
1290 lines
58 KiB
Markdown
<!-- Adopted 2026-09-13 from an untracked copy in the project root, which was
|
||
nine times longer than the stub this repository carried. See
|
||
docs/agents/CONSOLIDATION.md in the Sylpheed repo. -->
|
||
|
||
> ⚠️ **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".
|
||
|
||
# Building xenia-canary as a Windows Debug Binary on Linux — Complete Setup Guide
|
||
|
||
> **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.
|
||
|
||
---
|
||
|
||
## Preface — context for a fresh receiver
|
||
|
||
### Why this build exists
|
||
|
||
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:
|
||
|
||
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 |
|
||
|---|---|---|
|
||
| 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 |
|
||
|
||
### Time and disk budget
|
||
|
||
| 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. `<ObjBase.h>` 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 <https://learn.microsoft.com/windows/apps/windows-sdk/downloads>. 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 <sys/uio.h>
|
||
#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/<you>/…` 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 (`<ObjBase.h>`, `<Psapi.h>`, …) 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 <xenia-canary-root> <xwin-splat-dir>")
|
||
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: <XENIA_CANARY_SRC>/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 |
|
||
|---|---|
|
||
| **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) |
|
||
|
||
If a GUI dialog appeared or `^x>` count > 0: go to Appendix A, **Runtime** rows.
|
||
|
||
### 11.3 Inspect the binary in a debugger (optional)
|
||
|
||
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
|
||
```
|
||
|
||
> **[CHECKPOINT P11]** Smoke test passes: 0 fatals, ≥ 1000 log lines, no error dialog.
|
||
|
||
---
|
||
|
||
## Phase 12 — Wire into the xenia-rs audit workflow
|
||
|
||
The Windows debug build's role in the audit workflow:
|
||
|
||
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`.
|
||
|
||
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.
|
||
|
||
3. **Logs default to `<exe_dir>/xenia.log`**. For audit runs, point `--log_file=` somewhere predictable, e.g. `$XENIA_RS_ROOT/xenia-rs/audit-runs/audit_XX/canary.log`.
|
||
|
||
### 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-<TAG>` 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 |
|
||
|---|---|---|
|
||
| `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 |
|
||
|
||
`x>` (fatal) only comes from `xe::FatalError()` — **don't** use it for instrumentation; it `exit()`s the process.
|
||
|
||
### Golden rule
|
||
|
||
**Permitted edits**: new `XELOG*()` calls, new cvars, new `#if`-gated diagnostic blocks, new `audit_NN_*.cc` files (separate translation units that only observe).
|
||
|
||
**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.
|
||
|
||
If you think a behavior change is needed for instrumentation, ask first.
|
||
|
||
---
|
||
|
||
## 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 `<filesystem>`, 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.
|