Open-source project
ran-j/PS2Recomp avatar
ran-j/PS2Recomp

PS2Recomp: A Static Recompiler That Turns PS2 ELFs Into C++

Playstation 2 Static Recompiler & Runtime Tool to make native PC ports

3,284 stars137 forksC++GPL-3.0

At a glance

What is it?
PS2Recomp converts PlayStation 2 ELF binaries into C++ source and runs them through its own runtime. It is an experimental toolchain for people porting specific PS2 games, not a general-purpose emulator.
Who is it for?
Adopt PS2Recomp if you are porting one specific PS2 title and are willing to work in C++ with Ghidra: the toolchain expects a Ghidra-exported function map for retail or stripped games, and the runtime is described as a foundation to expand rather than a finished product. Do not adopt it if you want to insert a disc and play a library of games; that is what an emulator does, and PS2Recomp does not emulate the console.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What PS2Recomp actually produces

PS2Recomp is a static recompiler plus a runtime. It reads a PlayStation 2 ELF binary and writes C++ source that performs the same operations, which is a different proposition from an emulator that interprets the original machine code at run time. The README describes the translated code as very literal: `addiu $r4, $r4, 0x20` becomes `ctx->r4 = ADD32(ctx->r4, 0X20);`. There is no dynamic recompilation cache and no interpreter loop in the generated path.

The audience is narrow by design. This is for someone porting one specific game to a native PC build, who can read C++, work inside Ghidra, and patch the runtime when a syscall or hardware stub is missing. The repository is not archived, but the last push was on 2026-04-05, so it is not a project with a steady stream of commits behind it. Releases v0.2 through v0.4 all landed between 2026-03-09 and 2026-04-05, which is a burst of activity rather than a long maintenance record.

The four modules and the data flow between them

The toolchain splits into `ps2xAnalyzer`, `ps2xRecomp`, `ps2xRuntime` and `ps2xIOP`. The analyzer scans an ELF for functions and writes a TOML configuration covering stubs, skips and instruction patches. The recompiler reads that TOML plus the ELF, decodes R5900 instructions, and emits C++. The runtime supplies guest memory, a function registration table, syscall dispatch and hardware stubs. `ps2xIOP` is a separate layer of IOP high-level emulation services with game profiles and a C plugin ABI.

Two details in the README shape how you work with it. First, the recompiler tries relocation-symbol auto-binding at `J` and `JAL` callsites before falling back to raw address dispatch, so a known symbol such as `sceCdRead` can reach a runtime handler without a manual address map. Second, unresolved static `J`/`JAL` sites fall back to `runtime->lookupFunction(0x...)`, which means a missed entry point becomes a lookup at run time rather than a compile error. That fallback is convenient and also the place where a stripped retail game quietly loses accuracy.

Building PS2Recomp and running your first recompile

The README lists CMake 3.20+, a C++20 compiler (tested mainly with MSVC), and SSE4/AVX host support for some vector paths. Clone with submodules, because the build depends on them:

bash
git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git
cd PS2Recomp

cmake -S . -B out/build
cmake --build out/build --config Debug

The README's preferred workflow for retail or stripped games starts in Ghidra, not in the analyzer. Open the ELF, run `ps2xRecomp/tools/ghidra/ExportPS2Functions.java`, and keep both the exported TOML and the CSV map. Then recompile against the exported TOML:

bash
./ps2_recomp config.toml

The fallback path is the native analyzer, and the README is explicit that it is the weaker option for stripped retail games:

bash
./ps2_analyzer your_game.elf config.toml

Use that only when you do not have a Ghidra project yet. After generation, build the output and link it against `ps2xRuntime`; the README does not document a rollback or a clean way to regenerate after changing the TOML beyond rerunning the recompiler.

Configuring config.toml for a stripped game

The TOML is where most of the porting work happens. `general.input` is the source ELF, `general.ghidra_output` is the recommended function map CSV, `general.output` is the generated C++ folder, and `general.single_file_output` chooses one combined cpp or one file per function. For large titles, `general.low_memory_mode` avoids retaining disassembly strings and forces serial output generation, while `general.output_worker_threads` controls parallelism, clamped to nproc times two, with `0` meaning nproc minus one and `1` forcing serial generation.

For a stripped ELF, address binding goes inside `general.stubs`. The README gives this example:

toml
# 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"]

The generic handlers `ret0`, `ret1` and `reta0` exist for triage, so you can stub an unknown function and see how far execution gets. The README warns that the address must be the function start in that exact ELF build and that addresses are not portable across games, regions or builds. It also says to prefer a Ghidra-exported TOML before manual binding, because the extra boundaries and synthetic entry points matter more than early triage. The handler name must already exist in `PS2_SYSCALL_LIST` or `PS2_STUB_LIST`, so a typo produces a stub that cannot resolve.

Where the runtime stops and you start

The runtime section of the README is candid about scope. `ps2xRuntime` provides a guest memory model, a function dispatch table, a syscall dispatcher with common kernel IDs, and basic GS, VU, file and system stubs. It then calls itself a foundation to expand and port your game. That sentence is the honest summary of the project's maturity: the pieces exist, the coverage does not.

Game overrides are the escape hatch for per-title fixes. They are runtime-side, build-scoped C++ modules that run during `loadELF` and can replace EE function bindings by address for one specific game build. The API is declared in `ps2xRuntime/include/game_overrides.h`, with a registration macro `PS2_REGISTER_GAME_OVERRIDE(name, elfName, entry, crc32, applyFn)`. The README separates this from IOP behavior: RPC and DMA work belongs in a `ps2xIOP` profile instead. That split is a design decision worth respecting, because mixing EE binding overrides with IOP service emulation in one file makes both harder to reason about.

Limits, failure modes, and when this is the wrong tool

The first limit is that recompilation is per build. Address bindings are not portable across games, regions or builds, so a different dump of the same title invalidates the `handler@0xADDRESS` entries you wrote. Nothing in the README describes a way to carry that mapping forward automatically.

The second is accuracy on stripped retail games. The README states the native analyzer is less accurate there and more likely to miss internal callable entry points, which is why the Ghidra path is labeled preferred. If you skip Ghidra, you are choosing the path the documentation already tells you is weaker.

The third is the runtime's coverage. Basic GS, VU, file and system stubs are not a complete hardware implementation, and the README does not claim otherwise. If your goal is to play a broad library of PS2 games, this is the wrong tool: an emulator such as PCSX2 targets that job, and it works from the original binary without a per-game C++ porting effort. PS2Recomp asks you to own a build of one game and maintain it. The repository also carries `android/` and `vita/` directories at the top level, but the README does not document those targets, so treat them as unverified until you read the source.

How this differs from N64Recomp and from an emulator

The closest comparison in the search data is N64Recomp, and the difference is the console rather than the method. Both take a static binary and produce C++ that you compile natively. N64Recomp targets the Nintendo 64, so its instruction set, memory map and hardware stubs are built around that machine. PS2Recomp decodes the R5900 and supports PS2-specific MMI and VU0 macro instructions, which is a different decoder and a different runtime surface. A workflow learned on one does not transfer to the other at the config level.

The emulator comparison matters more for expectations. PCSX2 interprets or dynamically recompiles the original code and ships a general-purpose compatibility layer; PS2Recomp emits source you must compile, link and debug per title. The payoff is a native build with no emulation layer in the hot path. The cost is that every unresolved syscall or missing hardware stub is your problem, and the README's own framing of the runtime as a foundation tells you how much of that work is left.

Editorial conclusion

Adopt PS2Recomp if you are porting one specific PS2 title and are willing to work in C++ with Ghidra: the toolchain expects a Ghidra-exported function map for retail or stripped games, and the runtime is described as a foundation to expand rather than a finished product. Do not adopt it if you want to insert a disc and play a library of games; that is what an emulator does, and PS2Recomp does not emulate the console. Before committing, verify that your game's ELF survives the Ghidra export step, that the syscall IDs and stub names your title calls exist in PS2_SYSCALL_LIST or PS2_STUB_LIST, and that the generated C++ compiles against your host toolchain. The address bindings in general.stubs are tied to one exact ELF build, so any re-dump of the same game invalidates them.

Frequently asked questions

How do I use PS2Recomp?

For retail or stripped games the README's preferred path is to open the ELF in Ghidra, run ps2xRecomp/tools/ghidra/ExportPS2Functions.java, then recompile with the exported TOML using ./ps2_recomp config.toml. The native analyzer (./ps2_analyzer your_game.elf config.toml) is the fallback for quick experiments or ELFs with debug symbols. After generation you build the output and link it against ps2xRuntime.

What is PS2Recomp?

It is a PlayStation 2 static recompiler and runtime tool that translates PS2 ELF binaries into C++ and provides a runtime to execute the generated code. It decodes MIPS R5900 instructions, including PS2-specific MMI and VU0 macro support, and the translated code is described as very literal, with each MIPS instruction mapping to a C++ operation.

Is PCSX2 illegal?

The material for PS2Recomp does not discuss PCSX2's legal status, so this cannot be answered here. PS2Recomp itself is licensed under GPL-3.0.

Official sources

  1. Official README
  2. Project repository
  3. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ran-j-ps2recomp.svg)](https://hysenlabs.com/projects/ran-j-ps2recomp)