Skip to content

Ps2xRecomp

Ranieri edited this page Feb 18, 2026 · 5 revisions

Recompiler

The recompiler consumes the TOML configuration generated by the analyzer and produces C++ source code by translating PS2 MIPS instructions into equivalent C++ operations.


How the recompiler uses the TOML

The recompiler interprets the TOML entries as instructions for how to generate code:

stubs

Functions listed in stubs are not recompiled.
Instead, the recompiler generates small wrapper functions that call known runtime syscall/stub handlers by name.

skip

Functions listed in skip are not recompiled and are treated as “intentionally unsupported”.
The recompiler generates explicit placeholders like:

  • ps2_stubs::TODO_NAMED("FunctionName")

This makes it obvious what still needs manual work or runtime support.

When to Use stubs, skip, Syscall Impl, or Override

Use stubs (handler@addr) when:

  • You know the address and want to route it to an existing runtime handler.

Use skip when:

  • You want an explicit TODO wrapper for now.
  • It will call ps2_stubs::TODO_NAMED(...) and return an error-like value.

Implement/extend ps2_syscalls when:

  • Runtime logs show unknown numeric syscalls ([Syscall TODO] ...).
  • Behavior is kernel-style and keyed by syscall IDs.

[general] keys

  • general.input: path to the source ELF.
  • general.output: output folder where generated C++ will be written.
  • general.ghidra_output: optional function map CSV exported from Ghidra.
  • general.single_file_output: generate one combined .cpp file (true) or one file per function (false).
  • general.patch_syscalls: apply configured patches to SYSCALL instructions (false recommended).
  • general.patch_cop0: apply configured patches to COP0 instructions.
  • general.patch_cache: apply configured patches to CACHE instructions.
  • general.stubs: additional names to force as stubs (overrides detection).
  • general.skip: additional names to force as skipped wrappers (overrides detection).

[patches] keys

  • patches.instructions: raw instruction replacements by address (manual patch list).

What “recompiling” means

The recompiler translates each MIPS instruction into a C++ equivalent that updates an emulated CPU context.

Example:

  • MIPS:

    • addiu $r4, $r4, 0x20
  • C++:

    • ctx->r4 = ADD32(ctx->r4, 0x20);

This is the core idea: treat registers as fields on a CPU context and rewrite MIPS semantics into C++ operations.


Running the recompiler

./ps2_recomp config.toml

This will read the TOML, load the ELF, and generate C++ code into general.output.


After recompilation: what to check

The tool is not perfect. After generation, you must review the output for comments indicating instructions the recompiler could not translate.

Some examples:

// Unhandled opcode: <raw>
// Unhandled FPU.S instruction: <raw>
// Unhandled PMTHL instruction: <raw>

If your game uses an instruction (or a special-case behavior) that the recompiler doesn’t support yet, you will see these markers in the output.

Manual verification checklist

You typically need to confirm:

  • Functions that should be stubs are actually stubbed.
  • Functions that should be skipped are actually skipped.
  • Required patches are correct and we are not removing important things.

How to Infer What an Unknown sub_xxx Should Do

You usually cannot infer semantics from address alone. You combine static and dynamic clues:

Static clues:

  • Caller context: inspect all callsites to that address.
  • Arguments: what goes in $a0..$a3.
  • Return contract: how caller checks $v0 (zero/non-zero, pointer, negative error).
  • Side effects: writes to known structures, DMA/GS/VIF regions, file paths, RPC payloads.
  • Nearby strings/constants: paths (cdrom0:), format strings, module names.

Dynamic clues:

  • Stub fallback logs show name, PC, RA, and args.
  • Syscall TODO logs show encoded syscall and register state.
  • Runtime logs show repeated PC (spin), unresolved function addresses, and thread state.

Practical strategy:

  1. First try existing runtime handlers (sceCdRead, fio*, Sif*, etc.).
  2. If uncertain, use ret0/ret1/reta0 to test whether behavior is just a probe.
  3. If game progresses, replace temporary return stubs with real behavior later.
  4. If game breaks/hangs, implement real semantics (or a targeted override).

Once the generated code builds cleanly and your stubs/skip/patches configuration is correct, you can proceed to the runtime stage.

Clone this wiki locally