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..e557b809 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,66 @@ 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 + + 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); + + 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)), + baselineImr, + "GsGetIMR value after clear should match baseline built-in behavior"); + 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 + + 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), + "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)), + baselineImr, + "fallback GsGetIMR result should match baseline built-in behavior"); + }); }); }