Skip to content

# CPUState

MIPS R3000A register file, GTE registers and memory callbacks.

The recompiled game is C that reads and writes these fields where the original instructions read and wrote registers; there is no interpreter and no other copy of the state. One instance exists for the session, owned by the sim thread. An override receives a pointer to it at the entry of the function it overrides and the pointer is valid until the override returns: keep no copy of it, and touch it from no other thread.

Calling convention (MIPS o32, as the game was compiled): arguments in `a0..a3`, a fifth and later argument in the caller's frame at `sp + 16`, `sp + 20`, ... at entry, read through `read_word`. The return value is in `v0`, with `v1` carrying the high word of a 64-bit return. `ra` is the return address, written by the caller before the call, and `sp` at entry is the caller's stack pointer: the overridden function has not yet built its frame.

Preservation: the recompiled function saves and restores exactly the registers its original code did, so at return `s0..s7`, `sp`, `fp` and `gp` hold their entry values and every other register is clobbered. An override that runs around `api->base(cpu)` sees this too: read the arguments before base(), the result after it, and expect `a0..a3`, `t0..t9`, `v0`, `v1` and `ra` to have changed across it. The natural-signature wrappers save and restore `a0..a3`, `v0`, `v1` and `ra` around the calls they make and nothing else. An override that calls `api->call` itself and then returns into the original must restore the argument registers it overwrote.

`pc` is written by the recompiled code only in a build regenerated with per-instruction PC tracking (`OPENPETE_REGEN_PC_TRACKING=1`) and at indirect jumps; in a stock build it is stale and not a way to find where the game is. `cop0` is carried for completeness: the game executes no coprocessor-0 instruction. The GTE arrays are live: every recompiled COP2 instruction executes against them.

The memory callbacks translate a guest vaddr in kuseg, kseg0 or kseg1 to the 2 MB RAM, the 1 KB scratchpad or a registered region (the mod arena included) and access it at the requested width; an unmapped read returns 0 and an unmapped write is dropped. Addresses are naturally aligned, as the game's are. `api->guest` resolves the same map to a host pointer, so the two are interchangeable for bulk work.

Savestates, rewind and runahead carry the register file, from `zero` through `gte_ctrl`, with guest RAM; the callback pointers are bound once at boot and are not timeline state.

Declared in `psx_runtime.h`, shipped in `sdk/`; include it beside `openpete_mod_api.h`, which only forward-declares the type.

## Fields

Field | Type | Meaning
--- | --- | ---
`zero` | `uint32_t` | Hard-wired 0.
`at` | `uint32_t` | Assembler temporary.
`v0` | `uint32_t` | Return value; the low word of a 64-bit return.
`v1` | `uint32_t` | High word of a 64-bit return; otherwise scratch.
`a0` | `uint32_t` | First argument.
`a1` | `uint32_t` | Second argument.
`a2` | `uint32_t` | Third argument.
`a3` | `uint32_t` | Fourth argument; later ones are on the stack at `sp + 16` and up.
`t0` | `uint32_t` | Caller-saved temporaries.
`t1` | `uint32_t` | Caller-saved temporaries.
`t2` | `uint32_t` | Caller-saved temporaries.
`t3` | `uint32_t` | Caller-saved temporaries.
`t4` | `uint32_t` | Caller-saved temporaries.
`t5` | `uint32_t` | Caller-saved temporaries.
`t6` | `uint32_t` | Caller-saved temporaries.
`t7` | `uint32_t` | Caller-saved temporaries.
`s0` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s1` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s2` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s3` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s4` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s5` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s6` | `uint32_t` | Callee-saved; a function returns them as it found them.
`s7` | `uint32_t` | Callee-saved; a function returns them as it found them.
`t8` | `uint32_t` | Caller-saved temporaries.
`t9` | `uint32_t` | Caller-saved temporaries.
`k0` | `uint32_t` | Kernel reserved; unused by the game.
`k1` | `uint32_t` | Kernel reserved; unused by the game.
`gp` | `uint32_t` | Global pointer: base of the game's small-data area, constant across the session.
`sp` | `uint32_t` | Stack pointer; the caller's frame at override entry.
`fp` | `uint32_t` | Frame pointer (`s8`); callee-saved.
`ra` | `uint32_t` | Return address of the current call.
`pc` | `uint32_t` | Program counter; maintained only under PC-tracking regeneration.
`hi` | `uint32_t` | Multiply/divide result, high word or remainder.
`lo` | `uint32_t` | Multiply/divide result, low word or quotient.
`cop0[32]` | `uint32_t` | System coprocessor registers; the game never touches them.
`gte_data[32]` | `uint32_t` | GTE data registers (VXY0..LZCR), live.
`gte_ctrl[32]` | `uint32_t` | GTE control registers (rotation matrix, translation, screen offsets, flags), live.
`uint32_t (*read_word)(uint32_t addr)` | — | Aligned 32-bit load.
`void (*write_word)(uint32_t addr, uint32_t value)` | — | Aligned 32-bit store.
`uint16_t (*read_half)(uint32_t addr)` | — | Aligned 16-bit load.
`void (*write_half)(uint32_t addr, uint16_t value)` | — | Aligned 16-bit store.
`uint8_t (*read_byte)(uint32_t addr)` | — | 8-bit load.
`void (*write_byte)(uint32_t addr, uint8_t value)` | — | 8-bit store.
`uint32_t (*lwl)(uint32_t addr, uint32_t rt_value)` | — | Unaligned load, left part, merged into the `rt_value` word.
`uint32_t (*lwr)(uint32_t addr, uint32_t rt_value)` | — | Unaligned load, right part, merged into the `rt_value` word.
`void (*swl)(uint32_t addr, uint32_t rt_value)` | — | Unaligned store, left part.
`void (*swr)(uint32_t addr, uint32_t rt_value)` | — | Unaligned store, right part.