From 99ad85a8307aab3e09c0b4062c36307734c13500 Mon Sep 17 00:00:00 2001 From: Whoneon Date: Wed, 4 Mar 2026 11:04:14 +0100 Subject: [PATCH 1/2] kernel: implement syscall 0x74 SetSyscall override dispatch --- ps2xRuntime/include/ps2_call_list.h | 1 + ps2xRuntime/src/lib/ps2_syscalls.cpp | 16 ++++- .../helpers/ps2_syscalls_helpers_state.inl | 3 + .../src/lib/syscalls/ps2_syscalls_system.inl | 62 +++++++++++++++++- ps2xTest/src/ps2_runtime_kernel_tests.cpp | 63 +++++++++++++++++++ 5 files changed, 143 insertions(+), 2 deletions(-) diff --git a/ps2xRuntime/include/ps2_call_list.h b/ps2xRuntime/include/ps2_call_list.h index e8a5165f..818b5ea2 100644 --- a/ps2xRuntime/include/ps2_call_list.h +++ b/ps2xRuntime/include/ps2_call_list.h @@ -107,6 +107,7 @@ X(GsPutIMR) \ X(iGsPutIMR) \ X(SetVSyncFlag) \ + X(SetSyscall) \ X(GsSetVideoMode) \ \ X(GetOsdConfigParam) \ diff --git a/ps2xRuntime/src/lib/ps2_syscalls.cpp b/ps2xRuntime/src/lib/ps2_syscalls.cpp index 67647c59..ae3ed961 100644 --- a/ps2xRuntime/src/lib/ps2_syscalls.cpp +++ b/ps2xRuntime/src/lib/ps2_syscalls.cpp @@ -42,6 +42,11 @@ namespace ps2_syscalls bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { + if (dispatchSyscallOverride(syscallNumber, rdram, ctx, runtime)) + { + return true; + } + switch (syscallNumber) { case 0x01: @@ -272,7 +277,7 @@ namespace ps2_syscalls SetVSyncFlag(rdram, ctx, runtime); return true; case 0x74: - RegisterExitHandler(rdram, ctx, runtime); + SetSyscall(rdram, ctx, runtime); return true; case 0x76: case static_cast(-0x76): @@ -388,5 +393,14 @@ namespace ps2_syscalls g_alarms.clear(); } g_alarm_cv.notify_all(); + + { + std::lock_guard lock(g_exit_handler_mutex); + g_exit_handlers.clear(); + } + { + std::lock_guard lock(g_syscall_override_mutex); + g_syscall_overrides.clear(); + } } } diff --git a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl index 12d4f781..bad3f155 100644 --- a/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl +++ b/ps2xRuntime/src/lib/syscalls/helpers/ps2_syscalls_helpers_state.inl @@ -411,6 +411,9 @@ struct ExitHandlerEntry static std::mutex g_exit_handler_mutex; static std::unordered_map> g_exit_handlers; +static std::mutex g_syscall_override_mutex; +static std::unordered_map g_syscall_overrides; + static std::mutex g_bootmode_mutex; static bool g_bootmode_initialized = false; static uint32_t g_bootmode_pool_offset = 0; diff --git a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl index bba0b959..0e22142e 100644 --- a/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl +++ b/ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl @@ -264,6 +264,66 @@ void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encod setReturnS32(ctx, 0); } +static bool dispatchSyscallOverride(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + uint32_t handler = 0u; + { + std::lock_guard lock(g_syscall_override_mutex); + auto it = g_syscall_overrides.find(syscallNumber); + if (it == g_syscall_overrides.end()) + { + return false; + } + handler = it->second; + } + + if (!runtime || !ctx || handler == 0u) + { + return false; + } + + uint32_t retV0 = 0u; + const bool invoked = rpcInvokeFunction(rdram, + ctx, + runtime, + handler, + getRegU32(ctx, 4), + getRegU32(ctx, 5), + getRegU32(ctx, 6), + getRegU32(ctx, 7), + &retV0); + if (!invoked) + { + return false; + } + + setReturnU32(ctx, retV0); + return true; +} + +// 0x74 SetSyscall: replaces the handler for a numeric syscall index. +void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) +{ + (void)rdram; + (void)runtime; + const uint32_t syscallIndex = getRegU32(ctx, 4); + const uint32_t handler = getRegU32(ctx, 5); + + { + std::lock_guard lock(g_syscall_override_mutex); + if (handler == 0u) + { + g_syscall_overrides.erase(syscallIndex); + } + else + { + g_syscall_overrides[syscallIndex] = handler; + } + } + + setReturnS32(ctx, 0); +} + // 0x3C SetupThread // args: $a0 = gp, $a1 = stack, $a2 = stack_size, $a3 = args, $t0 = root_func void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) @@ -395,7 +455,7 @@ void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) setReturnU32(ctx, info->tlsBase); } -// 0x74 RegisterExitHandler (stub): return 0 +// Legacy helper used by some code paths; not currently bound to a numeric syscall. void RegisterExitHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) { uint32_t func = getRegU32(ctx, 4); diff --git a/ps2xTest/src/ps2_runtime_kernel_tests.cpp b/ps2xTest/src/ps2_runtime_kernel_tests.cpp index e05e6e1c..364cf979 100644 --- a/ps2xTest/src/ps2_runtime_kernel_tests.cpp +++ b/ps2xTest/src/ps2_runtime_kernel_tests.cpp @@ -93,6 +93,16 @@ namespace std::memset(&ctx, 0, sizeof(ctx)); } }; + + static uint32_t g_setSyscallHookCalls = 0u; + void testSetSyscallHook(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime) + { + (void)rdram; + (void)runtime; + ++g_setSyscallHookCalls; + setReturnU32(ctx, 0xCAFEBABEu); + ctx->pc = getRegU32(ctx, 31); + } } void register_ps2_runtime_kernel_tests() @@ -386,5 +396,58 @@ void register_ps2_runtime_kernel_tests() const uint32_t setupSp = static_cast(getRegS32(env.ctx, 2)); t.Equals(setupSp & 0xFu, 0u, "SetupThread should always return a 16-byte aligned stack pointer"); }); + + tc.Run("SetSyscall overrides and clears numeric syscall handlers", [](TestCase &t) + { + TestEnv env; + constexpr uint32_t kHookAddr = 0x00250000u; + constexpr uint32_t kTargetSyscall = 0x70u; // GsGetIMR + + g_setSyscallHookCalls = 0u; + env.runtime.registerFunction(kHookAddr, testSetSyscallHook); + + setRegU32(env.ctx, 4, kTargetSyscall); + setRegU32(env.ctx, 5, kHookAddr); + t.IsTrue(callSyscall(0x74u, env.rdram.data(), &env.ctx, &env.runtime), + "SetSyscall should dispatch"); + t.Equals(getRegS32(env.ctx, 2), 0, "SetSyscall should report success"); + + setRegU32(env.ctx, 4, 0x12345678u); + t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), + "target syscall should dispatch through override"); + t.Equals(static_cast(getRegS32(env.ctx, 2)), + 0xCAFEBABEu, + "override handler return value should be visible in v0"); + t.Equals(g_setSyscallHookCalls, 1u, "override handler should execute once"); + + setRegU32(env.ctx, 4, kTargetSyscall); + setRegU32(env.ctx, 5, 0u); + t.IsTrue(callSyscall(0x74u, env.rdram.data(), &env.ctx, &env.runtime), + "SetSyscall clear should dispatch"); + + t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), + "target syscall should fall back to built-in handler after clear"); + t.Equals(static_cast(getRegS32(env.ctx, 2)), + 0u, + "GsGetIMR default path should return zero in a fresh runtime"); + t.Equals(g_setSyscallHookCalls, 1u, "override handler should not run after clear"); + }); + + tc.Run("SetSyscall falls back to built-in handler when override target is missing", [](TestCase &t) + { + TestEnv env; + constexpr uint32_t kTargetSyscall = 0x70u; // GsGetIMR + + setRegU32(env.ctx, 4, kTargetSyscall); + setRegU32(env.ctx, 5, 0x00ABCDEFu); // not registered in runtime + t.IsTrue(callSyscall(0x74u, env.rdram.data(), &env.ctx, &env.runtime), + "SetSyscall should dispatch even for unknown handler address"); + + t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), + "dispatch should fall back to built-in syscall when override cannot be invoked"); + t.Equals(static_cast(getRegS32(env.ctx, 2)), + 0u, + "fallback GsGetIMR path should return zero"); + }); }); } From cd30ba3211d3aaaf80ad296f09c02797a80dec8a Mon Sep 17 00:00:00 2001 From: Whoneon Date: Wed, 4 Mar 2026 11:19:20 +0100 Subject: [PATCH 2/2] tests(kernel): fixed SetSyscall fallback checks on Windows --- ps2xTest/src/ps2_runtime_kernel_tests.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ps2xTest/src/ps2_runtime_kernel_tests.cpp b/ps2xTest/src/ps2_runtime_kernel_tests.cpp index 364cf979..e557b809 100644 --- a/ps2xTest/src/ps2_runtime_kernel_tests.cpp +++ b/ps2xTest/src/ps2_runtime_kernel_tests.cpp @@ -403,6 +403,10 @@ void register_ps2_runtime_kernel_tests() constexpr uint32_t kHookAddr = 0x00250000u; constexpr uint32_t kTargetSyscall = 0x70u; // GsGetIMR + t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), + "baseline syscall dispatch should succeed"); + const uint32_t baselineImr = static_cast(getRegS32(env.ctx, 2)); + g_setSyscallHookCalls = 0u; env.runtime.registerFunction(kHookAddr, testSetSyscallHook); @@ -428,8 +432,8 @@ void register_ps2_runtime_kernel_tests() t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), "target syscall should fall back to built-in handler after clear"); t.Equals(static_cast(getRegS32(env.ctx, 2)), - 0u, - "GsGetIMR default path should return zero in a fresh runtime"); + baselineImr, + "GsGetIMR value after clear should match baseline built-in behavior"); t.Equals(g_setSyscallHookCalls, 1u, "override handler should not run after clear"); }); @@ -438,6 +442,10 @@ void register_ps2_runtime_kernel_tests() TestEnv env; constexpr uint32_t kTargetSyscall = 0x70u; // GsGetIMR + t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), + "baseline syscall dispatch should succeed"); + const uint32_t baselineImr = static_cast(getRegS32(env.ctx, 2)); + setRegU32(env.ctx, 4, kTargetSyscall); setRegU32(env.ctx, 5, 0x00ABCDEFu); // not registered in runtime t.IsTrue(callSyscall(0x74u, env.rdram.data(), &env.ctx, &env.runtime), @@ -446,8 +454,8 @@ void register_ps2_runtime_kernel_tests() t.IsTrue(callSyscall(kTargetSyscall, env.rdram.data(), &env.ctx, &env.runtime), "dispatch should fall back to built-in syscall when override cannot be invoked"); t.Equals(static_cast(getRegS32(env.ctx, 2)), - 0u, - "fallback GsGetIMR path should return zero"); + baselineImr, + "fallback GsGetIMR result should match baseline built-in behavior"); }); }); }