Files
PS2Recomp/ps2xIOP/README.md
T
Ran-j a293fa433a feat: IOP emulator
refactor: codegen to catch callbacks on mips code
feat: added a lot of entries or IOP emulator
2026-08-19 16:53:00 -03:00

187 lines
7.9 KiB
Markdown

# ps2xIOP
`ps2xIOP` is the IOP subsystem used by `ps2xRuntime`. It combines high-level service/profile path with an R3000A-backed IRX execution path.
No PS2 BIOS is required by the emulator backend: IRX imports for the kernel-facing libraries are handled by a small virtual IOP kernel, while the IRX module itself executes as original MIPS code.
> [!IMPORTANT]
> `ps2_iop`/`ps2x::iop` is a C++20 static library linked into the runtime.
> Optional `.dll` and `.so` files are native profile plugins. They extend
> the HLE profile catalog; they are not PS2 IRX modules.
## Execution policy
The subsystem is always hybrid. Original IRX modules execute on the R3000A
path, while core and game-profile HLE services are available for endpoints
that the loaded modules do not provide. A server registered by a physical IRX
is normally authoritative for its SID; HLE is the fallback. A profile service
may explicitly replace a physical endpoint when it is a compatibility stub.
There is no runtime mode
switch or environment variable to create divergent boot paths.
## Built-in services and profiles
Core services are created for every `IopSubsystem`:
| Service | SID | Availability |
| --- | --- | --- |
| MCSERV | `0x80000400`, `0x80000480` | Always active |
| LIBSD | `0x80000701` | Always active |
| DBCMAN | `0x80001300` | Always active |
The current built-in game profiles are:
| Profile | Matcher | Services |
| --- | --- | --- |
| `recvx-us` | `slus_201.84` | TSNDDRV and CRI DTX |
| `lotr-two-towers-us` | `SLUS_205.78` | CLFILE and SOUND update compatibility |
| `fatal-frame-us` | `SLUS_203.88` | SDRDRV |
All current built-ins declare only the ELF basename; they do not yet constrain
the entry point or CRC32. Basename matching is case-insensitive.
If no profile matches, the subsystem still has MCSERV, LIBSD, and DBCMAN. It
does not create any game-specific service. An unknown SID remains unhandled so
the SIF transport can apply its normal fallback behavior and report it in the
debugger.
## Profile selection
When an ELF is loaded, the runtime calculates one `GameIdentity`:
- ELF basename;
- entry point;
- CRC-32/IEEE (the common ZIP CRC-32) over the complete ELF file.
A profile matcher may declare any combination of those fields. Every declared
field must match. The matcher with the greatest number of declared fields wins.
Two matching profiles with equal specificity are an error; `loadELF()` fails
instead of silently choosing one.
A duplicate SID within the same service layer is an
error. Routing selects one service per SID: if a profile shadows a core SID and
then returns `handled = 0`, the subsystem does not make a second attempt through
the shadowed core service.
## Dispatch and transfer flow
`IopSubsystem` exposes the profile/HLE operations plus emulator lifecycle entry points:
1. `configure(GameIdentity)` selects the active compatibility profile.
2. `reset()` resets both active HLE services and the emulator state.
3. `loadModule(...)` / `loadModuleBuffer(...)` load and start an IRX.
4. `stopModule(...)` releases an emulated module and its owned runtime state.
5. `runEeCycles(...)` advances the IOP from EE cycle accounting.
6. `selectRpcAbi(...)`, `handleRpc(...)`, and `onSifTransfer(...)` provide the SIF transport bridge.
`RpcResult::handled` indicates whether a service consumed the request. The
result can also request completion semaphore signals and can suppress the
runtime's default EE callback or registered-server dispatch. The transport
executes those actions; the service never reaches into runtime internals.
The transfer hook is deliberately generic. TSNDDRV uses it for compatibility
backfill and CRI DTX uses it to observe DMA, but the SIF transport contains no
game names, game addresses, or branches for those modules.
RPC ABI selection is offered to every active profile service before the core
services, and every active service receives each SIF transfer notification.
Implementations must filter the relevant SID/function or transfer
kind/phase/address range themselves.
## Linking the static library
```cmake
target_link_libraries(my_runtime PRIVATE ps2x::iop)
```
The public C++ API is
[`iop_subsystem.h`](include/ps2x/iop/iop_subsystem.h). Applications using
`PS2Runtime` normally do not construct it directly; the runtime creates the
subsystem and its `IopHost` adapter.
## Dynamic profile plugins
Dynamic plugins are optional and disabled by default. Enable them on Windows or
Linux with `PS2X_IOP_ENABLE_PLUGINS=ON`.
| Platform | Plugin format | Status |
| --- | --- | --- |
| Windows | `.dll` | Supported |
| Linux | `.so` | Supported |
When enabled, the runtime scans `iop_plugins/` next to the executable. Discovery
is non-recursive. An embedding application can replace the search directories
before calling `initialize()`:
```cpp
runtime.setIopPluginSearchPaths({
std::filesystem::path{"path/to/my/iop_plugins"},
});
```
Each native module can publish one or more profiles. Missing query symbols,
incompatible ABI versions, malformed descriptors, and unsupported modules are
ignored with a diagnostic. Profile ambiguity, an active-layer SID conflict, or
failure to create the selected profile makes `loadELF()` fail with a clear
error.
The v1 loader accepts at most 256 profiles per plugin and 256 SIDs per profile.
A profile needs a non-empty ID, at least one matcher field, at least one SID,
and valid `create`, `destroy`, `reset`, and `handle_rpc` callbacks.
## Plugin ABI v1
Plugins include
[`plugin_api.h`](include/ps2x/iop/plugin_api.h) and export exactly one C entry
point:
```c
PS2X_IOP_PLUGIN_EXPORT int32_t
ps2x_iop_query_v1(uint32_t host_abi_version, ps2x_iop_plugin_api_v1 *plugin_api);
```
The ABI uses only fixed C function tables and POD data:
- validate `abi_version` and `struct_size` before accessing a structure;
- use pointer-plus-length string and buffer views;
- keep the profile instance behind an opaque `void *` handle;
- implement `create`, `destroy`, `reset`, and `handle_rpc`;
- optionally implement RPC ABI selection, SIF transfer hooks, and debug metrics;
- use host callbacks for guest memory, files, audio, memory cards, logging, and
EE function invocation;
- never retain request/result pointers after a callback returns;
- never pass STL types, C++ classes, exceptions, runtime objects, allocators, or
raw guest-memory pointers across the ABI.
The plugin itself may be implemented in C or C++, but exceptions must not cross
the exported C boundary. Guest buffer fields are PS2 addresses, not host
pointers.
The `host` function table passed to `create` may be retained until `destroy`.
The identity and its strings, RPC request/result, transfer, and metric pointers
are callback-scoped and must not be retained. `invoke_guest_function` is valid
only during `handle_rpc` and must use that request's `call_token`. Close file
handles and release guest allocations in `reset`/`destroy`.
Most `int32_t`-returning host callbacks return a `PS2X_IOP_STATUS_*_V1` code.
Two are intentionally boolean-style: `has_guest_function` and
`invoke_guest_function` return `1` for yes/success, `0` for no/failure, and a
negative value for an API error. Do not compare their successful result with
`PS2X_IOP_STATUS_OK_V1`, which is zero.
When compiling as C++, keep the exported query function under `extern "C"`
linkage. Including `plugin_api.h` provides the matching C declaration.
FOr learn more you can check [PluginExample](./PluginExample.md)
## Diagnostics and tests
`debugSnapshot()` exposes emulator cycle/instruction counts, loaded
IRX/thread/RPC-server counts, the active profile and provider, registered core
and profile services, service metrics, loader diagnostics, and the last
selection error. The runtime debugger renders this data in the **IOP/SIF** tab.
Registry behavior, instance isolation, reset, built-in services, profile
precedence, plugin discovery, ABI rejection, ambiguity, dispatch, destruction,
and module lifetime are covered by
[`ps2_iop_tests.cpp`](../ps2xTest/src/ps2_iop_tests.cpp).