Guest code reads COP0 Status to decide whether interrupts are enabled,
and two different bits are involved:
IE (bit 0) the architectural MIPS interrupt enable, set once by the
kernel during boot and normally left set.
EIE (bit 16) the EE-specific enable that `ei` and `di` toggle.
We never execute the boot ROM, so nothing was setting either one, and
R5900Context started with Status at zero.
That is not cosmetic. libkernel's StartThread opens with
`mfc0 Status; xori 1; andi 1` and bails out with -1 when IE is clear --
its "you must call iStartThread from an interrupt handler" guard. With
Status at zero that guard fired every time, so every StartThread failed.
Dragon Quest VIII hits this during boot: it creates its CD streaming
thread, gets -1, prints "Can't start thread for streaming." and then
deadlocks with every thread blocked and none runnable. Nothing in the
runtime logs anything, because from its point of view the guest simply
asked a question and got an answer.
EIE matters for the matching reason: DIntr reports whether it was set so
the caller knows whether to pair it with an EIntr. Starting at zero makes
DIntr always answer "already disabled", so the re-enable never happens.
Two changes, both needed:
- R5900Context's constructor now sets Status to EIE | IE rather than 0.
BEV is deliberately left clear -- that selects the boot exception
vectors, which is the pre-handoff state, not this one.
- PS2Runtime's constructor no longer memsets m_cpuContext. R5900Context
already zeroes itself before applying its reset values, so the memset
only threw those values away. Threads created later were unaffected
because EeScheduler::startThread assigns `R5900Context{}`, which is
why this presented as "the main thread cannot start threads" rather
than something more obviously global.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Runtime Library
The runtime library provides the execution environment for recompiled code, including:
- Memory management (32MB main RAM, scratchpad, etc.)
- Register context (128-bit GPRs, VU0 registers, etc.)
- Function table for dynamic linking
- Basic PS2 system call stubs
How to use:
Take your decompiled code and place the cpp files on ps2xRuntime/src/runner and header files on ps2xRuntime/include and compile/be happy.
Vita Build Notes
The Vita runtime uses Quenom/raylib-5.5-vita for vita build. I recommend build runtime only.
Expected environment:
VITASDKpoints to your VitaSDK root.Quenom/raylib-5.5-vitahas already been built and installed into$VITASDK/arm-vita-eabi.- SDL2 with the PVR backend required by that raylib fork is also installed into the same VitaSDK prefix.
PS2X_DEFAULT_BOOT_ELFis mandatory, you need to define where your game is like "ux0:data/RANJ00001/game/SLUS_201.84".
The CMake for ps2xRuntime consumes those preinstalled headers and libraries from VitaSDK. It does not fetch or install the Vita raylib fork for you.
Adding Custom Function Implementations
You can add custom implementations for PS2 system calls or game functions by:
- Creating function implementations that match the signature:
void function_name(uint8_t* rdram, R5900Context* ctx, PS2Runtime *runtime);
- Registering them with the runtime:
runtime.registerFunction(address, function_name);
Advanced Features
Memory Translation The runtime handles PS2's memory addressing, including:
- KSEG0/KSEG1 direct mapping
- TLB lookups for user memory
- Special memory areas (scratchpad, I/O registers)
Vector Unit Support
PS2-specific 128-bit MMI instructions and VU0 macro mode instructions are supported via SSE/AVX intrinsics.
Instruction Patching
You can patch specific instructions in the recompiled code to fix game issues or implement custom behavior.
Limitations
- Graphics and sound output require external implementations
- Some PS2-specific hardware features may not be fully supported
- Performance may vary based on the complexity of the game