mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
ee149581aa
* feat: implement memory card IOP * feat: IOP trace * feat: move IOP logic to ps2xIOP refactor: small refactor on audio api on runtime
220 lines
5.4 KiB
Markdown
220 lines
5.4 KiB
Markdown
# Minimal plugin
|
|
|
|
This plugin matches one ELF basename and handles one function on a synthetic
|
|
SID. It is synchronous: it signals NOWAIT completion and suppresses a second
|
|
dispatch through a registered EE server.
|
|
|
|
```c
|
|
#include <ps2x/iop/plugin_api.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#define STRING_VIEW(literal) { (literal), sizeof(literal) - 1u }
|
|
|
|
enum
|
|
{
|
|
MY_SID = 0x6D795349u,
|
|
MY_FUNCTION = 1u,
|
|
};
|
|
|
|
struct my_state
|
|
{
|
|
const ps2x_iop_host_api_v1 *host;
|
|
};
|
|
|
|
static void *my_create(const ps2x_iop_host_api_v1 *host,
|
|
const ps2x_iop_game_identity_v1 *identity)
|
|
{
|
|
struct my_state *state;
|
|
(void)identity;
|
|
|
|
if (!host ||
|
|
host->abi_version != PS2X_IOP_ABI_VERSION_V1 ||
|
|
host->struct_size < sizeof(*host))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
state = (struct my_state *)calloc(1u, sizeof(*state));
|
|
if (state)
|
|
{
|
|
state->host = host;
|
|
}
|
|
return state;
|
|
}
|
|
|
|
static void my_destroy(void *instance)
|
|
{
|
|
free(instance);
|
|
}
|
|
|
|
static int32_t my_reset(void *instance)
|
|
{
|
|
return instance ? PS2X_IOP_STATUS_OK_V1
|
|
: PS2X_IOP_STATUS_INVALID_ARGUMENT_V1;
|
|
}
|
|
|
|
static int32_t my_handle_rpc(void *instance,
|
|
const ps2x_iop_rpc_request_v1 *request,
|
|
ps2x_iop_rpc_result_v1 *result)
|
|
{
|
|
struct my_state *state = (struct my_state *)instance;
|
|
const uint32_t value = 1u;
|
|
int32_t status;
|
|
|
|
if (!state || !request || !result ||
|
|
request->struct_size < sizeof(*request) ||
|
|
result->struct_size < sizeof(*result))
|
|
{
|
|
return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1;
|
|
}
|
|
|
|
result->handled = 0u;
|
|
result->result_address = 0u;
|
|
result->signal_nowait_completion = 0u;
|
|
result->signal_completion = 0u;
|
|
result->callback_policy = PS2X_IOP_CALLBACK_RUNTIME_DEFAULT_V1;
|
|
result->server_dispatch_policy =
|
|
PS2X_IOP_SERVER_DISPATCH_RUNTIME_DEFAULT_V1;
|
|
|
|
if (request->sid != MY_SID || request->function != MY_FUNCTION)
|
|
{
|
|
return PS2X_IOP_STATUS_OK_V1;
|
|
}
|
|
if (request->receive.size < sizeof(value))
|
|
{
|
|
return PS2X_IOP_STATUS_BUFFER_TOO_SMALL_V1;
|
|
}
|
|
if (!state->host->write_guest)
|
|
{
|
|
return PS2X_IOP_STATUS_UNSUPPORTED_V1;
|
|
}
|
|
|
|
status = state->host->write_guest(state->host->userdata,
|
|
request->receive.address,
|
|
&value,
|
|
sizeof(value));
|
|
if (status != PS2X_IOP_STATUS_OK_V1)
|
|
{
|
|
return status;
|
|
}
|
|
|
|
result->handled = 1u;
|
|
result->result_address = request->receive.address;
|
|
result->signal_nowait_completion = 1u;
|
|
result->server_dispatch_policy = PS2X_IOP_SERVER_DISPATCH_SUPPRESS_V1;
|
|
return PS2X_IOP_STATUS_OK_V1;
|
|
}
|
|
|
|
static const uint32_t my_sids[] = { MY_SID };
|
|
|
|
static const ps2x_iop_profile_api_v1 my_profiles[] = {
|
|
{
|
|
PS2X_IOP_ABI_VERSION_V1,
|
|
sizeof(ps2x_iop_profile_api_v1),
|
|
STRING_VIEW("my-game-profile"),
|
|
{
|
|
sizeof(ps2x_iop_game_matcher_v1),
|
|
STRING_VIEW("SLUS_000.00"),
|
|
0u,
|
|
0u,
|
|
},
|
|
1u,
|
|
my_sids,
|
|
my_create,
|
|
my_destroy,
|
|
my_reset,
|
|
NULL,
|
|
my_handle_rpc,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
},
|
|
};
|
|
|
|
PS2X_IOP_PLUGIN_EXPORT int32_t
|
|
ps2x_iop_query_v1(uint32_t host_abi_version,
|
|
ps2x_iop_plugin_api_v1 *out)
|
|
{
|
|
static const ps2x_iop_plugin_api_v1 plugin = {
|
|
PS2X_IOP_ABI_VERSION_V1,
|
|
sizeof(ps2x_iop_plugin_api_v1),
|
|
STRING_VIEW("my-iop-plugin"),
|
|
STRING_VIEW("1.0.0"),
|
|
1u,
|
|
my_profiles,
|
|
};
|
|
|
|
if (host_abi_version != PS2X_IOP_ABI_VERSION_V1)
|
|
{
|
|
return PS2X_IOP_STATUS_UNSUPPORTED_V1;
|
|
}
|
|
if (!out)
|
|
{
|
|
return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1;
|
|
}
|
|
if (out->struct_size < sizeof(*out))
|
|
{
|
|
return PS2X_IOP_STATUS_BUFFER_TOO_SMALL_V1;
|
|
}
|
|
|
|
*out = plugin;
|
|
return PS2X_IOP_STATUS_OK_V1;
|
|
}
|
|
```
|
|
|
|
### Standalone CMake target
|
|
|
|
The plugin consumes only the public ABI header; it does not link to `ps2xRuntime` or `ps2_iop`.
|
|
|
|
```cmake
|
|
cmake_minimum_required(VERSION 3.21)
|
|
project(my_iop_plugin LANGUAGES C)
|
|
|
|
set(PS2X_IOP_INCLUDE_DIR "" CACHE PATH
|
|
"Directory containing ps2x/iop/plugin_api.h"
|
|
)
|
|
if(NOT EXISTS "${PS2X_IOP_INCLUDE_DIR}/ps2x/iop/plugin_api.h")
|
|
message(FATAL_ERROR
|
|
"Set PS2X_IOP_INCLUDE_DIR to PS2Recomp/ps2xIOP/include"
|
|
)
|
|
endif()
|
|
|
|
add_library(my_iop_plugin MODULE my_iop_plugin.c)
|
|
target_include_directories(my_iop_plugin PRIVATE
|
|
"${PS2X_IOP_INCLUDE_DIR}"
|
|
)
|
|
set_target_properties(my_iop_plugin PROPERTIES
|
|
PREFIX ""
|
|
C_STANDARD 11
|
|
C_STANDARD_REQUIRED YES
|
|
C_EXTENSIONS NO
|
|
)
|
|
install(TARGETS my_iop_plugin
|
|
RUNTIME DESTINATION .
|
|
LIBRARY DESTINATION .
|
|
)
|
|
```
|
|
|
|
On Windows:
|
|
|
|
```powershell
|
|
cmake -S . -B build -A x64 `
|
|
-DPS2X_IOP_INCLUDE_DIR="C:/path/to/PS2Recomp/ps2xIOP/include"
|
|
cmake --build build --config Release
|
|
cmake --install build --config Release `
|
|
--prefix "C:/path/to/ps2EntryRunner/iop_plugins"
|
|
```
|
|
|
|
On Linux:
|
|
|
|
```sh
|
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
|
|
-DPS2X_IOP_INCLUDE_DIR=/path/to/PS2Recomp/ps2xIOP/include
|
|
cmake --build build -j
|
|
cmake --install build --prefix /path/to/ps2EntryRunner/iop_plugins
|
|
```
|
|
|
|
Build or install the resulting `.dll`/`.so` before the runtime calls
|
|
`initialize()`. If it is not installed directly, copy it into the executable's
|
|
`iop_plugins/` directory. |