The recomp pipeline
Blur’s x86 code is recompiled offline to arm64 C++ and compiled with clang; nothing is emulated at runtime. The pipeline runs on your game files at build time, and its output is never committed or distributed.
Modules
Section titled “Modules”Five modules go through it: Blur.exe (1.2 patch), binkw32.dll, msvcr90.dll, d3dx9_42.dll and D3DCompiler_42.dll.
from_game.sh modules copies them from the disc, ISO or an install folder and checks each md5 against the version the
recompiler, rebase step and loader expect. The app’s installer uses the same sources.
Stages
Section titled “Stages”- Modules (
from_game.sh modules): extract and verify. - P-code (
from_game.sh pcode):D3DCompiler_42.dllis rebased to0x10200000(tools/recomp/rebase_pe.py) because its preferred base collides withd3dx9_42. Each module then goes through headless Ghidra viatools/recomp/ghidra/discover.sh:RecompDiscover.javafinds functions and jump tables,RecompExport.javawrites raw P-code per function to<module>/after/pcode.jsonl. Modules run in parallel, ~8 min in total. Entries Ghidra misses but that are proven at run time go intools/recomp/ghidra/force/<module>.txt. - Generation (
from_game.sh gen,tools/recomp/build/gen.sh): concatenates the P-code, runstools/recomp/emit.pyto produce C++, andtools/recomp/build/shard.pysplits it into shards (default 128) inwork/recomp/m3/gen. Only shards whose content changed are rewritten, so incremental builds stay incremental. - Build: CMake compiles the shards into
blurhostwhenRECOMP_GEN_DIRexists.
The emitter
Section titled “The emitter”emit.py turns P-code into C++ that reads as native code rather than an emulated CPU:
- registers and flags are function-local variables, spilled to a per-thread context only at calls, returns and indirect jumps;
- intra-function control flow uses
goto; direct calls are C calls; indirect calls go through one dispatch table; - a target missing from the dispatch table is a hard error that logs the address. Fix it by adding the address to discovery
(a
force/entry) and regenerating; - x87 runs as host
double; SSE/MMX map to vector types; P-codeCALLOTHERops are implemented intools/recomp/ops_ext.h; - guest memory is one 4 GB space; every access uses acquire/release (TSO-safe) loads and stores.
Constructs the emitter can’t handle fail the whole function loudly; failures are listed in emitted.cpp.failed.
Functions the host replaces at run time (the HOOKS set in emit.py) are the only ones that pay a hook lookup per call.
Runtime support
Section titled “Runtime support”tools/recomp/rt.h and ops_ext.h are the runtime the generated code includes. MSVCR90 is not recompiled for the app’s
runtime path; its exports are libc-like and implemented natively in src/host.
CI caches P-code keyed by a hash of the module files, the Ghidra-side tooling in git and the Ghidra version, so emitter changes don’t rerun Ghidra (CI and releases).