Skip to content

Ps2xRuntime

Ranieri edited this page Feb 18, 2026 · 3 revisions

ps2xRuntime

ps2xRuntime is the runtime library that executes the recompiled C++ output generated by ps2_recomp.

It provides a minimal PlayStation 2 “Execution Environment” for the recompiled game code: memory, CPU context, MMIO routing, function dispatch, and syscall/stub plumbing.

Status: Work in progress. The runtime is currently the most underdeveloped part of the project. Expect to implement/fix syscalls and stubs per-game.


How to use with generated code

1) Copy generated files into the runtime project

  • Put your recompiled .cpp output into:

    • ps2xRuntime/src/runner/
  • Put any generated or required headers into:

    • ps2xRuntime/include/

What the runtime provides

Core features

  • Memory model
    • 32MB main RAM (RDRAM)
    • 16KB scratchpad
    • basic regions for EE I/O, BIOS window, GS registers, VU memory windows
  • CPU context (R5900Context)
    • 128-bit GPRs (stored as __m128i r[32])
    • PC, HI/LO + HI1/LO1, SA
    • COP0 registers
    • COP1 (FPU) registers
    • VU0 macro-mode state (VF/VI/Q/ACC/etc.)
  • Function table
    • Maps guest addresses → generated C++ function pointers
    • Acts like “dynamic linking” for translated code
  • Syscall / stub entry points
    • A central syscall handler (handleSyscall)
    • Stub hooks used by stubs / skip generated wrappers
  • Hybrid fast/slow memory access
    • Fast path: direct masked RDRAM reads/writes
    • Slow path: routes “special addresses” to runtime Load*/Store* to handle MMIO / scratchpad / BIOS / etc.

Repository layout (runtime)

Typical layout used by this project:

  • ps2xRuntime/include/
    • runtime public headers (ps2_runtime.h, ps2_runtime_macros.h, call lists, stubs APIs)
  • ps2xRuntime/src/
    • runtime implementation
    • src/lib/ contains syscall implementations and helpers
    • src/runner/ is the place where you drop generated game code

Recommended Iteration Loop

  1. Run with minimal config and no aggressive skipping.
  2. Fix hard blockers first (function not found, syscall TODO, critical IO stubs).
  3. Use temporary return stubs only to classify call importance.
  4. Promote temporary fixes to real implementations.
  5. Move per-game hacks into game overrides keyed by ELF metadata.
  6. Re-test from cold boot after each batch.

Minimal entry point example

This is a minimal main() that loads the ELF, registers functions, and runs:

#include "ps2_runtime.h"
#include "register_functions.h"
#include <iostream>
#include <string>

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cout << "Usage: " << argv[0] << " <elf_file>" << std::endl;
        return 1;
    }

    std::string elfPath = argv[1];

    PS2Runtime runtime;
    if (!runtime.initialize("ps2xRuntime (Raylib host)"))
    {
        std::cerr << "Failed to initialize PS2 runtime" << std::endl;
        return 1;
    }

    // Generated by the recompiler output revied by you
    registerAllFunctions(runtime);

    if (!runtime.loadELF(elfPath))
    {
        std::cerr << "Failed to load ELF file: " << elfPath << std::endl;
        return 1;
    }

    runtime.run();
    return 0;
}

Getting Unstuck: Symptom -> Action

Symptom: Warning: Function at address 0x... not found

  • Ensure generated register_functions.cpp is actually compiled into runner.
  • Ensure registerAllFunctions(runtime) is called before loadELF.
  • If address is valid but missing, add address binding or override registration for that address.

Symptom: Warning: Unimplemented PS2 stub called. name=...

  • Add/adjust general.stubs mapping for stripped address.
  • Or implement that stub in ps2xRuntime/src/lib/stubs/....

Symptom: [Syscall TODO] ...

  • Implement numeric syscall path in ps2xRuntime/src/lib/ps2_syscalls.cpp and related syscalls/*.inl.

Symptom: CPU is doing some work at PC 0x... PC not updating.

  • You are likely in a loop/spin/wait path.
  • Open the generated function for that PC address (sub_..._0x...cpp) and inspect loop condition and called helpers.
  • Set debugger breakpoints in:
    • PS2Runtime::lookupFunction
    • ps2_stubs::TODO_NAMED
    • ps2_syscalls::TODO

Syscalls and stubs (important)

The runtime is intentionally minimal. Many games will require additional work here.

Files to edit

You will likely need to implement missing syscalls and fix existing ones in:

  • ps2xRuntime/include/ps2_call_list.h
  • ps2xRuntime/src/lib/ps2_syscalls.cpp

Typical workflow

  1. Run the game and observe failures (missing syscall ID, TODO stub hit, or unhandled path).
  2. Identify the syscall/stub being used by the game.
  3. Add or fix the implementation in ps2_syscalls.cpp.
  4. Update the mapping/list in ps2_call_list.h.
  5. Rebuild and repeat.

How stubs/skip from the TOML relate to runtime

  • stubs: generated code calls a runtime stub handler (by name).
  • skip: generated code uses ps2_stubs::TODO_NAMED("...") style placeholders.

Both cases require the runtime to provide a safe behavior:

  • return a sane default value
  • emulate minimal behavior if needed
  • or log+abort when it must not proceed

Runtime macros (translation support)

ps2_runtime_macros.h provides:

  • arithmetic helpers (ADD32, SUB32_OV, etc.)
  • PS2 MMI/VU-friendly helpers via SSE intrinsics
  • fast read/write macros (FAST_READ32, etc.)
  • slow-path fallback macros (READ32, WRITE32, etc.) that route MMIO/special addresses through PS2Runtime

If you see weird game behavior, pay special attention to:

  • address classification in PS2Runtime::isSpecialAddress
  • correctness of Load*/Store* implementations for the region the game touches

Platform / SIMD notes

This runtime uses SIMD types and intrinsics heavily (__m128i, SSE).

  • MSVC: includes <intrin.h>
  • Clang/GCC on x86/x64: uses <immintrin.h> / <smmintrin.h>
  • Non-x86 platforms (ARM): build with USE_SSE2NEON and provide sse2neon.h

If your target platform does not support SSE natively, enabling USE_SSE2NEON is required.


Debugging tips

  • Start with a known, simple ELF first (homebrew / debug build).
  • Keep stubs and skip lists small and intentional.
  • Watch for:
    • bad pointer translations (guest → host)
    • missing syscall/stub behavior
    • games writing to MMIO regions (timers/DMAC/INTC/GS/VIF)
  • If you hit instruction translation issues earlier, fix those in the recompiler output first (the runtime assumes the translated code is semantically valid).

Current limitations

  • Many syscalls and I/O behaviors are incomplete
  • Complex DMA / VIF / GS behaviors may be missing or simplified
  • Some games will require per-title fixes (stubs, patches, and MMIO handling)