A static recompilation of WET (2009, Xbox 360, Title ID 425307DB) to native
Windows x86-64, built on the ReXGlue SDK.
Static recompilation translates the Xbox 360 PowerPC code inside the game's
default.xex into native C++ that compiles and runs on a PC. There is no
emulator and no interpreter in the loop; file I/O, GPU commands, audio and
threading go through the ReXGlue runtime.
- Codegen runs clean (0 analysis errors).
- The build compiles and the executable boots to real Direct3D 12 rendering and audio; the game is playable in short bursts before hitting the next missing-function crash.
- Iterative, not finished: every session tends to surface one or two new missing addresses or access violations further into the game. See Known issues below.
- Framerate was low (~20 FPS) due to a confirmed, fixed root cause: a busy-spin in a rate-limiter function that codegen accidentally turned into a zero-cost loop (millions of calls/sec instead of a real wait). See Known issues below for details and the fragile-patch caveat.
- CMake 3.25+
- Ninja
- Clang / LLVM (clang-cl works too) - MSVC alone will not build this
- The ReXGlue SDK release archive - see
rexglue/README.md - extract-xiso to unpack the Xbox 360 ISO
- Your own legally-owned copy of WET, extracted from the Xbox 360 disc/ISO
Download the win-amd64 release from the
ReXGlue SDK releases page
and extract it into rexglue/win-amd64 at the repo root. Full steps in
rexglue/README.md. The SDK itself is gitignored; only
that README is checked in.
- Extract the Xbox 360 ISO with
extract-xiso
(e.g.
extract-xiso.exe -x WET.iso), which unpacks the disc's file tree. - Copy the extracted contents directly into
assets/, so it looks like:assets/ default.xex nxeart lu0/ lu1/ media/ movies0/ movies1/ streams0/ streams1/ assets/is gitignored (assets/*in.gitignore) - nothing from the disc is, or should be, committed to this repo.
cmake --preset local-win-relwithdebinfo
cmake --build out\build\local-win-relwithdebinfocmake --preset local-lin-relwithdebinfo
cmake --build out/build/local-lin-relwithdebinfoOther available presets: local-win/lin-debug, local-win/lin-release. These are the
CMakeUserPresets.json presets (gitignored file, already set up in this repo)
that inherit the platform presets from CMakePresets.json and add
CMAKE_PREFIX_PATH pointing at the vendored SDK. Building against the bare
win-amd64-* presets from CMakePresets.json directly will fail with
"ReXGlue SDK not found" - always use the local-* presets.
Codegen (translating assets/default.xex into generated/default/*.cpp) runs
automatically as a build step (wetrecomp_codegen CMake target) whenever
wetrecomp_manifest.toml or an included .toml changes.
cd out\build\local-win-relwithdebinfo
.\wetrecomp.execd out/build/local-lin-release
./wetrecompBoth --game_data_root and --gpu_plugin are optional now: OnConfigurePaths()
in src/wetrecomp_app.h defaults game_data_root to
<repo_root>/assets (found by walking up from the exe folder) when it isn't
set via flag/env var, and gpu_plugin = "xenos" already lives in
settings/hardware.toml. Pass either flag to
override - useful for pointing at a game dump kept elsewhere.
Useful extra flags/env vars while developing:
| Flag / env var | Effect |
|---|---|
--game_data_root <path> |
Overrides the default <repo_root>/assets game-files location. |
--gpu_plugin xenos |
Overrides settings/hardware.toml's gpu_plugin. Only needed if you want a different plugin than the file specifies. |
--graphics_backend d3d12|vulkan|any |
Forces the graphics API rexgpu-xenos uses (cvar, default "any", which picks D3D12 first). See settings/README.md for how this is wired up. |
--vsync=false |
Disables vsync (cvar, default true). Doesn't affect the busy-spin fix below - kept for reference from earlier perf testing. |
--wet_fps60_unlock=true |
Experimental, off by default - ported xenia-canary game-patches "60 FPS" patch. See settings/README.md. |
WET_NO_STUB_SWEEP=1 (env var) |
Disables the safety-net stub sweep (see below) - useful to isolate whether it's contributing to the framerate issue, at the cost of hitting FATAL crashes on any address not yet in default_functions.toml. |
Logs are written to out\build\<preset>\logs\*.log (the exe is built WIN32,
so nothing prints to the console).
Rendering/window/vsync and input-backend defaults are checked in under
settings/ (hardware.toml / mapping.toml), loaded
automatically at startup. CLI flags and REX_* environment variables always
override them - see settings/README.md for the full
reference and precedence rules.
rexglue init --project-name WetRecomp --xex-path assets\default.xexgeneratedCMakeLists.txt,CMakePresets.json,wetrecomp_manifest.toml,generated/rexglue.cmake,src/main.cpp,src/wetrecomp_app.h.- First
rexglue codegen wetrecomp_manifest.tomlfailed analysis with 10UnresolvedCallerrors (plainbbranches to addresses the auto-analyzer never registered as functions, because nothing called them withbl). Fixed by adding each address todefault_functions.tomlunder[functions]as0xADDRESS = {}(empty table - lets codegen discover the function's natural boundary itself instead of requiring a manual size), and including that file fromwetrecomp_manifest.toml(includes = ["default_functions.toml"]). - Added
CMakeUserPresets.json(local-debug/local-release/local-relwithdebinfo) soCMAKE_PREFIX_PATHfinds the SDK without touching the generated, overwritableCMakePresets.json. - Added
GPU_PLUGINS xenosto therexglue_setup_target()call inCMakeLists.txtsorexgpu-xenos*.dllgets staged next to the exe. - First runtime boot hit a chain of
[FATAL] Call to invalid or unregistered function at guest address 0x...crashes - addresses only ever reached through a function pointer / vtable slot, so static analysis had no call edge pointing at them. Each one had to be added todefault_functions.tomlthe same way as step 2. - Replaced the one-crash-per-rebuild loop with a stub sweep
(
OnPostSetup()insrc/wetrecomp_app.h): at startup, every address in the code range that isn't already a registered function gets a no-op safety-net stub instead of crashing, and every call to one is logged (deduplicated) tologs/stub_sweep.txt. One play session now surfaces several missing addresses at once instead of one FATAL per run. New addresses found this way get appended todefault_functions.tomland the game is rebuilt/rerun; repeat until a session runs clean or hits a different kind of bug.
- Manual function boundaries are an ongoing, iterative process. WET's
code has call sites the static analyzer cannot see ahead of time (plain
branches to un-analyzed code, and indirect calls through vtables/callback
tables resolved only at runtime). There is no way to find them all up
front;
default_functions.tomlgrows one play session at a time via the stub sweep described above. - Access violations from stubbed calls. A stubbed function is a no-op -
it does not set a return value. If the caller dereferences whatever was
left in
r3, that can crash with an unrelated-looking guest access violation until the real function is added todefault_functions.tomland actually executes. - Low framerate (~20 FPS), root cause found and fixed. GPU sat
near-idle during play while the CPU stayed busy - a CPU-bound problem, not
GPU/render. Traced via CPU-usage profiling plus a call counter (added by
editing the generated
.cppdirectly, since these are direct C++ calls, not indirect calls through theFunctionDispatcher-SetFunction()hooks can't see them) tosub_83066340, a rate-limiter ("has enough time passed?") called from a retry loop insub_8305E9E0. Its body has adb16cyc-based delay loop that was a real ~160ns hardware pause on Xenon; codegen dropsdb16cycentirely, turning it into a zero-cost spin - measured at ~26.8 million calls/sec in real gameplay. Fix: a realstd::this_thread::sleep_for(std::chrono::microseconds(15));inserted at that point ingenerated/default/wetrecomp_recomp.154.cpp(yield()was tried first and barely helped - it returns instantly when nothing else is contending for the core). Dropped the call rate to ~500/sec in real gameplay; this function and its caller chain vanished from the CPU-usage profiler's hot list entirely (were 40-70%+ of total CPU before).- Caveat: this patch lives in a codegen-generated file. It survives
normal rebuilds (nothing regenerates
wetrecomp_recomp.154.cppunlesswetrecomp_manifest.toml/default_functions.toml/assets/default.xexchange), but this SDK version (0.10.0) has no hook/override mechanism to make it regen-proof. If codegen ever regenerates that file, re-addstd::this_thread::sleep_for(std::chrono::microseconds(15));(with#include <chrono>/#include <thread>) right after thedb16cycloop insideDEFINE_REX_FUNC(sub_83066340), before the// cctpmcomment. - Ruled out along the way: the stub sweep (benchmarked, no measurable
difference),
--vsync=false(no help), D3D12 debug layer (off by default already), and a Vulkan switch (rejected - the prebuilt Windows release SDK is D3D12-only, no Vulkan libraries are bundled inrexglue/win-amd64/lib, and it's a CMake-time flag, not a runtime one). - Remaining hot spots after the fix look like normal costs of a
recompiled title: DXGI
Present(vsync wait), the Xenos GPU-command emulation itself, and a spread of small per-object update calls - no single dominant bottleneck left.
- Caveat: this patch lives in a codegen-generated file. It survives
normal rebuilds (nothing regenerates
- The
rexglue.exeCLI must be invoked through thelocal-*CMake presets, not the bare presets inCMakePresets.json- see Build.
- ReXGlue SDK (releases)
- extract-xiso (releases) -
used to unpack the Xbox 360 ISO into the file tree copied into
assets/ - xenia / xenia-canary - ReXGlue's runtime is derived from Xenia's, and the compatibility list was used to help pick this game as a recompilation target.