Also check our WIKI
This project statically recompiles PS2 ELF binaries into C++ and provides a runtime to execute the generated code.
ps2xAnalyzer: scans ELF/functions and writes TOML config (stubs,skip, instruction patches).ps2xRecomp: reads TOML + ELF, decodes R5900 instructions, and generates C++ output.ps2xRuntime: hosts memory, function registration, syscall dispatch, and hardware stubs.
- Translates MIPS R5900 instructions to C++ code
- PS2-specific MMI and VU0 macro support.
- Single-file or multi-file output.
- Configurable stubs, skips, and instruction patches.
- Instruction-driven syscall handling.
PS2Recomp works by:
- Parsing a PS2 ELF file to extract functions, symbols, and relocations
- Decoding the MIPS R5900 instructions in each function
- Translating those instructions to equivalent C++ code
- Generating a runtime that can execute the recompiled code
The translated code is very literal, with each MIPS instruction mapping to a C++ operation. For example, addiu $r4, $r4, 0x20 becomes ctx->r4 = ADD32(ctx->r4, 0X20);.
stubsentries generate wrappers that call known runtime syscall/stub handlers by name.stubsalso supports address bindings withhandler@0xADDRESSfor stripped games (for examplesceCdRead@0x00123456).- Address bindings also support generic return handlers for triage:
ret0,ret1,reta0. - Recompiler now tries relocation-symbol auto-binding at callsites (
J/JAL) before raw address dispatch; when relocation symbol is known (for examplesceCdRead), it can call runtime handlers without manual address mapping. - Recompiler discovers additional internal static entry targets and emits
entry_...wrappers for those addresses. - For unresolved static
J/JALsites, generated code falls back toruntime->lookupFunction(0x...). skipentries are not recompiled and generate explicitps2_stubs::TODO_NAMED(...)wrappers.- Recompiled
SYSCALLnow callsruntime->handleSyscall(...)with the encoded syscall immediate. - Runtime syscall dispatch tries encoded syscall ID first, then falls back to
$v1.
- CMake 3.20+
- C++20 compiler (currently tested mainly with MSVC)
- SSE4/AVX host support for some vector paths
git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git
cd PS2Recomp
cmake -S . -B out/build
cmake --build out/build --config DebugPreferred workflow for retail or stripped games:
- Open the ELF in Ghidra.
- Run
ps2xRecomp/tools/ghidra/ExportPS2Functions.java. - Use the exported TOML and CSV map.
- Recompile with the exported TOML:
./ps2_recomp config.tomlFallback workflow for quick local experiments or ELFs with debug symbol :
./ps2_analyzer your_game.elf config.tomlUse this only when you do not have a Ghidra project yet. The native analyzer is faster to start, but it is less accurate on stripped retail games and more likely to miss internal callable entry points.
See the Ghidra Workflow for the recommended path.
Then build generated output and link with ps2xRuntime.
Main fields in config.toml:
general.input: source ELF path.general.ghidra_output: recommended function map CSV exported from Ghidra.general.output: generated C++ output folder.general.single_file_output: one combined cpp or one file per function.general.patch_syscalls: apply configured patches toSYSCALLinstructions (falserecommended).general.patch_cop0: apply configured patches to COP0 instructions.general.patch_cache: apply configured patches to CACHE instructions.general.stubs: names to force as stubs. Also acceptshandler@0xADDRESSto bind a stripped function address directly to a runtime syscall/stub handler. Includes generic handlersret0,ret1,reta0.general.skip: names to force as skipped wrappers.patches.instructions: raw instruction replacements by address.
Address binding for stripped ELFs:
- Use
handler@0xADDRESSinsidegeneral.stubsto map a stripped function start directly to a runtime handler. - Example:
sceCdRead@0x00123456binds function start0x00123456tops2_stubs::sceCdRead(...). - Generic temporary handlers are available:
ret0@0xADDR,ret1@0xADDR,reta0@0xADDR. - Before manual binding, prefer recompilation from a Ghidra-exported TOML/CSV first. The extra boundaries and synthetic entry points are usually more important than manual early triage.
- The address must be the function start in that exact ELF build.
- Addresses are not portable across different games/regions/builds.
- The handler name must exist in runtime call lists (
PS2_SYSCALL_LISTorPS2_STUB_LIST).
Example:
[general]
input = "path/to/game.elf"
ghidra_output = ""
output = "output/"
single_file_output = true
patch_syscalls = false
patch_cop0 = true
patch_cache = true
stubs = ["printf", "malloc", "free"]
# stripped function binding by address:
# stubs = ["sceCdRead@0x00123456", "SifLoadModule@0x00127890"]
# temporary return handlers:
# stubs = ["ret0@0x001D9410", "ret1@0x001D5BC8", "reta0@0x0024B7C0"]
# mixed example:
# stubs = ["printf", "sceCdRead@0x00123456", "SifLoadModule@0x00127890"]
skip = ["abort", "exit"]
[patches]
instructions = [
{ address = "0x100004", value = "0x00000000" }
]To execute the recompiled code.
ps2xRuntime currently provides:
- Guest memory model and function dispatch table.
- Some syscall dispatcher with common kernel IDs.
- Basic GS/VU/file/system stubs.
- Foundation to expand and port your game.
Game overrides are runtime-side, build-scoped patch modules.
A game override is C++ code that runs during loadELF and can replace function bindings by address for one specific game build. This is separate from recompilation output and separate from global runtime stubs/syscalls.
API:
- Header:
ps2xRuntime/include/game_overrides.h - Register macro:
PS2_REGISTER_GAME_OVERRIDE(name, elfName, entry, crc32, applyFn) - Direct bind helper:
ps2_game_overrides::bindAddressHandler(runtime, addr, "handler")
Use Game Override modules when:
- You need per-game/per-build routing or patches without polluting global behavior.
- You need to bind many addresses, or install custom replacement logic for a specific title.
- Run with minimal config and no aggressive skipping.
- Fix hard blockers first (
function not found, syscall TODO, critical IO stubs). - Use temporary return stubs only to classify call importance.
- Promote temporary fixes to real implementations.
- Move per-game hacks into game overrides keyed by ELF metadata.
- Re-test from cold boot after each batch.
- Graphics Synthesizer and other hardware components need external implementation
- VU1 microcode is not complete.
- Hardware emulation is partial and many paths are stubbed.
- Inspired by N64Recomp
- Uses ELFIO for ELF parsing
- Uses toml11 for TOML parsing
- Uses fmt for string formatting