From 39251a05f451c111f26611549511aef2dee0c24a Mon Sep 17 00:00:00 2001 From: Ran-j Date: Sat, 19 Sep 2026 21:17:58 -0300 Subject: [PATCH] feat: remove LLE IOPs --- README.md | 8 +- ps2xIOP/CMakeLists.txt | 16 - ps2xIOP/PluginExample.md | 220 --- ps2xIOP/README.md | 209 +-- ps2xIOP/include/ps2x/iop/iop_subsystem.h | 5 - ps2xIOP/include/ps2x/iop/iop_types.h | 3 - ps2xIOP/include/ps2x/iop/plugin_api.h | 307 ---- ps2xIOP/src/builtin_profiles.cpp | 151 -- ps2xIOP/src/iop_service.h | 21 +- ps2xIOP/src/iop_subsystem.cpp | 304 +--- ps2xIOP/src/module_factories.h | 148 -- ps2xIOP/src/modules/clfile.cpp | 635 -------- ps2xIOP/src/modules/cri_dtx.cpp | 1306 ----------------- ps2xIOP/src/modules/sdrdrv.cpp | 335 ----- ps2xIOP/src/modules/sound_update_stub.cpp | 241 --- ps2xIOP/src/modules/tsnddrv.cpp | 591 -------- ps2xIOP/src/plugin_loader.cpp | 956 ------------ ps2xIOP/src/plugin_loader.h | 34 - ps2xIOP/tests/iop_emulator_tests.cpp | 62 +- ps2xRecomp/src/lib/function_table_emitter.cpp | 4 +- ps2xRuntime/include/ps2_runtime.h | 1 - ps2xRuntime/src/lib/ps2_debug_panel.cpp | 15 +- ps2xRuntime/src/lib/ps2_iop_transport.h | 9 - ps2xRuntime/src/lib/ps2_runtime.cpp | 30 +- ps2xTest/CMakeLists.txt | 38 - ps2xTest/src/fake_iop_bad_abi.c | 15 - ps2xTest/src/fake_iop_missing_symbol.cpp | 6 - ps2xTest/src/fake_iop_plugin.cpp | 350 ----- ps2xTest/src/ps2_iop_tests.cpp | 911 +----------- ps2xTest/src/ps2_sif_dma_tests.cpp | 659 --------- ps2xTest/src/ps2_sif_rpc_tests.cpp | 752 +--------- 31 files changed, 120 insertions(+), 8222 deletions(-) delete mode 100644 ps2xIOP/PluginExample.md delete mode 100644 ps2xIOP/include/ps2x/iop/plugin_api.h delete mode 100644 ps2xIOP/src/builtin_profiles.cpp delete mode 100644 ps2xIOP/src/modules/clfile.cpp delete mode 100644 ps2xIOP/src/modules/cri_dtx.cpp delete mode 100644 ps2xIOP/src/modules/sdrdrv.cpp delete mode 100644 ps2xIOP/src/modules/sound_update_stub.cpp delete mode 100644 ps2xIOP/src/modules/tsnddrv.cpp delete mode 100644 ps2xIOP/src/plugin_loader.cpp delete mode 100644 ps2xIOP/src/plugin_loader.h delete mode 100644 ps2xTest/src/fake_iop_bad_abi.c delete mode 100644 ps2xTest/src/fake_iop_missing_symbol.cpp delete mode 100644 ps2xTest/src/fake_iop_plugin.cpp diff --git a/README.md b/README.md index dc3c489..469b279 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This project statically recompiles PS2 ELF binaries into C++ and provides a runt * `ps2xAnalyzer`: scans ELF/functions and writes TOML config (`stubs`, `skip`, instruction patches). * `ps2xRecomp`: reads TOML + ELF, decodes R5900 instructions, and generates C++ output. * `ps2xRuntime`: hosts memory, function registration, syscall dispatch, and hardware stubs. -* `ps2xIOP`: portable, instance-owned IOP HLE services, game profiles, and the C plugin ABI. +* `ps2xIOP`: R3000A IRX execution, a virtual IOP kernel, and generic HLE fallbacks. ### Features @@ -131,15 +131,15 @@ To execute the recompiled code. * Some syscall dispatcher with common kernel IDs. * Basic GS/VU/file/system stubs. * Foundation to expand and port your game. -* `ps2xIOP` profile selection and optional `.dll`/`.so` discovery for game-specific IOP HLE. +* `ps2xIOP` execution of original IRX modules with generic HLE fallbacks. -See [IOP HLE profiles and plugins](ps2xIOP/README.md) for the service boundary and external plugin workflow. +See [IOP emulation](ps2xIOP/README.md) for module execution and the service boundary. ### Game Override Hooks Game overrides are runtime-side, build-scoped patch modules. -A game override is C++ code that runs during `loadELF` and can replace EE function bindings by address for one specific game build. IOP RPC/DMA behavior belongs in a `ps2xIOP` profile instead. This is separate from recompilation output and separate from global runtime stubs/syscalls. +A game override is C++ code that runs during `loadELF` and can replace EE function bindings by address for one specific game build. IOP RPC/DMA behavior is handled by the `ps2xIOP` emulator and its runtime transport. This is separate from recompilation output and separate from global runtime stubs/syscalls. API: diff --git a/ps2xIOP/CMakeLists.txt b/ps2xIOP/CMakeLists.txt index 1e93ca0..f8d3e44 100644 --- a/ps2xIOP/CMakeLists.txt +++ b/ps2xIOP/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.21) project(ps2xIOP LANGUAGES CXX) -option(PS2X_IOP_ENABLE_PLUGINS "Enable dynamic ps2xIOP plugins" OFF) option(PS2X_IOP_BUILD_TESTS "Build ps2xIOP emulator smoke tests" OFF) add_library(ps2_iop STATIC @@ -26,16 +25,9 @@ add_library(ps2_iop STATIC src/emulator/imports/iop_sysmem.cpp src/emulator/imports/iop_timrman.cpp src/emulator/imports/iop_vblank.cpp - src/builtin_profiles.cpp - src/plugin_loader.cpp src/modules/dbcman.cpp src/modules/libsd.cpp src/modules/mcserv.cpp - src/modules/tsnddrv.cpp - src/modules/cri_dtx.cpp - src/modules/clfile.cpp - src/modules/sound_update_stub.cpp - src/modules/sdrdrv.cpp ) target_compile_features(ps2_iop PUBLIC cxx_std_20) @@ -50,14 +42,6 @@ target_include_directories(ps2_iop add_library(ps2x::iop ALIAS ps2_iop) -target_compile_definitions(ps2_iop PUBLIC - PS2X_IOP_ENABLE_PLUGINS=$ -) - -if(PS2X_IOP_ENABLE_PLUGINS AND UNIX AND NOT APPLE) - target_link_libraries(ps2_iop PRIVATE ${CMAKE_DL_LIBS}) -endif() - if(PS2X_IOP_BUILD_TESTS) enable_testing() add_executable(ps2_iop_emulator_tests tests/iop_emulator_tests.cpp) diff --git a/ps2xIOP/PluginExample.md b/ps2xIOP/PluginExample.md deleted file mode 100644 index 00d5f19..0000000 --- a/ps2xIOP/PluginExample.md +++ /dev/null @@ -1,220 +0,0 @@ -# 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 - -#include - -#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. \ No newline at end of file diff --git a/ps2xIOP/README.md b/ps2xIOP/README.md index ad20d0c..2f47d9e 100644 --- a/ps2xIOP/README.md +++ b/ps2xIOP/README.md @@ -1,186 +1,57 @@ # 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. +`ps2xIOP` runs original IRX modules on an R3000A interpreter, with a virtual +IOP kernel providing imports without a PS2 BIOS. The C++20 static library +`ps2_iop` / `ps2x::iop` is linked into `ps2xRuntime`. ## 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. +Game-specific IOP code executes from IRX modules. There is no game-profile +selection or native profile-plugin loader. A physical IRX RPC server is +authoritative for its SID. -## Built-in services and profiles +Generic HLE services remain available when no loaded IRX provides an endpoint: -Core services are created for every `IopSubsystem`: - -| Service | SID | Availability | +| Service | SID | Activation | | --- | --- | --- | -| MCSERV | `0x80000400`, `0x80000480` | Always active | -| LIBSD | `0x80000701` | Always active | -| DBCMAN | `0x80001300` | Always active | +| MCSERV | `0x80000400`, `0x80000480` | Recognized module load | +| LIBSD | `0x80000701` | Recognized module load | +| DBCMAN | `0x80001300` | Recognized module load | -The current built-in game profiles are: +These services are dormant before module load and after reset or the final +module stop. Unknown modules fail to load; unknown RPC SIDs remain unhandled. +Games previously using TSNDDRV, CRI DTX, CLFILE, SOUND or SDRDRV profiles now +require their IRX modules and support for the imports and hardware they use. -| 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 | +## Lifecycle and transport -All current built-ins declare only the ELF basename; they do not yet constrain -the entry point or CRC32. Basename matching is case-insensitive. +- `reset()` clears loaded modules, HLE service state and emulator state. +- `loadModule(...)` / `loadModuleBuffer(...)` load and start an IRX. +- `stopModule(...)` releases a module and its owned state. +- `runEeCycles(...)` advances the IOP from EE cycle accounting. +- `selectRpcAbi(...)`, `handleRpc(...)` and `onSifTransfer(...)` connect SIF transport. -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. +IOP RAM is separate from EE RAM. The transport copies data through the IOP +memory accessors; SIF notifications do not mirror bytes into equal-numbered EE +addresses. `RpcResult` describes completion and dispatch actions for the runtime. -## 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) +Link with `target_link_libraries(my_runtime PRIVATE ps2x::iop)`. The public API +is [iop_subsystem.h](include/ps2x/iop/iop_subsystem.h); `PS2Runtime` owns its +subsystem and host adapter. ## 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. +`debugSnapshot()` exposes emulator cycle/instruction counts, loaded module, +thread and RPC-server counts, generic service metrics and load diagnostics. +The runtime debugger renders these 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). +Build standalone tests with: + +```sh +cmake -S ps2xIOP -B out/build/iop-tests -DPS2X_IOP_BUILD_TESTS=ON +cmake --build out/build/iop-tests +ctest --test-dir out/build/iop-tests --output-on-failure +``` + +The suites cover IRX execution, RPC, imports, version resolution and generic +HLE compatibility. `ps2x_tests` also covers runtime SIF RPC/DMA integration. diff --git a/ps2xIOP/include/ps2x/iop/iop_subsystem.h b/ps2xIOP/include/ps2x/iop/iop_subsystem.h index 284be55..68fec81 100644 --- a/ps2xIOP/include/ps2x/iop/iop_subsystem.h +++ b/ps2xIOP/include/ps2x/iop/iop_subsystem.h @@ -3,7 +3,6 @@ #include "ps2x/iop/iop_host.h" #include "ps2x/iop/iop_types.h" -#include #include #include #include @@ -22,10 +21,6 @@ namespace ps2x::iop IopSubsystem(IopSubsystem &&) noexcept; IopSubsystem &operator=(IopSubsystem &&) noexcept; - void setPluginSearchPaths(std::vector paths); - bool loadPlugins(std::string *error = nullptr); - - bool configure(const GameIdentity &identity, std::string *error = nullptr); void reset(); [[nodiscard]] ModuleLoadResult loadModule(std::string_view path, const void *arguments = nullptr, uint32_t argumentSize = 0); diff --git a/ps2xIOP/include/ps2x/iop/iop_types.h b/ps2xIOP/include/ps2x/iop/iop_types.h index 7529a43..3491440 100644 --- a/ps2xIOP/include/ps2x/iop/iop_types.h +++ b/ps2xIOP/include/ps2x/iop/iop_types.h @@ -138,7 +138,6 @@ namespace ps2x::iop { std::string name; std::vector sids; - bool profileSpecific = false; bool active = true; std::vector metrics; }; @@ -150,8 +149,6 @@ namespace ps2x::iop uint32_t emulatorLoadedModules = 0; uint32_t emulatorThreads = 0; uint32_t emulatorRpcServers = 0; - std::string activeProfile; - std::string activeProvider; std::vector services; std::vector diagnostics; }; diff --git a/ps2xIOP/include/ps2x/iop/plugin_api.h b/ps2xIOP/include/ps2x/iop/plugin_api.h deleted file mode 100644 index 79748ac..0000000 --- a/ps2xIOP/include/ps2x/iop/plugin_api.h +++ /dev/null @@ -1,307 +0,0 @@ -#ifndef PS2X_IOP_PLUGIN_API_H -#define PS2X_IOP_PLUGIN_API_H - -#include -#include - -#if defined(_WIN32) -#define PS2X_IOP_PLUGIN_EXPORT __declspec(dllexport) -#elif defined(__GNUC__) || defined(__clang__) -#define PS2X_IOP_PLUGIN_EXPORT __attribute__((visibility("default"))) -#else -#define PS2X_IOP_PLUGIN_EXPORT -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -#define PS2X_IOP_ABI_VERSION_V1 1u -#define PS2X_IOP_QUERY_SYMBOL_V1 "ps2x_iop_query_v1" - - enum ps2x_iop_status_v1 - { - PS2X_IOP_STATUS_OK_V1 = 0, - PS2X_IOP_STATUS_BUFFER_TOO_SMALL_V1 = 1, - PS2X_IOP_STATUS_INVALID_ARGUMENT_V1 = -1, - PS2X_IOP_STATUS_UNSUPPORTED_V1 = -2, - PS2X_IOP_STATUS_FAILED_V1 = -3, - }; - - enum ps2x_iop_rpc_abi_v1 - { - PS2X_IOP_RPC_ABI_DEFAULT_V1 = 0, - PS2X_IOP_RPC_ABI_REGISTERS_V1 = 1, - PS2X_IOP_RPC_ABI_STACK_V1 = 2, - }; - - enum ps2x_iop_callback_policy_v1 - { - PS2X_IOP_CALLBACK_RUNTIME_DEFAULT_V1 = 0, - PS2X_IOP_CALLBACK_SUPPRESS_V1 = 1, - }; - - enum ps2x_iop_server_dispatch_policy_v1 - { - PS2X_IOP_SERVER_DISPATCH_RUNTIME_DEFAULT_V1 = 0, - PS2X_IOP_SERVER_DISPATCH_SUPPRESS_V1 = 1, - }; - - enum ps2x_iop_transfer_kind_v1 - { - PS2X_IOP_TRANSFER_SET_DMA_V1 = 0, - PS2X_IOP_TRANSFER_GET_OTHER_DATA_V1 = 1, - }; - - enum ps2x_iop_transfer_phase_v1 - { - PS2X_IOP_TRANSFER_BEFORE_COPY_V1 = 0, - PS2X_IOP_TRANSFER_AFTER_COPY_V1 = 1, - }; - - enum ps2x_iop_host_path_kind_v1 - { - PS2X_IOP_PATH_ELF_DIRECTORY_V1 = 0, - PS2X_IOP_PATH_CD_ROOT_V1 = 1, - PS2X_IOP_PATH_CD_IMAGE_V1 = 2, - PS2X_IOP_PATH_HOST_ROOT_V1 = 3, - PS2X_IOP_PATH_MEMORY_CARD_ROOT_V1 = 4, - }; - - enum ps2x_iop_handle_kind_v1 - { - PS2X_IOP_HANDLE_RPC_SERVER_V1 = 0, - PS2X_IOP_HANDLE_RPC_PACKET_V1 = 1, - }; - - enum ps2x_iop_log_level_v1 - { - PS2X_IOP_LOG_DEBUG_V1 = 0, - PS2X_IOP_LOG_INFO_V1 = 1, - PS2X_IOP_LOG_WARNING_V1 = 2, - PS2X_IOP_LOG_ERROR_V1 = 3, - }; - - enum ps2x_iop_memory_card_operation_v1 - { - PS2X_IOP_MC_INIT_V1 = 0, - PS2X_IOP_MC_GET_INFO_V1 = 1, - PS2X_IOP_MC_OPEN_V1 = 2, - PS2X_IOP_MC_CLOSE_V1 = 3, - PS2X_IOP_MC_SEEK_V1 = 4, - PS2X_IOP_MC_READ_V1 = 5, - PS2X_IOP_MC_WRITE_V1 = 6, - PS2X_IOP_MC_FLUSH_V1 = 7, - PS2X_IOP_MC_CHDIR_V1 = 8, - PS2X_IOP_MC_GET_DIR_V1 = 9, - PS2X_IOP_MC_SET_FILE_INFO_V1 = 10, - PS2X_IOP_MC_DELETE_V1 = 11, - PS2X_IOP_MC_FORMAT_V1 = 12, - PS2X_IOP_MC_UNFORMAT_V1 = 13, - PS2X_IOP_MC_MKDIR_V1 = 14, - }; - - typedef struct ps2x_iop_string_view_v1 - { - const char *data; - size_t size; - } ps2x_iop_string_view_v1; - - typedef struct ps2x_iop_guest_buffer_v1 - { - uint32_t address; - uint32_t size; - } ps2x_iop_guest_buffer_v1; - - typedef struct ps2x_iop_game_identity_v1 - { - uint32_t struct_size; - ps2x_iop_string_view_v1 elf_name; - uint32_t entry_point; - uint32_t crc32; - } ps2x_iop_game_identity_v1; - - typedef struct ps2x_iop_game_matcher_v1 - { - uint32_t struct_size; - ps2x_iop_string_view_v1 elf_name; - uint32_t entry_point; - uint32_t crc32; - } ps2x_iop_game_matcher_v1; - - typedef struct ps2x_iop_rpc_candidate_v1 - { - uint32_t send_size; - uint32_t receive_address; - uint32_t receive_size; - uint32_t end_function; - uint32_t end_parameter; - uint32_t plausible; - } ps2x_iop_rpc_candidate_v1; - - typedef struct ps2x_iop_rpc_abi_request_v1 - { - uint32_t struct_size; - uint32_t bound_sid; - uint32_t function; - ps2x_iop_rpc_candidate_v1 registers; - ps2x_iop_rpc_candidate_v1 stack; - } ps2x_iop_rpc_abi_request_v1; - - typedef struct ps2x_iop_rpc_request_v1 - { - uint32_t struct_size; - uint64_t call_token; - uint32_t client_address; - uint32_t server_address; - uint32_t server_function; - uint32_t server_buffer; - uint32_t sid; - uint32_t function; - uint32_t mode; - ps2x_iop_guest_buffer_v1 send; - ps2x_iop_guest_buffer_v1 receive; - uint32_t end_function; - uint32_t end_parameter; - } ps2x_iop_rpc_request_v1; - - typedef struct ps2x_iop_rpc_result_v1 - { - uint32_t struct_size; - uint32_t handled; - uint32_t result_address; - uint32_t signal_nowait_completion; - uint32_t signal_completion; - uint32_t callback_policy; - uint32_t server_dispatch_policy; - } ps2x_iop_rpc_result_v1; - - typedef struct ps2x_iop_sif_transfer_v1 - { - uint32_t struct_size; - uint32_t kind; - uint32_t phase; - uint32_t source_address; - uint32_t destination_address; - uint32_t size; - } ps2x_iop_sif_transfer_v1; - - typedef struct ps2x_iop_debug_metric_v1 - { - uint32_t struct_size; - ps2x_iop_string_view_v1 name; - uint64_t value; - uint32_t hexadecimal; - } ps2x_iop_debug_metric_v1; - - typedef struct ps2x_iop_memory_card_request_v1 - { - uint32_t struct_size; - uint32_t operation; - uint32_t arguments[5]; - } ps2x_iop_memory_card_request_v1; - - typedef struct ps2x_iop_host_api_v1 - { - uint32_t abi_version; - uint32_t struct_size; - void *userdata; - - int32_t (*read_guest)(void *userdata, uint32_t address, void *destination, size_t size); - int32_t (*write_guest)(void *userdata, uint32_t address, const void *source, size_t size); - int32_t (*zero_guest)(void *userdata, uint32_t address, size_t size); - int32_t (*normalize_guest_address)(void *userdata, uint32_t address, uint32_t *normalized); - uint32_t (*allocate_iop_handle)(void *userdata, uint32_t kind); - uint32_t (*allocate_guest)(void *userdata, uint32_t size, uint32_t alignment); - void (*free_guest)(void *userdata, uint32_t address); - - int32_t (*audio_command)(void *userdata, - uint32_t sid, - uint32_t function, - ps2x_iop_guest_buffer_v1 send, - ps2x_iop_guest_buffer_v1 receive); - - int32_t (*get_host_path)(void *userdata, - uint32_t kind, - char *destination, - size_t capacity, - size_t *required_size); - int32_t (*translate_guest_path)(void *userdata, - ps2x_iop_string_view_v1 path, - char *destination, - size_t capacity, - size_t *required_size); - uint64_t (*open_host_file)(void *userdata, ps2x_iop_string_view_v1 path); - int32_t (*host_file_size)(void *userdata, uint64_t handle, uint64_t *size); - int32_t (*read_host_file)(void *userdata, - uint64_t handle, - uint64_t offset, - void *destination, - size_t size, - size_t *bytes_read); - void (*close_host_file)(void *userdata, uint64_t handle); - - int32_t (*memory_card)(void *userdata, const ps2x_iop_memory_card_request_v1 *request, int32_t *result); - - int32_t (*has_guest_function)(void *userdata, uint32_t address); - int32_t (*invoke_guest_function)(void *userdata, - uint64_t call_token, - uint32_t address, - uint32_t a0, - uint32_t a1, - uint32_t a2, - uint32_t a3, - uint32_t *result_address); - void (*log)(void *userdata, uint32_t level, ps2x_iop_string_view_v1 message); - } ps2x_iop_host_api_v1; - - typedef void *(*ps2x_iop_profile_create_v1)(const ps2x_iop_host_api_v1 *host, - const ps2x_iop_game_identity_v1 *identity); - typedef void (*ps2x_iop_profile_destroy_v1)(void *instance); - typedef int32_t (*ps2x_iop_profile_reset_v1)(void *instance); - typedef uint32_t (*ps2x_iop_profile_select_rpc_abi_v1)(void *instance, const ps2x_iop_rpc_abi_request_v1 *request); - typedef int32_t (*ps2x_iop_profile_handle_rpc_v1)(void *instance, - const ps2x_iop_rpc_request_v1 *request, - ps2x_iop_rpc_result_v1 *result); - typedef int32_t (*ps2x_iop_profile_on_sif_transfer_v1)(void *instance, const ps2x_iop_sif_transfer_v1 *transfer); - typedef size_t (*ps2x_iop_profile_debug_metric_count_v1)(void *instance); - typedef int32_t (*ps2x_iop_profile_debug_metric_v1)(void *instance, size_t index, ps2x_iop_debug_metric_v1 *metric); - - typedef struct ps2x_iop_profile_api_v1 - { - uint32_t abi_version; - uint32_t struct_size; - ps2x_iop_string_view_v1 id; - ps2x_iop_game_matcher_v1 matcher; - size_t sid_count; - const uint32_t *sids; - ps2x_iop_profile_create_v1 create; - ps2x_iop_profile_destroy_v1 destroy; - ps2x_iop_profile_reset_v1 reset; - ps2x_iop_profile_select_rpc_abi_v1 select_rpc_abi; - ps2x_iop_profile_handle_rpc_v1 handle_rpc; - ps2x_iop_profile_on_sif_transfer_v1 on_sif_transfer; - ps2x_iop_profile_debug_metric_count_v1 debug_metric_count; - ps2x_iop_profile_debug_metric_v1 debug_metric; - } ps2x_iop_profile_api_v1; - - typedef struct ps2x_iop_plugin_api_v1 - { - uint32_t abi_version; - uint32_t struct_size; - ps2x_iop_string_view_v1 name; - ps2x_iop_string_view_v1 version; - size_t profile_count; - const ps2x_iop_profile_api_v1 *profiles; - } ps2x_iop_plugin_api_v1; - - typedef int32_t (*ps2x_iop_query_v1_fn)(uint32_t host_abi_version, ps2x_iop_plugin_api_v1 *plugin_api); - - PS2X_IOP_PLUGIN_EXPORT int32_t ps2x_iop_query_v1(uint32_t host_abi_version, ps2x_iop_plugin_api_v1 *plugin_api); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ps2xIOP/src/builtin_profiles.cpp b/ps2xIOP/src/builtin_profiles.cpp deleted file mode 100644 index f1447bf..0000000 --- a/ps2xIOP/src/builtin_profiles.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "iop_service.h" -#include "module_factories.h" - -#include - -namespace ps2x::iop::detail -{ - namespace - { - TsnddrvBindings recvxTsnddrvBindings() - { - return { - .serviceName = "TSNDDRV", - .protocol = TsnddrvProtocolVariant::SndQueueV1, - .arena = { - .base = 0x00120000u, - .limit = 0x00200000u, - .statusAlignment = 0x100u, - .tableAlignment = 0x100u, - .storageAlignment = 0x1000u, - .hdBytes = 0x4000u, - .sqBytes = 0x18000u, - .dataBytes = 0x40000u, - }, - .checksumCandidates = { - {0x01E0EF10u, 0x01E0EF20u}, - {0x01E1EF10u, 0x01E1EF20u}, - }, - .busyFlagAddress = 0x01E212C8u, - .completionRules = { - {0x002EAC20u, true, true, false}, - {0x002EAC30u, true, true, true}, - {0x002FAC20u, true, true, false}, - {0x002FAC30u, true, true, true}, - }, - }; - } - - CriDtxBindings recvxCriDtxBindings() - { - return { - .serviceName = "CRI DTX", - .sid = 0x7D000000u, - .urpcObjectBase = 0x01F18000u, - .urpcObjectLimit = 0x01F1FF00u, - .urpcObjectStride = 0x20u, - .urpcFunctionTableBase = 0x0033FED0u, - .urpcObjectTableBase = 0x0033FFD0u, - .dispatcherFunctionAddress = 0x002FABC0u, - .rpcServerPoolBase = 0x01F10000u, - .rpcServerStride = 0x80u, - }; - } - - ClFileBindings lotrClFileBindings() - { - return { - .serviceName = "CLFILE", - .sid = 0x0000FF01u, - .rpc = {}, - }; - } - - SoundUpdateStubBindings lotrSoundBindings() - { - return { - .serviceName = "SOUND update compatibility stub", - .sid = 0x00012345u, - .activeStreamCountOffset = 0u, - .responseCounterOffset = 4u, - .zeroReceiveBuffer = true, - .signalNowaitCompletion = true, - .completeQueuedPlayStreams = true, - .overridePhysicalServer = true, - .suppressedCompletionCallbacks = {}, - }; - } - - SdrdrvBindings fatalFrameSdrdrvBindings() - { - return { - .serviceName = "SDRDRV", - .sid = 0x19740512u, - .imageHeaderAddress = 0x012F0000u, - .sectorSize = 2048u, - .statusOffset = 0x6Cu, - .statusStride = 8u, - .statusSlotMask = 0x1Fu, - .completeValue = 0u, - .imageHeaderLowerName = "img_hd.bin", - .imageHeaderUpperName = "IMG_HD.BIN", - .imageBodyLowerName = "img_bd.bin", - .imageBodyUpperName = "IMG_BD.BIN", - }; - } - } - - ServiceList createCoreServices(IopHost &host) - { - ServiceList services; - services.emplace_back(createMcservService(host)); - services.emplace_back(createDbcmanService(host)); - services.emplace_back(createLibSdService(host)); - return services; - } - - std::vector createBuiltinProfiles() - { - std::vector profiles; - profiles.push_back({ - "recvx-us", - "builtin", - {.elfName = "slus_201.84"}, - [](IopHost &host, const GameIdentity &) - { - ServiceList services; - services.emplace_back(createTsnddrvService(host, recvxTsnddrvBindings())); - services.emplace_back(createCriDtxService(host, recvxCriDtxBindings())); - return services; - }, - }); - - // TODO remove this on next release - // profiles.push_back({ - // "lotr-two-towers-us", - // "builtin", - // {.elfName = "SLUS_205.78"}, - // [](IopHost &host, const GameIdentity &) - // { - // ServiceList services; - // services.emplace_back(createClFileService(host, lotrClFileBindings())); - // services.emplace_back(createSoundUpdateStubService(host, lotrSoundBindings())); - // return services; - // }, - // }); - - // profiles.push_back({ - // "fatal-frame-us", - // "builtin", - // {.elfName = "SLUS_203.88"}, - // [](IopHost &host, const GameIdentity &) - // { - // ServiceList services; - // services.emplace_back(createSdrdrvService(host, fatalFrameSdrdrvBindings())); - // return services; - // }, - // }); - - return profiles; - } -} diff --git a/ps2xIOP/src/iop_service.h b/ps2xIOP/src/iop_service.h index 631c73a..79fed4f 100644 --- a/ps2xIOP/src/iop_service.h +++ b/ps2xIOP/src/iop_service.h @@ -3,7 +3,6 @@ #include "ps2x/iop/iop_host.h" #include "ps2x/iop/iop_types.h" -#include #include #include #include @@ -19,8 +18,7 @@ namespace ps2x::iop::detail [[nodiscard]] virtual std::string_view name() const = 0; [[nodiscard]] virtual std::span sids() const = 0; // A service with aliases is dormant until one of these IOP modules is - // actually loaded. Profile services can omit aliases when the profile - // itself is the explicit compatibility contract. + // actually loaded. [[nodiscard]] virtual std::span moduleAliases() const { return {}; @@ -33,11 +31,6 @@ namespace ps2x::iop::detail return RpcAbi::RuntimeDefault; } - [[nodiscard]] virtual bool overridesPhysicalRpcServer() const noexcept - { - return false; - } - [[nodiscard]] virtual RpcResult handleRpc(const RpcRequest &request) = 0; virtual void onSifTransfer(const SifTransfer &transfer) @@ -52,16 +45,4 @@ namespace ps2x::iop::detail }; using ServiceList = std::vector>; - using ProfileFactory = std::function; - - struct ProfileDefinition - { - std::string id; - std::string provider = "builtin"; - GameMatcher matcher; - ProfileFactory factory; - }; - - ServiceList createCoreServices(IopHost &host); - std::vector createBuiltinProfiles(); } diff --git a/ps2xIOP/src/iop_subsystem.cpp b/ps2xIOP/src/iop_subsystem.cpp index af0d891..b498155 100644 --- a/ps2xIOP/src/iop_subsystem.cpp +++ b/ps2xIOP/src/iop_subsystem.cpp @@ -3,81 +3,26 @@ #include "iop_service.h" #include "iop_module_manager.h" #include "emulator/iop_emulator.h" -#include "plugin_loader.h" +#include "module_factories.h" #include "ps2x/iop/ps2_path.h" -#include -#include #include -#include #include #include #include namespace ps2x::iop { - namespace - { - bool equalsIgnoreCaseAscii(std::string_view lhs, std::string_view rhs) - { - if (lhs.size() != rhs.size()) - { - return false; - } - - for (size_t i = 0; i < lhs.size(); ++i) - { - const auto left = static_cast(lhs[i]); - const auto right = static_cast(rhs[i]); - if (std::tolower(left) != std::tolower(right)) - { - return false; - } - } - return true; - } - - int matchSpecificity(const GameMatcher &matcher, const GameIdentity &identity) - { - int specificity = 0; - if (!matcher.elfName.empty()) - { - if (!equalsIgnoreCaseAscii(matcher.elfName, identity.elfName)) - { - return -1; - } - ++specificity; - } - if (matcher.entryPoint != 0) - { - if (matcher.entryPoint != identity.entryPoint) - { - return -1; - } - ++specificity; - } - if (matcher.crc32 != 0) - { - if (matcher.crc32 != identity.crc32) - { - return -1; - } - ++specificity; - } - return specificity; - } - } - class IopSubsystem::Impl { public: explicit Impl(IopHost &hostRef) : host(hostRef), - pluginCatalog(hostRef), - coreServices(detail::createCoreServices(hostRef)), - profiles(detail::createBuiltinProfiles()), emulator(hostRef) { + coreServices.emplace_back(detail::createMcservService(host)); + coreServices.emplace_back(detail::createDbcmanService(host)); + coreServices.emplace_back(detail::createLibSdService(host)); refreshServiceModuleKeys(); rebuildRoutes(); } @@ -90,52 +35,34 @@ namespace ps2x::iop void refreshServiceModuleKeys() { std::vector keys; - auto collect = [&](const detail::ServiceList &services) + for (const auto &service : coreServices) { - for (const auto &service : services) - { - if (!service) - continue; - for (std::string_view alias : service->moduleAliases()) - keys.emplace_back(alias); - } - }; - collect(coreServices); - collect(profileServices); + for (std::string_view alias : service->moduleAliases()) + keys.emplace_back(alias); + } moduleManager.setServiceModuleKeys(std::move(keys)); } void rebuildRoutes() { routes.clear(); - auto addLayer = [&](detail::ServiceList &services, bool profileSpecific) -> bool + lastError.clear(); + for (const auto &service : coreServices) { - std::unordered_map layer; - for (const auto &service : services) + if (!serviceActive(*service)) + continue; + for (const uint32_t sid : service->sids()) { - if (!service || !serviceActive(*service)) + if (!routes.emplace(sid, service.get()).second) { - continue; - } - for (const uint32_t sid : service->sids()) - { - if (!layer.emplace(sid, service.get()).second) - { - std::ostringstream out; - out << "duplicate IOP SID 0x" << std::hex << sid << " in " << (profileSpecific ? "profile" : "core") << " layer"; - lastError = out.str(); - return false; - } + std::ostringstream out; + out << "duplicate IOP SID 0x" << std::hex << sid << " in core services"; + lastError = out.str(); + routes.clear(); + return; } } - for (const auto &[sid, service] : layer) - { - routes[sid] = service; - } - return true; - }; - - routesValid = addLayer(coreServices, false) && addLayer(profileServices, true); + } } void recordLoadOutcome(std::string_view path, bool hle) @@ -152,19 +79,11 @@ namespace ps2x::iop } IopHost &host; - detail::PluginCatalog pluginCatalog; detail::ServiceList coreServices; - detail::ServiceList profileServices; - std::vector profiles; std::unordered_map routes; - std::vector pluginSearchPaths; - std::vector diagnostics; std::vector loadOutcomes; std::unordered_set loggedLoadPaths; - std::string activeProfile; - std::string activeProvider; std::string lastError; - bool routesValid = true; detail::IopModuleManager moduleManager; detail::IopEmulator emulator; }; @@ -178,116 +97,6 @@ namespace ps2x::iop IopSubsystem::IopSubsystem(IopSubsystem &&) noexcept = default; IopSubsystem &IopSubsystem::operator=(IopSubsystem &&) noexcept = default; - void IopSubsystem::setPluginSearchPaths(std::vector paths) - { - m_impl->pluginSearchPaths = std::move(paths); - } - - bool IopSubsystem::loadPlugins(std::string *error) - { - return m_impl->pluginCatalog.load(m_impl->pluginSearchPaths, m_impl->profiles, m_impl->diagnostics, error); - } - - bool IopSubsystem::configure(const GameIdentity &identity, std::string *error) - { - if (error) - error->clear(); - m_impl->profileServices.clear(); - m_impl->activeProfile.clear(); - m_impl->activeProvider.clear(); - m_impl->lastError.clear(); - - const detail::ProfileDefinition *selected = nullptr; - const detail::ProfileDefinition *selectedTie = nullptr; - int selectedSpecificity = -1; - for (const auto &profile : m_impl->profiles) - { - const int specificity = matchSpecificity(profile.matcher, identity); - if (specificity < 0) - { - continue; - } - if (specificity > selectedSpecificity) - { - selected = &profile; - selectedTie = nullptr; - selectedSpecificity = specificity; - continue; - } - if (specificity == selectedSpecificity && selected) - { - selectedTie = &profile; - } - } - - if (selected && selectedTie) - { - m_impl->lastError = "ambiguous IOP profiles '" + selected->provider + ":" + - selected->id + "' and '" + selectedTie->provider + ":" + - selectedTie->id + "'"; - if (error) - { - *error = m_impl->lastError; - } - m_impl->refreshServiceModuleKeys(); - m_impl->rebuildRoutes(); - return false; - } - - if (selected) - { - try - { - m_impl->profileServices = selected->factory(m_impl->host, identity); - m_impl->activeProfile = selected->id; - m_impl->activeProvider = selected->provider; - } - catch (const std::exception &exception) - { - m_impl->lastError = "failed to create IOP profile '" + selected->id + "': " + exception.what(); - if (error) - { - *error = m_impl->lastError; - } - m_impl->refreshServiceModuleKeys(); - m_impl->rebuildRoutes(); - return false; - } - catch (...) - { - m_impl->lastError = "failed to create IOP profile '" + selected->id + "': unknown plugin exception"; - if (error) - { - *error = m_impl->lastError; - } - m_impl->refreshServiceModuleKeys(); - m_impl->rebuildRoutes(); - return false; - } - } - - m_impl->refreshServiceModuleKeys(); - m_impl->rebuildRoutes(); - if (!m_impl->routesValid) - { - const std::string routeError = m_impl->lastError; - m_impl->profileServices.clear(); - m_impl->activeProfile.clear(); - m_impl->activeProvider.clear(); - m_impl->refreshServiceModuleKeys(); - m_impl->rebuildRoutes(); - m_impl->lastError = routeError; - if (error) - { - *error = m_impl->lastError; - } - return false; - } - - reset(); - return true; - } - void IopSubsystem::reset() { m_impl->moduleManager.reset(); @@ -300,13 +109,6 @@ namespace ps2x::iop service->reset(); } } - for (auto &service : m_impl->profileServices) - { - if (service) - { - service->reset(); - } - } m_impl->emulator.reset(); m_impl->refreshServiceModuleKeys(); m_impl->rebuildRoutes(); @@ -369,17 +171,6 @@ namespace ps2x::iop RpcAbi IopSubsystem::selectRpcAbi(const RpcAbiRequest &request) const { - for (const auto &service : m_impl->profileServices) - { - if (service && m_impl->serviceActive(*service)) - { - const RpcAbi selected = service->selectRpcAbi(request); - if (selected != RpcAbi::RuntimeDefault) - { - return selected; - } - } - } for (const auto &service : m_impl->coreServices) { if (service && m_impl->serviceActive(*service)) @@ -408,23 +199,8 @@ namespace ps2x::iop const auto route = m_impl->routes.find(request.sid); detail::IopService *hle = route != m_impl->routes.end() ? route->second : nullptr; - // A profile can deliberately replace a physical endpoint when running - // that IRX is outside the selected compatibility scope (for example, - // disabling a game's audio driver while keeping the rest of its IOP - // modules physical). - if (hle && hle->overridesPhysicalRpcServer()) - { - RpcResult overridden = hle->handleRpc(request); - if (overridden.handled) - { - return overridden; - } - } - - // Otherwise physical servers are authoritative and HLE remains a - // compatibility fallback for endpoints no loaded IRX provides. RpcResult emulated = m_impl->emulator.handleRpc(request); - if (emulated.handled || !hle || hle->overridesPhysicalRpcServer()) + if (emulated.handled || !hle) { return emulated; } @@ -440,13 +216,6 @@ namespace ps2x::iop service->onSifTransfer(transfer); } } - for (auto &service : m_impl->profileServices) - { - if (service && m_impl->serviceActive(*service)) - { - service->onSifTransfer(transfer); - } - } m_impl->emulator.onSifTransfer(transfer); } @@ -488,34 +257,21 @@ namespace ps2x::iop snapshot.emulatorLoadedModules = m_impl->emulator.loadedModuleCount(); snapshot.emulatorThreads = m_impl->emulator.threadCount(); snapshot.emulatorRpcServers = m_impl->emulator.rpcServerCount(); - snapshot.activeProfile = m_impl->activeProfile; - snapshot.activeProvider = m_impl->activeProvider; - snapshot.diagnostics = m_impl->diagnostics; - snapshot.diagnostics.insert(snapshot.diagnostics.end(), m_impl->loadOutcomes.begin(), m_impl->loadOutcomes.end()); + snapshot.diagnostics = m_impl->loadOutcomes; if (!m_impl->lastError.empty()) { snapshot.diagnostics.push_back(m_impl->lastError); } - auto append = [&](const detail::ServiceList &services, bool profileSpecific) + for (const auto &service : m_impl->coreServices) { - for (const auto &service : services) - { - if (!service) - { - continue; - } - DebugService row; - row.name = service->name(); - row.sids.assign(service->sids().begin(), service->sids().end()); - row.profileSpecific = profileSpecific; - row.active = m_impl->serviceActive(*service); - service->appendDebugMetrics(row.metrics); - snapshot.services.push_back(std::move(row)); - } - }; - append(m_impl->coreServices, false); - append(m_impl->profileServices, true); + DebugService row; + row.name = service->name(); + row.sids.assign(service->sids().begin(), service->sids().end()); + row.active = m_impl->serviceActive(*service); + service->appendDebugMetrics(row.metrics); + snapshot.services.push_back(std::move(row)); + } return snapshot; } } diff --git a/ps2xIOP/src/module_factories.h b/ps2xIOP/src/module_factories.h index ce21280..f0da295 100644 --- a/ps2xIOP/src/module_factories.h +++ b/ps2xIOP/src/module_factories.h @@ -2,157 +2,9 @@ #include "iop_service.h" -#include -#include -#include -#include - namespace ps2x::iop::detail { - struct CriDtxBindings - { - std::string serviceName; - uint32_t sid = 0u; - uint32_t urpcObjectBase = 0u; - uint32_t urpcObjectLimit = 0u; - uint32_t urpcObjectStride = 0u; - uint32_t urpcFunctionTableBase = 0u; - uint32_t urpcObjectTableBase = 0u; - uint32_t dispatcherFunctionAddress = 0u; - uint32_t rpcServerPoolBase = 0u; - uint32_t rpcServerStride = 0u; - }; - - enum class TsnddrvProtocolVariant - { - SndQueueV1, - }; - - struct TsnddrvGuestArena - { - uint32_t base = 0u; - uint32_t limit = 0u; - uint32_t statusAlignment = 0u; - uint32_t tableAlignment = 0u; - uint32_t storageAlignment = 0u; - uint32_t hdBytes = 0u; - uint32_t sqBytes = 0u; - uint32_t dataBytes = 0u; - }; - - struct TsnddrvChecksumTables - { - uint32_t seAddress = 0u; - uint32_t midiAddress = 0u; - }; - - struct TsnddrvCompletionRule - { - uint32_t eeFunction = 0u; - bool suppressGuestCallback = false; - bool signalCompletion = false; - bool clearBusy = false; - }; - - struct TsnddrvBindings - { - std::string serviceName; - TsnddrvProtocolVariant protocol = TsnddrvProtocolVariant::SndQueueV1; - TsnddrvGuestArena arena; - std::vector checksumCandidates; - uint32_t busyFlagAddress = 0u; - std::vector completionRules; - }; - - struct ClFileRpcLayout - { - uint32_t directLoadFunction = 0x01u; - uint32_t getStatusFunction = 0x03u; - uint32_t initializeFunction = 0x04u; - uint32_t waitFunction = 0x05u; - uint32_t getSizeFunction = 0x06u; - uint32_t openFunction = 0x08u; - uint32_t closeFunction = 0x09u; - uint32_t readFunction = 0x0Au; - uint32_t secondaryWaitFunction = 0x15u; - uint32_t setRootFunction = 0x16u; - uint32_t pathBytes = 0x100u; - uint32_t directLoadSizeOffset = 0x100u; - uint32_t directLoadDestinationOffset = 0x104u; - uint32_t responseStatusOffset = 0u; - uint32_t responseValueOffset = 4u; - uint32_t responseClearBytes = 0x40u; - uint32_t maximumReadBytes = 0x2000u; - uint32_t loadResultQueued = 5u; - uint32_t loadStatusFailed = 3u; - uint32_t loadStatusComplete = 7u; - uint32_t invalidHandleStatus = 9u; - uint32_t firstLoadHandle = 0x00010000u; - bool acknowledgeUnknownFunctions = true; - }; - - struct ClFileBindings - { - std::string serviceName; - uint32_t sid = 0u; - ClFileRpcLayout rpc; - }; - - // TODO This is for the lord of the rings better name for that one - struct SoundUpdateStubBindings - { - std::string serviceName; - uint32_t sid = 0u; - uint32_t activeStreamCountOffset = 0u; - uint32_t responseCounterOffset = 0u; - bool zeroReceiveBuffer = true; - bool signalNowaitCompletion = false; - bool completeQueuedPlayStreams = false; - bool overridePhysicalServer = false; - std::vector suppressedCompletionCallbacks; - }; - - struct SdrdrvBindings - { - std::string serviceName; - uint32_t sid = 0u; - uint32_t imageHeaderAddress = 0u; - uint32_t sectorSize = 0u; - uint32_t statusOffset = 0u; - uint32_t statusStride = 0u; - uint32_t statusSlotMask = 0u; - uint8_t completeValue = 0u; - uint32_t initFunction = 0u; - uint32_t submitFunction = 1u; - uint32_t shutdownFunction = 2u; - uint32_t headerCommand = 0x0Cu; - uint32_t loadCommand = 0x0Eu; - uint32_t commandBytes = 32u; - uint32_t maxCommands = 32u; - uint32_t lbnWord = 2u; - uint32_t byteCountWord = 3u; - uint32_t destinationWord = 4u; - uint32_t destinationKindWord = 5u; - uint32_t loadIdWord = 6u; - uint32_t eeDestinationKind = 0u; - bool fallbackBodyToCdImage = true; - bool clearReceiveBeforeDispatch = true; - bool completeFailedLoads = true; - bool pretendNonEeLoadsComplete = true; - uint32_t headerWarningLimit = 4u; - uint32_t bodyWarningLimit = 8u; - std::string imageHeaderLowerName; - std::string imageHeaderUpperName; - std::string imageBodyLowerName; - std::string imageBodyUpperName; - }; - std::unique_ptr createDbcmanService(IopHost &host); std::unique_ptr createLibSdService(IopHost &host); std::unique_ptr createMcservService(IopHost &host); - std::unique_ptr createTsnddrvService(IopHost &host, TsnddrvBindings bindings); - std::unique_ptr createCriDtxService(IopHost &host, CriDtxBindings bindings); - std::unique_ptr createClFileService(IopHost &host, ClFileBindings bindings); - std::unique_ptr createSoundUpdateStubService(IopHost &host, SoundUpdateStubBindings bindings); - std::unique_ptr createSdrdrvService(IopHost &host, SdrdrvBindings bindings); } diff --git a/ps2xIOP/src/modules/clfile.cpp b/ps2xIOP/src/modules/clfile.cpp deleted file mode 100644 index 93bd236..0000000 --- a/ps2xIOP/src/modules/clfile.cpp +++ /dev/null @@ -1,635 +0,0 @@ -#include "module_factories.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ps2x::iop::detail -{ - namespace - { - class ClFileService final : public IopService - { - public: - ClFileService(IopHost &host, ClFileBindings bindings) - : m_host(host), - m_bindings(std::move(bindings)), - m_sids{m_bindings.sid}, - m_nextLoadHandle(m_bindings.rpc.firstLoadHandle) - { - } - - ~ClFileService() override - { - reset(); - } - - [[nodiscard]] std::string_view name() const override - { - return m_bindings.serviceName; - } - - [[nodiscard]] std::span sids() const override - { - return m_sids; - } - - void reset() override - { - std::lock_guard lock(m_mutex); - for (auto &[handle, entry] : m_fileHandles) - { - (void)handle; - if (entry.handle != 0u) - { - m_host.closeHostFile(entry.handle); - entry.handle = 0u; - } - } - - m_fileHandles.clear(); - m_loads.clear(); - m_root.clear(); - m_nextFileHandle = 1u; - m_nextLoadHandle = m_bindings.rpc.firstLoadHandle; - } - - [[nodiscard]] RpcResult handleRpc(const RpcRequest &request) override - { - RpcResult result; - if (request.sid != m_bindings.sid) - { - return result; - } - const Operation operation = decodeFunction(request.function); - if (operation == Operation::Unknown && - !m_bindings.rpc.acknowledgeUnknownFunctions) - { - return result; - } - - result.handled = true; - result.resultAddress = request.receive.address; - if (request.receive.address != 0u && request.receive.size != 0u) - { - (void)m_host.zeroGuest(request.receive.address, - std::min(request.receive.size, - m_bindings.rpc.responseClearBytes)); - } - - const auto writeRpcResult = [&](int32_t status, uint32_t value) - { - writeResult(request.receive, status, value); - }; - - switch (operation) - { - case Operation::DirectLoad: - { - const uint32_t stringBytes = request.send.size != 0u - ? std::min(request.send.size, - m_bindings.rpc.pathBytes) - : m_bindings.rpc.pathBytes; - const std::string guestPath = readGuestString(request.send.address, stringBytes); - uint32_t requestedBytes = 0u; - uint32_t destinationAddress = 0u; - (void)readGuestU32(request.send.address + m_bindings.rpc.directLoadSizeOffset, - requestedBytes); - (void)readGuestU32(request.send.address + m_bindings.rpc.directLoadDestinationOffset, - destinationAddress); - - uint32_t status = m_bindings.rpc.loadStatusFailed; - uint32_t fileSize = 0u; - const std::string hostPath = resolvePath(guestPath); - if (!hostPath.empty()) - { - const uint64_t file = m_host.openHostFile(hostPath); - uint64_t hostFileSize = 0u; - if (file != 0u && m_host.hostFileSize(file, hostFileSize)) - { - fileSize = static_cast( - std::min(hostFileSize, 0xFFFFFFFFull)); - const uint64_t maxRequestedBytes = requestedBytes != 0u - ? requestedBytes - : hostFileSize; - const uint64_t bytesToCopy = std::min(hostFileSize, maxRequestedBytes); - - status = m_bindings.rpc.loadStatusComplete; - if (destinationAddress != 0u && bytesToCopy != 0u) - { - if (!copyFileToGuest(file, destinationAddress, bytesToCopy)) - { - status = m_bindings.rpc.loadStatusFailed; - } - } - } - if (file != 0u) - { - m_host.closeHostFile(file); - } - } - - uint32_t loadHandle = 0u; - { - std::lock_guard lock(m_mutex); - loadHandle = allocateLoadLocked(status, fileSize); - } - writeRpcResult(static_cast(m_bindings.rpc.loadResultQueued), loadHandle); - return result; - } - - case Operation::Initialize: - writeRpcResult(0, 1u); - return result; - - case Operation::Wait: - case Operation::SecondaryWait: - writeRpcResult(0, 0u); - return result; - - case Operation::SetRoot: - { - const std::string root = readGuestString(request.send.address, - request.send.size != 0u - ? request.send.size - : m_bindings.rpc.pathBytes); - { - std::lock_guard lock(m_mutex); - m_root = root; - } - writeRpcResult(0, 1u); - return result; - } - - case Operation::Open: - { - const std::string guestPath = readGuestString(request.send.address, - request.send.size != 0u - ? request.send.size - : m_bindings.rpc.pathBytes); - const std::string hostPath = resolvePath(guestPath); - if (hostPath.empty()) - { - writeRpcResult(-1, 0u); - return result; - } - - const uint64_t file = m_host.openHostFile(hostPath); - if (file == 0u) - { - writeRpcResult(-1, 0u); - return result; - } - - uint64_t hostFileSize = 0u; - if (!m_host.hostFileSize(file, hostFileSize)) - { - m_host.closeHostFile(file); - writeRpcResult(-1, 0u); - return result; - } - const uint32_t fileSize = static_cast( - std::min(hostFileSize, 0x7FFFFFFFull)); - - uint32_t handle = 0u; - { - std::lock_guard lock(m_mutex); - handle = allocateFileHandleLocked(file, fileSize); - } - if (handle == 0u) - { - m_host.closeHostFile(file); - writeRpcResult(-1, 0u); - return result; - } - - writeRpcResult(0, handle); - return result; - } - - case Operation::Close: - { - uint32_t handle = 0u; - (void)readGuestU32(request.send.address, handle); - - uint64_t file = 0u; - bool closedLoad = false; - { - std::lock_guard lock(m_mutex); - const auto fileIt = m_fileHandles.find(handle); - if (fileIt != m_fileHandles.end()) - { - file = fileIt->second.handle; - m_fileHandles.erase(fileIt); - } - - const auto loadIt = m_loads.find(handle); - if (loadIt != m_loads.end()) - { - m_loads.erase(loadIt); - closedLoad = true; - } - } - if (file != 0u) - { - m_host.closeHostFile(file); - } - - const bool closed = file != 0u || closedLoad; - writeRpcResult(closed ? 0 : -1, closed ? 1u : 0u); - return result; - } - - case Operation::Read: - { - uint32_t handle = 0u; - uint32_t requestedBytes = 0u; - uint32_t destinationAddress = 0u; - (void)readGuestU32(request.send.address + 0u, handle); - (void)readGuestU32(request.send.address + 4u, requestedBytes); - (void)readGuestU32(request.send.address + 8u, destinationAddress); - - if (destinationAddress == 0u) - { - writeRpcResult(-1, 0u); - return result; - } - - std::vector bytes(std::min(requestedBytes, - m_bindings.rpc.maximumReadBytes)); - size_t bytesRead = 0u; - bool readFailed = false; - { - std::lock_guard lock(m_mutex); - const auto fileIt = m_fileHandles.find(handle); - if (fileIt == m_fileHandles.end() || fileIt->second.handle == 0u) - { - readFailed = true; - } - else if (!bytes.empty()) - { - size_t hostBytesRead = 0u; - if (!m_host.readHostFile(fileIt->second.handle, - fileIt->second.position, - bytes.data(), - bytes.size(), - hostBytesRead)) - { - readFailed = true; - } - else - { - bytesRead = hostBytesRead; - fileIt->second.position += hostBytesRead; - } - } - } - - if (readFailed || - (bytesRead != 0u && - !m_host.writeGuest(destinationAddress, bytes.data(), bytesRead))) - { - writeRpcResult(-1, 0u); - return result; - } - - writeRpcResult(0, static_cast(bytesRead)); - return result; - } - - case Operation::GetStatus: - { - uint32_t handle = 0u; - (void)readGuestU32(request.send.address, handle); - - bool loadFound = false; - uint32_t loadStatus = 0u; - bool fileFound = false; - { - std::lock_guard lock(m_mutex); - const auto loadIt = m_loads.find(handle); - if (loadIt != m_loads.end()) - { - loadFound = true; - loadStatus = loadIt->second.status; - } - else - { - fileFound = m_fileHandles.find(handle) != m_fileHandles.end(); - } - } - - if (loadFound) - { - writeRpcResult(static_cast(loadStatus), 0u); - } - else - { - writeRpcResult(0, fileFound ? 0u : m_bindings.rpc.invalidHandleStatus); - } - return result; - } - - case Operation::GetSize: - { - uint32_t handle = 0u; - (void)readGuestU32(request.send.address, handle); - - bool found = false; - uint32_t size = 0u; - { - std::lock_guard lock(m_mutex); - const auto loadIt = m_loads.find(handle); - if (loadIt != m_loads.end()) - { - found = true; - size = loadIt->second.size; - } - else - { - const auto fileIt = m_fileHandles.find(handle); - if (fileIt != m_fileHandles.end()) - { - found = true; - size = fileIt->second.size; - } - } - } - - writeRpcResult(found ? 0 : -1, found ? size : 0u); - return result; - } - - case Operation::Unknown: - writeRpcResult(0, 0u); - return result; - } - return result; - } - - void appendDebugMetrics(std::vector &metrics) const override - { - std::lock_guard lock(m_mutex); - metrics.push_back({"open_files", m_fileHandles.size(), false}); - metrics.push_back({"load_records", m_loads.size(), false}); - metrics.push_back({"next_file_handle", m_nextFileHandle, true}); - metrics.push_back({"next_load_handle", m_nextLoadHandle, true}); - } - - private: - enum class Operation - { - DirectLoad, - GetStatus, - Initialize, - Wait, - GetSize, - Open, - Close, - Read, - SecondaryWait, - SetRoot, - Unknown, - }; - - [[nodiscard]] Operation decodeFunction(uint32_t function) const - { - const ClFileRpcLayout &rpc = m_bindings.rpc; - if (function == rpc.directLoadFunction) return Operation::DirectLoad; - if (function == rpc.getStatusFunction) return Operation::GetStatus; - if (function == rpc.initializeFunction) return Operation::Initialize; - if (function == rpc.waitFunction) return Operation::Wait; - if (function == rpc.getSizeFunction) return Operation::GetSize; - if (function == rpc.openFunction) return Operation::Open; - if (function == rpc.closeFunction) return Operation::Close; - if (function == rpc.readFunction) return Operation::Read; - if (function == rpc.secondaryWaitFunction) return Operation::SecondaryWait; - if (function == rpc.setRootFunction) return Operation::SetRoot; - return Operation::Unknown; - } - - struct ClFileHandle - { - uint64_t handle = 0u; - uint32_t size = 0u; - uint64_t position = 0u; - }; - - struct ClFileLoad - { - uint32_t status = 0u; - uint32_t size = 0u; - }; - - [[nodiscard]] bool readGuestU32(uint32_t address, uint32_t &value) const - { - value = 0u; - return m_host.readGuest(address, &value, sizeof(value)); - } - - [[nodiscard]] std::string readGuestString(uint32_t address, uint32_t maxBytes) const - { - if (address == 0u || maxBytes == 0u) - { - return {}; - } - - std::vector bytes(maxBytes); - if (!m_host.readGuest(address, bytes.data(), bytes.size())) - { - return {}; - } - - size_t length = 0u; - while (length < bytes.size() && bytes[length] != '\0') - { - ++length; - } - return std::string(bytes.data(), length); - } - - [[nodiscard]] static bool hasDevice(std::string_view path) - { - return path.find(':') != std::string_view::npos; - } - - [[nodiscard]] static std::string joinGuestPath(const std::string &root, - const std::string &leaf) - { - if (root.empty() || leaf.empty() || hasDevice(leaf)) - { - return leaf; - } - - const char tail = root.back(); - if (tail == '/' || tail == '\\' || tail == ':') - { - return root + leaf; - } - return root + "/" + leaf; - } - - [[nodiscard]] std::string resolvePath(const std::string &path) const - { - std::string root; - { - std::lock_guard lock(m_mutex); - root = m_root; - } - - const std::string translated = m_host.translateGuestPath(joinGuestPath(root, path)); - return translated; - } - - [[nodiscard]] uint32_t allocateFileHandleLocked(uint64_t file, uint32_t size) - { - if (file == 0u) - { - return 0u; - } - - for (uint32_t attempt = 0u; attempt < 0xFFFFu; ++attempt) - { - uint32_t handle = m_nextFileHandle++; - if (handle == 0u) - { - handle = m_nextFileHandle++; - } - if (m_fileHandles.find(handle) == m_fileHandles.end() && - m_loads.find(handle) == m_loads.end()) - { - m_fileHandles.emplace(handle, ClFileHandle{file, size, 0u}); - return handle; - } - } - return 0u; - } - - [[nodiscard]] uint32_t allocateLoadLocked(uint32_t status, uint32_t size) - { - for (uint32_t attempt = 0u; attempt < 0xFFFFu; ++attempt) - { - uint32_t handle = m_nextLoadHandle++; - if (handle < 3u) - { - handle = m_bindings.rpc.firstLoadHandle; - m_nextLoadHandle = m_bindings.rpc.firstLoadHandle + 1u; - } - if (m_loads.find(handle) == m_loads.end() && - m_fileHandles.find(handle) == m_fileHandles.end()) - { - m_loads.emplace(handle, ClFileLoad{status, size}); - return handle; - } - } - return 0u; - } - - void writeResult(GuestBuffer receive, int32_t status, uint32_t value) - { - if (receive.address != 0u && - receive.size >= m_bindings.rpc.responseStatusOffset + sizeof(uint32_t)) - { - const uint32_t encodedStatus = static_cast(status); - (void)m_host.writeGuest(receive.address + m_bindings.rpc.responseStatusOffset, - &encodedStatus, - sizeof(encodedStatus)); - } - if (receive.address != 0u && - receive.size >= m_bindings.rpc.responseValueOffset + sizeof(uint32_t)) - { - (void)m_host.writeGuest(receive.address + m_bindings.rpc.responseValueOffset, - &value, - sizeof(value)); - } - } - - [[nodiscard]] bool copyFileToGuest(uint64_t file, - uint32_t destinationAddress, - uint64_t bytesToCopy) - { - constexpr size_t kChunkBytes = 16u * 1024u; - if (bytesToCopy > 0xFFFFFFFFull - static_cast(destinationAddress) + 1ull) - { - return false; - } - - std::vector chunk(kChunkBytes); - uint64_t copied = 0u; - while (copied < bytesToCopy) - { - const size_t wanted = static_cast( - std::min(chunk.size(), bytesToCopy - copied)); - size_t received = 0u; - if (!m_host.readHostFile(file, - copied, - chunk.data(), - wanted, - received) || - received != wanted) - { - return false; - } - - const uint32_t chunkAddress = destinationAddress + static_cast(copied); - if (!m_host.writeGuest(chunkAddress, chunk.data(), received)) - { - return false; - } - copied += received; - } - return true; - } - - IopHost &m_host; - ClFileBindings m_bindings; - std::array m_sids; - mutable std::mutex m_mutex; - std::unordered_map m_fileHandles; - std::unordered_map m_loads; - uint32_t m_nextFileHandle = 1u; - uint32_t m_nextLoadHandle = 0u; - std::string m_root; - }; - } - - std::unique_ptr createClFileService(IopHost &host, - ClFileBindings bindings) - { - const ClFileRpcLayout &rpc = bindings.rpc; - const std::array functions = { - rpc.directLoadFunction, - rpc.getStatusFunction, - rpc.initializeFunction, - rpc.waitFunction, - rpc.getSizeFunction, - rpc.openFunction, - rpc.closeFunction, - rpc.readFunction, - rpc.secondaryWaitFunction, - rpc.setRootFunction, - }; - std::unordered_set uniqueFunctions; - for (const uint32_t function : functions) - { - if (!uniqueFunctions.emplace(function).second) - { - throw std::invalid_argument("duplicate CLFILE RPC function binding"); - } - } - if (bindings.serviceName.empty() || bindings.sid == 0u || - rpc.pathBytes == 0u || rpc.maximumReadBytes == 0u || - rpc.firstLoadHandle < 3u) - { - throw std::invalid_argument("invalid CLFILE bindings"); - } - return std::make_unique(host, std::move(bindings)); - } -} diff --git a/ps2xIOP/src/modules/cri_dtx.cpp b/ps2xIOP/src/modules/cri_dtx.cpp deleted file mode 100644 index df74f8f..0000000 --- a/ps2xIOP/src/modules/cri_dtx.cpp +++ /dev/null @@ -1,1306 +0,0 @@ -#include "../module_factories.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ps2x::iop::detail -{ - namespace - { - constexpr uint32_t kDtxHeaderSize = 16u; - constexpr uint32_t kDtxCommandSize = 16u; - constexpr uint32_t kMaxDtxCommands = 128u; - constexpr uint32_t kMinimumDtxWorkSize = 64u; - constexpr uint32_t kDefaultSjrmtCapacity = 0x4000u; - constexpr uint32_t kMaximumSjrmtCapacity = 0x01000000u; - - template - bool readGuestPod(const IopHost &host, uint32_t address, T &value) - { - value = {}; - return host.readGuest(address, &value, sizeof(value)); - } - - template - bool writeGuestPod(IopHost &host, uint32_t address, const T &value) - { - return host.writeGuest(address, &value, sizeof(value)); - } - - uint32_t normalizeSjrmtCapacity(uint32_t requestedBytes) - { - if (requestedBytes == 0u || requestedBytes > kMaximumSjrmtCapacity) - { - return kDefaultSjrmtCapacity; - } - return requestedBytes; - } - - bool looksLikeCreate34Candidate(const RpcCallCandidate &candidate) - { - return candidate.receiveAddress != 0u && - candidate.receiveSize >= sizeof(uint32_t) && - candidate.receiveSize <= 0x40u && - candidate.sendSize >= 12u && - candidate.sendSize <= 0x1000u; - } - - class CriDtxService final : public IopService - { - public: - CriDtxService(IopHost &host, CriDtxBindings bindings) - : m_host(host), - m_bindings(std::move(bindings)), - m_sids{m_bindings.sid} - { - reset(); - } - - [[nodiscard]] std::string_view name() const override - { - return m_bindings.serviceName; - } - - [[nodiscard]] std::span sids() const override - { - return m_sids; - } - - void reset() override - { - std::lock_guard lock(m_mutex); - m_remoteById.clear(); - m_transferById.clear(); - m_sjxByHandle.clear(); - m_ps2RnaByHandle.clear(); - m_sjrmtByHandle.clear(); - m_nextUrpcObject = m_bindings.urpcObjectBase; - m_dmaAcks = 0u; - m_dmaMisses = 0u; - m_urpcCalls = 0u; - } - - [[nodiscard]] RpcAbi selectRpcAbi(const RpcAbiRequest &request) const override - { - if (request.boundSid == m_bindings.sid && - request.function == 0x422u && - request.stack.plausible && - looksLikeCreate34Candidate(request.stack) && - !looksLikeCreate34Candidate(request.registers)) - { - return RpcAbi::Stack; - } - return RpcAbi::RuntimeDefault; - } - - [[nodiscard]] RpcResult handleRpc(const RpcRequest &request) override - { - RpcResult result{}; - if (request.sid != m_bindings.sid) - { - return result; - } - - // DTX owns this SID. Its base protocol intentionally bypasses generic EE - // server dispatch, and URPC dispatch is gated by the guest function table. - result.serverDispatchPolicy = ServerDispatchPolicy::Suppress; - - const bool isUrpc = request.function >= 0x400u && request.function < 0x500u; - const uint32_t command = isUrpc ? (request.function & 0xFFu) : 0u; - uint32_t urpcFunction = 0u; - uint32_t urpcObject = 0u; - if (isUrpc && command < 64u) - { - (void)readGuestPod(m_host, - m_bindings.urpcFunctionTableBase + (command * sizeof(uint32_t)), - urpcFunction); - (void)readGuestPod(m_host, - m_bindings.urpcObjectTableBase + (command * sizeof(uint32_t)), - urpcObject); - } - (void)urpcObject; - - const bool hasUrpcHandler = isUrpc && command < 64u && urpcFunction != 0u; - if (hasUrpcHandler && request.serverFunction != 0u) - { - result.guestFunction = request.serverFunction; - result.guestArguments[0] = request.function; - result.guestArguments[1] = request.serverBuffer; - result.guestArguments[2] = request.send.size; - result.guestDefaultResultAddress = request.serverBuffer != 0u - ? request.serverBuffer - : request.receive.address; - return result; - } - - if (hasUrpcHandler && - request.send.address != 0u && - request.send.size > 0u) - { - result.guestFunction = m_bindings.dispatcherFunctionAddress; - result.guestArguments[0] = request.function; - result.guestArguments[1] = request.send.address; - result.guestArguments[2] = request.send.size; - result.guestDefaultResultAddress = request.send.address; - return result; - } - - if (request.function == 2u && - request.receive.address != 0u && - request.receive.size >= sizeof(uint32_t)) - { - return handleCreateTransport(request, result); - } - if (request.function == 3u) - { - return handleDestroyTransport(request, result); - } - if (isUrpc) - { - return emulateUrpc(request, command, result); - } - - // Unknown calls remain visibly unhandled, but must never escape to a - // generic registered server for this configured SID. - return result; - } - - void onSifTransfer(const SifTransfer &transfer) override - { - if (transfer.kind != SifTransferKind::SetDma || - transfer.phase != SifTransferPhase::AfterCopy || - transfer.size < kMinimumDtxWorkSize) - { - return; - } - - uint32_t normalizedSource = 0u; - uint32_t normalizedDestination = 0u; - if (!m_host.normalizeGuestAddress(transfer.sourceAddress, normalizedSource) || - !m_host.normalizeIopAddress(transfer.destinationAddress, normalizedDestination)) - { - return; - } - - TransferState matched{}; - bool found = false; - { - std::lock_guard lock(m_mutex); - for (const auto &[id, state] : m_transferById) - { - (void)id; - if (matchesTransfer(state, - normalizedSource, - normalizedDestination, - transfer.size)) - { - matched = state; - found = true; - break; - } - } - - if (!found) - { - found = inferTransferFromPayloadLocked(normalizedSource, - normalizedDestination, - transfer.size, - matched); - } - - if (!found) - { - ++m_dmaMisses; - } - } - - if (!found) - { - return; - } - - const uint32_t footerAddress = - matched.eeWorkAddress + matched.workSize - sizeof(uint32_t); - uint32_t ticket = 0u; - if (!readGuestPod(m_host, footerAddress, ticket)) - { - return; - } - (void)writeGuestPod(m_host, footerAddress, ticket + 1u); - - if (matched.dtxId == 0u) - { - applySjxPayload(matched); - } - else if (matched.dtxId == 1u) - { - applyPs2RnaPayload(matched); - } - - std::lock_guard lock(m_mutex); - ++m_dmaAcks; - } - - void appendDebugMetrics(std::vector &metrics) const override - { - std::lock_guard lock(m_mutex); - metrics.push_back({"sid", m_bindings.sid, true}); - metrics.push_back({"remote_handles", m_remoteById.size(), false}); - metrics.push_back({"transfers", m_transferById.size(), false}); - metrics.push_back({"sjx_objects", m_sjxByHandle.size(), false}); - metrics.push_back({"ps2rna_objects", m_ps2RnaByHandle.size(), false}); - metrics.push_back({"sjrmt_objects", m_sjrmtByHandle.size(), false}); - metrics.push_back({"next_urpc_object", m_nextUrpcObject, true}); - metrics.push_back({"urpc_calls", m_urpcCalls, false}); - metrics.push_back({"dma_acks", m_dmaAcks, false}); - metrics.push_back({"dma_misses", m_dmaMisses, false}); - } - - private: - struct TransferState - { - uint32_t dtxId = 0u; - uint32_t remoteHandle = 0u; - uint32_t eeWorkAddress = 0u; - uint32_t iopWorkAddress = 0u; - uint32_t workSize = 0u; - }; - - struct SjxState - { - uint32_t handle = 0u; - uint32_t sourceSjHandle = 0u; - uint32_t destinationSjHandle = 0u; - uint32_t line = 0u; - uint32_t eeObjectAddress = 0u; - uint16_t xid = 0u; - }; - - struct Ps2RnaState - { - uint32_t handle = 0u; - uint32_t maxChannels = 0u; - uint32_t sjHandle0 = 0u; - uint32_t sjHandle1 = 0u; - uint32_t channelCount = 0u; - uint32_t sampleFrequency = 0u; - uint32_t volume = 0u; - bool playEnabled = false; - }; - - struct SjrmtState - { - uint32_t handle = 0u; - uint32_t mode = 0u; - uint32_t workAddress = 0u; - uint32_t workSize = 0u; - uint32_t readPosition = 0u; - uint32_t writePosition = 0u; - uint32_t roomBytes = 0u; - uint32_t dataBytes = 0u; - uint32_t uuid0 = 0u; - uint32_t uuid1 = 0u; - uint32_t uuid2 = 0u; - uint32_t uuid3 = 0u; - }; - - static bool matchesTransfer(const TransferState &state, - uint32_t sourceAddress, - uint32_t destinationAddress, - uint32_t size) - { - (void)destinationAddress; - return state.eeWorkAddress != 0u && - state.workSize >= kMinimumDtxWorkSize && - state.eeWorkAddress == sourceAddress && - state.workSize == size; - } - - uint32_t normalizeAddress(uint32_t address) const - { - uint32_t normalized = 0u; - (void)m_host.normalizeGuestAddress(address, normalized); - return normalized; - } - - uint32_t normalizeIopAddress(uint32_t address) const - { - uint32_t normalized = 0u; - (void)m_host.normalizeIopAddress(address, normalized); - return normalized; - } - - RpcResult handleCreateTransport(const RpcRequest &request, RpcResult result) - { - uint32_t dtxId = 0u; - uint32_t eeWorkAddress = 0u; - uint32_t iopWorkAddress = 0u; - uint32_t workSize = 0u; - if (request.send.address != 0u && request.send.size >= sizeof(uint32_t)) - { - (void)readGuestPod(m_host, request.send.address, dtxId); - } - if (request.send.address != 0u && request.send.size >= 4u * sizeof(uint32_t)) - { - (void)readGuestPod(m_host, request.send.address + 4u, eeWorkAddress); - (void)readGuestPod(m_host, request.send.address + 8u, iopWorkAddress); - (void)readGuestPod(m_host, request.send.address + 12u, workSize); - } - - const uint32_t normalizedEeWorkAddress = normalizeAddress(eeWorkAddress); - const uint32_t normalizedIopWorkAddress = normalizeIopAddress(iopWorkAddress); - - uint32_t remoteHandle = 0u; - { - std::lock_guard lock(m_mutex); - const auto existing = m_remoteById.find(dtxId); - if (existing != m_remoteById.end()) - { - remoteHandle = existing->second; - } - - if (remoteHandle == 0u) - { - remoteHandle = m_host.allocateIopHandle(IopHandleKind::RpcServer); - if (remoteHandle == 0u) - { - remoteHandle = m_host.allocateIopHandle(IopHandleKind::RpcPacket); - } - if (remoteHandle == 0u) - { - remoteHandle = m_bindings.rpcServerPoolBase + - ((dtxId & 0xFFu) * m_bindings.rpcServerStride); - } - m_remoteById[dtxId] = remoteHandle; - } - - m_transferById[dtxId] = TransferState{ - dtxId, - remoteHandle, - normalizedEeWorkAddress, - normalizedIopWorkAddress, - workSize, - }; - } - - (void)writeGuestPod(m_host, request.receive.address, remoteHandle); - if (request.receive.size > sizeof(uint32_t)) - { - (void)m_host.zeroGuest(request.receive.address + sizeof(uint32_t), - request.receive.size - sizeof(uint32_t)); - } - - result.handled = true; - result.resultAddress = request.receive.address; - return result; - } - - RpcResult handleDestroyTransport(const RpcRequest &request, RpcResult result) - { - uint32_t remoteHandle = 0u; - if (request.send.address != 0u && - request.send.size >= sizeof(uint32_t) && - readGuestPod(m_host, request.send.address, remoteHandle) && - remoteHandle != 0u) - { - std::lock_guard lock(m_mutex); - for (auto it = m_remoteById.begin(); it != m_remoteById.end(); ++it) - { - if (it->second == remoteHandle) - { - m_transferById.erase(it->first); - m_remoteById.erase(it); - break; - } - } - } - - if (request.receive.address != 0u && request.receive.size > 0u) - { - (void)m_host.zeroGuest(request.receive.address, request.receive.size); - } - result.handled = true; - result.resultAddress = request.receive.address; - return result; - } - - uint32_t allocateUrpcHandleLocked() - { - if (m_nextUrpcObject < m_bindings.urpcObjectBase || - m_nextUrpcObject >= m_bindings.urpcObjectLimit) - { - m_nextUrpcObject = m_bindings.urpcObjectBase; - } - - for (uint32_t attempt = 0u; attempt < 4096u; ++attempt) - { - const uint32_t candidate = m_nextUrpcObject; - m_nextUrpcObject += m_bindings.urpcObjectStride; - if (m_nextUrpcObject < m_bindings.urpcObjectBase || - m_nextUrpcObject >= m_bindings.urpcObjectLimit) - { - m_nextUrpcObject = m_bindings.urpcObjectBase; - } - - if (candidate < m_bindings.urpcObjectBase || - candidate >= m_bindings.urpcObjectLimit || - m_sjrmtByHandle.find(candidate) != m_sjrmtByHandle.end() || - m_sjxByHandle.find(candidate) != m_sjxByHandle.end() || - m_ps2RnaByHandle.find(candidate) != m_ps2RnaByHandle.end()) - { - continue; - } - - const bool usedByRemote = - std::any_of(m_remoteById.begin(), m_remoteById.end(), - [candidate](const auto &entry) - { return entry.second == candidate; }); - if (!usedByRemote) - { - return candidate; - } - } - - return m_bindings.urpcObjectBase; - } - - RpcResult emulateUrpc(const RpcRequest &request, - uint32_t command, - RpcResult result) - { - std::array output = {1u, 0u, 0u, 0u}; - uint32_t outputWordCount = 1u; - - auto readSendWord = [&](uint32_t index, uint32_t &value) - { - const uint64_t byteOffset = - static_cast(index) * sizeof(uint32_t); - if (request.send.address == 0u || - request.send.size < byteOffset + sizeof(uint32_t)) - { - value = 0u; - return false; - } - return readGuestPod(m_host, - request.send.address + static_cast(byteOffset), - value); - }; - - switch (command) - { - case 0u: // SJX_CREATE - { - uint32_t sourceSjHandle = 0u; - uint32_t destinationSjHandle = 0u; - uint32_t line = 0u; - uint32_t eeObjectAddress = 0u; - (void)readSendWord(0u, sourceSjHandle); - (void)readSendWord(1u, destinationSjHandle); - (void)readSendWord(2u, line); - (void)readSendWord(3u, eeObjectAddress); - - std::lock_guard lock(m_mutex); - const uint32_t handle = allocateUrpcHandleLocked(); - m_sjxByHandle[handle] = SjxState{ - handle, - sourceSjHandle, - destinationSjHandle, - line, - eeObjectAddress, - 0u, - }; - output[0] = handle != 0u ? handle : 1u; - break; - } - case 1u: // SJX_DESTROY - { - uint32_t handle = 0u; - (void)readSendWord(0u, handle); - std::lock_guard lock(m_mutex); - m_sjxByHandle.erase(handle); - output[0] = 1u; - break; - } - case 2u: // SJX_RESET - { - uint32_t handle = 0u; - uint32_t xid = 0u; - (void)readSendWord(0u, handle); - (void)readSendWord(1u, xid); - std::lock_guard lock(m_mutex); - const auto it = m_sjxByHandle.find(handle); - if (it != m_sjxByHandle.end()) - { - it->second.xid = static_cast(xid & 0xFFFFu); - } - output[0] = 1u; - break; - } - case 8u: // PS2RNA_CREATE - { - uint32_t maxChannels = 0u; - uint32_t sjHandle0 = 0u; - uint32_t sjHandle1 = 0u; - (void)readSendWord(0u, maxChannels); - (void)readSendWord(2u, sjHandle0); - (void)readSendWord(3u, sjHandle1); - - std::lock_guard lock(m_mutex); - const uint32_t handle = allocateUrpcHandleLocked(); - m_ps2RnaByHandle[handle] = Ps2RnaState{ - handle, - maxChannels, - sjHandle0, - sjHandle1, - maxChannels, - 0u, - 0u, - false, - }; - output[0] = handle != 0u ? handle : 1u; - break; - } - case 9u: // PS2RNA_DESTROY - { - uint32_t handle = 0u; - (void)readSendWord(0u, handle); - std::lock_guard lock(m_mutex); - m_ps2RnaByHandle.erase(handle); - output[0] = 1u; - break; - } - case 32u: // SJRMT_RBF_CREATE - case 33u: // SJRMT_MEM_CREATE - case 34u: // SJRMT_UNI_CREATE - { - uint32_t argument0 = 0u; - uint32_t argument1 = 0u; - uint32_t argument2 = 0u; - (void)readSendWord(0u, argument0); - (void)readSendWord(1u, argument1); - (void)readSendWord(2u, argument2); - - uint32_t mode = 0u; - uint32_t workAddress = 0u; - uint32_t workSize = 0u; - if (command == 34u) - { - mode = argument0; - workAddress = argument1; - workSize = argument2; - } - else if (command == 33u) - { - workAddress = argument0; - workSize = argument1; - } - else - { - workAddress = argument0; - workSize = argument1 != 0u ? argument1 : argument2; - } - workSize = normalizeSjrmtCapacity(workSize); - - std::lock_guard lock(m_mutex); - const uint32_t handle = allocateUrpcHandleLocked(); - m_sjrmtByHandle[handle] = SjrmtState{ - handle, - mode, - workAddress, - workSize, - 0u, - 0u, - workSize, - 0u, - 0x53524D54u, - handle, - workAddress, - workSize, - }; - output[0] = handle != 0u ? handle : 1u; - break; - } - case 35u: // SJRMT_DESTROY - { - uint32_t handle = 0u; - (void)readSendWord(0u, handle); - std::lock_guard lock(m_mutex); - m_sjrmtByHandle.erase(handle); - output[0] = 1u; - break; - } - case 36u: // SJRMT_GET_UUID - { - uint32_t handle = 0u; - (void)readSendWord(0u, handle); - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - if (it != m_sjrmtByHandle.end()) - { - output[0] = it->second.uuid0; - output[1] = it->second.uuid1; - output[2] = it->second.uuid2; - output[3] = it->second.uuid3; - } - else - { - output = {0u, 0u, 0u, 0u}; - } - outputWordCount = 4u; - break; - } - case 37u: // SJRMT_RESET - { - uint32_t handle = 0u; - (void)readSendWord(0u, handle); - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - if (it != m_sjrmtByHandle.end()) - { - const uint32_t capacity = it->second.workSize == 0u - ? kDefaultSjrmtCapacity - : it->second.workSize; - it->second.readPosition = 0u; - it->second.writePosition = 0u; - it->second.roomBytes = capacity; - it->second.dataBytes = 0u; - } - output[0] = 1u; - break; - } - case 38u: // SJRMT_GET_CHUNK - { - uint32_t handle = 0u; - uint32_t streamId = 0u; - uint32_t requestedBytes = 0u; - (void)readSendWord(0u, handle); - (void)readSendWord(1u, streamId); - (void)readSendWord(2u, requestedBytes); - - uint32_t pointer = 0u; - uint32_t length = 0u; - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - if (it != m_sjrmtByHandle.end()) - { - SjrmtState &state = it->second; - const uint32_t capacity = state.workSize == 0u - ? kDefaultSjrmtCapacity - : state.workSize; - if (streamId == 0u) - { - length = std::min(requestedBytes, state.roomBytes); - pointer = state.workAddress + - (capacity != 0u ? state.writePosition % capacity : 0u); - if (capacity != 0u) - { - state.writePosition = (state.writePosition + length) % capacity; - } - state.roomBytes -= length; - } - else if (streamId == 1u) - { - length = std::min(requestedBytes, state.dataBytes); - pointer = state.workAddress + - (capacity != 0u ? state.readPosition % capacity : 0u); - if (capacity != 0u) - { - state.readPosition = (state.readPosition + length) % capacity; - } - state.dataBytes -= length; - } - } - output[0] = pointer; - output[1] = length; - outputWordCount = 2u; - break; - } - case 39u: // SJRMT_UNGET_CHUNK - { - uint32_t handle = 0u; - uint32_t streamId = 0u; - uint32_t length = 0u; - (void)readSendWord(0u, handle); - (void)readSendWord(1u, streamId); - (void)readSendWord(3u, length); - - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - if (it != m_sjrmtByHandle.end()) - { - SjrmtState &state = it->second; - const uint32_t capacity = state.workSize == 0u - ? kDefaultSjrmtCapacity - : state.workSize; - const uint32_t delta = capacity == 0u ? 0u : length % capacity; - if (streamId == 0u) - { - if (capacity != 0u) - { - state.writePosition = - (state.writePosition + capacity - delta) % capacity; - } - state.roomBytes = std::min(capacity, state.roomBytes + length); - } - else if (streamId == 1u) - { - if (capacity != 0u) - { - state.readPosition = - (state.readPosition + capacity - delta) % capacity; - } - state.dataBytes = std::min(capacity, state.dataBytes + length); - } - } - output[0] = 1u; - break; - } - case 40u: // SJRMT_PUT_CHUNK - { - uint32_t handle = 0u; - uint32_t streamId = 0u; - uint32_t length = 0u; - (void)readSendWord(0u, handle); - (void)readSendWord(1u, streamId); - (void)readSendWord(3u, length); - - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - if (it != m_sjrmtByHandle.end()) - { - SjrmtState &state = it->second; - const uint32_t capacity = state.workSize == 0u - ? kDefaultSjrmtCapacity - : state.workSize; - if (streamId == 0u) - { - state.roomBytes = std::min(capacity, state.roomBytes + length); - } - else if (streamId == 1u) - { - state.dataBytes = std::min(capacity, state.dataBytes + length); - } - } - output[0] = 1u; - break; - } - case 41u: // SJRMT_GET_NUM_DATA - { - uint32_t handle = 0u; - uint32_t streamId = 0u; - (void)readSendWord(0u, handle); - (void)readSendWord(1u, streamId); - - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - output[0] = it != m_sjrmtByHandle.end() - ? (streamId == 0u ? it->second.roomBytes - : it->second.dataBytes) - : 0u; - break; - } - case 42u: // SJRMT_IS_GET_CHUNK - { - uint32_t handle = 0u; - uint32_t streamId = 0u; - uint32_t requestedBytes = 0u; - (void)readSendWord(0u, handle); - (void)readSendWord(1u, streamId); - (void)readSendWord(2u, requestedBytes); - - uint32_t available = 0u; - std::lock_guard lock(m_mutex); - const auto it = m_sjrmtByHandle.find(handle); - if (it != m_sjrmtByHandle.end()) - { - available = streamId == 0u ? it->second.roomBytes - : it->second.dataBytes; - } - output[0] = available >= requestedBytes ? 1u : 0u; - output[1] = available; - outputWordCount = 2u; - break; - } - case 43u: // SJRMT_INIT - case 44u: // SJRMT_FINISH - output[0] = 1u; - break; - default: - { - uint32_t value = 1u; - if (request.send.address != 0u && - request.send.size >= sizeof(uint32_t)) - { - (void)readGuestPod(m_host, request.send.address, value); - } - if (command == 0u) - { - std::lock_guard lock(m_mutex); - value = allocateUrpcHandleLocked(); - } - output[0] = value != 0u ? value : 1u; - break; - } - } - - if (request.receive.address != 0u && request.receive.size > 0u) - { - const uint32_t receiveWordCapacity = - request.receive.size / sizeof(uint32_t); - const uint32_t wordsToWrite = - std::min(outputWordCount, receiveWordCapacity); - for (uint32_t index = 0u; index < wordsToWrite; ++index) - { - (void)writeGuestPod(m_host, - request.receive.address + - (index * sizeof(uint32_t)), - output[index]); - } - - // The original HLE deliberately makes rbuf[1] available to command - // 42 callers even when their declared output word count is one. - if (command == 42u && outputWordCount > 1u) - { - (void)writeGuestPod(m_host, - request.receive.address + sizeof(uint32_t), - output[1]); - } - - const uint32_t writtenBytes = wordsToWrite * sizeof(uint32_t); - if (request.receive.size > writtenBytes) - { - (void)m_host.zeroGuest(request.receive.address + writtenBytes, - request.receive.size - writtenBytes); - } - } - - { - std::lock_guard lock(m_mutex); - ++m_urpcCalls; - } - result.handled = true; - result.resultAddress = request.receive.address; - return result; - } - - bool hasReadableRange(uint32_t address, uint32_t length) const - { - if (length == 0u) - { - return true; - } - const uint32_t lastAddress = address + length - 1u; - if (address > lastAddress) - { - return false; - } - - uint8_t byte = 0u; - return m_host.readGuest(address, &byte, sizeof(byte)) && - m_host.readGuest(lastAddress, &byte, sizeof(byte)); - } - - bool readValidCommandCount(uint32_t workAddress, - uint32_t workSize, - uint32_t &commandCount) const - { - commandCount = 0u; - if (workSize < kMinimumDtxWorkSize || - !readGuestPod(m_host, workAddress, commandCount) || - commandCount == 0u || - commandCount > kMaxDtxCommands) - { - return false; - } - - const uint64_t commandBytes = - static_cast(commandCount) * kDtxCommandSize; - const uint64_t requiredBytes = - kDtxHeaderSize + commandBytes + sizeof(uint32_t); - return requiredBytes <= workSize; - } - - bool looksLikeSjxPayloadLocked(uint32_t workAddress, uint32_t workSize) const - { - uint32_t commandCount = 0u; - if (!readValidCommandCount(workAddress, workSize, commandCount)) - { - return false; - } - - for (uint32_t index = 0u; index < commandCount; ++index) - { - const uint32_t commandAddress = - workAddress + kDtxHeaderSize + (index * kDtxCommandSize); - std::array command{}; - if (!m_host.readGuest(commandAddress, command.data(), command.size())) - { - break; - } - - const uint8_t commandNumber = command[0]; - const uint8_t line = command[1]; - uint16_t xid = 0u; - uint32_t sjxHandle = 0u; - uint32_t chunkDataAddress = 0u; - uint32_t chunkLength = 0u; - std::memcpy(&xid, command.data() + 2u, sizeof(xid)); - std::memcpy(&sjxHandle, command.data() + 4u, sizeof(sjxHandle)); - std::memcpy(&chunkDataAddress, command.data() + 8u, - sizeof(chunkDataAddress)); - std::memcpy(&chunkLength, command.data() + 12u, sizeof(chunkLength)); - - if (commandNumber != 0u || chunkLength == 0u || - !hasReadableRange(chunkDataAddress, chunkLength)) - { - continue; - } - - const auto sjx = m_sjxByHandle.find(sjxHandle); - if (sjx == m_sjxByHandle.end() || - (sjx->second.xid != 0u && sjx->second.xid != xid) || - line != sjx->second.line || - m_sjrmtByHandle.find(sjx->second.destinationSjHandle) == - m_sjrmtByHandle.end()) - { - continue; - } - return true; - } - return false; - } - - bool looksLikePs2RnaPayloadLocked(uint32_t workAddress, - uint32_t workSize) const - { - uint32_t commandCount = 0u; - if (!readValidCommandCount(workAddress, workSize, commandCount)) - { - return false; - } - - for (uint32_t index = 0u; index < commandCount; ++index) - { - const uint32_t commandAddress = - workAddress + kDtxHeaderSize + (index * kDtxCommandSize); - std::array command{}; - if (!m_host.readGuest(commandAddress, command.data(), command.size())) - { - break; - } - - uint16_t commandNumber = 0u; - uint32_t rnaHandle = 0u; - std::memcpy(&commandNumber, command.data(), sizeof(commandNumber)); - std::memcpy(&rnaHandle, command.data() + 4u, sizeof(rnaHandle)); - if (commandNumber <= 5u && - m_ps2RnaByHandle.find(rnaHandle) != m_ps2RnaByHandle.end()) - { - return true; - } - } - return false; - } - - bool inferTransferFromPayloadLocked(uint32_t sourceAddress, - uint32_t destinationAddress, - uint32_t size, - TransferState &result) const - { - if (m_transferById.empty()) - { - return false; - } - - if (looksLikeSjxPayloadLocked(sourceAddress, size)) - { - result = TransferState{0u, 0u, sourceAddress, destinationAddress, size}; - return true; - } - if (looksLikePs2RnaPayloadLocked(sourceAddress, size)) - { - result = TransferState{1u, 0u, sourceAddress, destinationAddress, size}; - return true; - } - return false; - } - - bool copyBytes(uint32_t destinationAddress, - uint32_t sourceAddress, - uint32_t length) - { - for (uint32_t index = 0u; index < length; ++index) - { - uint8_t value = 0u; - if (!m_host.readGuest(sourceAddress + index, &value, sizeof(value)) || - !m_host.writeGuest(destinationAddress + index, &value, sizeof(value))) - { - return false; - } - } - return true; - } - - void appendToSjrmtData(SjrmtState &state, - uint32_t sourceDataAddress, - uint32_t requestedLength) - { - const uint32_t capacity = state.workSize == 0u - ? kDefaultSjrmtCapacity - : state.workSize; - if (capacity == 0u || state.workAddress == 0u || - state.roomBytes == 0u || requestedLength == 0u) - { - return; - } - - const uint32_t requestedCopyLength = - std::min(requestedLength, state.roomBytes); - uint32_t copiedLength = 0u; - for (uint32_t index = 0u; index < requestedCopyLength; ++index) - { - const uint32_t writeOffset = (state.writePosition + index) % capacity; - if (!copyBytes(state.workAddress + writeOffset, - sourceDataAddress + index, - 1u)) - { - break; - } - ++copiedLength; - } - - state.writePosition = (state.writePosition + copiedLength) % capacity; - state.roomBytes -= copiedLength; - state.dataBytes = std::min(capacity, state.dataBytes + copiedLength); - } - - static void consumeSjrmtData(SjrmtState &state) - { - const uint32_t capacity = state.workSize == 0u - ? kDefaultSjrmtCapacity - : state.workSize; - if (capacity == 0u || state.dataBytes == 0u) - { - return; - } - - const uint32_t consumed = std::min(capacity, state.dataBytes); - state.readPosition = (state.readPosition + consumed) % capacity; - state.dataBytes -= consumed; - state.roomBytes = std::min(capacity, state.roomBytes + consumed); - } - - void consumeActivePs2RnaStreamsLocked() - { - for (const auto &[handle, rna] : m_ps2RnaByHandle) - { - (void)handle; - if (!rna.playEnabled) - { - continue; - } - - auto consumeHandle = [&](uint32_t sjHandle) - { - if (sjHandle == 0u) - { - return; - } - const auto it = m_sjrmtByHandle.find(sjHandle); - if (it != m_sjrmtByHandle.end()) - { - consumeSjrmtData(it->second); - } - }; - consumeHandle(rna.sjHandle0); - consumeHandle(rna.sjHandle1); - } - } - - void applySjxPayload(const TransferState &transfer) - { - if (transfer.eeWorkAddress == 0u || - transfer.workSize < kMinimumDtxWorkSize) - { - return; - } - - uint32_t commandCount = 0u; - if (!readGuestPod(m_host, transfer.eeWorkAddress, commandCount)) - { - return; - } - commandCount = std::min(commandCount, kMaxDtxCommands); - if (commandCount == 0u) - { - return; - } - - std::lock_guard lock(m_mutex); - for (uint32_t index = 0u; index < commandCount; ++index) - { - const uint32_t commandAddress = - transfer.eeWorkAddress + kDtxHeaderSize + - (index * kDtxCommandSize); - std::array command{}; - if (!m_host.readGuest(commandAddress, command.data(), command.size())) - { - break; - } - - const uint8_t commandNumber = command[0]; - const uint8_t line = command[1]; - uint16_t xid = 0u; - uint32_t sjxHandle = 0u; - uint32_t chunkDataAddress = 0u; - uint32_t chunkLength = 0u; - std::memcpy(&xid, command.data() + 2u, sizeof(xid)); - std::memcpy(&sjxHandle, command.data() + 4u, sizeof(sjxHandle)); - std::memcpy(&chunkDataAddress, command.data() + 8u, - sizeof(chunkDataAddress)); - std::memcpy(&chunkLength, command.data() + 12u, sizeof(chunkLength)); - - if (commandNumber != 0u || chunkLength == 0u) - { - continue; - } - - const auto sjx = m_sjxByHandle.find(sjxHandle); - if (sjx == m_sjxByHandle.end() || - (sjx->second.xid != 0u && sjx->second.xid != xid)) - { - continue; - } - const auto sjrmt = - m_sjrmtByHandle.find(sjx->second.destinationSjHandle); - if (sjrmt == m_sjrmtByHandle.end() || line != sjx->second.line) - { - continue; - } - - appendToSjrmtData(sjrmt->second, chunkDataAddress, chunkLength); - (void)writeGuestPod(m_host, commandAddress + 4u, sjx->second.eeObjectAddress); - constexpr uint8_t roomLine = 0u; - (void)m_host.writeGuest(commandAddress + 1u, &roomLine, sizeof(roomLine)); - } - consumeActivePs2RnaStreamsLocked(); - } - - void applyPs2RnaPayload(const TransferState &transfer) - { - if (transfer.eeWorkAddress == 0u || - transfer.workSize < kMinimumDtxWorkSize) - { - return; - } - - uint32_t commandCount = 0u; - if (!readGuestPod(m_host, transfer.eeWorkAddress, commandCount)) - { - return; - } - commandCount = std::min(commandCount, kMaxDtxCommands); - if (commandCount == 0u) - { - return; - } - - std::lock_guard lock(m_mutex); - for (uint32_t index = 0u; index < commandCount; ++index) - { - const uint32_t commandAddress = - transfer.eeWorkAddress + kDtxHeaderSize + - (index * kDtxCommandSize); - std::array command{}; - if (!m_host.readGuest(commandAddress, command.data(), command.size())) - { - break; - } - - uint16_t commandNumber = 0u; - uint32_t rnaHandle = 0u; - uint32_t argument1 = 0u; - uint32_t argument2 = 0u; - std::memcpy(&commandNumber, command.data(), sizeof(commandNumber)); - std::memcpy(&rnaHandle, command.data() + 4u, sizeof(rnaHandle)); - std::memcpy(&argument1, command.data() + 8u, sizeof(argument1)); - std::memcpy(&argument2, command.data() + 12u, sizeof(argument2)); - - const auto it = m_ps2RnaByHandle.find(rnaHandle); - if (it == m_ps2RnaByHandle.end()) - { - continue; - } - - Ps2RnaState &state = it->second; - switch (commandNumber) - { - case 0u: - state.playEnabled = true; - break; - case 1u: - state.playEnabled = false; - break; - case 2u: - state.playEnabled = argument1 != 0u; - break; - case 3u: - state.channelCount = argument1; - break; - case 4u: - state.sampleFrequency = argument1; - break; - case 5u: - state.volume = argument2; - break; - default: - break; - } - } - consumeActivePs2RnaStreamsLocked(); - } - - IopHost &m_host; - CriDtxBindings m_bindings; - mutable std::mutex m_mutex; - std::unordered_map m_remoteById; - std::unordered_map m_transferById; - std::unordered_map m_sjxByHandle; - std::unordered_map m_ps2RnaByHandle; - std::unordered_map m_sjrmtByHandle; - uint32_t m_nextUrpcObject = 0u; - uint64_t m_dmaAcks = 0u; - uint64_t m_dmaMisses = 0u; - uint64_t m_urpcCalls = 0u; - const std::array m_sids; - }; - } - - std::unique_ptr createCriDtxService(IopHost &host, - CriDtxBindings bindings) - { - if (bindings.serviceName.empty() || bindings.sid == 0u || - bindings.urpcObjectBase == 0u || - bindings.urpcObjectLimit <= bindings.urpcObjectBase || - bindings.urpcObjectStride == 0u || - bindings.urpcFunctionTableBase == 0u || - bindings.urpcObjectTableBase == 0u || - bindings.dispatcherFunctionAddress == 0u || - bindings.rpcServerPoolBase == 0u || - bindings.rpcServerStride == 0u) - { - throw std::invalid_argument("invalid CRI DTX bindings"); - } - return std::make_unique(host, std::move(bindings)); - } -} diff --git a/ps2xIOP/src/modules/sdrdrv.cpp b/ps2xIOP/src/modules/sdrdrv.cpp deleted file mode 100644 index bd86e26..0000000 --- a/ps2xIOP/src/modules/sdrdrv.cpp +++ /dev/null @@ -1,335 +0,0 @@ -#include "module_factories.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ps2x::iop::detail -{ - namespace - { - constexpr uint32_t kEeRamSize = 32u * 1024u * 1024u; - - class SdrdrvService final : public IopService - { - public: - SdrdrvService(IopHost &host, SdrdrvBindings bindings) - : m_host(host), m_bindings(std::move(bindings)), m_sids{m_bindings.sid} - { - } - - std::string_view name() const override { return m_bindings.serviceName; } - std::span sids() const override { return m_sids; } - - void reset() override - { - std::lock_guard lock(m_mutex); - m_headerWarnCount = 0; - m_bodyWarnCount = 0; - } - - RpcResult handleRpc(const RpcRequest &request) override - { - RpcResult result; - if (request.sid != m_bindings.sid) - { - return result; - } - - result.handled = true; - result.resultAddress = request.receive.address; - if (m_bindings.clearReceiveBeforeDispatch && - request.receive.address && request.receive.size) - { - (void)m_host.zeroGuest(request.receive.address, request.receive.size); - } - - if (request.function == m_bindings.initFunction) - { - if (!loadImageHeader()) - { - warnHeader(); - } - return result; - } - if (request.function == m_bindings.shutdownFunction) - { - return result; - } - if (request.function != m_bindings.submitFunction) - { - return result; - } - - const uint32_t count = std::min(request.send.size / m_bindings.commandBytes, - m_bindings.maxCommands); - for (uint32_t commandIndex = 0; commandIndex < count; ++commandIndex) - { - std::vector words(m_bindings.commandBytes / sizeof(uint32_t)); - const uint32_t commandAddress = request.send.address + - commandIndex * m_bindings.commandBytes; - if (!m_host.readGuest(commandAddress, - words.data(), - m_bindings.commandBytes)) - { - continue; - } - - if (words[0] == m_bindings.headerCommand) - { - if (!loadImageHeader()) - { - warnHeader(); - } - continue; - } - if (words[0] != m_bindings.loadCommand) - { - continue; - } - - const uint32_t lbn = words[m_bindings.lbnWord]; - const uint32_t byteCount = words[m_bindings.byteCountWord]; - const uint32_t destination = words[m_bindings.destinationWord]; - const bool eeLoad = words[m_bindings.destinationKindWord] == - m_bindings.eeDestinationKind; - const uint32_t loadId = words[m_bindings.loadIdWord]; - const bool loaded = eeLoad - ? readBody(lbn, byteCount, destination) - : m_bindings.pretendNonEeLoadsComplete; - if (eeLoad && !loaded) - { - (void)m_host.zeroGuest(destination, byteCount); - bool shouldWarn = false; - { - std::lock_guard lock(m_mutex); - if (m_bodyWarnCount < m_bindings.bodyWarningLimit) - { - ++m_bodyWarnCount; - shouldWarn = true; - } - } - if (shouldWarn) - { - std::ostringstream message; - message << '[' << m_bindings.serviceName - << "] failed data read lbn=0x" << std::hex << lbn - << " bytes=0x" << byteCount << " dst=0x" << destination; - m_host.log(LogLevel::Warning, message.str()); - } - } - if (loaded || m_bindings.completeFailedLoads) - { - markLoadComplete(request.receive, loadId); - } - } - return result; - } - - void appendDebugMetrics(std::vector &metrics) const override - { - std::lock_guard lock(m_mutex); - metrics.push_back({"header_warnings", m_headerWarnCount, false}); - metrics.push_back({"body_warnings", m_bodyWarnCount, false}); - } - - private: - uint64_t openSiblingFile(const std::string &lowerName, - const std::string &upperName) - { - const std::array roots = { - m_host.hostPath(HostPathKind::CdRoot), - m_host.hostPath(HostPathKind::ElfDirectory), - }; - for (const std::string &rootValue : roots) - { - if (rootValue.empty()) - { - continue; - } - const std::filesystem::path root(rootValue); - for (const std::string *name : {&lowerName, &upperName}) - { - if (name->empty()) - { - continue; - } - const std::filesystem::path candidate = root / *name; - const uint64_t handle = m_host.openHostFile(candidate.string()); - if (handle != 0u) - { - return handle; - } - } - } - return 0u; - } - - bool copyHostRange(uint64_t handle, - uint64_t offset, - uint32_t destination, - uint64_t byteCount) - { - if (byteCount == 0) - { - return true; - } - std::array chunk{}; - uint64_t copied = 0; - while (copied < byteCount) - { - const size_t wanted = static_cast(std::min(chunk.size(), byteCount - copied)); - std::fill(chunk.begin(), chunk.begin() + static_cast(wanted), 0u); - size_t got = 0u; - if (!m_host.readHostFile(handle, - offset + copied, - chunk.data(), - wanted, - got) || - got > wanted || - !m_host.writeGuest(destination + static_cast(copied), - chunk.data(), - wanted)) - { - return false; - } - copied += wanted; - } - return true; - } - - bool loadImageHeader() - { - const uint64_t handle = openSiblingFile(m_bindings.imageHeaderLowerName, - m_bindings.imageHeaderUpperName); - if (handle == 0u) - { - return false; - } - uint64_t fileSize = 0u; - if (!m_host.hostFileSize(handle, fileSize)) - { - m_host.closeHostFile(handle); - return false; - } - uint32_t normalized = 0; - if (!m_host.normalizeGuestAddress(m_bindings.imageHeaderAddress, normalized) || - normalized >= kEeRamSize) - { - m_host.closeHostFile(handle); - return false; - } - const bool copied = copyHostRange(handle, - 0u, - m_bindings.imageHeaderAddress, - std::min(fileSize, - kEeRamSize - normalized)); - m_host.closeHostFile(handle); - return copied; - } - - bool readBody(uint32_t lbn, uint32_t byteCount, uint32_t destination) - { - uint32_t normalized = 0; - if (!m_host.normalizeGuestAddress(destination, normalized) || normalized >= kEeRamSize) - { - return false; - } - uint64_t handle = openSiblingFile(m_bindings.imageBodyLowerName, - m_bindings.imageBodyUpperName); - if (handle == 0u && m_bindings.fallbackBodyToCdImage) - { - handle = m_host.openHostFile(m_host.hostPath(HostPathKind::CdImage)); - } - if (handle == 0u) - { - return false; - } - const uint64_t bytes = std::min(byteCount, kEeRamSize - normalized); - const bool copied = copyHostRange(handle, - static_cast(lbn) * m_bindings.sectorSize, - destination, - bytes); - m_host.closeHostFile(handle); - return copied; - } - - void markLoadComplete(GuestBuffer receive, uint32_t loadId) - { - const uint32_t offset = m_bindings.statusOffset + - ((loadId & m_bindings.statusSlotMask) * - m_bindings.statusStride); - if (receive.address && offset < receive.size) - { - const uint8_t complete = m_bindings.completeValue; - (void)m_host.writeGuest(receive.address + offset, &complete, sizeof(complete)); - } - } - - void warnHeader() - { - bool shouldWarn = false; - { - std::lock_guard lock(m_mutex); - if (m_headerWarnCount < m_bindings.headerWarningLimit) - { - ++m_headerWarnCount; - shouldWarn = true; - } - } - if (shouldWarn) - { - m_host.log(LogLevel::Warning, - '[' + m_bindings.serviceName + "] failed to load image header"); - } - } - - IopHost &m_host; - SdrdrvBindings m_bindings; - std::array m_sids; - mutable std::mutex m_mutex; - uint32_t m_headerWarnCount = 0; - uint32_t m_bodyWarnCount = 0; - }; - } - - std::unique_ptr createSdrdrvService(IopHost &host, - SdrdrvBindings bindings) - { - const uint32_t largestWord = std::max({bindings.lbnWord, - bindings.byteCountWord, - bindings.destinationWord, - bindings.destinationKindWord, - bindings.loadIdWord}); - if (bindings.serviceName.empty() || - bindings.sid == 0u || - bindings.imageHeaderAddress == 0u || - bindings.commandBytes == 0u || - (bindings.commandBytes % sizeof(uint32_t)) != 0u || - largestWord >= bindings.commandBytes / sizeof(uint32_t) || - bindings.maxCommands == 0u || - bindings.sectorSize == 0u || - bindings.statusStride == 0u || - bindings.initFunction == bindings.submitFunction || - bindings.initFunction == bindings.shutdownFunction || - bindings.submitFunction == bindings.shutdownFunction || - bindings.headerCommand == bindings.loadCommand || - (bindings.imageHeaderLowerName.empty() && - bindings.imageHeaderUpperName.empty()) || - (bindings.imageBodyLowerName.empty() && - bindings.imageBodyUpperName.empty() && - !bindings.fallbackBodyToCdImage)) - { - throw std::invalid_argument("invalid SDRDRV bindings"); - } - return std::make_unique(host, std::move(bindings)); - } -} diff --git a/ps2xIOP/src/modules/sound_update_stub.cpp b/ps2xIOP/src/modules/sound_update_stub.cpp deleted file mode 100644 index 4fa055c..0000000 --- a/ps2xIOP/src/modules/sound_update_stub.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include "module_factories.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ps2x::iop::detail -{ - namespace - { - constexpr uint16_t kPlayStreamCommand = 1u; - constexpr uint32_t kResponseRecordStride = 0x20u; - constexpr uint32_t kPackedStreamOffset = 4u; - constexpr uint32_t kStreamSlotMask = 0x3Fu; - constexpr uint32_t kStreamSlotCount = 48u; - constexpr uint32_t kCommandStreamSlotShift = 8u; - constexpr uint32_t kResponseStreamSlotShift = 4u; - - class SoundUpdateStubService final : public IopService - { - public: - SoundUpdateStubService(IopHost &host, SoundUpdateStubBindings bindings) - : m_host(host), m_bindings(std::move(bindings)), m_sids{m_bindings.sid} - { - } - - [[nodiscard]] std::string_view name() const override - { - return m_bindings.serviceName; - } - - [[nodiscard]] std::span sids() const override - { - return m_sids; - } - - [[nodiscard]] bool overridesPhysicalRpcServer() const noexcept override - { - return m_bindings.overridePhysicalServer; - } - - void reset() override - { - std::lock_guard lock(m_mutex); - m_updateCounter = 0u; - m_completedStreamCount = 0u; - } - - [[nodiscard]] RpcResult handleRpc(const RpcRequest &request) override - { - if (request.sid != m_bindings.sid) - { - return {}; - } - - RpcResult result; - result.handled = true; - result.resultAddress = request.receive.address; - result.signalNowaitCompletion = m_bindings.signalNowaitCompletion; - - if (std::find(m_bindings.suppressedCompletionCallbacks.begin(), - m_bindings.suppressedCompletionCallbacks.end(), - request.endFunction) != m_bindings.suppressedCompletionCallbacks.end()) - { - result.signalCompletion = true; - result.callbackPolicy = CallbackPolicy::Suppress; - } - - if (m_bindings.zeroReceiveBuffer && - request.receive.address != 0u && request.receive.size != 0u) - { - (void)m_host.zeroGuest(request.receive.address, request.receive.size); - } - - std::vector activeStreamSlots; - if (m_bindings.completeQueuedPlayStreams && request.receive.address != 0u) - { - // PlayStream leaves the EE slot in state 2. One active record moves it - // to state 1; the following empty update lets SOUND_CopyIOPBuffer clear it. - activeStreamSlots = findQueuedPlayStreams(request); - trimToReceiveCapacity(activeStreamSlots, request.receive.size); - } - - uint32_t counter = 0u; - { - std::lock_guard lock(m_mutex); - counter = ++m_updateCounter; - m_completedStreamCount += activeStreamSlots.size(); - } - - const uint32_t activeStreams = static_cast(activeStreamSlots.size()); - if (request.receive.address != 0u && - request.receive.size >= m_bindings.activeStreamCountOffset + sizeof(activeStreams)) - { - const uint32_t address = request.receive.address + m_bindings.activeStreamCountOffset; - (void)m_host.writeGuest(address, &activeStreams, sizeof(activeStreams)); - } - - for (size_t index = 0u; index < activeStreamSlots.size(); ++index) - { - const uint32_t packedStream = activeStreamSlots[index] << kResponseStreamSlotShift; - const uint32_t offset = m_bindings.activeStreamCountOffset + static_cast(index) * kResponseRecordStride + kPackedStreamOffset; - const uint32_t address = request.receive.address + offset; - (void)m_host.writeGuest(address, &packedStream, sizeof(packedStream)); - } - - const uint32_t counterOffset = m_bindings.responseCounterOffset + - activeStreams * kResponseRecordStride; - if (request.receive.address != 0u && - request.receive.size >= counterOffset + sizeof(counter)) - { - const uint32_t address = request.receive.address + counterOffset; - (void)m_host.writeGuest(address, &counter, sizeof(counter)); - } - - return result; - } - - void appendDebugMetrics(std::vector &metrics) const override - { - std::lock_guard lock(m_mutex); - metrics.push_back({"update_counter", m_updateCounter, false}); - metrics.push_back({"completed_streams", m_completedStreamCount, false}); - } - - private: - [[nodiscard]] std::vector findQueuedPlayStreams(const RpcRequest &request) const - { - std::vector slots; - if (request.send.address == 0u || request.send.size < sizeof(uint16_t)) - { - return slots; - } - - uint16_t commandCount = 0u; - if (!m_host.readGuest(request.send.address, &commandCount, sizeof(commandCount))) - { - return slots; - } - - uint32_t offset = sizeof(commandCount); - for (uint32_t commandIndex = 0u; commandIndex < commandCount; ++commandIndex) - { - constexpr uint32_t headerSize = sizeof(uint16_t) * 2u; - if (offset > request.send.size || request.send.size - offset < headerSize) - { - break; - } - - std::array header{}; - if (!m_host.readGuest(request.send.address + offset, - header.data(), - sizeof(header))) - { - break; - } - offset += headerSize; - - const uint32_t argumentBytes = - static_cast(header[1]) * sizeof(uint16_t); - if (argumentBytes > request.send.size - offset) - { - break; - } - - if (header[0] == kPlayStreamCommand && header[1] >= 2u) - { - uint16_t encodedSlot = 0u; - if (m_host.readGuest(request.send.address + offset + sizeof(uint16_t), - &encodedSlot, - sizeof(encodedSlot))) - { - const uint32_t slot = - (encodedSlot >> kCommandStreamSlotShift) & kStreamSlotMask; - if (slot < kStreamSlotCount && - std::find(slots.begin(), slots.end(), slot) == slots.end()) - { - slots.push_back(slot); - } - } - } - - offset += argumentBytes; - } - return slots; - } - - void trimToReceiveCapacity(std::vector &slots, uint32_t receiveSize) const - { - size_t count = 0u; - for (; count < slots.size(); ++count) - { - const uint64_t recordOffset = - static_cast(m_bindings.activeStreamCountOffset) + - static_cast(count) * kResponseRecordStride + - kPackedStreamOffset; - const uint64_t counterOffset = - static_cast(m_bindings.responseCounterOffset) + - static_cast(count + 1u) * kResponseRecordStride; - if (recordOffset + sizeof(uint32_t) > receiveSize || - counterOffset + sizeof(uint32_t) > receiveSize) - { - break; - } - } - slots.resize(count); - } - - IopHost &m_host; - SoundUpdateStubBindings m_bindings; - std::array m_sids; - mutable std::mutex m_mutex; - uint32_t m_updateCounter = 0u; - uint64_t m_completedStreamCount = 0u; - }; - } - - std::unique_ptr createSoundUpdateStubService(IopHost &host, - SoundUpdateStubBindings bindings) - { - if (bindings.serviceName.empty() || bindings.sid == 0u || - bindings.activeStreamCountOffset == bindings.responseCounterOffset) - { - throw std::invalid_argument("invalid SOUND update stub bindings"); - } - std::unordered_set callbacks; - for (const uint32_t callback : bindings.suppressedCompletionCallbacks) - { - if (callback == 0u || !callbacks.emplace(callback).second) - { - throw std::invalid_argument("invalid SOUND update callback binding"); - } - } - return std::make_unique(host, std::move(bindings)); - } -} diff --git a/ps2xIOP/src/modules/tsnddrv.cpp b/ps2xIOP/src/modules/tsnddrv.cpp deleted file mode 100644 index 01d9999..0000000 --- a/ps2xIOP/src/modules/tsnddrv.cpp +++ /dev/null @@ -1,591 +0,0 @@ -#include "../module_factories.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ps2x::iop::detail -{ - namespace - { - constexpr uint32_t kCommandSid = 0x00000000u; - constexpr uint32_t kStateSid = 0x00000001u; - constexpr uint32_t kSubmitFunction = 0x00000000u; - constexpr uint32_t kGetStatusAddressFunction = 0x00000012u; - constexpr uint32_t kGetAddressTableFunction = 0x00000013u; - - constexpr uint32_t kStatusSize = 0x42u; - constexpr uint32_t kSeInfoOffset = 0x00u; - constexpr uint32_t kMidiInfoOffset = 0x0Cu; - constexpr uint32_t kMidiSumOffset = 0x1Eu; - constexpr uint32_t kSeSumOffset = 0x26u; - constexpr uint32_t kAddressTableEntries = 16u; - constexpr uint32_t alignUp(uint32_t value, uint32_t alignment) - { - if (alignment == 0u) - { - return value; - } - return (value + (alignment - 1u)) & ~(alignment - 1u); - } - - template - bool readGuestPod(const IopHost &host, uint32_t address, T &value) - { - value = {}; - return host.readGuest(address, &value, sizeof(value)); - } - - template - bool writeGuestPod(IopHost &host, uint32_t address, const T &value) - { - return host.writeGuest(address, &value, sizeof(value)); - } - - template - bool readIopPod(const IopHost &host, uint32_t address, T &value) - { - value = {}; - return host.readIopMemory(address, &value, sizeof(value)); - } - - template - bool writeIopPod(IopHost &host, uint32_t address, const T &value) - { - return host.writeIopMemory(address, &value, sizeof(value)); - } - - template - bool hasAnyNonZero(const std::array &values) - { - return std::any_of(values.begin(), values.end(), [](const T value) - { return value != static_cast(0); }); - } - - size_t commandLength(uint8_t command) - { - const uint8_t hi = static_cast(command & 0xF0u); - switch (hi) - { - case 0x00u: - { - size_t length = 4u; - if ((command & 0x01u) != 0u) - { - ++length; - } - if ((command & 0x02u) != 0u) - { - ++length; - } - if ((command & 0x04u) != 0u) - { - length += 2u; - } - return length; - } - case 0x10u: - return command == 0x11u ? 3u : 1u; - case 0x20u: - if (command == 0x22u || command == 0x23u || command == 0x24u || command == 0x25u) - { - return 3u; - } - if (command == 0x26u) - { - return 4u; - } - if (command == 0x20u) - { - return 5u; - } - if (command == 0x27u || command == 0x28u || command == 0x29u || - command == 0x2Cu || command == 0x2Du) - { - return 8u; - } - return 2u; - case 0x40u: - if (command == 0x47u || command == 0x48u || command == 0x49u || command == 0x4Au || - command == 0x41u || command == 0x42u) - { - return 2u; - } - if (command == 0x4Bu) - { - return 3u; - } - if (command == 0x45u || command == 0x4Cu) - { - return 4u; - } - if (command == 0x44u) - { - return 6u; - } - if (command == 0x4Du || command == 0x4Eu) - { - return 3u; - } - if (command == 0x4Fu) - { - return 6u; - } - return 1u; - case 0x50u: - case 0x60u: - if (command == 0x51u || command == 0x52u || command == 0x53u || command == 0x54u) - { - return 8u; - } - return 2u; - default: - return 0u; - } - } - - class TsnddrvService final : public IopService - { - public: - TsnddrvService(IopHost &host, TsnddrvBindings bindings) - : m_host(host), m_bindings(std::move(bindings)) - { - } - - [[nodiscard]] std::string_view name() const override - { - return m_bindings.serviceName; - } - - [[nodiscard]] std::span sids() const override - { - return m_sids; - } - - void reset() override - { - std::lock_guard lock(m_mutex); - m_state = {}; - } - - [[nodiscard]] RpcResult handleRpc(const RpcRequest &request) override - { - RpcResult result{}; - - if (request.sid == kCommandSid && request.function == kSubmitFunction) - { - handleCommandBuffer(request.send); - result.handled = true; - } - else if (request.sid == kStateSid && (request.function == kGetStatusAddressFunction || request.function == kGetAddressTableFunction)) - { - uint32_t responseAddress = 0u; - { - std::lock_guard lock(m_mutex); - if (!ensureMemoryLocked()) - { - return result; - } - responseAddress = request.function == kGetStatusAddressFunction - ? m_state.statusAddress - : m_state.addressTableAddress; - } - - if (request.receive.address != 0u && request.receive.size >= sizeof(uint32_t)) - { - (void)writeGuestPod(m_host, request.receive.address, responseAddress); - if (request.receive.size > sizeof(uint32_t)) - { - (void)m_host.zeroGuest(request.receive.address + sizeof(uint32_t), request.receive.size - sizeof(uint32_t)); - } - result.resultAddress = request.receive.address; - } - - result.handled = true; - result.signalNowaitCompletion = true; - } - - if (result.handled) - { - const auto rule = std::find_if( - m_bindings.completionRules.begin(), - m_bindings.completionRules.end(), - [&](const TsnddrvCompletionRule &candidate) - { - return candidate.eeFunction == request.endFunction; - }); - if (rule != m_bindings.completionRules.end()) - { - if (rule->suppressGuestCallback) - { - result.callbackPolicy = CallbackPolicy::Suppress; - } - result.signalCompletion = rule->signalCompletion; - if (rule->clearBusy) - { - constexpr uint32_t idle = 0u; - (void)writeGuestPod(m_host, m_bindings.busyFlagAddress, idle); - } - } - } - - return result; - } - - void onSifTransfer(const SifTransfer &transfer) override - { - if (transfer.kind != SifTransferKind::GetOtherData || - transfer.phase != SifTransferPhase::BeforeCopy || - transfer.size != kStatusSize) - { - return; - } - - std::lock_guard lock(m_mutex); - if (!m_state.initialized || transfer.sourceAddress != m_state.statusAddress) - { - return; - } - backfillStatusLocked(); - } - - void appendDebugMetrics(std::vector &metrics) const override - { - std::lock_guard lock(m_mutex); - metrics.push_back({"initialized", m_state.initialized ? 1u : 0u, false}); - metrics.push_back({"status_address", m_state.statusAddress, true}); - metrics.push_back({"address_table", m_state.addressTableAddress, true}); - metrics.push_back({"hd_base", m_state.hdBaseAddress, true}); - metrics.push_back({"sq_base", m_state.sqBaseAddress, true}); - metrics.push_back({"data_base", m_state.dataBaseAddress, true}); - } - - private: - struct State - { - bool initialized = false; - uint32_t storageBaseAddress = 0u; - uint32_t storageSize = 0u; - uint32_t statusAddress = 0u; - uint32_t addressTableAddress = 0u; - uint32_t hdBaseAddress = 0u; - uint32_t sqBaseAddress = 0u; - uint32_t dataBaseAddress = 0u; - }; - - bool ensureMemoryLocked() - { - if (m_state.statusAddress == 0u) - { - const TsnddrvGuestArena &arena = m_bindings.arena; - const uint32_t statusAddress = alignUp(arena.base, arena.statusAlignment); - const uint32_t addressTableAddress = alignUp(statusAddress + kStatusSize, arena.tableAlignment); - const uint32_t hdBaseAddress = alignUp(addressTableAddress + (kAddressTableEntries * sizeof(uint32_t)), arena.storageAlignment); - const uint32_t sqBaseAddress = alignUp(hdBaseAddress + arena.hdBytes, arena.storageAlignment); - const uint32_t dataBaseAddress = alignUp(sqBaseAddress + arena.sqBytes, arena.storageAlignment); - const uint32_t storageEnd = dataBaseAddress + arena.dataBytes; - if (storageEnd > arena.limit) - { - return false; - } - - m_state.statusAddress = statusAddress; - m_state.addressTableAddress = addressTableAddress; - m_state.hdBaseAddress = hdBaseAddress; - m_state.sqBaseAddress = sqBaseAddress; - m_state.dataBaseAddress = dataBaseAddress; - m_state.storageBaseAddress = hdBaseAddress; - m_state.storageSize = storageEnd - hdBaseAddress; - } - - if (m_state.statusAddress == 0u || - m_state.addressTableAddress == 0u || - m_state.storageBaseAddress == 0u) - { - return false; - } - - if (!m_state.initialized) - { - if (!m_host.zeroIopMemory(m_state.statusAddress, kStatusSize) || - !m_host.zeroIopMemory(m_state.addressTableAddress, kAddressTableEntries * sizeof(uint32_t)) || - !m_host.zeroIopMemory(m_state.storageBaseAddress, m_state.storageSize)) - { - return false; - } - - if ( - !writeIopPod(m_host, m_state.addressTableAddress + (0u * sizeof(uint32_t)), m_state.hdBaseAddress) || - !writeIopPod(m_host, m_state.addressTableAddress + (1u * sizeof(uint32_t)), m_state.sqBaseAddress) || - !writeIopPod(m_host, m_state.addressTableAddress + (2u * sizeof(uint32_t)), m_state.dataBaseAddress)) - { - return false; - } - m_state.initialized = true; - } - - return true; - } - - int16_t checkValue(bool seTable, uint32_t index, uint32_t count) const - { - if (index >= count) - { - return 0; - } - - for (const TsnddrvChecksumTables &candidate : m_bindings.checksumCandidates) - { - const uint32_t base = seTable ? candidate.seAddress : candidate.midiAddress; - int16_t value = 0; - if (readGuestPod(m_host, base + (index * sizeof(int16_t)), value) && value != 0) - { - return value; - } - } - return 0; - } - - bool selectCompatChecks(uint32_t &seBase, uint32_t &midiBase) const - { - const TsnddrvChecksumTables *firstReadable = nullptr; - for (const TsnddrvChecksumTables &candidate : m_bindings.checksumCandidates) - { - std::array seValues{}; - std::array midiValues{}; - const bool seReadable = m_host.readGuest(candidate.seAddress, seValues.data(), sizeof(seValues)); - const bool midiReadable = m_host.readGuest(candidate.midiAddress, midiValues.data(), sizeof(midiValues)); - if (seReadable && midiReadable && !firstReadable) - { - firstReadable = &candidate; - } - const bool looksLive = (seReadable && hasAnyNonZero(seValues)) || (midiReadable && hasAnyNonZero(midiValues)); - if (seReadable && midiReadable && looksLive) - { - seBase = candidate.seAddress; - midiBase = candidate.midiAddress; - return true; - } - } - - if (firstReadable) - { - seBase = firstReadable->seAddress; - midiBase = firstReadable->midiAddress; - return true; - } - return false; - } - - void backfillStatusLocked() - { - uint32_t seBase = 0u; - uint32_t midiBase = 0u; - if (!selectCompatChecks(seBase, midiBase)) - { - return; - } - - auto backfillSlots = [&](uint32_t statusOffset, uint32_t compatBase, uint32_t slotCount) - { - for (uint32_t slot = 0u; slot < slotCount; ++slot) - { - int16_t liveValue = 0; - if (!readIopPod(m_host, m_state.statusAddress + statusOffset + (slot * sizeof(int16_t)), liveValue) || liveValue != 0) - { - continue; - } - - int16_t compatValue = 0; - if (!readGuestPod(m_host, compatBase + (slot * sizeof(int16_t)), compatValue) || compatValue == 0) - { - continue; - } - - (void)writeIopPod(m_host, m_state.statusAddress + statusOffset + (slot * sizeof(int16_t)), compatValue); - } - }; - - backfillSlots(kSeSumOffset, seBase, 5u); - backfillSlots(kMidiSumOffset, midiBase, 4u); - } - - void applyCommandLocked(const std::array &command) - { - if (m_state.statusAddress == 0u) - { - return; - } - - switch (command[0]) - { - case 0x20u: // SdrBgmReq - { - const uint32_t port = command[1] & 0x0Fu; - uint16_t midiInfo = 0u; - (void)readIopPod(m_host, - m_state.statusAddress + kMidiInfoOffset, - midiInfo); - midiInfo = static_cast(midiInfo | - static_cast(1u << port)); - (void)writeIopPod(m_host, - m_state.statusAddress + kMidiInfoOffset, - midiInfo); - break; - } - case 0x21u: // SdrBgmStop - { - const uint32_t port = command[1] & 0x0Fu; - uint16_t midiInfo = 0u; - (void)readIopPod(m_host, m_state.statusAddress + kMidiInfoOffset, midiInfo); - midiInfo = static_cast(midiInfo & ~static_cast(1u << port)); - (void)writeIopPod(m_host, m_state.statusAddress + kMidiInfoOffset, midiInfo); - break; - } - case 0x28u: // SdrHDDataSet - { - const uint32_t port = command[1] & 0x0Fu; - if (port >= 4u) - { - break; - } - const int16_t checksum = checkValue(false, port, 4u); - (void)writeIopPod(m_host, m_state.statusAddress + kMidiSumOffset + (port * sizeof(int16_t)), checksum); - break; - } - case 0x29u: // SdrHDDataSet2 - { - const uint32_t port = command[1] & 0x0Fu; - if (port >= 5u) - { - break; - } - const int16_t checksum = checkValue(true, port, 5u); - (void)writeIopPod(m_host, m_state.statusAddress + kSeSumOffset + (port * sizeof(int16_t)), checksum); - break; - } - case 0x10u: // SdrSeAllStop - (void)m_host.zeroIopMemory(m_state.statusAddress + kSeInfoOffset, 6u * sizeof(uint16_t)); - break; - default: - break; - } - } - - void handleCommandBuffer(GuestBuffer send) - { - if (send.address == 0u || send.size == 0u) - { - return; - } - - std::lock_guard lock(m_mutex); - if (!ensureMemoryLocked()) - { - return; - } - - for (uint32_t offset = 0u; offset < send.size;) - { - uint8_t operation = 0u; - if (!m_host.readGuest(send.address + offset, &operation, sizeof(operation)) || - operation == 0xFFu) - { - break; - } - - const size_t length = commandLength(operation); - if (length == 0u || - static_cast(offset) + length > send.size) - { - break; - } - - std::array command{}; - if (!m_host.readGuest(send.address + offset, command.data(), length)) - { - break; - } - applyCommandLocked(command); - offset += static_cast(length); - } - } - - IopHost &m_host; - TsnddrvBindings m_bindings; - mutable std::mutex m_mutex; - State m_state; - const std::array m_sids = {kCommandSid, kStateSid}; - }; - } - - std::unique_ptr createTsnddrvService(IopHost &host, - TsnddrvBindings bindings) - { - const auto isPowerOfTwo = [](uint32_t value) - { - return value != 0u && (value & (value - 1u)) == 0u; - }; - const auto alignUp64 = [](uint64_t value, uint32_t alignment) - { - return (value + (alignment - 1u)) & ~static_cast(alignment - 1u); - }; - - const TsnddrvGuestArena &arena = bindings.arena; - if (bindings.serviceName.empty() || - arena.base >= arena.limit || - !isPowerOfTwo(arena.statusAlignment) || - !isPowerOfTwo(arena.tableAlignment) || - !isPowerOfTwo(arena.storageAlignment) || - arena.hdBytes == 0u || arena.sqBytes == 0u || arena.dataBytes == 0u || - bindings.checksumCandidates.empty()) - { - throw std::invalid_argument("invalid TSNDDRV bindings"); - } - - uint64_t end = alignUp64(arena.base, arena.statusAlignment) + kStatusSize; - end = alignUp64(end, arena.tableAlignment) + (kAddressTableEntries * sizeof(uint32_t)); - end = alignUp64(end, arena.storageAlignment) + arena.hdBytes; - end = alignUp64(end, arena.storageAlignment) + arena.sqBytes; - end = alignUp64(end, arena.storageAlignment) + arena.dataBytes; - if (end > arena.limit || end > std::numeric_limits::max()) - { - throw std::invalid_argument("TSNDDRV guest arena is too small"); - } - - for (const TsnddrvChecksumTables &candidate : bindings.checksumCandidates) - { - if (candidate.seAddress == 0u || candidate.midiAddress == 0u) - { - throw std::invalid_argument("incomplete TSNDDRV checksum binding"); - } - } - - std::unordered_set callbacks; - for (const TsnddrvCompletionRule &rule : bindings.completionRules) - { - if (rule.eeFunction == 0u || !callbacks.emplace(rule.eeFunction).second || - (rule.clearBusy && bindings.busyFlagAddress == 0u)) - { - throw std::invalid_argument("invalid TSNDDRV completion rule"); - } - } - - switch (bindings.protocol) - { - case TsnddrvProtocolVariant::SndQueueV1: - break; - } - return std::make_unique(host, std::move(bindings)); - } -} diff --git a/ps2xIOP/src/plugin_loader.cpp b/ps2xIOP/src/plugin_loader.cpp deleted file mode 100644 index 5490786..0000000 --- a/ps2xIOP/src/plugin_loader.cpp +++ /dev/null @@ -1,956 +0,0 @@ -#include "plugin_loader.h" - -#include "ps2x/iop/plugin_api.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#if PS2X_IOP_ENABLE_PLUGINS && defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#include -#elif PS2X_IOP_ENABLE_PLUGINS && defined(__linux__) -#include -#endif - -namespace ps2x::iop::detail -{ - namespace - { - constexpr size_t kMaxPluginProfiles = 256u; - constexpr size_t kMaxPluginSids = 256u; - constexpr size_t kMaxPluginStringBytes = 4096u; - - bool validStringView(ps2x_iop_string_view_v1 value) - { - return value.size <= kMaxPluginStringBytes && (value.size == 0u || value.data != nullptr); - } - - std::string copyString(ps2x_iop_string_view_v1 value) - { - if (!validStringView(value) || value.size == 0u) - { - return {}; - } - return std::string(value.data, value.size); - } - - ps2x_iop_string_view_v1 makeStringView(std::string_view value) - { - return {value.data(), value.size()}; - } - - int32_t copyHostString(const std::string &value, - char *destination, - size_t capacity, - size_t *requiredSize) - { - const size_t required = value.size() + 1; - if (requiredSize) - { - *requiredSize = required; - } - if (!destination || capacity < required) - { - return PS2X_IOP_STATUS_BUFFER_TOO_SMALL_V1; - } - std::memcpy(destination, value.c_str(), required); - return PS2X_IOP_STATUS_OK_V1; - } - - IopHandleKind toHandleKind(uint32_t kind) - { - return kind == PS2X_IOP_HANDLE_RPC_PACKET_V1 - ? IopHandleKind::RpcPacket - : IopHandleKind::RpcServer; - } - - HostPathKind toHostPathKind(uint32_t kind) - { - switch (kind) - { - case PS2X_IOP_PATH_CD_ROOT_V1: - return HostPathKind::CdRoot; - case PS2X_IOP_PATH_CD_IMAGE_V1: - return HostPathKind::CdImage; - case PS2X_IOP_PATH_HOST_ROOT_V1: - return HostPathKind::HostRoot; - case PS2X_IOP_PATH_MEMORY_CARD_ROOT_V1: - return HostPathKind::MemoryCardRoot; - default: - return HostPathKind::ElfDirectory; - } - } - - MemoryCardOperation toMemoryCardOperation(uint32_t operation) - { - const uint32_t last = static_cast(MemoryCardOperation::Mkdir); - if (operation > last) - { - throw std::out_of_range("invalid memory-card operation"); - } - return static_cast(operation); - } - - class HostApiBridge - { - public: - explicit HostApiBridge(IopHost &hostRef) - : host(hostRef) - { - api.abi_version = PS2X_IOP_ABI_VERSION_V1; - api.struct_size = sizeof(api); - api.userdata = this; - api.read_guest = &readGuest; - api.write_guest = &writeGuest; - api.zero_guest = &zeroGuest; - api.normalize_guest_address = &normalizeGuestAddress; - api.allocate_iop_handle = &allocateIopHandle; - api.allocate_guest = &allocateGuest; - api.free_guest = &freeGuest; - api.audio_command = &audioCommand; - api.get_host_path = &getHostPath; - api.translate_guest_path = &translateGuestPath; - api.open_host_file = &openHostFile; - api.host_file_size = &hostFileSize; - api.read_host_file = &readHostFile; - api.close_host_file = &closeHostFile; - api.memory_card = &memoryCard; - api.has_guest_function = &hasGuestFunction; - api.invoke_guest_function = &invokeGuestFunction; - api.log = &log; - } - - ps2x_iop_host_api_v1 api{}; - IopHost &host; - - private: - static HostApiBridge *self(void *userdata) - { - return static_cast(userdata); - } - - template - static int32_t guardedStatus(Callback &&callback) noexcept - { - try - { - return static_cast( - std::forward(callback)()); - } - catch (...) - { - return PS2X_IOP_STATUS_FAILED_V1; - } - } - - template - static Value guardedValue(Value fallback, Callback &&callback) noexcept - { - try - { - return static_cast( - std::forward(callback)()); - } - catch (...) - { - return fallback; - } - } - - template - static void guardedVoid(Callback &&callback) noexcept - { - try - { - std::forward(callback)(); - } - catch (...) - { - } - } - - static int32_t readGuest(void *userdata, uint32_t address, void *destination, size_t size) - { - if (!userdata || (!destination && size != 0)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.readGuest(address, destination, size) - ? PS2X_IOP_STATUS_OK_V1 - : PS2X_IOP_STATUS_FAILED_V1; }); - } - - static int32_t writeGuest(void *userdata, uint32_t address, const void *source, size_t size) - { - if (!userdata || (!source && size != 0)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.writeGuest(address, source, size) - ? PS2X_IOP_STATUS_OK_V1 - : PS2X_IOP_STATUS_FAILED_V1; }); - } - - static int32_t zeroGuest(void *userdata, uint32_t address, size_t size) - { - if (!userdata) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.zeroGuest(address, size) - ? PS2X_IOP_STATUS_OK_V1 - : PS2X_IOP_STATUS_FAILED_V1; }); - } - - static int32_t normalizeGuestAddress(void *userdata, uint32_t address, uint32_t *normalized) - { - if (!userdata || !normalized) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.normalizeGuestAddress(address, *normalized) - ? PS2X_IOP_STATUS_OK_V1 - : PS2X_IOP_STATUS_FAILED_V1; }); - } - - static uint32_t allocateIopHandle(void *userdata, uint32_t kind) - { - if (!userdata) - { - return 0; - } - return guardedValue(0u, [&]() - { return self(userdata)->host.allocateIopHandle(toHandleKind(kind)); }); - } - - static uint32_t allocateGuest(void *userdata, uint32_t size, uint32_t alignment) - { - if (!userdata) - { - return 0; - } - return guardedValue(0u, [&]() - { return self(userdata)->host.allocateGuest(size, alignment); }); - } - - static void freeGuest(void *userdata, uint32_t address) - { - if (userdata && address) - { - guardedVoid([&]() - { self(userdata)->host.freeGuest(address); }); - } - } - - static int32_t audioCommand(void *userdata, - uint32_t sid, - uint32_t function, - ps2x_iop_guest_buffer_v1 send, - ps2x_iop_guest_buffer_v1 receive) - { - if (!userdata) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { - self(userdata)->host.audioCommand(sid, - function, - {send.address, send.size}, - {receive.address, receive.size}); - return PS2X_IOP_STATUS_OK_V1; }); - } - - static int32_t getHostPath(void *userdata, - uint32_t kind, - char *destination, - size_t capacity, - size_t *requiredSize) - { - if (!userdata) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return copyHostString(self(userdata)->host.hostPath(toHostPathKind(kind)), - destination, - capacity, - requiredSize); }); - } - - static int32_t translateGuestPath(void *userdata, - ps2x_iop_string_view_v1 path, - char *destination, - size_t capacity, - size_t *requiredSize) - { - if (!userdata || (!path.data && path.size != 0)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { - const std::string translated = self(userdata)->host.translateGuestPath( - std::string_view(path.data ? path.data : "", path.size)); - return copyHostString(translated, destination, capacity, requiredSize); }); - } - - static uint64_t openHostFile(void *userdata, - ps2x_iop_string_view_v1 path) - { - if (!userdata || (!path.data && path.size != 0u)) - { - return 0u; - } - return guardedValue(0u, [&]() - { return self(userdata)->host.openHostFile( - std::string_view(path.data ? path.data : "", path.size)); }); - } - - static int32_t hostFileSize(void *userdata, - uint64_t handle, - uint64_t *size) - { - if (!userdata || handle == 0u || !size) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.hostFileSize(handle, *size) - ? PS2X_IOP_STATUS_OK_V1 - : PS2X_IOP_STATUS_FAILED_V1; }); - } - - static int32_t readHostFile(void *userdata, - uint64_t handle, - uint64_t offset, - void *destination, - size_t size, - size_t *bytesRead) - { - if (!userdata || handle == 0u || !bytesRead || - (!destination && size != 0u)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.readHostFile(handle, - offset, - destination, - size, - *bytesRead) - ? PS2X_IOP_STATUS_OK_V1 - : PS2X_IOP_STATUS_FAILED_V1; }); - } - - static void closeHostFile(void *userdata, uint64_t handle) - { - if (userdata && handle != 0u) - { - guardedVoid([&]() - { self(userdata)->host.closeHostFile(handle); }); - } - } - - static int32_t memoryCard(void *userdata, - const ps2x_iop_memory_card_request_v1 *request, - int32_t *result) - { - if (!userdata || !request || !result || request->struct_size < sizeof(*request)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - try - { - MemoryCardRequest converted; - converted.operation = toMemoryCardOperation(request->operation); - std::copy(std::begin(request->arguments), std::end(request->arguments), converted.arguments.begin()); - *result = self(userdata)->host.memoryCard(converted); - return PS2X_IOP_STATUS_OK_V1; - } - catch (...) - { - return PS2X_IOP_STATUS_FAILED_V1; - } - } - - static int32_t hasGuestFunction(void *userdata, uint32_t address) - { - if (!userdata) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.hasGuestFunction(address) ? 1 : 0; }); - } - - static int32_t invokeGuestFunction(void *userdata, - uint64_t callToken, - uint32_t address, - uint32_t a0, - uint32_t a1, - uint32_t a2, - uint32_t a3, - uint32_t *resultAddress) - { - if (!userdata) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return guardedStatus([&]() - { return self(userdata)->host.invokeGuestFunction(callToken, - address, - a0, - a1, - a2, - a3, - resultAddress) - ? 1 - : 0; }); - } - - static void log(void *userdata, uint32_t level, ps2x_iop_string_view_v1 message) - { - if (!userdata || (!message.data && message.size != 0)) - { - return; - } - const uint32_t maxLevel = static_cast(LogLevel::Error); - const auto converted = static_cast(std::min(level, maxLevel)); - guardedVoid([&]() - { self(userdata)->host.log( - converted, - std::string_view(message.data ? message.data : "", message.size)); }); - } - }; - - ps2x_iop_rpc_candidate_v1 toPluginCandidate(const RpcCallCandidate &candidate) - { - return { - candidate.sendSize, - candidate.receiveAddress, - candidate.receiveSize, - candidate.endFunction, - candidate.endParameter, - candidate.plausible ? 1u : 0u, - }; - } - - class PluginService final : public IopService - { - public: - PluginService(IopHost &host, - std::shared_ptr libraryKeepAlive, - ps2x_iop_profile_api_v1 profileApi, - std::string serviceName, - std::vector serviceSids, - const GameIdentity &identity) - : m_libraryKeepAlive(std::move(libraryKeepAlive)), - m_api(profileApi), - m_name(std::move(serviceName)), - m_sids(std::move(serviceSids)), - m_host(host) - { - const ps2x_iop_game_identity_v1 pluginIdentity{ - sizeof(ps2x_iop_game_identity_v1), - makeStringView(identity.elfName), - identity.entryPoint, - identity.crc32, - }; - try - { - m_instance = m_api.create(&m_host.api, &pluginIdentity); - } - catch (...) - { - throw std::runtime_error("plugin profile create threw an exception"); - } - if (!m_instance) - { - throw std::runtime_error("plugin profile create returned null"); - } - } - - ~PluginService() override - { - if (m_instance && m_api.destroy) - { - try - { - m_api.destroy(m_instance); - } - catch (...) - { - m_host.host.log(LogLevel::Error, - "IOP plugin destroy threw for " + m_name); - } - } - m_instance = nullptr; - } - - std::string_view name() const override - { - return m_name; - } - - std::span sids() const override - { - return m_sids; - } - - void reset() override - { - if (!m_api.reset) - { - return; - } - int32_t status = PS2X_IOP_STATUS_FAILED_V1; - try - { - status = m_api.reset(m_instance); - } - catch (...) - { - } - if (status != PS2X_IOP_STATUS_OK_V1) - { - m_host.host.log(LogLevel::Warning, "IOP plugin reset failed for " + m_name); - } - } - - RpcAbi selectRpcAbi(const RpcAbiRequest &request) const override - { - if (!m_api.select_rpc_abi) - { - return RpcAbi::RuntimeDefault; - } - const ps2x_iop_rpc_abi_request_v1 converted{ - sizeof(ps2x_iop_rpc_abi_request_v1), - request.boundSid, - request.function, - toPluginCandidate(request.registers), - toPluginCandidate(request.stack), - }; - uint32_t result = PS2X_IOP_RPC_ABI_DEFAULT_V1; - try - { - result = m_api.select_rpc_abi(m_instance, &converted); - } - catch (...) - { - m_host.host.log(LogLevel::Warning, - "IOP plugin ABI selector threw for " + m_name); - } - if (result == PS2X_IOP_RPC_ABI_REGISTERS_V1) - { - return RpcAbi::Registers; - } - if (result == PS2X_IOP_RPC_ABI_STACK_V1) - { - return RpcAbi::Stack; - } - return RpcAbi::RuntimeDefault; - } - - RpcResult handleRpc(const RpcRequest &request) override - { - const ps2x_iop_rpc_request_v1 converted{ - sizeof(ps2x_iop_rpc_request_v1), - request.callToken, - request.clientAddress, - request.serverAddress, - request.serverFunction, - request.serverBuffer, - request.sid, - request.function, - request.mode, - {request.send.address, request.send.size}, - {request.receive.address, request.receive.size}, - request.endFunction, - request.endParameter, - }; - ps2x_iop_rpc_result_v1 result{}; - result.struct_size = sizeof(result); - int32_t status = PS2X_IOP_STATUS_FAILED_V1; - try - { - status = m_api.handle_rpc(m_instance, &converted, &result); - } - catch (...) - { - } - if (status != PS2X_IOP_STATUS_OK_V1 || - result.struct_size < sizeof(result)) - { - m_host.host.log(LogLevel::Warning, "IOP plugin RPC failed for " + m_name); - return {}; - } - return { - result.handled != 0, - result.result_address, - result.signal_nowait_completion != 0, - result.signal_completion != 0, - result.callback_policy == PS2X_IOP_CALLBACK_SUPPRESS_V1 - ? CallbackPolicy::Suppress - : CallbackPolicy::RuntimeDefault, - result.server_dispatch_policy == PS2X_IOP_SERVER_DISPATCH_SUPPRESS_V1 - ? ServerDispatchPolicy::Suppress - : ServerDispatchPolicy::RuntimeDefault, - }; - } - - void onSifTransfer(const SifTransfer &transfer) override - { - if (!m_api.on_sif_transfer) - { - return; - } - const ps2x_iop_sif_transfer_v1 converted{ - sizeof(ps2x_iop_sif_transfer_v1), - static_cast(transfer.kind), - static_cast(transfer.phase), - transfer.sourceAddress, - transfer.destinationAddress, - transfer.size, - }; - int32_t status = PS2X_IOP_STATUS_FAILED_V1; - try - { - status = m_api.on_sif_transfer(m_instance, &converted); - } - catch (...) - { - } - if (status != PS2X_IOP_STATUS_OK_V1) - { - m_host.host.log(LogLevel::Warning, "IOP plugin transfer hook failed for " + m_name); - } - } - - void appendDebugMetrics(std::vector &metrics) const override - { - if (!m_api.debug_metric_count || !m_api.debug_metric) - { - return; - } - size_t rawCount = 0u; - try - { - rawCount = m_api.debug_metric_count(m_instance); - } - catch (...) - { - return; - } - const size_t count = std::min(rawCount, 256); - for (size_t i = 0; i < count; ++i) - { - ps2x_iop_debug_metric_v1 metric{}; - metric.struct_size = sizeof(metric); - int32_t status = PS2X_IOP_STATUS_FAILED_V1; - try - { - status = m_api.debug_metric(m_instance, i, &metric); - } - catch (...) - { - } - if (status != PS2X_IOP_STATUS_OK_V1 || - metric.struct_size < sizeof(metric)) - { - continue; - } - metrics.push_back({copyString(metric.name), metric.value, metric.hexadecimal != 0}); - } - } - - private: - std::shared_ptr m_libraryKeepAlive; - ps2x_iop_profile_api_v1 m_api{}; - std::string m_name; - std::vector m_sids; - HostApiBridge m_host; - void *m_instance = nullptr; - }; - - bool hasPluginExtension(const std::filesystem::path &path) - { - std::string extension = path.extension().string(); - std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char value) - { return static_cast(std::tolower(value)); }); -#if defined(_WIN32) - return extension == ".dll"; -#elif defined(__linux__) - return extension == ".so"; -#else - (void)extension; - return false; -#endif - } - - std::string formatPluginDiagnostic(const std::filesystem::path &path, std::string_view reason) - { - return "IOP plugin '" + path.string() + "': " + std::string(reason); - } - } - - class PluginCatalog::DynamicLibrary - { - public: - explicit DynamicLibrary(std::filesystem::path sourcePath) - : path(std::move(sourcePath)) - { - } - - ~DynamicLibrary() - { -#if PS2X_IOP_ENABLE_PLUGINS && defined(_WIN32) - if (handle) - { - FreeLibrary(static_cast(handle)); - } -#elif PS2X_IOP_ENABLE_PLUGINS && defined(__linux__) - if (handle) - { - dlclose(handle); - } -#endif - } - - bool open(std::string &error) - { -#if PS2X_IOP_ENABLE_PLUGINS && defined(_WIN32) - handle = LoadLibraryW(path.c_str()); - if (!handle) - { - error = "LoadLibraryW failed with code " + std::to_string(GetLastError()); - return false; - } - return true; -#elif PS2X_IOP_ENABLE_PLUGINS && defined(__linux__) - handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); - if (!handle) - { - const char *message = dlerror(); - error = message ? message : "dlopen failed"; - return false; - } - return true; -#else - error = "dynamic IOP plugins are disabled on this platform"; - return false; -#endif - } - - void *symbol(const char *name) const - { -#if PS2X_IOP_ENABLE_PLUGINS && defined(_WIN32) - return handle ? reinterpret_cast(GetProcAddress(static_cast(handle), name)) : nullptr; -#elif PS2X_IOP_ENABLE_PLUGINS && defined(__linux__) - return handle ? dlsym(handle, name) : nullptr; -#else - (void)name; - return nullptr; -#endif - } - - std::filesystem::path path; - void *handle = nullptr; - }; - - PluginCatalog::PluginCatalog(IopHost &host) - : m_host(host) - { - } - - PluginCatalog::~PluginCatalog() = default; - - // TODO I never test this one - bool PluginCatalog::load(const std::vector &searchPaths, - std::vector &profiles, - std::vector &diagnostics, - std::string *error) - { - (void)error; -#if !PS2X_IOP_ENABLE_PLUGINS - if (!searchPaths.empty()) - { - diagnostics.push_back("dynamic IOP plugins are disabled on this platform"); - } - return true; -#else - for (const auto &searchPath : searchPaths) - { - std::error_code ec; - if (!std::filesystem::exists(searchPath, ec) || ec) - { - continue; - } - if (!std::filesystem::is_directory(searchPath, ec) || ec) - { - diagnostics.push_back(formatPluginDiagnostic(searchPath, "search path is not a directory")); - continue; - } - - for (std::filesystem::directory_iterator iterator(searchPath, ec), end; !ec && iterator != end; iterator.increment(ec)) - { - const std::filesystem::directory_entry &entry = *iterator; - if (!entry.is_regular_file(ec) || ec || !hasPluginExtension(entry.path())) - { - ec.clear(); - continue; - } - - std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(entry.path(), ec); - if (ec) - { - ec.clear(); - canonicalPath = entry.path().lexically_normal(); - } - const std::string pathKey = canonicalPath.generic_string(); - if (!m_loadedPaths.insert(pathKey).second) - { - continue; - } - - auto library = std::make_shared(canonicalPath); - std::string openError; - if (!library->open(openError)) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, openError)); - continue; - } - - const auto query = reinterpret_cast( - library->symbol(PS2X_IOP_QUERY_SYMBOL_V1)); - if (!query) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, "missing " PS2X_IOP_QUERY_SYMBOL_V1)); - continue; - } - - ps2x_iop_plugin_api_v1 plugin{}; - plugin.struct_size = sizeof(plugin); - int32_t queryStatus = PS2X_IOP_STATUS_FAILED_V1; - try - { - queryStatus = query(PS2X_IOP_ABI_VERSION_V1, &plugin); - } - catch (...) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, - "query entry threw an exception")); - continue; - } - if (queryStatus != PS2X_IOP_STATUS_OK_V1 || - plugin.abi_version != PS2X_IOP_ABI_VERSION_V1 || - plugin.struct_size < sizeof(plugin)) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, "incompatible ABI or invalid descriptor")); - continue; - } - if (plugin.profile_count > 0 && !plugin.profiles) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, "profile table is null")); - continue; - } - if (plugin.profile_count > kMaxPluginProfiles) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, "too many profiles")); - continue; - } - - const std::string provider = copyString(plugin.name).empty() - ? canonicalPath.filename().string() - : copyString(plugin.name); - size_t acceptedProfiles = 0; - for (size_t index = 0; index < plugin.profile_count; ++index) - { - const ps2x_iop_profile_api_v1 &profile = plugin.profiles[index]; - if (profile.abi_version != PS2X_IOP_ABI_VERSION_V1 || - profile.struct_size < sizeof(profile) || - profile.matcher.struct_size < sizeof(profile.matcher)) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, - "ignored invalid profile at index " + std::to_string(index))); - continue; - } - - const std::string profileId = copyString(profile.id); - const bool validMatcherName = validStringView(profile.matcher.elf_name); - const bool matcherPresent = profile.matcher.elf_name.size != 0 || - profile.matcher.entry_point != 0 || - profile.matcher.crc32 != 0; - if (!validStringView(profile.id) || !validMatcherName || - profileId.empty() || !matcherPresent || - profile.sid_count == 0 || !profile.sids || - !profile.create || !profile.destroy || !profile.reset || - !profile.handle_rpc) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, - "ignored invalid profile at index " + std::to_string(index))); - continue; - } - - if (profile.sid_count > kMaxPluginSids) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, - "ignored profile with too many SIDs: " + profileId)); - continue; - } - std::vector sids(profile.sids, profile.sids + profile.sid_count); - - ProfileDefinition definition; - definition.id = profileId; - definition.provider = provider; - definition.matcher.elfName = copyString(profile.matcher.elf_name); - definition.matcher.entryPoint = profile.matcher.entry_point; - definition.matcher.crc32 = profile.matcher.crc32; - const std::shared_ptr keepAlive = library; - definition.factory = [keepAlive, profile, profileId, sids = std::move(sids)]( - IopHost &host, - const GameIdentity &identity) mutable - { - ServiceList services; - services.push_back(std::make_unique(host, - keepAlive, - profile, - profileId, - sids, - identity)); - return services; - }; - profiles.push_back(std::move(definition)); - ++acceptedProfiles; - } - - if (acceptedProfiles == 0) - { - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, "no valid profiles")); - continue; - } - diagnostics.push_back(formatPluginDiagnostic(canonicalPath, - "loaded " + std::to_string(acceptedProfiles) + " profile(s)")); - m_libraries.push_back(std::move(library)); - } - - if (ec) - { - diagnostics.push_back(formatPluginDiagnostic(searchPath, ec.message())); - } - } - return true; -#endif - } -} diff --git a/ps2xIOP/src/plugin_loader.h b/ps2xIOP/src/plugin_loader.h deleted file mode 100644 index 012bce1..0000000 --- a/ps2xIOP/src/plugin_loader.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "iop_service.h" - -#include -#include -#include -#include -#include - -namespace ps2x::iop::detail -{ - class PluginCatalog - { - public: - explicit PluginCatalog(IopHost &host); - ~PluginCatalog(); - - PluginCatalog(const PluginCatalog &) = delete; - PluginCatalog &operator=(const PluginCatalog &) = delete; - - bool load(const std::vector &searchPaths, - std::vector &profiles, - std::vector &diagnostics, - std::string *error); - - private: - class DynamicLibrary; - - IopHost &m_host; - std::vector> m_libraries; - std::unordered_set m_loadedPaths; - }; -} diff --git a/ps2xIOP/tests/iop_emulator_tests.cpp b/ps2xIOP/tests/iop_emulator_tests.cpp index b449715..44c2494 100644 --- a/ps2xIOP/tests/iop_emulator_tests.cpp +++ b/ps2xIOP/tests/iop_emulator_tests.cpp @@ -926,36 +926,33 @@ int main() "IOP reset did not remove the registered RPC server")) return 1; constexpr uint32_t lotrSoundSid = 0x00012345u; - GameIdentity lotrIdentity{}; - lotrIdentity.elfName = "SLUS_205.78"; - std::string profileError; - if (!expect(iop.configure(lotrIdentity, &profileError), - "Could not configure the LotR IOP profile")) return 1; writeRpcServerIrx(host, 0x100u, lotrSoundSid); const ModuleLoadResult physicalSoundModule = iop.loadModuleBuffer(0x100u); if (!expect(physicalSoundModule.handled && physicalSoundModule.startResult == 0, "Synthetic physical sound RPC server did not start")) return 1; - constexpr uint32_t soundSendAddress = 0x800u; - constexpr uint32_t soundReceiveAddress = 0x900u; - uint16_t emptySoundCommandCount = 0u; - std::memcpy(host.guest.data() + soundSendAddress, - &emptySoundCommandCount, - sizeof(emptySoundCommandCount)); - std::fill_n(host.guest.data() + soundReceiveAddress, 32u, 0xA5u); + const uint32_t soundHandler[] = { + 0x3C020001u, // lui v0, 1 + 0x34420400u, // ori v0, v0, 0x400 (registered server buffer) + 0x03E00008u, // jr ra + 0x00000000u, // nop + }; + if (!expect(iop.writeMemory(0x00010300u, soundHandler, sizeof(soundHandler)), + "Could not install the physical sound RPC handler")) return 1; + constexpr uint32_t soundPayload = 0x1234ABCDu; + std::memcpy(host.guest.data() + 0x800u, &soundPayload, sizeof(soundPayload)); RpcRequest soundRequest{}; soundRequest.sid = lotrSoundSid; - soundRequest.send = {soundSendAddress, 32u}; - soundRequest.receive = {soundReceiveAddress, 32u}; + soundRequest.send = {0x800u, sizeof(soundPayload)}; + soundRequest.receive = {0x900u, sizeof(soundPayload)}; + const uint64_t soundInstructionsBefore = iop.debugSnapshot().emulatorInstructions; const RpcResult soundResult = iop.handleRpc(soundRequest); - uint32_t soundResponseCounter = 0u; - std::memcpy(&soundResponseCounter, - host.guest.data() + soundReceiveAddress + sizeof(uint32_t), - sizeof(soundResponseCounter)); - if (!expect(soundResult.handled, - "LotR sound RPC was not handled")) return 1; - if (!expect(soundResponseCounter == 1u, - "Physical sound server bypassed the LotR compatibility stub")) return 1; + uint32_t soundResponse = 0u; + std::memcpy(&soundResponse, host.guest.data() + 0x900u, sizeof(soundResponse)); + if (!expect(soundResult.handled && soundResponse == soundPayload, + "Physical sound RPC did not return its own payload")) return 1; + if (!expect(iop.debugSnapshot().emulatorInstructions > soundInstructionsBefore, + "Sound RPC did not execute the physical IOP handler")) return 1; constexpr uint32_t relocatableRpcSid = 0xA11CE001u; writeRelocatableRpcServerIrx(host, 0x100u); @@ -997,6 +994,8 @@ int main() constexpr uint32_t eeDestination = 0x900u; const uint32_t transferPayload = 0x53494621u; // "SIF!" std::memcpy(host.guest.data() + eeSource, &transferPayload, sizeof(transferPayload)); + if (!expect(iop.writeMemory(iopBuffer, &transferPayload, sizeof(transferPayload)), + "Could not initialize physical IOP memory")) return 1; iop.onSifTransfer({ SifTransferKind::SetDma, SifTransferPhase::AfterCopy, @@ -1014,8 +1013,12 @@ int main() }); uint32_t stagedIopPayload = 0u; std::memcpy(&stagedIopPayload, host.guest.data() + iopBuffer, sizeof(stagedIopPayload)); - if (!expect(stagedIopPayload == transferPayload, - "sceSifGetOtherData did not stage physical IOP memory for an EE copy")) return 1; + if (!expect(stagedIopPayload == 0u, + "SIF notification aliased physical IOP memory into EE RAM")) return 1; + uint32_t physicalIopPayload = 0u; + if (!expect(iop.readMemory(iopBuffer, &physicalIopPayload, sizeof(physicalIopPayload)) && + physicalIopPayload == transferPayload, + "SIF notification corrupted physical IOP memory")) return 1; iop.reset(); constexpr uint32_t sifDmaDestination = 0xC00u; @@ -1043,7 +1046,8 @@ int main() sizeof(uint32_t), }); uint32_t lowPriorityMarker = 0u; - std::memcpy(&lowPriorityMarker, host.guest.data() + 0x00010400u, sizeof(lowPriorityMarker)); + if (!expect(iop.readMemory(0x00010400u, &lowPriorityMarker, sizeof(lowPriorityMarker)), + "Could not read the IOP scheduling marker")) return 1; if (!expect(lowPriorityMarker == 1u, "WaitVblankEnd returned immediately and starved a lower-priority IOP thread")) return 1; @@ -1103,8 +1107,8 @@ int main() sizeof(uint32_t), }); uint32_t cdvdCallbackReason = 0u; - std::memcpy(&cdvdCallbackReason, host.guest.data() + 0x000101C0u, - sizeof(cdvdCallbackReason)); + if (!expect(iop.readMemory(0x000101C0u, &cdvdCallbackReason, sizeof(cdvdCallbackReason)), + "Could not read the physical CDVD callback result")) return 1; host.cdRoot.clear(); std::filesystem::remove(virtualCdRoot, cdRootError); if (!expect(cdvdPvdRead.handled && cdvdPvdRead.startResult == 0, @@ -1125,8 +1129,8 @@ int main() sizeof(uint32_t), }); uint32_t cdvdSeekCallbackReason = 0u; - std::memcpy(&cdvdSeekCallbackReason, host.guest.data() + 0x000101C0u, - sizeof(cdvdSeekCallbackReason)); + if (!expect(iop.readMemory(0x000101C0u, &cdvdSeekCallbackReason, sizeof(cdvdSeekCallbackReason)), + "Could not read the physical CDVD callback result")) return 1; if (!expect(cdvdSeek.handled && cdvdSeek.startResult == 1, "sceCdSeek did not accept a valid LBN")) return 1; if (!expect(cdvdSeekCallbackReason == 4u, diff --git a/ps2xRecomp/src/lib/function_table_emitter.cpp b/ps2xRecomp/src/lib/function_table_emitter.cpp index 27dc9b7..b82b259 100644 --- a/ps2xRecomp/src/lib/function_table_emitter.cpp +++ b/ps2xRecomp/src/lib/function_table_emitter.cpp @@ -147,9 +147,9 @@ namespace ps2recomp std::stringstream ss; ss << "#include \"ps2_runtime.h\"\n"; - ss << "#include \"ps2_recompiled_functions.h\"\n"; + ss << "#include \n"; ss << "#include \"ps2_stubs.h\"\n"; - ss << "#include \"ps2_recompiled_stubs.h\"//this will give duplicated erros because runtime maybe has it define already, just delete the TODOS ones\n"; + ss << "#include \n"; ss << "#include \"ps2_syscalls.h\"\n\n"; ss << "extern const uint32_t g_ps2RecompiledFunctionTableBase = 0x" << std::hex << tableBase << "u;\n"; diff --git a/ps2xRuntime/include/ps2_runtime.h b/ps2xRuntime/include/ps2_runtime.h index 36c299e..4b9242c 100644 --- a/ps2xRuntime/include/ps2_runtime.h +++ b/ps2xRuntime/include/ps2_runtime.h @@ -290,7 +290,6 @@ public: bool loadELF(const std::string &elfPath); void run(); - void setIopPluginSearchPaths(std::vector paths); [[nodiscard]] ps2x::iop::ModuleLoadResult loadIopModule(std::string_view path, const void *arguments = nullptr, uint32_t argumentSize = 0); [[nodiscard]] ps2x::iop::ModuleLoadResult loadIopModuleBuffer(uint32_t guestAddress, const void *arguments = nullptr, uint32_t argumentSize = 0); [[nodiscard]] bool stopIopModule(int32_t moduleId, int32_t *result = nullptr); diff --git a/ps2xRuntime/src/lib/ps2_debug_panel.cpp b/ps2xRuntime/src/lib/ps2_debug_panel.cpp index 6419444..c9aa2c6 100644 --- a/ps2xRuntime/src/lib/ps2_debug_panel.cpp +++ b/ps2xRuntime/src/lib/ps2_debug_panel.cpp @@ -1067,18 +1067,9 @@ namespace } ImGui::SeparatorText("ps2xIOP HLE services"); - ImGui::Text("Profile: %s provider: %s", - iopSnapshot.activeProfile.empty() - ? "" - : iopSnapshot.activeProfile.c_str(), - iopSnapshot.activeProvider.empty() - ? "builtin" - : iopSnapshot.activeProvider.c_str()); - - if (ImGui::BeginTable("iop_hle_services", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) + if (ImGui::BeginTable("iop_hle_services", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn("Service"); - ImGui::TableSetupColumn("Layer"); ImGui::TableSetupColumn("Active"); ImGui::TableSetupColumn("SID"); ImGui::TableSetupColumn("EE server"); @@ -1092,8 +1083,6 @@ namespace ImGui::TableNextColumn(); ImGui::TextUnformatted(service.name.c_str()); ImGui::TableNextColumn(); - ImGui::TextUnformatted(service.profileSpecific ? "profile" : "core"); - ImGui::TableNextColumn(); ImGui::TextUnformatted(service.active ? "yes" : "no"); ImGui::TableNextColumn(); ImGui::TextUnformatted("-"); @@ -1110,8 +1099,6 @@ namespace ImGui::TableNextColumn(); ImGui::TextUnformatted(service.name.c_str()); ImGui::TableNextColumn(); - ImGui::TextUnformatted(service.profileSpecific ? "profile" : "core"); - ImGui::TableNextColumn(); ImGui::TextUnformatted(service.active ? "yes" : "no"); ImGui::TableNextColumn(); ImGui::Text("0x%08X", sid); diff --git a/ps2xRuntime/src/lib/ps2_iop_transport.h b/ps2xRuntime/src/lib/ps2_iop_transport.h index 4c9d109..6bd1835 100644 --- a/ps2xRuntime/src/lib/ps2_iop_transport.h +++ b/ps2xRuntime/src/lib/ps2_iop_transport.h @@ -8,15 +8,6 @@ class PS2IopTransport { public: - static bool configureForTesting( - PS2Runtime *runtime, - const ps2x::iop::GameIdentity &identity, - std::string *error = nullptr) - { - return runtime && runtime->m_iopSubsystem && - runtime->m_iopSubsystem->configure(identity, error); - } - [[nodiscard]] static ps2x::iop::RpcAbi selectRpcAbi( const PS2Runtime *runtime, const ps2x::iop::RpcAbiRequest &request) diff --git a/ps2xRuntime/src/lib/ps2_runtime.cpp b/ps2xRuntime/src/lib/ps2_runtime.cpp index 5e68959..45cb9d2 100644 --- a/ps2xRuntime/src/lib/ps2_runtime.cpp +++ b/ps2xRuntime/src/lib/ps2_runtime.cpp @@ -483,14 +483,6 @@ PS2Runtime::PS2Runtime() m_iopSubsystem = std::make_unique(*m_iopHost); m_eeScheduler = std::make_unique(*this); -#if defined(PS2X_IOP_ENABLE_PLUGINS) && PS2X_IOP_ENABLE_PLUGINS && \ - !defined(PLATFORM_VITA) && (defined(_WIN32) || defined(__linux__)) - if (const char *applicationDirectory = GetApplicationDirectory(); - applicationDirectory && applicationDirectory[0] != '\0') - { - m_iopSubsystem->setPluginSearchPaths({std::filesystem::path(applicationDirectory) / "iop_plugins"}); - } -#endif // Assign rather than memset: R5900Context's constructor zeroes itself and // then applies the COP0 reset values, which a memset here would discard. @@ -572,11 +564,6 @@ PS2Runtime::~PS2Runtime() } } -void PS2Runtime::setIopPluginSearchPaths(std::vector paths) -{ - m_iopSubsystem->setPluginSearchPaths(std::move(paths)); -} - ps2x::iop::ModuleLoadResult PS2Runtime::loadIopModule(std::string_view path, const void *arguments, uint32_t argumentSize) { auto scope = m_iopHost->enterCall(nullptr, m_memory.getRDRAM()); @@ -741,15 +728,6 @@ bool PS2Runtime::initialize(const char *title) std::cerr << "Failed to bind runtime core subsystems" << std::endl; return false; } -#if defined(PS2X_IOP_ENABLE_PLUGINS) && PS2X_IOP_ENABLE_PLUGINS && \ - !defined(PLATFORM_VITA) && (defined(_WIN32) || defined(__linux__)) - std::string pluginError; - if (!m_iopSubsystem->loadPlugins(&pluginError)) - { - std::cerr << "Failed to load IOP plugins: " << pluginError << std::endl; - return false; - } -#endif #if defined(PLATFORM_VITA) InitWindow(HOST_WINDOW_WIDTH, HOST_WINDOW_HEIGHT, title); // raylib vita does not support audio #else @@ -1022,12 +1000,8 @@ bool PS2Runtime::loadELF(const std::string &elfPath) std::cerr << "[ROM0] failed to configure profile: " << romError << std::endl; return false; } - std::string iopError; - if (!m_iopSubsystem->configure(identity, &iopError)) - { - std::cerr << "[ps2xIOP] failed to configure profile: " << iopError << std::endl; - return false; - } + + m_iopSubsystem->reset(); ps2_game_overrides::applyMatching(*this, elfPath, diff --git a/ps2xTest/CMakeLists.txt b/ps2xTest/CMakeLists.txt index 4d19781..5db04b9 100644 --- a/ps2xTest/CMakeLists.txt +++ b/ps2xTest/CMakeLists.txt @@ -10,33 +10,6 @@ if(BUILD_TESTING) add_subdirectory(gs_cache) endif() -if(PS2X_IOP_ENABLE_PLUGINS AND (WIN32 OR (UNIX AND NOT APPLE))) - add_library(ps2_iop_fake_plugin MODULE - src/fake_iop_plugin.cpp - ) - add_library(ps2_iop_bad_abi_plugin MODULE - src/fake_iop_bad_abi.c - ) - add_library(ps2_iop_missing_symbol_plugin MODULE - src/fake_iop_missing_symbol.cpp - ) - - foreach(plugin_target IN ITEMS - ps2_iop_fake_plugin - ps2_iop_bad_abi_plugin - ps2_iop_missing_symbol_plugin) - target_compile_features(${plugin_target} PRIVATE cxx_std_20) - target_include_directories(${plugin_target} PRIVATE - ${CMAKE_SOURCE_DIR}/ps2xIOP/include - ) - set_target_properties(${plugin_target} PROPERTIES - PREFIX "" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/iop_test_plugins" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/iop_test_plugins" - ) - endforeach() -endif() - # Static library with test logic (no main), used by ps2xStudio add_library(ps2_test_function_table OBJECT src/test_function_table.cpp @@ -85,17 +58,6 @@ if(UNIX AND NOT APPLE) target_link_libraries(ps2_test_lib PRIVATE ${CMAKE_DL_LIBS}) endif() -if(TARGET ps2_iop_fake_plugin) - target_compile_definitions(ps2_test_lib PRIVATE - PS2X_TEST_IOP_PLUGIN_DIR="$" - ) - add_dependencies(ps2_test_lib - ps2_iop_fake_plugin - ps2_iop_bad_abi_plugin - ps2_iop_missing_symbol_plugin - ) -endif() - add_executable(ps2x_tests src/main.cpp $ diff --git a/ps2xTest/src/fake_iop_bad_abi.c b/ps2xTest/src/fake_iop_bad_abi.c deleted file mode 100644 index 5d794b8..0000000 --- a/ps2xTest/src/fake_iop_bad_abi.c +++ /dev/null @@ -1,15 +0,0 @@ -#include "ps2x/iop/plugin_api.h" - -PS2X_IOP_PLUGIN_EXPORT int32_t ps2x_iop_query_v1( - uint32_t host_abi_version, - ps2x_iop_plugin_api_v1 *plugin_api) -{ - (void)host_abi_version; - if (!plugin_api || plugin_api->struct_size < sizeof(*plugin_api)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - plugin_api->abi_version = PS2X_IOP_ABI_VERSION_V1 + 1u; - plugin_api->struct_size = sizeof(*plugin_api); - return PS2X_IOP_STATUS_OK_V1; -} diff --git a/ps2xTest/src/fake_iop_missing_symbol.cpp b/ps2xTest/src/fake_iop_missing_symbol.cpp deleted file mode 100644 index 08701f2..0000000 --- a/ps2xTest/src/fake_iop_missing_symbol.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "ps2x/iop/plugin_api.h" - -extern "C" PS2X_IOP_PLUGIN_EXPORT int32_t ps2x_iop_not_the_query_symbol() -{ - return PS2X_IOP_STATUS_OK_V1; -} diff --git a/ps2xTest/src/fake_iop_plugin.cpp b/ps2xTest/src/fake_iop_plugin.cpp deleted file mode 100644 index dc8b13b..0000000 --- a/ps2xTest/src/fake_iop_plugin.cpp +++ /dev/null @@ -1,350 +0,0 @@ -#include "ps2x/iop/plugin_api.h" - -#include -#include -#include - -namespace -{ - constexpr uint32_t kSyntheticSid = 0xF00DCAFEu; - constexpr uint32_t kCoreCollisionSid = 0x80001300u; - constexpr uint32_t kSyntheticFunction = 0x42u; - constexpr uint32_t kCoreCollisionFunction = 0x99u; - constexpr uint32_t kSyntheticEntryPoint = 0x00123456u; - constexpr uint32_t kSpecificRecvXEntryPoint = kSyntheticEntryPoint + 0x100u; - constexpr uint32_t kSyntheticCrc32 = 0xA1B2C3D4u; - constexpr uint32_t kResponseXor = 0xA5A55A5Au; - constexpr uint32_t kCoreCollisionResponse = 0xC0DEF00Du; - - template - constexpr ps2x_iop_string_view_v1 stringView(const char (&value)[Size]) - { - return {value, Size - 1u}; - } - - struct FakePluginState - { - const ps2x_iop_host_api_v1 *host = nullptr; - uint64_t resetGeneration = 0; - uint64_t rpcCalls = 0; - uint64_t transfers = 0; - }; - - void log(FakePluginState *state, uint32_t level, ps2x_iop_string_view_v1 message) - { - if (state && state->host && state->host->log) - { - state->host->log(state->host->userdata, level, message); - } - } - - void *createProfile(const ps2x_iop_host_api_v1 *host, - const ps2x_iop_game_identity_v1 *identity) - { - if (!host || host->abi_version != PS2X_IOP_ABI_VERSION_V1 || - host->struct_size < sizeof(*host) || !identity || - identity->struct_size < sizeof(*identity) || - (identity->entry_point != kSyntheticEntryPoint && - identity->entry_point != kSpecificRecvXEntryPoint) || - identity->crc32 != kSyntheticCrc32) - { - return nullptr; - } - - auto *state = new (std::nothrow) FakePluginState{}; - if (!state) - { - return nullptr; - } - state->host = host; - log(state, PS2X_IOP_LOG_INFO_V1, stringView("fake-plugin-create")); - return state; - } - - void destroyProfile(void *instance) - { - auto *state = static_cast(instance); - log(state, PS2X_IOP_LOG_INFO_V1, stringView("fake-plugin-destroy")); - delete state; - } - - int32_t resetProfile(void *instance) - { - auto *state = static_cast(instance); - if (!state) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - ++state->resetGeneration; - state->rpcCalls = 0; - state->transfers = 0; - return PS2X_IOP_STATUS_OK_V1; - } - - uint32_t selectRpcAbi(void *instance, const ps2x_iop_rpc_abi_request_v1 *request) - { - if (!instance || !request || request->struct_size < sizeof(*request)) - { - return PS2X_IOP_RPC_ABI_DEFAULT_V1; - } - if (request->bound_sid == kSyntheticSid && request->function == kSyntheticFunction) - { - return PS2X_IOP_RPC_ABI_STACK_V1; - } - return PS2X_IOP_RPC_ABI_DEFAULT_V1; - } - - int32_t handleRpc(void *instance, - const ps2x_iop_rpc_request_v1 *request, - ps2x_iop_rpc_result_v1 *result) - { - auto *state = static_cast(instance); - if (!state || !request || request->struct_size < sizeof(*request) || - !result || 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->callback_policy = PS2X_IOP_CALLBACK_RUNTIME_DEFAULT_V1; - if (request->sid == kCoreCollisionSid && - request->function == kCoreCollisionFunction) - { - if (request->receive.size < sizeof(kCoreCollisionResponse) || - !state->host->write_guest || - state->host->write_guest(state->host->userdata, - request->receive.address, - &kCoreCollisionResponse, - sizeof(kCoreCollisionResponse)) != - PS2X_IOP_STATUS_OK_V1) - { - return PS2X_IOP_STATUS_FAILED_V1; - } - ++state->rpcCalls; - result->handled = 1u; - result->result_address = request->receive.address; - return PS2X_IOP_STATUS_OK_V1; - } - - if (request->sid != kSyntheticSid || request->function != kSyntheticFunction) - { - return PS2X_IOP_STATUS_OK_V1; - } - - uint32_t input = 0u; - if (request->send.size < sizeof(input) || !state->host->read_guest || - state->host->read_guest(state->host->userdata, - request->send.address, - &input, - sizeof(input)) != PS2X_IOP_STATUS_OK_V1) - { - return PS2X_IOP_STATUS_FAILED_V1; - } - - const uint32_t output = input ^ kResponseXor; - if (request->receive.size < sizeof(output) || !state->host->write_guest || - state->host->write_guest(state->host->userdata, - request->receive.address, - &output, - sizeof(output)) != PS2X_IOP_STATUS_OK_V1) - { - return PS2X_IOP_STATUS_FAILED_V1; - } - - ++state->rpcCalls; - result->handled = 1u; - result->result_address = request->receive.address; - result->signal_nowait_completion = 1u; - result->callback_policy = PS2X_IOP_CALLBACK_SUPPRESS_V1; - return PS2X_IOP_STATUS_OK_V1; - } - - int32_t onSifTransfer(void *instance, const ps2x_iop_sif_transfer_v1 *transfer) - { - auto *state = static_cast(instance); - if (!state || !transfer || transfer->struct_size < sizeof(*transfer)) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - ++state->transfers; - return PS2X_IOP_STATUS_OK_V1; - } - - size_t debugMetricCount(void *instance) - { - return instance ? 3u : 0u; - } - - int32_t debugMetric(void *instance, size_t index, ps2x_iop_debug_metric_v1 *metric) - { - const auto *state = static_cast(instance); - if (!state || !metric || metric->struct_size < sizeof(*metric) || index >= 3u) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - - metric->struct_size = sizeof(*metric); - metric->hexadecimal = 0u; - switch (index) - { - case 0u: - metric->name = stringView("reset_generation"); - metric->value = state->resetGeneration; - break; - case 1u: - metric->name = stringView("rpc_calls"); - metric->value = state->rpcCalls; - break; - case 2u: - metric->name = stringView("sif_transfers"); - metric->value = state->transfers; - break; - default: - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - return PS2X_IOP_STATUS_OK_V1; - } - - constexpr uint32_t kSids[] = {kSyntheticSid, kCoreCollisionSid}; - constexpr uint32_t kDuplicateSids[] = {kSyntheticSid, kSyntheticSid}; - constexpr uint32_t kAmbiguousSids[] = {kSyntheticSid}; - const ps2x_iop_profile_api_v1 kProfiles[] = { - { - PS2X_IOP_ABI_VERSION_V1, - sizeof(ps2x_iop_profile_api_v1), - stringView("synthetic-test-profile"), - { - sizeof(ps2x_iop_game_matcher_v1), - stringView("synthetic_iop_test.elf"), - kSyntheticEntryPoint, - kSyntheticCrc32, - }, - 2u, - kSids, - &createProfile, - &destroyProfile, - &resetProfile, - &selectRpcAbi, - &handleRpc, - &onSifTransfer, - &debugMetricCount, - &debugMetric, - }, - { - PS2X_IOP_ABI_VERSION_V1, - sizeof(ps2x_iop_profile_api_v1), - stringView("synthetic-duplicate-sid-profile"), - { - sizeof(ps2x_iop_game_matcher_v1), - stringView("synthetic_duplicate.elf"), - kSyntheticEntryPoint, - kSyntheticCrc32, - }, - 2u, - kDuplicateSids, - &createProfile, - &destroyProfile, - &resetProfile, - &selectRpcAbi, - &handleRpc, - &onSifTransfer, - &debugMetricCount, - &debugMetric, - }, - { - PS2X_IOP_ABI_VERSION_V1, - sizeof(ps2x_iop_profile_api_v1), - stringView("synthetic-ambiguous-recvx-profile"), - { - sizeof(ps2x_iop_game_matcher_v1), - stringView("slus_201.84"), - 0u, - 0u, - }, - 1u, - kAmbiguousSids, - &createProfile, - &destroyProfile, - &resetProfile, - &selectRpcAbi, - &handleRpc, - &onSifTransfer, - &debugMetricCount, - &debugMetric, - }, - { - PS2X_IOP_ABI_VERSION_V1, - sizeof(ps2x_iop_profile_api_v1), - stringView("synthetic-specific-recvx-profile"), - { - sizeof(ps2x_iop_game_matcher_v1), - stringView("slus_201.84"), - kSpecificRecvXEntryPoint, - kSyntheticCrc32, - }, - 1u, - kAmbiguousSids, - &createProfile, - &destroyProfile, - &resetProfile, - &selectRpcAbi, - &handleRpc, - &onSifTransfer, - &debugMetricCount, - &debugMetric, - }, - { - PS2X_IOP_ABI_VERSION_V1, - sizeof(ps2x_iop_profile_api_v1), - stringView("synthetic-invalid-large-sid-profile"), - { - sizeof(ps2x_iop_game_matcher_v1), - stringView("synthetic_invalid.elf"), - kSyntheticEntryPoint, - kSyntheticCrc32, - }, - 257u, - kAmbiguousSids, - &createProfile, - &destroyProfile, - &resetProfile, - &selectRpcAbi, - &handleRpc, - &onSifTransfer, - &debugMetricCount, - &debugMetric, - }, - }; - - const ps2x_iop_plugin_api_v1 kPlugin = { - PS2X_IOP_ABI_VERSION_V1, - sizeof(ps2x_iop_plugin_api_v1), - stringView("ps2x-test-plugin"), - stringView("1.0.0"), - 5u, - kProfiles, - }; -} - -extern "C" PS2X_IOP_PLUGIN_EXPORT int32_t ps2x_iop_query_v1( - uint32_t hostAbiVersion, - ps2x_iop_plugin_api_v1 *pluginApi) -{ - if (hostAbiVersion != PS2X_IOP_ABI_VERSION_V1) - { - return PS2X_IOP_STATUS_UNSUPPORTED_V1; - } - if (!pluginApi) - { - return PS2X_IOP_STATUS_INVALID_ARGUMENT_V1; - } - if (pluginApi->struct_size < sizeof(*pluginApi)) - { - return PS2X_IOP_STATUS_BUFFER_TOO_SMALL_V1; - } - - *pluginApi = kPlugin; - return PS2X_IOP_STATUS_OK_V1; -} diff --git a/ps2xTest/src/ps2_iop_tests.cpp b/ps2xTest/src/ps2_iop_tests.cpp index dd3c846..f1172e9 100644 --- a/ps2xTest/src/ps2_iop_tests.cpp +++ b/ps2xTest/src/ps2_iop_tests.cpp @@ -1,395 +1,6 @@ #include "MiniTest.h" -#include "ps2x/iop/iop_subsystem.h" #include "ps2x/iop/ps2_path.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX -#include -#elif defined(__linux__) -#include -#endif - -namespace -{ - using namespace ps2x::iop; - - constexpr uint32_t kSyntheticSid = 0xF00DCAFEu; - constexpr uint32_t kCoreCollisionSid = 0x80001300u; - constexpr uint32_t kSyntheticFunction = 0x42u; - constexpr uint32_t kCoreCollisionFunction = 0x99u; - constexpr uint32_t kSyntheticEntryPoint = 0x00123456u; - constexpr uint32_t kSpecificRecvXEntryPoint = kSyntheticEntryPoint + 0x100u; - constexpr uint32_t kSyntheticCrc32 = 0xA1B2C3D4u; - constexpr uint32_t kResponseXor = 0xA5A55A5Au; - constexpr uint32_t kCoreCollisionResponse = 0xC0DEF00Du; - - class FakeIopHost final : public IopHost - { - public: - explicit FakeIopHost(size_t memorySize = 0x10000u) - : memory(memorySize, 0u), iopMemory(0x00200000u, 0u) - { - } - - bool readGuest(uint32_t address, void *destination, size_t size) const override - { - if ((!destination && size != 0u) || !contains(address, size)) - { - return false; - } - if (size != 0u) - { - std::memcpy(destination, memory.data() + address, size); - } - return true; - } - - bool writeGuest(uint32_t address, const void *source, size_t size) override - { - if ((!source && size != 0u) || !contains(address, size)) - { - return false; - } - if (size != 0u) - { - std::memcpy(memory.data() + address, source, size); - } - return true; - } - - bool zeroGuest(uint32_t address, size_t size) override - { - if (!contains(address, size)) - { - return false; - } - std::fill(memory.begin() + address, memory.begin() + address + size, 0u); - return true; - } - - bool normalizeGuestAddress(uint32_t address, uint32_t &normalized) const override - { - normalized = address & 0x1FFFFFFFu; - return normalized < memory.size(); - } - - bool readIopMemory(uint32_t address, void *destination, size_t size) const override - { - uint32_t normalized = 0u; - if ((!destination && size != 0u) || !normalizeIopAddress(address, normalized) || - static_cast(normalized) + size > iopMemory.size()) - return false; - if (size != 0u) - std::memcpy(destination, iopMemory.data() + normalized, size); - return true; - } - - bool writeIopMemory(uint32_t address, const void *source, size_t size) override - { - uint32_t normalized = 0u; - if ((!source && size != 0u) || !normalizeIopAddress(address, normalized) || - static_cast(normalized) + size > iopMemory.size()) - return false; - if (size != 0u) - std::memcpy(iopMemory.data() + normalized, source, size); - return true; - } - - bool zeroIopMemory(uint32_t address, size_t size) override - { - uint32_t normalized = 0u; - if (!normalizeIopAddress(address, normalized) || - static_cast(normalized) + size > iopMemory.size()) - return false; - std::fill(iopMemory.begin() + normalized, iopMemory.begin() + normalized + size, 0u); - return true; - } - - bool normalizeIopAddress(uint32_t address, uint32_t &normalized) const override - { - const bool physical = address < 0x00200000u; - const bool cached = address >= 0x80000000u && address < 0x80200000u; - const bool uncached = address >= 0xA0000000u && address < 0xA0200000u; - if (!physical && !cached && !uncached) - { - normalized = 0u; - return false; - } - normalized = address & 0x1FFFFFFFu; - return normalized < iopMemory.size(); - } - - uint32_t allocateIopHandle(IopHandleKind kind) override - { - const uint32_t value = nextHandle; - nextHandle += (kind == IopHandleKind::RpcPacket) ? 0x40u : 0x80u; - return value; - } - - uint32_t allocateGuest(uint32_t size, uint32_t alignment) override - { - if (size == 0u) - { - return 0u; - } - const uint64_t effectiveAlignment = alignment == 0u ? 1u : alignment; - const uint64_t aligned = ((static_cast(nextGuestAddress) + effectiveAlignment - 1u) / - effectiveAlignment) * - effectiveAlignment; - if (aligned + size > memory.size()) - { - return 0u; - } - nextGuestAddress = static_cast(aligned + size); - guestAllocations.push_back(static_cast(aligned)); - return static_cast(aligned); - } - - void freeGuest(uint32_t address) override - { - freedGuestAddresses.push_back(address); - } - - void audioCommand(uint32_t sid, - uint32_t function, - GuestBuffer send, - GuestBuffer receive) override - { - lastAudioSid = sid; - lastAudioFunction = function; - lastAudioSend = send; - lastAudioReceive = receive; - ++audioCalls; - } - - std::string hostPath(HostPathKind kind) const override - { - switch (kind) - { - case HostPathKind::CdRoot: - return "fake/cd"; - case HostPathKind::CdImage: - return "fake/disc.iso"; - case HostPathKind::HostRoot: - return "fake/host"; - case HostPathKind::MemoryCardRoot: - return "fake/mc0"; - default: - return "fake/elf"; - } - } - - std::string translateGuestPath(std::string_view path) const override - { - return "translated/" + std::string(path); - } - - uint64_t openHostFile(std::string_view path) override - { - const auto file = hostFileContents.find(std::string(path)); - if (file == hostFileContents.end()) - { - return 0u; - } - const uint64_t handle = nextHostFileHandle++; - openHostFiles.emplace(handle, file->first); - return handle; - } - - bool hostFileSize(uint64_t handle, uint64_t &size) const override - { - size = 0u; - const auto open = openHostFiles.find(handle); - if (open == openHostFiles.end()) - { - return false; - } - const auto file = hostFileContents.find(open->second); - if (file == hostFileContents.end()) - { - return false; - } - size = file->second.size(); - return true; - } - - bool readHostFile(uint64_t handle, - uint64_t offset, - void *destination, - size_t size, - size_t &bytesRead) override - { - bytesRead = 0u; - if (!destination && size != 0u) - { - return false; - } - const auto open = openHostFiles.find(handle); - if (open == openHostFiles.end()) - { - return false; - } - const auto file = hostFileContents.find(open->second); - if (file == hostFileContents.end() || offset > file->second.size()) - { - return false; - } - bytesRead = std::min(size, file->second.size() - static_cast(offset)); - if (bytesRead != 0u) - { - std::memcpy(destination, - file->second.data() + static_cast(offset), - bytesRead); - } - return true; - } - - void closeHostFile(uint64_t handle) override - { - if (openHostFiles.erase(handle) != 0u) - { - closedHostFileHandles.push_back(handle); - } - } - - int32_t memoryCard(const MemoryCardRequest &request) override - { - lastMemoryCardRequest = request; - ++memoryCardCalls; - return 0; - } - - bool hasGuestFunction(uint32_t address) const override - { - return address == guestFunctionAddress; - } - - bool invokeGuestFunction(uint64_t callToken, - uint32_t address, - uint32_t a0, - uint32_t a1, - uint32_t a2, - uint32_t a3, - uint32_t *resultAddress) override - { - if (!hasGuestFunction(address)) - { - return false; - } - lastCallToken = callToken; - lastGuestArguments = {a0, a1, a2, a3}; - if (resultAddress) - { - *resultAddress = guestFunctionResult; - } - return true; - } - - void log(LogLevel level, std::string_view message) override - { - logs.emplace_back(level, std::string(message)); - } - - bool writeWord(uint32_t address, uint32_t value) - { - return writeGuest(address, &value, sizeof(value)); - } - - uint32_t readWord(uint32_t address) const - { - uint32_t value = 0u; - (void)readGuest(address, &value, sizeof(value)); - return value; - } - - bool hasLog(std::string_view expected) const - { - return std::any_of(logs.begin(), logs.end(), [&](const auto &entry) - { return entry.second == expected; }); - } - - std::vector memory; - std::vector iopMemory; - uint32_t nextHandle = 0x8000u; - uint32_t nextGuestAddress = 0x4000u; - std::vector guestAllocations; - std::vector freedGuestAddresses; - uint32_t audioCalls = 0u; - uint32_t lastAudioSid = 0u; - uint32_t lastAudioFunction = 0u; - GuestBuffer lastAudioSend{}; - GuestBuffer lastAudioReceive{}; - uint32_t memoryCardCalls = 0u; - MemoryCardRequest lastMemoryCardRequest{}; - uint32_t guestFunctionAddress = 0x2000u; - uint32_t guestFunctionResult = 0x3000u; - uint64_t lastCallToken = 0u; - std::vector lastGuestArguments; - std::vector> logs; - std::unordered_map> hostFileContents; - std::unordered_map openHostFiles; - std::vector closedHostFileHandles; - uint64_t nextHostFileHandle = 1u; - - private: - bool contains(uint32_t address, size_t size) const - { - const uint64_t end = static_cast(address) + static_cast(size); - return end <= memory.size(); - } - }; - - bool containsDiagnostic(const DebugSnapshot &snapshot, std::string_view text) - { - return std::any_of(snapshot.diagnostics.begin(), snapshot.diagnostics.end(), [&](const std::string &diagnostic) - { return diagnostic.find(text) != std::string::npos; }); - } - - const DebugService *findService(const DebugSnapshot &snapshot, std::string_view name) - { - const auto it = std::find_if(snapshot.services.begin(), snapshot.services.end(), [&](const DebugService &service) - { return service.name == name; }); - return it == snapshot.services.end() ? nullptr : &*it; - } - - uint64_t metricValue(const DebugService &service, std::string_view name) - { - const auto it = std::find_if(service.metrics.begin(), service.metrics.end(), [&](const DebugMetric &metric) - { return metric.name == name; }); - return it == service.metrics.end() ? std::numeric_limits::max() : it->value; - } - - bool pluginModuleIsLoaded(const std::filesystem::path &path) - { -#if defined(_WIN32) - return GetModuleHandleW(path.c_str()) != nullptr; -#elif defined(__linux__) - void *handle = dlopen(path.c_str(), RTLD_NOW | RTLD_NOLOAD); - if (!handle) - { - return false; - } - dlclose(handle); - return true; -#else - (void)path; - return false; -#endif - } -} +#include "../../ps2xIOP/tests/iop_compat_test_support.h" void register_ps2_iop_tests() { @@ -414,11 +25,8 @@ void register_ps2_iop_tests() tc.Run("HLE services activate only after a recognized module load", [](TestCase &t) { - FakeIopHost host; + iop_test::Host host; ps2x::iop::IopSubsystem subsystem(host); - std::string error; - t.IsTrue(subsystem.configure({"unmatched.elf", 0x100000u, 0u}, &error), - "core-only IOP configuration should succeed"); t.IsFalse(subsystem.canBindRpc(0x80000701u), "LIBSD RPC must not exist before LIBSD is loaded"); @@ -446,15 +54,11 @@ void register_ps2_iop_tests() "stopping LIBSD should deactivate its RPC endpoint"); }); - tc.Run("unknown SID remains unhandled without a matching profile", [](TestCase &t) + tc.Run("unknown SID remains unhandled without a loaded IRX", [](TestCase &t) { - FakeIopHost host; + iop_test::Host host; ps2x::iop::IopSubsystem subsystem(host); - std::string error; - const bool configured = subsystem.configure({"unmatched.elf", 0x100000u, 0x12345678u}, &error); - t.IsTrue(configured, "configuring an unmatched game should keep core-only IOP services available"); - ps2x::iop::RpcRequest request{}; request.sid = 0xDEADC0DEu; request.function = 0x99u; @@ -465,514 +69,7 @@ void register_ps2_iop_tests() t.Equals(result.callbackPolicy, ps2x::iop::CallbackPolicy::RuntimeDefault, "an unknown SID should preserve runtime callback handling"); - const ps2x::iop::DebugSnapshot snapshot = subsystem.debugSnapshot(); - t.IsTrue(snapshot.activeProfile.empty(), "an unmatched game should not activate a profile"); - t.IsTrue(snapshot.activeProvider.empty(), "an unmatched game should not report a profile provider"); }); - tc.Run("built-in profiles select by ELF basename and keep core services active", [](TestCase &t) - { - FakeIopHost host; - ps2x::iop::IopSubsystem subsystem(host); - std::string error; - - t.IsTrue(subsystem.configure({"SLUS_201.84", 0u, 0u}, &error), - "RECVX profile should match case-insensitively by basename"); - ps2x::iop::DebugSnapshot snapshot = subsystem.debugSnapshot(); - t.Equals(snapshot.activeProfile, std::string("recvx-us"), - "RECVX ELF should select its built-in profile"); - t.IsNotNull(findService(snapshot, "TSNDDRV"), - "RECVX profile should register TSNDDRV"); - t.IsNotNull(findService(snapshot, "CRI DTX"), - "RECVX profile should register CRI DTX"); - t.IsNotNull(findService(snapshot, "dbcman"), - "core DBCMAN should remain active with a game profile"); - t.IsNotNull(findService(snapshot, "libsd"), - "core LIBSD should remain active with a game profile"); - t.IsNotNull(findService(snapshot, "MCSERV"), - "core MCSERV should remain active with a game profile"); - - error.clear(); - t.IsTrue(subsystem.configure({"slus_203.88", 0u, 0u}, &error), - "Fatal Frame profile should configure after a different game"); - snapshot = subsystem.debugSnapshot(); - t.Equals(snapshot.activeProfile, std::string("fatal-frame-us"), - "reload should replace the active profile"); - t.IsNull(findService(snapshot, "CRI DTX"), - "reload should destroy services from the previous profile"); - t.IsNotNull(findService(snapshot, "SDRDRV"), - "Fatal Frame profile should expose SDRDRV"); - }); - - tc.Run("two subsystem instances isolate profile state and reset deterministically", [](TestCase &t) - { - FakeIopHost hostA; - FakeIopHost hostB; - ps2x::iop::IopSubsystem subsystemA(hostA); - ps2x::iop::IopSubsystem subsystemB(hostB); - std::string error; - t.IsTrue(subsystemA.configure({"SLUS_205.78", 0u, 0u}, &error), - "first LotR instance should configure"); - t.IsTrue(subsystemB.configure({"SLUS_205.78", 0u, 0u}, &error), - "second LotR instance should configure"); - - ps2x::iop::RpcRequest request{}; - request.sid = 0x00012345u; - request.receive = {0x1000u, 8u}; - - t.IsTrue(subsystemA.handleRpc(request).handled, - "first instance should handle LotR sound RPC"); - t.Equals(hostA.readWord(0x1004u), 1u, - "first instance should start its counter at one"); - (void)subsystemA.handleRpc(request); - t.Equals(hostA.readWord(0x1004u), 2u, - "first instance should advance independently"); - - t.IsTrue(subsystemB.handleRpc(request).handled, - "second instance should handle LotR sound RPC"); - t.Equals(hostB.readWord(0x1004u), 1u, - "second instance must not inherit the first counter"); - - subsystemA.reset(); - (void)subsystemA.handleRpc(request); - t.Equals(hostA.readWord(0x1004u), 1u, - "reset should restore per-instance service state"); - }); - - tc.Run("LotR sound update completes queued PlayStream slots", [](TestCase &t) - { - FakeIopHost host; - ps2x::iop::IopSubsystem subsystem(host); - std::string error; - t.IsTrue(subsystem.configure({"SLUS_205.78", 0u, 0u}, &error), - "LotR profile should configure"); - - constexpr uint32_t kSendAddress = 0x0800u; - constexpr uint32_t kReceiveAddress = 0x1000u; - constexpr uint16_t kStreamSlot = 7u; - const std::array playStreamPacket = { - 1u, // command count - 1u, // PlayStream - 7u, // argument count - 0u, - static_cast(kStreamSlot << 8u), - 0u, - 0u, - 0u, - 0u, - 0u, - }; - t.IsTrue(host.writeGuest(kSendAddress, - playStreamPacket.data(), - sizeof(playStreamPacket)), - "PlayStream command packet should fit in guest memory"); - - ps2x::iop::RpcRequest request{}; - request.sid = 0x00012345u; - request.send = {kSendAddress, sizeof(playStreamPacket)}; - request.receive = {kReceiveAddress, 0x100u}; - - t.IsTrue(subsystem.handleRpc(request).handled, - "LotR sound service should handle PlayStream"); - t.Equals(host.readWord(kReceiveAddress), 1u, - "PlayStream response should expose one active record"); - const uint32_t packedStream = host.readWord(kReceiveAddress + 4u); - t.Equals((packedStream >> 4u) & 0x3Fu, - static_cast(kStreamSlot), - "active record should identify the queued EE stream slot"); - t.Equals(host.readWord(kReceiveAddress + 0x24u), 1u, - "response counter should follow the active record"); - - const std::array statusPacket = { - 1u, // command count - 9u, // GetStatus - 2u, // argument count - kStreamSlot, - 0u, - }; - t.IsTrue(host.writeGuest(kSendAddress, statusPacket.data(), sizeof(statusPacket)), - "GetStatus command packet should fit in guest memory"); - request.send.size = sizeof(statusPacket); - - t.IsTrue(subsystem.handleRpc(request).handled, - "LotR sound service should handle the following status update"); - t.Equals(host.readWord(kReceiveAddress), 0u, - "the update after PlayStream should report no active records"); - t.Equals(host.readWord(kReceiveAddress + 4u), 2u, - "empty response counter should return to the base offset"); - }); - - tc.Run("TSNDDRV uses profile checksum bindings without writing invalid ports", [](TestCase &t) - { - FakeIopHost host(0x02000000u); - ps2x::iop::IopSubsystem subsystem(host); - std::string error; - t.IsTrue(subsystem.configure({"slus_201.84", 0u, 0u}, &error), - "RECVX profile should configure for TSNDDRV command testing"); - - constexpr uint32_t kResponseAddress = 0x1000u; - ps2x::iop::RpcRequest stateRequest{}; - stateRequest.sid = 1u; - stateRequest.function = 0x12u; - stateRequest.receive = {kResponseAddress, sizeof(uint32_t)}; - t.IsTrue(subsystem.handleRpc(stateRequest).handled, - "TSNDDRV should return its configured status buffer"); - const uint32_t statusAddress = host.readWord(kResponseAddress); - t.IsTrue(statusAddress != 0u, "TSNDDRV status buffer should be allocated"); - - constexpr int16_t kChecksum = 0x1234; - t.IsTrue(host.writeGuest(0x01E0EF10u, &kChecksum, sizeof(kChecksum)), - "RECVX primary checksum binding should be writable in the fake guest"); - - constexpr uint32_t kCommandAddress = 0x2000u; - std::array command{}; - command[0] = 0x29u; - command[1] = 0u; - t.IsTrue(host.writeGuest(kCommandAddress, command.data(), command.size()), - "valid TSNDDRV command should be writable"); - - ps2x::iop::RpcRequest commandRequest{}; - commandRequest.sid = 0u; - commandRequest.function = 0u; - commandRequest.send = {kCommandAddress, static_cast(command.size())}; - t.IsTrue(subsystem.handleRpc(commandRequest).handled, - "TSNDDRV should handle the characterized command queue"); - - int16_t writtenChecksum = 0; - t.IsTrue(host.readIopMemory(statusAddress + 0x26u, - &writtenChecksum, - sizeof(writtenChecksum)), - "TSNDDRV SE checksum slot should be readable"); - t.Equals(writtenChecksum, kChecksum, - "valid port should mirror the profile-bound checksum table"); - - constexpr uint32_t kPastStatusAddress = 0x44u; - constexpr uint16_t kSentinel = 0xBEEFu; - t.IsTrue(host.writeIopMemory(statusAddress + kPastStatusAddress, - &kSentinel, - sizeof(kSentinel)), - "sentinel after the status structure should be writable"); - command[1] = 0x0Fu; - (void)host.writeGuest(kCommandAddress, command.data(), command.size()); - (void)subsystem.handleRpc(commandRequest); - - uint16_t sentinelAfter = 0u; - (void)host.readIopMemory(statusAddress + kPastStatusAddress, - &sentinelAfter, - sizeof(sentinelAfter)); - t.Equals(sentinelAfter, kSentinel, - "invalid port must not overwrite memory past the 0x42-byte status structure"); - }); - - tc.Run("RECVX reset clears CRI object maps without global state", [](TestCase &t) - { - FakeIopHost host(0x02000000u); - ps2x::iop::IopSubsystem subsystem(host); - std::string error; - t.IsTrue(subsystem.configure({"slus_201.84", 0u, 0u}, &error), - "RECVX profile should configure"); - - constexpr uint32_t kSendAddress = 0x2000u; - constexpr uint32_t kReceiveAddress = 0x2100u; - host.writeWord(kSendAddress + 0u, 0u); - host.writeWord(kSendAddress + 4u, 0x4000u); - host.writeWord(kSendAddress + 8u, 0x100u); - - ps2x::iop::RpcRequest request{}; - request.sid = 0x7D000000u; - request.function = 0x422u; - request.send = {kSendAddress, 12u}; - request.receive = {kReceiveAddress, 4u}; - t.IsTrue(subsystem.handleRpc(request).handled, - "SJRMT create should be emulated by the RECVX profile"); - - ps2x::iop::DebugSnapshot snapshot = subsystem.debugSnapshot(); - const ps2x::iop::DebugService *service = - findService(snapshot, "CRI DTX"); - if (!service) - { - t.Fail("CRI DTX service should be visible in the debug snapshot"); - return; - } - t.Equals(metricValue(*service, "sjrmt_objects"), uint64_t{1}, - "created CRI object should be tracked by this instance"); - - subsystem.reset(); - snapshot = subsystem.debugSnapshot(); - service = findService(snapshot, "CRI DTX"); - if (!service) - { - t.Fail("CRI DTX service should survive reset"); - return; - } - t.Equals(metricValue(*service, "sjrmt_objects"), uint64_t{0}, - "reset should clear CRI object maps"); - }); - - tc.Run("reset closes profile-owned host file handles", [](TestCase &t) - { - FakeIopHost host; - host.hostFileContents["translated/test.bin"] = {0x10u, 0x20u, 0x30u}; - - ps2x::iop::IopSubsystem subsystem(host); - std::string error; - t.IsTrue(subsystem.configure({"SLUS_205.78", 0u, 0u}, &error), - "LotR profile should configure for file lifecycle testing"); - - constexpr uint32_t kPathAddress = 0x1000u; - constexpr uint32_t kReceiveAddress = 0x1100u; - constexpr char kPath[] = "test.bin"; - t.IsTrue(host.writeGuest(kPathAddress, kPath, sizeof(kPath)), - "fake guest path should be writable"); - - ps2x::iop::RpcRequest request{}; - request.sid = 0x0000FF01u; - request.function = 0x08u; - request.send = {kPathAddress, sizeof(kPath)}; - request.receive = {kReceiveAddress, 8u}; - t.IsTrue(subsystem.handleRpc(request).handled, - "LotR CLFILE open should be handled"); - t.Equals(host.openHostFiles.size(), size_t{1}, - "open RPC should retain one opaque host file handle"); - - ps2x::iop::DebugSnapshot snapshot = subsystem.debugSnapshot(); - const ps2x::iop::DebugService *service = - findService(snapshot, "CLFILE"); - if (!service) - { - t.Fail("LotR CLFILE service should be visible before reset"); - return; - } - t.Equals(metricValue(*service, "open_files"), uint64_t{1}, - "debug state should report the open file"); - - subsystem.reset(); - t.IsTrue(host.openHostFiles.empty(), - "reset should release every retained host file handle"); - t.Equals(host.closedHostFileHandles.size(), size_t{1}, - "host close callback should run exactly once"); - snapshot = subsystem.debugSnapshot(); - service = findService(snapshot, "CLFILE"); - if (!service) - { - t.Fail("LotR CLFILE service should survive reset"); - return; - } - t.Equals(metricValue(*service, "open_files"), uint64_t{0}, - "reset should clear the CLFILE handle registry"); - }); - -#if defined(PS2X_TEST_IOP_PLUGIN_DIR) - tc.Run("plugin module remains loaded through instances and unloads after subsystem destruction", [](TestCase &t) - { - const std::filesystem::path pluginDirectory(PS2X_TEST_IOP_PLUGIN_DIR); -#if defined(_WIN32) - const std::filesystem::path pluginPath = - pluginDirectory / "ps2_iop_fake_plugin.dll"; -#else - const std::filesystem::path pluginPath = - pluginDirectory / "ps2_iop_fake_plugin.so"; -#endif - t.IsFalse(pluginModuleIsLoaded(pluginPath), - "synthetic plugin should not be loaded before discovery"); - { - FakeIopHost host; - ps2x::iop::IopSubsystem subsystem(host); - subsystem.setPluginSearchPaths({pluginDirectory}); - std::string error; - t.IsTrue(subsystem.loadPlugins(&error), - "synthetic plugins should load for lifetime testing"); - t.IsTrue(subsystem.configure({"synthetic_iop_test.elf", - kSyntheticEntryPoint, - kSyntheticCrc32}, - &error), - "synthetic plugin instance should be created"); - t.IsTrue(pluginModuleIsLoaded(pluginPath), - "module must stay loaded while a profile instance exists"); - } - t.IsFalse(pluginModuleIsLoaded(pluginPath), - "module should unload after profile destruction and catalog teardown"); - }); - - tc.Run("plugin discovery matches all identity fields and dispatches through the host bridge", [](TestCase &t) - { - FakeIopHost host; - ps2x::iop::IopSubsystem subsystem(host); - const std::filesystem::path pluginDirectory(PS2X_TEST_IOP_PLUGIN_DIR); - - t.IsTrue(std::filesystem::is_directory(pluginDirectory), - "the synthetic IOP plugin directory should be staged by the test build"); - subsystem.setPluginSearchPaths({pluginDirectory}); - - std::string error; - t.IsTrue(subsystem.loadPlugins(&error), "synthetic IOP plugin discovery should succeed"); - ps2x::iop::DebugSnapshot discoverySnapshot = subsystem.debugSnapshot(); - t.IsTrue(containsDiagnostic(discoverySnapshot, "loaded 4 profile(s)"), - "plugin discovery diagnostics should report all accepted synthetic profiles"); - t.IsTrue(containsDiagnostic(discoverySnapshot, "too many SIDs"), - "an invalid profile descriptor should be ignored with a diagnostic"); - t.IsTrue(containsDiagnostic(discoverySnapshot, "bad_abi"), - "an ABI-incompatible plugin should be ignored with a diagnostic"); - t.IsTrue(containsDiagnostic(discoverySnapshot, "incompatible ABI"), - "the incompatible-plugin diagnostic should explain the ABI failure"); - t.IsTrue(containsDiagnostic(discoverySnapshot, "missing_symbol"), - "a plugin without the query symbol should be ignored with a diagnostic"); - t.IsTrue(containsDiagnostic(discoverySnapshot, "missing ps2x_iop_query_v1"), - "the missing-symbol diagnostic should name the required entry point"); - - auto expectNoProfile = [&](const ps2x::iop::GameIdentity &identity, const std::string &reason) { - error.clear(); - t.IsTrue(subsystem.configure(identity, &error), "mismatching plugin identity should configure core-only services"); - const ps2x::iop::DebugSnapshot snapshot = subsystem.debugSnapshot(); - t.IsTrue(snapshot.activeProfile.empty(), reason); - - ps2x::iop::RpcRequest request{}; - request.sid = kSyntheticSid; - request.function = kSyntheticFunction; - t.IsFalse(subsystem.handleRpc(request).handled, - "a mismatching profile must not expose its synthetic SID"); - }; - - expectNoProfile({"different.elf", kSyntheticEntryPoint, kSyntheticCrc32}, - "a different ELF basename should not match the plugin profile"); - expectNoProfile({"synthetic_iop_test.elf", kSyntheticEntryPoint + 4u, kSyntheticCrc32}, - "a different entry point should not match the plugin profile"); - expectNoProfile({"synthetic_iop_test.elf", kSyntheticEntryPoint, kSyntheticCrc32 ^ 1u}, - "a different CRC32 should not match the plugin profile"); - - error.clear(); - t.IsTrue(subsystem.configure({"synthetic_iop_test.elf", kSyntheticEntryPoint, kSyntheticCrc32}, &error), - "the synthetic ELF identity should activate the plugin profile"); - - ps2x::iop::DebugSnapshot snapshot = subsystem.debugSnapshot(); - t.Equals(snapshot.activeProfile, std::string("synthetic-test-profile"), - "debug snapshot should expose the active plugin profile id"); - t.Equals(snapshot.activeProvider, std::string("ps2x-test-plugin"), - "debug snapshot should expose the plugin provider name"); - const ps2x::iop::DebugService *service = findService(snapshot, "synthetic-test-profile"); - if (!service) - { - t.Fail("debug snapshot should include the synthetic profile service"); - return; - } - t.IsTrue(service->profileSpecific, "plugin service should be marked profile-specific"); - t.IsTrue(std::find(service->sids.begin(), service->sids.end(), kSyntheticSid) != service->sids.end(), - "plugin service should advertise its synthetic SID"); - t.Equals(metricValue(*service, "reset_generation"), uint64_t{1}, - "profile configuration should reset a new plugin instance once"); - - ps2x::iop::RpcAbiRequest abiRequest{}; - abiRequest.boundSid = kSyntheticSid; - abiRequest.function = kSyntheticFunction; - abiRequest.registers.plausible = true; - abiRequest.stack.plausible = true; - t.Equals(subsystem.selectRpcAbi(abiRequest), ps2x::iop::RpcAbi::Stack, - "plugin should be able to select the stack RPC ABI"); - abiRequest.function = kSyntheticFunction + 1u; - t.Equals(subsystem.selectRpcAbi(abiRequest), ps2x::iop::RpcAbi::RuntimeDefault, - "plugin ABI selection should fall back for unrelated functions"); - - constexpr uint32_t kSendAddress = 0x1000u; - constexpr uint32_t kReceiveAddress = 0x1100u; - constexpr uint32_t kInput = 0x1234ABCDu; - t.IsTrue(host.writeWord(kSendAddress, kInput), "fake host should seed the plugin send buffer"); - t.IsTrue(host.writeWord(kReceiveAddress, 0u), "fake host should clear the plugin receive buffer"); - - ps2x::iop::RpcRequest request{}; - request.callToken = 0x1122334455667788ull; - request.sid = kSyntheticSid; - request.function = kSyntheticFunction; - request.send = {kSendAddress, sizeof(uint32_t)}; - request.receive = {kReceiveAddress, sizeof(uint32_t)}; - const ps2x::iop::RpcResult result = subsystem.handleRpc(request); - - t.IsTrue(result.handled, "matching synthetic SID/function should dispatch to the plugin"); - t.Equals(result.resultAddress, kReceiveAddress, "plugin should return its receive-buffer address"); - t.IsTrue(result.signalNowaitCompletion, "plugin should request nowait completion signaling"); - t.Equals(result.callbackPolicy, ps2x::iop::CallbackPolicy::Suppress, - "plugin should be able to suppress the runtime callback"); - t.Equals(host.readWord(kReceiveAddress), kInput ^ kResponseXor, - "plugin should read and write guest memory through the IopHost bridge"); - - ps2x::iop::RpcRequest unknownRequest{}; - unknownRequest.sid = 0xDEADC0DEu; - unknownRequest.function = kSyntheticFunction; - t.IsFalse(subsystem.handleRpc(unknownRequest).handled, - "unknown SID should remain unhandled while a plugin profile is active"); - - constexpr uint32_t kCoreCollisionReceiveAddress = 0x1200u; - ps2x::iop::RpcRequest collisionRequest{}; - collisionRequest.sid = kCoreCollisionSid; - collisionRequest.function = kCoreCollisionFunction; - collisionRequest.receive = {kCoreCollisionReceiveAddress, sizeof(uint32_t)}; - const ps2x::iop::RpcResult collisionResult = subsystem.handleRpc(collisionRequest); - t.IsTrue(collisionResult.handled, - "a profile service should take precedence over a core service for the same SID"); - t.Equals(host.readWord(kCoreCollisionReceiveAddress), kCoreCollisionResponse, - "the profile collision route should reach the plugin implementation"); - - subsystem.onSifTransfer({ps2x::iop::SifTransferKind::SetDma, - ps2x::iop::SifTransferPhase::AfterCopy, - kSendAddress, - kReceiveAddress, - sizeof(uint32_t)}); - snapshot = subsystem.debugSnapshot(); - service = findService(snapshot, "synthetic-test-profile"); - if (!service) - { - t.Fail("synthetic profile service should remain visible after dispatch"); - return; - } - t.Equals(metricValue(*service, "rpc_calls"), uint64_t{2}, - "plugin debug metrics should count dispatched RPCs"); - t.Equals(metricValue(*service, "sif_transfers"), uint64_t{1}, - "plugin debug metrics should count SIF transfer hooks"); - - subsystem.reset(); - snapshot = subsystem.debugSnapshot(); - service = findService(snapshot, "synthetic-test-profile"); - if (!service) - { - t.Fail("synthetic profile service should remain visible after reset"); - return; - } - t.Equals(metricValue(*service, "reset_generation"), uint64_t{2}, - "explicit subsystem reset should reach the plugin instance"); - t.Equals(metricValue(*service, "rpc_calls"), uint64_t{0}, - "plugin reset should clear per-instance RPC state"); - t.Equals(metricValue(*service, "sif_transfers"), uint64_t{0}, - "plugin reset should clear per-instance transfer state"); - - error.clear(); - t.IsFalse(subsystem.configure({"synthetic_duplicate.elf", kSyntheticEntryPoint, kSyntheticCrc32}, &error), - "duplicate SIDs inside one profile layer should reject configuration"); - t.IsTrue(error.find("duplicate IOP SID") != std::string::npos, - "duplicate-SID failure should clearly identify the registry conflict"); - - error.clear(); - t.IsFalse(subsystem.configure({"slus_201.84", kSyntheticEntryPoint, kSyntheticCrc32}, &error), - "equally specific built-in and plugin matchers should be ambiguous"); - t.IsTrue(error.find("ambiguous IOP profiles") != std::string::npos, - "ambiguous profile selection should fail clearly"); - - error.clear(); - t.IsTrue(subsystem.configure({"slus_201.84", - kSpecificRecvXEntryPoint, - kSyntheticCrc32}, - &error), - "a more-specific matcher should win over a lower-specificity tie"); - t.Equals(subsystem.debugSnapshot().activeProfile, - std::string("synthetic-specific-recvx-profile"), - "the most specific plugin profile should be selected"); - - error.clear(); - t.IsTrue(subsystem.configure({"different.elf", kSyntheticEntryPoint, kSyntheticCrc32}, &error), - "switching to an unmatched ELF should destroy the active plugin profile"); - t.IsTrue(host.hasLog("fake-plugin-destroy"), - "plugin profile destroy callback should run when the active profile is replaced"); - t.IsTrue(subsystem.debugSnapshot().activeProfile.empty(), - "switching to an unmatched ELF should leave no active profile"); - }); -#endif }); } diff --git a/ps2xTest/src/ps2_sif_dma_tests.cpp b/ps2xTest/src/ps2_sif_dma_tests.cpp index ffdeff3..aace83c 100644 --- a/ps2xTest/src/ps2_sif_dma_tests.cpp +++ b/ps2xTest/src/ps2_sif_dma_tests.cpp @@ -10,9 +10,6 @@ #include #include #include -#include -#include -#include #include namespace ps2_stubs @@ -37,17 +34,6 @@ namespace } }; - void configureProfile(TestEnv &env, std::string_view elfName) - { - std::string error; - const bool configured = PS2IopTransport::configureForTesting( - &env.runtime, {std::string(elfName), 0u, 0u}, &error); - if (!configured) - { - throw std::runtime_error("failed to configure test IOP profile: " + error); - } - } - #pragma pack(push, 1) struct Ps2SifDmaTransfer { @@ -99,24 +85,6 @@ namespace return value; } - void writeGuestS16(uint8_t *rdram, uint32_t addr, int16_t value) - { - std::memcpy(rdram + addr, &value, sizeof(value)); - } - - int16_t readGuestS16(const uint8_t *rdram, uint32_t addr) - { - int16_t value = 0; - std::memcpy(&value, rdram + addr, sizeof(value)); - return value; - } - - void writeIopS16(PS2Runtime &runtime, uint32_t addr, int16_t value) - { - if (!runtime.writeIopMemory(addr, &value, sizeof(value))) - throw std::runtime_error("failed to write IOP test memory"); - } - uint32_t g_dmacHandlerWriteAddr = 0u; uint32_t g_dmacHandlerValue = 0u; uint32_t g_dmacHandlerLastCause = 0u; @@ -357,505 +325,6 @@ void register_ps2_sif_dma_tests() "DMAC handler should receive registered argument"); }); - tc.Run("sceSifSetDma acknowledges DTX work-buffer transfers by advancing the EE footer ticket", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x0002D000u; - constexpr uint32_t kDtxSid = 0x7D000000u; - constexpr uint32_t kSendAddr = 0x0002D100u; - constexpr uint32_t kRecvAddr = 0x0002D200u; - constexpr uint32_t kDescAddr = 0x0002D300u; - constexpr uint32_t kEeWorkAddr = 0x0002D400u; - constexpr uint32_t kIopWorkAddr = 0x0002D800u; - constexpr uint32_t kDtxId = 3u; - constexpr uint32_t kWorkLen = 0x100u; - constexpr uint32_t kFooterTicketAddr = kEeWorkAddr + kWorkLen - sizeof(uint32_t); - - ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kDtxSid); - setRegU32(env.ctx, 6, 0u); - ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for the DTX sid"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, kDtxId); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWorkAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWorkAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); - writeGuestU32(env.rdram.data(), kRecvAddr + 0x00u, 0u); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 2u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc should create the DTX transport"); - t.IsTrue(readGuestU32(env.rdram.data(), kRecvAddr) != 0u, "DTX create should return a remote handle"); - - std::memset(env.rdram.data() + kEeWorkAddr, 0x44, kWorkLen); - std::memset(env.rdram.data() + kIopWorkAddr, 0x00, kWorkLen); - writeGuestU32(env.rdram.data(), kFooterTicketAddr, 1u); - - const Ps2SifDmaTransfer desc{ - kEeWorkAddr, - kIopWorkAddr, - static_cast(kWorkLen), - 0}; - std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc)); - - setRegU32(env.ctx, 4, kDescAddr); - setRegU32(env.ctx, 5, 1u); - ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); - t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the DTX transfer"); - - t.Equals(readGuestU32(env.rdram.data(), kFooterTicketAddr), 2u, - "sceSifSetDma should advance the EE footer ticket so DTX clears wait_flag"); - }); - - tc.Run("sceSifSetDma applies SJX DTX payloads into the emulated SJRMT data ring", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x0002E000u; - constexpr uint32_t kDtxSid = 0x7D000000u; - constexpr uint32_t kRecvAddr = 0x0002E100u; - constexpr uint32_t kSendAddr = 0x0002E200u; - constexpr uint32_t kDescAddr = 0x0002E300u; - constexpr uint32_t kEeWorkAddr = 0x0002E400u; - constexpr uint32_t kIopWorkAddr = 0x0002E800u; - constexpr uint32_t kRingAddr = 0x0002EC00u; - constexpr uint32_t kChunkDataAddr = 0x0002ED00u; - constexpr uint32_t kWorkLen = 0x100u; - constexpr uint32_t kChunkLen = 8u; - - ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kDtxSid); - setRegU32(env.ctx, 6, 0u); - ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should bind the DTX sid"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kRingAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x422u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 12u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t sjrmtHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(sjrmtHandle != 0u, "SJRMT_UNI_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0x12345678u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x400u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t sjxHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(sjxHandle != 0u, "SJX_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWorkAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWorkAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 2u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed"); - - std::memset(env.rdram.data() + kEeWorkAddr, 0, kWorkLen); - std::memset(env.rdram.data() + kIopWorkAddr, 0, kWorkLen); - std::memset(env.rdram.data() + kRingAddr, 0, kWorkLen); - for (uint32_t i = 0; i < kChunkLen; ++i) - { - env.rdram[kChunkDataAddr + i] = static_cast(0xA0u + i); - } - - writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x00u, 1u); - env.rdram[kEeWorkAddr + 0x10u] = 0u; - env.rdram[kEeWorkAddr + 0x11u] = 1u; - std::memcpy(env.rdram.data() + kEeWorkAddr + 0x12u, "\0\0", 2u); - writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x14u, sjxHandle); - writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x18u, kChunkDataAddr); - writeGuestU32(env.rdram.data(), kEeWorkAddr + 0x1Cu, kChunkLen); - writeGuestU32(env.rdram.data(), kEeWorkAddr + kWorkLen - sizeof(uint32_t), 1u); - - const Ps2SifDmaTransfer desc{ - kEeWorkAddr, - kIopWorkAddr, - static_cast(kWorkLen), - 0}; - std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc)); - - setRegU32(env.ctx, 4, kDescAddr); - setRegU32(env.ctx, 5, 1u); - ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); - t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the SJX transport"); - t.Equals(env.rdram[kEeWorkAddr + 0x11u], static_cast(0u), - "SJX DMA ack should rewrite the response line to room so EE recycles the chunk"); - t.Equals(readGuestU32(env.rdram.data(), kEeWorkAddr + 0x14u), 0x12345678u, - "SJX DMA ack should translate the remote handle back to the EE callback object"); - t.Equals(readGuestU32(env.rdram.data(), kEeWorkAddr + kWorkLen - sizeof(uint32_t)), 2u, - "SJX DMA ack should still advance the EE footer ticket"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 1u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x429u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 8u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), kChunkLen, - "SJX DMA should make SJRMT report available data"); - t.IsTrue(std::memcmp(env.rdram.data() + kRingAddr, env.rdram.data() + kChunkDataAddr, kChunkLen) == 0, - "SJX DMA should copy the chunk payload into the emulated SJRMT ring"); - }); - - tc.Run("sceSifSetDma recognizes SJX DTX payloads from rotated EE work buffers", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x00031000u; - constexpr uint32_t kDtxSid = 0x7D000000u; - constexpr uint32_t kRecvAddr = 0x00031100u; - constexpr uint32_t kSendAddr = 0x00031200u; - constexpr uint32_t kDescAddr = 0x00031300u; - constexpr uint32_t kRegisteredEeWorkAddr = 0x00031400u; - constexpr uint32_t kRegisteredIopWorkAddr = 0x00031800u; - constexpr uint32_t kAltEeWorkAddr = 0x00031C00u; - constexpr uint32_t kAltIopWorkAddr = 0x00032000u; - constexpr uint32_t kRingAddr = 0x00032400u; - constexpr uint32_t kChunkDataAddr = 0x00032500u; - constexpr uint32_t kRegisteredWorkLen = 0x100u; - constexpr uint32_t kAltWorkLen = 0x180u; - constexpr uint32_t kChunkLen = 12u; - - ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kDtxSid); - setRegU32(env.ctx, 6, 0u); - ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should bind the DTX sid"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kRingAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kRegisteredWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x422u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 12u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t sjrmtHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(sjrmtHandle != 0u, "SJRMT_UNI_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0x87654321u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x400u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t sjxHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(sjxHandle != 0u, "SJX_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kRegisteredEeWorkAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kRegisteredIopWorkAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kRegisteredWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 2u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed"); - - std::memset(env.rdram.data() + kRegisteredEeWorkAddr, 0, kRegisteredWorkLen); - std::memset(env.rdram.data() + kRegisteredIopWorkAddr, 0, kRegisteredWorkLen); - std::memset(env.rdram.data() + kAltEeWorkAddr, 0, kAltWorkLen); - std::memset(env.rdram.data() + kAltIopWorkAddr, 0, kAltWorkLen); - std::memset(env.rdram.data() + kRingAddr, 0, kRegisteredWorkLen); - for (uint32_t i = 0; i < kChunkLen; ++i) - { - env.rdram[kChunkDataAddr + i] = static_cast(0xC0u + i); - } - - writeGuestU32(env.rdram.data(), kAltEeWorkAddr + 0x00u, 1u); - env.rdram[kAltEeWorkAddr + 0x10u] = 0u; - env.rdram[kAltEeWorkAddr + 0x11u] = 1u; - std::memcpy(env.rdram.data() + kAltEeWorkAddr + 0x12u, "\0\0", 2u); - writeGuestU32(env.rdram.data(), kAltEeWorkAddr + 0x14u, sjxHandle); - writeGuestU32(env.rdram.data(), kAltEeWorkAddr + 0x18u, kChunkDataAddr); - writeGuestU32(env.rdram.data(), kAltEeWorkAddr + 0x1Cu, kChunkLen); - writeGuestU32(env.rdram.data(), kAltEeWorkAddr + kAltWorkLen - sizeof(uint32_t), 9u); - - const Ps2SifDmaTransfer desc{ - kAltEeWorkAddr, - kAltIopWorkAddr, - static_cast(kAltWorkLen), - 0}; - std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc)); - - setRegU32(env.ctx, 4, kDescAddr); - setRegU32(env.ctx, 5, 1u); - ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); - t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the rotated SJX transport"); - t.Equals(env.rdram[kAltEeWorkAddr + 0x11u], static_cast(0u), - "rotated SJX DMA ack should rewrite the response line to room"); - t.Equals(readGuestU32(env.rdram.data(), kAltEeWorkAddr + kAltWorkLen - sizeof(uint32_t)), 10u, - "rotated SJX DMA ack should advance the alternate EE footer ticket"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 1u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x429u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 8u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), kChunkLen, - "rotated SJX DMA should make SJRMT report available data"); - t.IsTrue(std::memcmp(env.rdram.data() + kRingAddr, env.rdram.data() + kChunkDataAddr, kChunkLen) == 0, - "rotated SJX DMA should copy the chunk payload into the emulated SJRMT ring"); - }); - - tc.Run("sceSifSetDma lets active PS2RNA playback drain emulated SJRMT data", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x0002F000u; - constexpr uint32_t kDtxSid = 0x7D000000u; - constexpr uint32_t kRecvAddr = 0x0002F100u; - constexpr uint32_t kSendAddr = 0x0002F200u; - constexpr uint32_t kDesc0Addr = 0x0002F300u; - constexpr uint32_t kDesc1Addr = 0x0002F320u; - constexpr uint32_t kEeWork0Addr = 0x0002F400u; - constexpr uint32_t kIopWork0Addr = 0x0002F800u; - constexpr uint32_t kEeWork1Addr = 0x0002FC00u; - constexpr uint32_t kIopWork1Addr = 0x00030000u; - constexpr uint32_t kRingAddr = 0x00030400u; - constexpr uint32_t kChunkDataAddr = 0x00030500u; - constexpr uint32_t kWorkLen = 0x100u; - constexpr uint32_t kChunkLen = 8u; - - ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kDtxSid); - setRegU32(env.ctx, 6, 0u); - ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should bind the DTX sid"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kRingAddr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x422u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 12u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t sjrmtHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(sjrmtHandle != 0u, "SJRMT_UNI_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0xCAFEBABEu); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x400u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t sjxHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(sjxHandle != 0u, "SJX_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, 0u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x408u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t ps2RnaHandle = readGuestU32(env.rdram.data(), kRecvAddr); - t.IsTrue(ps2RnaHandle != 0u, "PS2RNA_CREATE should return a handle"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWork0Addr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWork0Addr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 2u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed for SJX transport"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, kEeWork1Addr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, kIopWork1Addr); - writeGuestU32(env.rdram.data(), kSendAddr + 0x0Cu, kWorkLen); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 2u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 16u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX create should succeed for PS2RNA transport"); - - std::memset(env.rdram.data() + kEeWork0Addr, 0, kWorkLen); - std::memset(env.rdram.data() + kIopWork0Addr, 0, kWorkLen); - std::memset(env.rdram.data() + kEeWork1Addr, 0, kWorkLen); - std::memset(env.rdram.data() + kIopWork1Addr, 0, kWorkLen); - std::memset(env.rdram.data() + kRingAddr, 0, kWorkLen); - for (uint32_t i = 0; i < kChunkLen; ++i) - { - env.rdram[kChunkDataAddr + i] = static_cast(0xB0u + i); - } - - writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x00u, 1u); - writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x10u, 2u); - writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x14u, ps2RnaHandle); - writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x18u, 1u); - writeGuestU32(env.rdram.data(), kEeWork1Addr + 0x1Cu, 0u); - writeGuestU32(env.rdram.data(), kEeWork1Addr + kWorkLen - sizeof(uint32_t), 1u); - - const Ps2SifDmaTransfer desc1{ - kEeWork1Addr, - kIopWork1Addr, - static_cast(kWorkLen), - 0}; - std::memcpy(env.rdram.data() + kDesc1Addr, &desc1, sizeof(desc1)); - - setRegU32(env.ctx, 4, kDesc1Addr); - setRegU32(env.ctx, 5, 1u); - ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); - t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the PS2RNA control transport"); - t.Equals(readGuestU32(env.rdram.data(), kEeWork1Addr + kWorkLen - sizeof(uint32_t)), 2u, - "PS2RNA control DMA should advance the EE footer ticket"); - - writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x00u, 1u); - env.rdram[kEeWork0Addr + 0x10u] = 0u; - env.rdram[kEeWork0Addr + 0x11u] = 1u; - std::memcpy(env.rdram.data() + kEeWork0Addr + 0x12u, "\0\0", 2u); - writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x14u, sjxHandle); - writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x18u, kChunkDataAddr); - writeGuestU32(env.rdram.data(), kEeWork0Addr + 0x1Cu, kChunkLen); - writeGuestU32(env.rdram.data(), kEeWork0Addr + kWorkLen - sizeof(uint32_t), 1u); - - const Ps2SifDmaTransfer desc0{ - kEeWork0Addr, - kIopWork0Addr, - static_cast(kWorkLen), - 0}; - std::memcpy(env.rdram.data() + kDesc0Addr, &desc0, sizeof(desc0)); - - setRegU32(env.ctx, 4, kDesc0Addr); - setRegU32(env.ctx, 5, 1u); - ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime); - t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should succeed for the SJX transport"); - t.Equals(env.rdram[kEeWork0Addr + 0x11u], static_cast(0u), - "SJX DMA ack should still rewrite the response line to room"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 1u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x429u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 8u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), 0u, - "active PS2RNA playback should drain remote SJRMT data instead of leaving it queued forever"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, sjrmtHandle); - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 0u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x429u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 8u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(readGuestU32(env.rdram.data(), kRecvAddr), kWorkLen, - "drained PS2RNA playback should return remote SJRMT room to full capacity"); - }); - tc.Run("resetSifState seeds boot-ready SIF registers", [](TestCase &t) { TestEnv env; @@ -986,134 +455,6 @@ void register_ps2_sif_dma_tests() t.Equals(static_cast(rd.size), kSize, "receive metadata size should be populated"); }); - tc.Run("sceSifGetOtherData preserves live sound-status sums when compat backfill is enabled", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kRdAddr = 0x00023300u; - constexpr uint32_t kDstAddr = 0x00023400u; - constexpr uint32_t kSize = 0x42u; - constexpr uint32_t kPrimarySeCheckAddr = 0x01E0EF10u; - constexpr uint32_t kPrimaryMidiCheckAddr = 0x01E0EF20u; - constexpr uint32_t kMidiSumOffset = 0x1Eu; - constexpr uint32_t kSeSumOffset = 0x26u; - constexpr uint32_t kBank = 1u; - - constexpr uint32_t kClientAddr = 0x00023500u; - constexpr uint32_t kRecvAddr = 0x00023600u; - constexpr uint32_t kSid = 1u; - - ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kSid); - setRegU32(env.ctx, 6, 0u); - ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sound-driver sid"); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x12u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t kSrcAddr = readGuestU32(env.rdram.data(), kRecvAddr); - - std::memset(env.rdram.data() + kDstAddr, 0, kSize); - std::memset(env.rdram.data() + kRdAddr, 0, sizeof(SifRpcReceiveData)); - - writeIopS16(env.runtime, kSrcAddr + kSeSumOffset + (kBank * 2u), static_cast(0x1357)); - writeIopS16(env.runtime, kSrcAddr + kMidiSumOffset + (kBank * 2u), static_cast(0x2468)); - - writeGuestS16(env.rdram.data(), kPrimarySeCheckAddr + (kBank * 2u), static_cast(0x7B7B)); - writeGuestS16(env.rdram.data(), kPrimaryMidiCheckAddr + (kBank * 2u), static_cast(0x6A6A)); - - setRegU32(env.ctx, 4, kRdAddr); - setRegU32(env.ctx, 5, kSrcAddr); - setRegU32(env.ctx, 6, kDstAddr); - setRegU32(env.ctx, 7, kSize); - ps2_stubs::sceSifGetOtherData(env.rdram.data(), &env.ctx, &env.runtime); - - t.Equals(getRegS32(env.ctx, 2), 0, - "sceSifGetOtherData should succeed for sound-status transfer"); - t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kBank * 2u)), - static_cast(0x1357), - "live se_sum for the active bank should not be clobbered by compat check arrays"); - t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kBank * 2u)), - static_cast(0x2468), - "live midi_sum for the active bank should not be clobbered by compat check arrays"); - }); - - tc.Run("sceSifGetOtherData backfills zero sound-status sums for later banks", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kRdAddr = 0x00023700u; - constexpr uint32_t kDstAddr = 0x00023800u; - constexpr uint32_t kSize = 0x42u; - constexpr uint32_t kPrimarySeCheckAddr = 0x01E0EF10u; - constexpr uint32_t kPrimaryMidiCheckAddr = 0x01E0EF20u; - constexpr uint32_t kMidiSumOffset = 0x1Eu; - constexpr uint32_t kSeSumOffset = 0x26u; - constexpr uint32_t kLiveBank = 0u; - constexpr uint32_t kPendingBank = 1u; - - constexpr uint32_t kClientAddr = 0x00023900u; - constexpr uint32_t kRecvAddr = 0x00023A00u; - - ps2_syscalls::SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 1u); - setRegU32(env.ctx, 6, 0u); - ps2_syscalls::SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for sound-driver sid"); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x12u); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 4u); - setRegU32(env.ctx, 11, 0u); - ps2_syscalls::SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t kSrcAddr = readGuestU32(env.rdram.data(), kRecvAddr); - - std::memset(env.rdram.data() + kDstAddr, 0, kSize); - std::memset(env.rdram.data() + kRdAddr, 0, sizeof(SifRpcReceiveData)); - - writeIopS16(env.runtime, kSrcAddr + kSeSumOffset + (kLiveBank * 2u), static_cast(0x1111)); - writeIopS16(env.runtime, kSrcAddr + kMidiSumOffset + (kLiveBank * 2u), static_cast(0x2222)); - - writeGuestS16(env.rdram.data(), kPrimarySeCheckAddr + (kPendingBank * 2u), static_cast(0x3333)); - writeGuestS16(env.rdram.data(), kPrimaryMidiCheckAddr + (kPendingBank * 2u), static_cast(0x4444)); - - setRegU32(env.ctx, 4, kRdAddr); - setRegU32(env.ctx, 5, kSrcAddr); - setRegU32(env.ctx, 6, kDstAddr); - setRegU32(env.ctx, 7, kSize); - ps2_stubs::sceSifGetOtherData(env.rdram.data(), &env.ctx, &env.runtime); - - t.Equals(getRegS32(env.ctx, 2), 0, - "sceSifGetOtherData should succeed for later-bank sound-status transfer"); - t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kLiveBank * 2u)), - static_cast(0x1111), - "existing live se_sum values should remain intact"); - t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kLiveBank * 2u)), - static_cast(0x2222), - "existing live midi_sum values should remain intact"); - t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kSeSumOffset + (kPendingBank * 2u)), - static_cast(0x3333), - "zero se_sum slots should backfill from compat tables for later banks"); - t.Equals(readGuestS16(env.rdram.data(), kDstAddr + kMidiSumOffset + (kPendingBank * 2u)), - static_cast(0x4444), - "zero midi_sum slots should backfill from compat tables for later banks"); - }); - tc.Run("sceSifGetOtherData rejects unsupported guest segments", [](TestCase &t) { TestEnv env; diff --git a/ps2xTest/src/ps2_sif_rpc_tests.cpp b/ps2xTest/src/ps2_sif_rpc_tests.cpp index 88383e3..7242e65 100644 --- a/ps2xTest/src/ps2_sif_rpc_tests.cpp +++ b/ps2xTest/src/ps2_sif_rpc_tests.cpp @@ -7,14 +7,10 @@ #include "runtime/ee_scheduler.h" #include -#include #include #include #include -#include #include -#include -#include #include using namespace ps2_syscalls; @@ -31,16 +27,8 @@ namespace constexpr uint32_t K_SIF_RPC_MODE_NOWAIT = 0x01u; constexpr uint32_t K_STACK_ADDR = 0x00100000u; - constexpr uint32_t IOP_SID_SNDDRV_COMMAND = 0x00000000u; - constexpr uint32_t IOP_SID_SNDDRV_STATE = 0x00000001u; - constexpr uint32_t IOP_SID_LOTR_CLFILE = 0x0000FF01u; - constexpr uint32_t IOP_SID_LOTR_SOUND = 0x00012345u; constexpr uint32_t IOP_SID_MCSERV = 0x80000400u; constexpr uint32_t IOP_SID_LIBSD = 0x80000701u; - constexpr uint32_t IOP_SID_FATAL_FRAME_SDRDRV = 0x19740512u; - constexpr uint32_t IOP_RPC_SNDDRV_SUBMIT = 0x00000000u; - constexpr uint32_t IOP_RPC_SNDDRV_GET_STATUS_ADDR = 0x00000012u; - constexpr uint32_t IOP_RPC_SNDDRV_GET_ADDR_TABLE = 0x00000013u; #pragma pack(push, 1) struct SifRpcHeader @@ -126,17 +114,6 @@ namespace } }; - void configureProfile(TestEnv &env, std::string_view elfName) - { - std::string error; - const bool configured = PS2IopTransport::configureForTesting( - &env.runtime, {std::string(elfName), 0u, 0u}, &error); - if (!configured) - { - throw std::runtime_error("failed to configure test IOP profile: " + error); - } - } - ps2x::iop::RpcResult callIop(TestEnv &env, uint32_t sid, uint32_t function, @@ -154,107 +131,6 @@ namespace &env.runtime, env.rdram.data(), &env.ctx, std::move(request)); } - std::atomic g_lotrSoundCallbackHits{0u}; - std::atomic g_recvxSoundCallbackHits{0u}; - std::atomic g_dtxDispatcherHits{0u}; - std::atomic g_dtxDispatcherRpcNum{0u}; - std::atomic g_dtxDispatcherSendBuf{0u}; - std::atomic g_dtxDispatcherSendSize{0u}; - - constexpr uint32_t K_DTX_DISPATCH_RESULT_ADDR = 0x0002D800u; - constexpr uint32_t K_DTX_DISPATCH_RESULT_MARKER = 0xD15CA7C1u; - constexpr uint32_t K_DTX_SCHEDULER_CALL = 0x00102000u; - constexpr uint32_t K_DTX_SCHEDULER_RESUME = 0x00102010u; - constexpr uint32_t K_LOTR_SOUND_SCHEDULER_CALL = 0x00102020u; - constexpr uint32_t K_LOTR_SOUND_SCHEDULER_RESUME = 0x00102030u; - uint32_t g_schedulerRpcClient = 0u; - uint32_t g_schedulerRpcNumber = 0u; - uint32_t g_schedulerRpcSend = 0u; - uint32_t g_schedulerRpcReceive = 0u; - uint32_t g_schedulerRpcResult = 0u; - uint32_t g_schedulerLotrSoundClient = 0u; - uint32_t g_schedulerLotrSoundSend = 0u; - uint32_t g_schedulerLotrSoundReceive = 0u; - uint32_t g_schedulerLotrSoundEndFunction = 0u; - uint32_t g_schedulerLotrSoundSemaphore = 0u; - uint32_t g_schedulerLotrSoundResult = 0u; - - void writeGuestU32(uint8_t *rdram, uint32_t addr, uint32_t value); - - void lotrSoundEndCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) - { - (void)rdram; - (void)runtime; - ++g_lotrSoundCallbackHits; - ctx->pc = ::getRegU32(ctx, 31); - } - - void recvxSoundEndCallbackShouldNotRun(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) - { - (void)rdram; - (void)runtime; - ++g_recvxSoundCallbackHits; - ctx->pc = ::getRegU32(ctx, 31); - } - - void schedulerLotrSoundRpcCall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) - { - SET_GPR_U32(ctx, 4, g_schedulerLotrSoundClient); - SET_GPR_U32(ctx, 5, 0u); - SET_GPR_U32(ctx, 6, K_SIF_RPC_MODE_NOWAIT); - SET_GPR_U32(ctx, 7, g_schedulerLotrSoundSend); - SET_GPR_U32(ctx, 8, 0x2000u); - SET_GPR_U32(ctx, 9, g_schedulerLotrSoundReceive); - SET_GPR_U32(ctx, 10, 0x2000u); - SET_GPR_U32(ctx, 11, g_schedulerLotrSoundEndFunction); - writeGuestU32(rdram, ::getRegU32(ctx, 29), g_schedulerLotrSoundSemaphore); - ctx->pc = K_LOTR_SOUND_SCHEDULER_RESUME; - SifCallRpc(rdram, ctx, runtime); - } - - void schedulerLotrSoundRpcResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime) - { - g_schedulerLotrSoundResult = ::getRegU32(ctx, 2); - ctx->pc = 0u; - runtime->requestStop(); - } - - void schedulerDtxRpcCall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) - { - SET_GPR_U32(ctx, 4, g_schedulerRpcClient); - SET_GPR_U32(ctx, 5, g_schedulerRpcNumber); - SET_GPR_U32(ctx, 6, 0u); - SET_GPR_U32(ctx, 7, g_schedulerRpcSend); - SET_GPR_U32(ctx, 8, 8u); - SET_GPR_U32(ctx, 9, g_schedulerRpcReceive); - SET_GPR_U32(ctx, 10, sizeof(uint32_t)); - SET_GPR_U32(ctx, 11, 0u); - ctx->pc = K_DTX_SCHEDULER_RESUME; - SifCallRpc(rdram, ctx, runtime); - } - - void schedulerDtxRpcResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime) - { - g_schedulerRpcResult = ::getRegU32(ctx, 2); - ctx->pc = 0u; - runtime->requestStop(); - } - - void recvxDtxDispatcher(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) - { - (void)runtime; - ++g_dtxDispatcherHits; - g_dtxDispatcherRpcNum = ::getRegU32(ctx, 4); - g_dtxDispatcherSendBuf = ::getRegU32(ctx, 5); - g_dtxDispatcherSendSize = ::getRegU32(ctx, 6); - - std::memcpy(rdram + K_DTX_DISPATCH_RESULT_ADDR, - &K_DTX_DISPATCH_RESULT_MARKER, - sizeof(K_DTX_DISPATCH_RESULT_MARKER)); - ctx->r[2] = _mm_set_epi64x(0, static_cast(K_DTX_DISPATCH_RESULT_ADDR)); - ctx->pc = ::getRegU32(ctx, 31); - } - void setRegU32(R5900Context &ctx, int reg, uint32_t value) { ctx.r[reg] = _mm_set_epi64x(0, static_cast(value)); @@ -294,12 +170,6 @@ namespace } }; - void writeFile(const std::filesystem::path &path, const std::vector &data) - { - std::ofstream out(path, std::ios::binary); - out.write(reinterpret_cast(data.data()), static_cast(data.size())); - } - template void writeGuestStruct(uint8_t *rdram, uint32_t addr, const T &value) { @@ -479,70 +349,6 @@ void register_ps2_sif_rpc_tests() t.Equals(getRegS32(env.ctx, 2), 0, "SifCheckStatRpc should report not busy after synchronous completion"); }); - tc.Run("Fatal Frame SDRDRV RPC initializes header and loads body archives", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "SLUS_203.88"); - ScopedTempDir temp("fatal_frame_sdrdrv"); - - std::vector header(64u, 0); - const char headerPayload[] = "img-header"; - std::memcpy(header.data(), headerPayload, sizeof(headerPayload) - 1u); - writeFile(temp.path / "img_hd.bin", header); - - constexpr uint32_t kSectorSize = 2048u; - std::vector body(kSectorSize * 2u, 0); - const char bodyPayload[] = "archive-data"; - std::memcpy(body.data() + kSectorSize, bodyPayload, sizeof(bodyPayload) - 1u); - writeFile(temp.path / "img_bd.bin", body); - - const PS2Runtime::IoPaths oldPaths = PS2Runtime::getIoPaths(); - PS2Runtime::IoPaths ioPaths; - ioPaths.elfDirectory = temp.path; - ioPaths.hostRoot = temp.path; - ioPaths.cdRoot = temp.path; - ioPaths.mcRoot = temp.path / "mc0"; - PS2Runtime::setIoPaths(ioPaths); - - constexpr uint32_t kSendAddr = 0x00030000u; - constexpr uint32_t kRecvAddr = 0x00031000u; - constexpr uint32_t kDstAddr = 0x00032000u; - constexpr uint32_t kImgHeaderAddr = 0x012F0000u; - constexpr uint32_t kLoadId = 3u; - - std::array commands{}; - commands[0] = 0x0Eu; - commands[2] = 1u; // lbn - commands[3] = sizeof(bodyPayload) - 1u; - commands[4] = kDstAddr; - commands[6] = kLoadId; - std::memcpy(env.rdram.data() + kSendAddr, commands.data(), commands.size() * sizeof(uint32_t)); - std::memset(env.rdram.data() + kRecvAddr, 0xCC, 0x180u); - - const ps2x::iop::RpcResult initResult = - callIop(env, IOP_SID_FATAL_FRAME_SDRDRV, 0u, - 0u, 0u, kRecvAddr, 0x180u); - t.IsTrue(initResult.handled, "Fatal Frame SDRDRV init RPC should be handled"); - t.Equals(std::memcmp(env.rdram.data() + kImgHeaderAddr, "img-header", 10), 0, - "SDRDRV init RPC should load img_hd.bin into the arrangement table"); - - const ps2x::iop::RpcResult result = - callIop(env, IOP_SID_FATAL_FRAME_SDRDRV, 1u, - kSendAddr, - static_cast(commands.size() * sizeof(uint32_t)), - kRecvAddr, 0x180u); - - PS2Runtime::setIoPaths(oldPaths); - - t.IsTrue(result.handled, "Fatal Frame SDRDRV SID should be handled"); - t.Equals(result.resultAddress, kRecvAddr, "SDRDRV RPC should return recv buffer"); - t.IsFalse(result.signalNowaitCompletion, "SDRDRV RPC should not request special nowait signaling"); - t.Equals(std::memcmp(env.rdram.data() + kDstAddr, "archive-data", 12), 0, - "SDRDRV load command should copy bytes from img_bd.bin by LBN"); - t.Equals(env.rdram[kRecvAddr + 0x6Cu + (kLoadId * 8u)], static_cast(0), - "SDRDRV load status should report completed"); - }); - tc.Run("MCSERV RPC init and get info report a formatted PS2 card", [](TestCase &t) { TestEnv env; @@ -603,7 +409,7 @@ void register_ps2_sif_rpc_tests() "get info should report formatted card"); }); - tc.Run("DBCMAN version RPC returns the 3.20 compatibility response", [](TestCase &t) + tc.Run("DBCMAN version RPC returns the 3.10 compatibility response", [](TestCase &t) { TestEnv env; const auto dbcmanModule = env.runtime.loadIopModule("rom0:DBCMAN"); @@ -612,7 +418,7 @@ void register_ps2_sif_rpc_tests() constexpr uint32_t kDbcManSid = 0x80001300u; constexpr uint32_t kCheckVersionRpc = 0x80001363u; constexpr uint32_t kRecvAddr = 0x00035A00u; - constexpr uint32_t kDbcManVersion = 0x0320u; + constexpr uint32_t kDbcManVersion = 0x0310u; std::memset(env.rdram.data() + kRecvAddr, 0xCC, 16u); const ps2x::iop::RpcResult result = @@ -626,7 +432,7 @@ void register_ps2_sif_rpc_tests() { t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + (index * 4u)), kDbcManVersion, - "DBCMAN should repeat version 3.20 across the response words"); + "DBCMAN should repeat version 3.10 across the response words"); } }); @@ -661,245 +467,6 @@ void register_ps2_sif_rpc_tests() } }); - tc.Run("LotR ClFile RPC opens reads and reports EOF", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "SLUS_205.78"); - ScopedTempDir temp("lotr_clfile_rpc"); - - const std::vector payload = {'a', 'b', 'c'}; - const std::vector loadPayload = {'x', 'y', 'z', '!'}; - writeFile(temp.path / "boot.cfg", payload); - writeFile(temp.path / "load.bin", loadPayload); - - const PS2Runtime::IoPaths oldPaths = PS2Runtime::getIoPaths(); - PS2Runtime::IoPaths ioPaths; - ioPaths.elfDirectory = temp.path; - ioPaths.hostRoot = temp.path; - ioPaths.cdRoot = temp.path; - ioPaths.mcRoot = temp.path / "mc0"; - PS2Runtime::setIoPaths(ioPaths); - - constexpr uint32_t kSendAddr = 0x00036000u; - constexpr uint32_t kRecvAddr = 0x00037000u; - constexpr uint32_t kDstAddr = 0x00038000u; - - auto callClFileRpc = [&](uint32_t rpcNum, uint32_t sendSize) { - const ps2x::iop::RpcResult result = - callIop(env, IOP_SID_LOTR_CLFILE, rpcNum, - kSendAddr, sendSize, kRecvAddr, 0x40u); - t.IsTrue(result.handled, "LotR ClFile SID should be handled"); - t.Equals(result.resultAddress, kRecvAddr, "ClFile RPC should return recv buffer"); - t.IsFalse(result.signalNowaitCompletion, "ClFile RPC should not request special nowait signaling"); - }; - - std::memset(env.rdram.data() + kSendAddr, 0, 0x100u); - std::memcpy(env.rdram.data() + kSendAddr, "boot.cfg", 9u); - std::memset(env.rdram.data() + kRecvAddr, 0xAA, 0x40u); - callClFileRpc(0x08u, 0x100u); - - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 0u), 0u, - "open should report success status"); - const uint32_t handle = readGuestStruct(env.rdram.data(), kRecvAddr + 4u); - t.IsTrue(handle != 0u, "open should return a non-zero remote file handle"); - - std::memset(env.rdram.data() + kSendAddr, 0, 0x100u); - std::memcpy(env.rdram.data() + kSendAddr, "missing.cfg", 12u); - std::memset(env.rdram.data() + kRecvAddr, 0xAA, 0x40u); - callClFileRpc(0x08u, 0x100u); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 4u), 0u, - "missing file open should not manufacture a handle from path bytes"); - - constexpr uint32_t kLoadDstAddr = 0x00039000u; - std::memset(env.rdram.data() + kSendAddr, 0, 0x110u); - std::memcpy(env.rdram.data() + kSendAddr, "load.bin", 9u); - writeGuestU32(env.rdram.data(), kSendAddr + 0x100u, static_cast(loadPayload.size())); - writeGuestU32(env.rdram.data(), kSendAddr + 0x104u, kLoadDstAddr); - std::memset(env.rdram.data() + kLoadDstAddr, 0xCC, 0x20u); - callClFileRpc(0x01u, 0x110u); - - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 0u), 5u, - "direct load should report a queued load result"); - const uint32_t loadHandle = readGuestStruct(env.rdram.data(), kRecvAddr + 4u); - t.IsTrue(loadHandle >= 3u, "direct load should return a status handle usable by getStatus"); - t.Equals(std::memcmp(env.rdram.data() + kLoadDstAddr, loadPayload.data(), loadPayload.size()), 0, - "direct load should copy file bytes into the requested guest destination"); - - writeGuestU32(env.rdram.data(), kSendAddr, loadHandle); - callClFileRpc(0x05u, 0u); - writeGuestU32(env.rdram.data(), kSendAddr, loadHandle); - callClFileRpc(0x03u, sizeof(uint32_t)); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 0u), 7u, - "direct load getStatus should report completed"); - - writeGuestU32(env.rdram.data(), kSendAddr, loadHandle); - callClFileRpc(0x06u, sizeof(uint32_t)); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 4u), - static_cast(loadPayload.size()), - "direct load getSize should report the loaded host file size"); - - writeGuestU32(env.rdram.data(), kSendAddr, loadHandle); - callClFileRpc(0x09u, sizeof(uint32_t)); - - if (handle != 0u) - { - std::array readPacket = {handle, 2u, kDstAddr, 0u}; - writeGuestStruct(env.rdram.data(), kSendAddr, readPacket); - std::memset(env.rdram.data() + kDstAddr, 0, 8u); - callClFileRpc(0x0Au, static_cast(readPacket.size() * sizeof(uint32_t))); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 4u), 2u, - "first read should report actual byte count"); - t.Equals(std::memcmp(env.rdram.data() + kDstAddr, "ab", 2), 0, - "first read should copy file bytes into guest destination"); - - writeGuestStruct(env.rdram.data(), kSendAddr, readPacket); - std::memset(env.rdram.data() + kDstAddr, 0, 8u); - callClFileRpc(0x0Au, static_cast(readPacket.size() * sizeof(uint32_t))); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 4u), 1u, - "short read should report remaining byte count"); - t.Equals(std::memcmp(env.rdram.data() + kDstAddr, "c", 1), 0, - "short read should copy remaining file byte"); - - writeGuestStruct(env.rdram.data(), kSendAddr, readPacket); - std::memset(env.rdram.data() + kDstAddr, 0xCC, 8u); - callClFileRpc(0x0Au, static_cast(readPacket.size() * sizeof(uint32_t))); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 4u), 0u, - "EOF read should report zero bytes"); - - writeGuestU32(env.rdram.data(), kSendAddr, handle); - callClFileRpc(0x09u, sizeof(uint32_t)); - } - - PS2Runtime::setIoPaths(oldPaths); - }); - - tc.Run("LotR sound RPC invokes guest callback to consume HLE response", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "SLUS_205.78"); - - constexpr uint32_t kClientAddr = 0x00039000u; - constexpr uint32_t kSendAddr = 0x0003A000u; - constexpr uint32_t kRecvAddr = 0x0003C000u; - constexpr uint32_t kEndFunc = 0x001FFD70u; - - env.runtime.registerFunction(kEndFunc, lotrSoundEndCallback); - g_lotrSoundCallbackHits = 0u; - - SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_SID_LOTR_SOUND); - setRegU32(env.ctx, 6, 0u); - SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for LotR sound SID"); - - writeGuestU32(env.rdram.data(), kSendAddr, 3u); - std::memset(env.rdram.data() + kRecvAddr, 0xAA, 0x2000u); - env.runtime.registerFunction(K_LOTR_SOUND_SCHEDULER_CALL, schedulerLotrSoundRpcCall); - env.runtime.registerFunction(K_LOTR_SOUND_SCHEDULER_RESUME, schedulerLotrSoundRpcResume); - g_schedulerLotrSoundClient = kClientAddr; - g_schedulerLotrSoundSend = kSendAddr; - g_schedulerLotrSoundReceive = kRecvAddr; - g_schedulerLotrSoundEndFunction = kEndFunc; - g_schedulerLotrSoundResult = static_cast(-1); - - R5900Context mainContext{}; - mainContext.pc = K_LOTR_SOUND_SCHEDULER_CALL; - setRegU32(mainContext, 29, K_STACK_ADDR); - env.runtime.eeScheduler().reset(env.rdram.data(), mainContext); - const int32_t semaId = env.runtime.eeScheduler().createSemaphore(0, 1, 0u, 0u); - t.IsTrue(semaId > 0, "scheduler should create a positive semaphore id"); - g_schedulerLotrSoundSemaphore = static_cast(semaId); - env.runtime.eeScheduler().run(); - - t.Equals(g_schedulerLotrSoundResult, static_cast(KE_OK), - "SifCallRpc should resume with KE_OK for LotR sound RPC"); - t.Equals(g_lotrSoundCallbackHits.load(), 1u, - "LotR SOUND_JP callback should consume the HLE response"); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr + 0u), 0u, - "LotR sound response should report no active stream records"); - t.IsTrue(readGuestStruct(env.rdram.data(), kRecvAddr + 4u) != 0u, - "LotR sound response should advance the IOP update counter"); - - t.Equals(env.runtime.eeScheduler().pollSemaphore(semaId), semaId, - "LotR sound callback completion should signal the sema"); - }); - - tc.Run("RECVX sound callbacks complete in HLE and only clear busy on designated callbacks", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x0003D000u; - constexpr uint32_t kSemaParamAddr = 0x0003D100u; - constexpr uint32_t kCompletionOnlyCallback = 0x002EAC20u; - constexpr uint32_t kClearBusyCallback = 0x002EAC30u; - constexpr uint32_t kBusyFlagAddr = 0x01E212C8u; - - env.runtime.registerFunction(kCompletionOnlyCallback, recvxSoundEndCallbackShouldNotRun); - env.runtime.registerFunction(kClearBusyCallback, recvxSoundEndCallbackShouldNotRun); - g_recvxSoundCallbackHits = 0u; - - SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - const uint32_t semaParam[6] = {0u, 1u, 0u, 0u, 0u, 0u}; - std::memcpy(env.rdram.data() + kSemaParamAddr, semaParam, sizeof(semaParam)); - setRegU32(env.ctx, 4, kSemaParamAddr); - CreateSema(env.rdram.data(), &env.ctx, &env.runtime); - const int32_t semaId = getRegS32(env.ctx, 2); - t.IsTrue(semaId > 0, "CreateSema should return a positive semaphore id"); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_SID_SNDDRV_COMMAND); - setRegU32(env.ctx, 6, 0u); - SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for RECVX snddrv command SID"); - - auto callSoundDriver = [&](uint32_t endFunc) { - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_SUBMIT); - setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, 0u); - setRegU32(env.ctx, 10, 0u); - setRegU32(env.ctx, 11, endFunc); - setRegU32(env.ctx, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); - - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "RECVX snddrv submission should complete"); - }; - - constexpr uint32_t kBusyBeforeCompletion = 0x11111111u; - writeGuestU32(env.rdram.data(), kBusyFlagAddr, kBusyBeforeCompletion); - callSoundDriver(kCompletionOnlyCallback); - - t.Equals(g_recvxSoundCallbackHits.load(), 0u, - "recognized RECVX completion callback should not execute guest code"); - t.Equals(readGuestStruct(env.rdram.data(), kBusyFlagAddr), kBusyBeforeCompletion, - "completion-only callback should preserve the RECVX busy flag"); - setRegU32(env.ctx, 4, static_cast(semaId)); - PollSema(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), semaId, "completion-only callback should signal its semaphore"); - - writeGuestU32(env.rdram.data(), kBusyFlagAddr, 0x22222222u); - callSoundDriver(kClearBusyCallback); - - t.Equals(g_recvxSoundCallbackHits.load(), 0u, - "recognized RECVX clear-busy callback should not execute guest code"); - t.Equals(readGuestStruct(env.rdram.data(), kBusyFlagAddr), 0u, - "designated RECVX callback should clear the guest busy flag"); - setRegU32(env.ctx, 4, static_cast(semaId)); - PollSema(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), semaId, "clear-busy callback should signal its semaphore"); - - setRegU32(env.ctx, 4, kClientAddr); - SifCheckStatRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), 0, "RECVX snddrv client should no longer be RPC-busy"); - }); - tc.Run("hybrid bind before register waits then remaps", [](TestCase &t) { TestEnv env; @@ -983,172 +550,6 @@ void register_ps2_sif_rpc_tests() t.Equals(getRegU32Result(env.ctx, 2), 0u, "removing the same queue twice should return 0"); }); - tc.Run("snddrv state RPC returns stable buffers and signals sema", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x00028000u; - constexpr uint32_t kSemaParamAddr = 0x00028100u; - constexpr uint32_t kRecvAddr = 0x00028200u; - constexpr uint32_t kSid = IOP_SID_SNDDRV_STATE; - - SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - const uint32_t semaParam[6] = { - 0u, // count (unused by runtime decode) - 1u, // max_count - 0u, // init_count - 0u, // wait_threads - 0u, // attr - 0u // option - }; - std::memcpy(env.rdram.data() + kSemaParamAddr, semaParam, sizeof(semaParam)); - - setRegU32(env.ctx, 4, kSemaParamAddr); - CreateSema(env.rdram.data(), &env.ctx, &env.runtime); - const int32_t semaId = getRegS32(env.ctx, 2); - t.IsTrue(semaId > 0, "CreateSema should return a positive semaphore id"); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kSid); - setRegU32(env.ctx, 6, 0u); - SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for snddrv state sid"); - - setRegU32(env.ctx, 4, static_cast(semaId)); - PollSema(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_SEMA_ZERO, "semaphore should start at zero before nowait rpc"); - - std::memset(env.rdram.data() + kRecvAddr, 0, 16u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_STATUS_ADDR); - setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 16u); - setRegU32(env.ctx, 11, 0u); - setRegU32(env.ctx, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc(sound status) should succeed"); - const uint32_t statusAddr = readGuestStruct(env.rdram.data(), kRecvAddr); - t.IsTrue(statusAddr != 0u, "rpc 0x12 should return a nonzero sound-status pointer"); - - setRegU32(env.ctx, 4, static_cast(semaId)); - PollSema(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), semaId, "nowait rpc should signal completion sema"); - - std::memset(env.rdram.data() + kRecvAddr, 0, 16u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_ADDR_TABLE); - setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 16u); - setRegU32(env.ctx, 11, 0u); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc(sound addr table) should succeed"); - const uint32_t addrTableAddr = readGuestStruct(env.rdram.data(), kRecvAddr); - t.IsTrue(addrTableAddr != 0u, "rpc 0x13 should return a nonzero address-table pointer"); - t.IsTrue(addrTableAddr != statusAddr, "sound-status and address-table pointers should be distinct"); - - setRegU32(env.ctx, 4, static_cast(semaId)); - PollSema(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), semaId, "each nowait rpc should signal completion sema"); - - std::memset(env.rdram.data() + kRecvAddr, 0, 16u); - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_STATUS_ADDR); - setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 16u); - setRegU32(env.ctx, 11, 0u); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr), statusAddr, - "sound-status pointer should remain stable across repeated rpc 0x12 calls"); - }); - - tc.Run("snddrv state RPC returns low guest sound-driver addresses", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x00028300u; - constexpr uint32_t kSemaParamAddr = 0x00028400u; - constexpr uint32_t kRecvAddr = 0x00028500u; - constexpr uint32_t kSid = IOP_SID_SNDDRV_STATE; - - SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - const uint32_t semaParam[6] = {0u, 1u, 0u, 0u, 0u, 0u}; - std::memcpy(env.rdram.data() + kSemaParamAddr, semaParam, sizeof(semaParam)); - - setRegU32(env.ctx, 4, kSemaParamAddr); - CreateSema(env.rdram.data(), &env.ctx, &env.runtime); - const int32_t semaId = getRegS32(env.ctx, 2); - t.IsTrue(semaId > 0, "CreateSema should return a positive semaphore id"); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kSid); - setRegU32(env.ctx, 6, 0u); - SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for snddrv state sid"); - - SifRpcClientData client = readGuestStruct(env.rdram.data(), kClientAddr); - client.hdr.sema_id = semaId; - writeGuestStruct(env.rdram.data(), kClientAddr, client); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_STATUS_ADDR); - setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 16u); - setRegU32(env.ctx, 11, 0u); - setRegU32(env.ctx, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast(semaId)); - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t statusAddr = readGuestStruct(env.rdram.data(), kRecvAddr); - t.IsTrue(statusAddr > 0u && statusAddr < 0x00200000u, - "rpc 0x12 should return a low guest address like an IOP pointer"); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, IOP_RPC_SNDDRV_GET_ADDR_TABLE); - setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT); - setRegU32(env.ctx, 7, 0u); - setRegU32(env.ctx, 8, 0u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, 16u); - setRegU32(env.ctx, 11, 0u); - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - const uint32_t addrTableAddr = readGuestStruct(env.rdram.data(), kRecvAddr); - t.IsTrue(addrTableAddr > 0u && addrTableAddr < 0x00200000u, - "rpc 0x13 should return a low guest address like an IOP pointer"); - - std::array addressTable{}; - t.IsTrue(env.runtime.readIopMemory(addrTableAddr, - addressTable.data(), - sizeof(addressTable)), - "the sound-driver address table should live in physical IOP RAM"); - const uint32_t hdBaseAddr = addressTable[0]; - const uint32_t sqBaseAddr = addressTable[1]; - const uint32_t dataBaseAddr = addressTable[2]; - t.IsTrue(hdBaseAddr > 0u && hdBaseAddr < 0x00200000u, - "sound-driver hd base should stay in low guest address space"); - t.IsTrue(sqBaseAddr > hdBaseAddr && sqBaseAddr < 0x00200000u, - "sound-driver sq base should be a later low guest address"); - t.IsTrue(dataBaseAddr > sqBaseAddr && dataBaseAddr < 0x00200000u, - "sound-driver data base should be a later low guest address"); - }); - tc.Run("SifCallRpc falls back to stack ABI when register pack is implausible", [](TestCase &t) { TestEnv env; @@ -1225,152 +626,5 @@ void register_ps2_sif_rpc_tests() "recv payload should match stack-selected transfer size"); }); - tc.Run("DTX URPC uses the guest dispatcher only when its function-table slot is registered", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x0002C000u; - constexpr uint32_t kDtxSid = 0x7D000000u; - constexpr uint32_t kSendAddr = 0x0002C100u; - constexpr uint32_t kRecvAddr = 0x0002C200u; - constexpr uint32_t kUrpcCommand = 7u; - constexpr uint32_t kRpcNum = 0x400u | kUrpcCommand; - constexpr uint32_t kFnTableSlot = 0x0033FED0u + (kUrpcCommand * sizeof(uint32_t)); - constexpr uint32_t kObjTableSlot = 0x0033FFD0u + (kUrpcCommand * sizeof(uint32_t)); - constexpr uint32_t kWrongFnTableSlot = 0x0034FED0u + (kUrpcCommand * sizeof(uint32_t)); - constexpr uint32_t kWrongObjTableSlot = 0x0034FFD0u + (kUrpcCommand * sizeof(uint32_t)); - constexpr uint32_t kDispatcherAddr = 0x002FABC0u; - constexpr uint32_t kRegisteredHandlerAddr = 0x002FADE0u; - constexpr uint32_t kRegisteredObjectAddr = 0x01F18100u; - constexpr uint32_t kFallbackValue = 0x13579BDFu; - - g_dtxDispatcherHits = 0u; - g_dtxDispatcherRpcNum = 0u; - g_dtxDispatcherSendBuf = 0u; - g_dtxDispatcherSendSize = 0u; - env.runtime.registerFunction(kDispatcherAddr, recvxDtxDispatcher); - - SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kDtxSid); - setRegU32(env.ctx, 6, 0u); - SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for DTX dispatcher test"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0u, kFallbackValue); - writeGuestU32(env.rdram.data(), kSendAddr + 4u, 0x2468ACE0u); - - auto callUrpc = [&]() { - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kRpcNum); - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - setRegU32(env.ctx, 8, 8u); - setRegU32(env.ctx, 9, kRecvAddr); - setRegU32(env.ctx, 10, sizeof(uint32_t)); - setRegU32(env.ctx, 11, 0u); - setRegU32(env.ctx, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, 0u); - - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "DTX URPC should complete"); - }; - - writeGuestU32(env.rdram.data(), kFnTableSlot, 0u); - writeGuestU32(env.rdram.data(), kObjTableSlot, kRegisteredObjectAddr); - writeGuestU32(env.rdram.data(), kWrongFnTableSlot, kRegisteredHandlerAddr); - writeGuestU32(env.rdram.data(), kWrongObjTableSlot, kRegisteredObjectAddr); - writeGuestU32(env.rdram.data(), kRecvAddr, 0u); - callUrpc(); - - t.Equals(g_dtxDispatcherHits.load(), 0u, - "empty DTX function-table slot should use fallback emulation"); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr), kFallbackValue, - "fallback DTX emulation should return the first send word for an unknown command"); - - writeGuestU32(env.rdram.data(), kFnTableSlot, kRegisteredHandlerAddr); - writeGuestU32(env.rdram.data(), kRecvAddr, 0u); - env.runtime.registerFunction(K_DTX_SCHEDULER_CALL, schedulerDtxRpcCall); - env.runtime.registerFunction(K_DTX_SCHEDULER_RESUME, schedulerDtxRpcResume); - g_schedulerRpcClient = kClientAddr; - g_schedulerRpcNumber = kRpcNum; - g_schedulerRpcSend = kSendAddr; - g_schedulerRpcReceive = kRecvAddr; - g_schedulerRpcResult = static_cast(-1); - R5900Context mainContext{}; - mainContext.pc = K_DTX_SCHEDULER_CALL; - setRegU32(mainContext, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, 0u); - env.runtime.eeScheduler().reset(env.rdram.data(), mainContext); - env.runtime.eeScheduler().run(); - - t.Equals(g_schedulerRpcResult, static_cast(KE_OK), - "DTX URPC should resume its base context with KE_OK"); - - t.Equals(g_dtxDispatcherHits.load(), 1u, - "registered DTX function-table slot should enter the guest dispatcher"); - t.Equals(g_dtxDispatcherRpcNum.load(), kRpcNum, - "DTX dispatcher should receive the full URPC number"); - t.Equals(g_dtxDispatcherSendBuf.load(), kSendAddr, - "DTX dispatcher should receive the send-buffer address"); - t.Equals(g_dtxDispatcherSendSize.load(), 8u, - "DTX dispatcher should receive the send-buffer size"); - t.Equals(readGuestStruct(env.rdram.data(), kRecvAddr), K_DTX_DISPATCH_RESULT_MARKER, - "SifCallRpc should copy the dispatcher result into the receive buffer"); - }); - - tc.Run("SifCallRpc prefers stack ABI for DTX URPC when both packs look plausible", [](TestCase &t) - { - TestEnv env; - configureProfile(env, "slus_201.84"); - - constexpr uint32_t kClientAddr = 0x0002B000u; - constexpr uint32_t kDtxSid = 0x7D000000u; - constexpr uint32_t kSendAddr = 0x0002B100u; - constexpr uint32_t kRecvStackAddr = 0x0002B200u; - constexpr uint32_t kRecvRegAddr = 0x0002B300u; - - SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, kDtxSid); - setRegU32(env.ctx, 6, 0u); - SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc should succeed for DTX sid"); - - writeGuestU32(env.rdram.data(), kSendAddr + 0x00u, 1u); // mode - writeGuestU32(env.rdram.data(), kSendAddr + 0x04u, 0x1E21440u); // wk addr - writeGuestU32(env.rdram.data(), kSendAddr + 0x08u, 0x100u); // wk size - writeGuestU32(env.rdram.data(), kRecvStackAddr, 0u); - writeGuestU32(env.rdram.data(), kRecvRegAddr, 0u); - - setRegU32(env.ctx, 29, K_STACK_ADDR); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x10u, 12u); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x14u, kRecvStackAddr); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x18u, 4u); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x1Cu, 0u); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x20u, 0u); - writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, 0u); - - setRegU32(env.ctx, 4, kClientAddr); - setRegU32(env.ctx, 5, 0x422u); // DTX URPC command 34 (SJUNI create) - setRegU32(env.ctx, 6, 0u); - setRegU32(env.ctx, 7, kSendAddr); - // Plausible but intentionally wrong register-side packed args. - setRegU32(env.ctx, 8, 4u); - setRegU32(env.ctx, 9, kRecvRegAddr); - setRegU32(env.ctx, 10, 12u); - setRegU32(env.ctx, 11, 0u); - - SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime); - t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc should succeed for DTX URPC"); - - const uint32_t stackHandle = readGuestStruct(env.rdram.data(), kRecvStackAddr); - const uint32_t regHandle = readGuestStruct(env.rdram.data(), kRecvRegAddr); - t.IsTrue(stackHandle != 0u, "DTX handle should be written to stack-selected recv buffer"); - t.Equals(regHandle, 0u, "register recv buffer should remain untouched when stack ABI is preferred"); - }); }); }