Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ps2xRuntime/include/ps2_call_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
X(GsPutIMR) \
X(iGsPutIMR) \
X(SetVSyncFlag) \
X(SetSyscall) \
X(GsSetVideoMode) \
\
X(GetOsdConfigParam) \
Expand Down
16 changes: 15 additions & 1 deletion ps2xRuntime/src/lib/ps2_syscalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -272,7 +277,7 @@ namespace ps2_syscalls
SetVSyncFlag(rdram, ctx, runtime);
return true;
case 0x74:
RegisterExitHandler(rdram, ctx, runtime);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't remove exit. Move it to correct syscall id

SetSyscall(rdram, ctx, runtime);
return true;
case 0x76:
case static_cast<uint32_t>(-0x76):
Expand Down Expand Up @@ -388,5 +393,14 @@ namespace ps2_syscalls
g_alarms.clear();
}
g_alarm_cv.notify_all();

{
std::lock_guard<std::mutex> lock(g_exit_handler_mutex);
g_exit_handlers.clear();
}
{
std::lock_guard<std::mutex> lock(g_syscall_override_mutex);
g_syscall_overrides.clear();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ struct ExitHandlerEntry
static std::mutex g_exit_handler_mutex;
static std::unordered_map<int, std::vector<ExitHandlerEntry>> g_exit_handlers;

static std::mutex g_syscall_override_mutex;
static std::unordered_map<uint32_t, uint32_t> g_syscall_overrides;

static std::mutex g_bootmode_mutex;
static bool g_bootmode_initialized = false;
static uint32_t g_bootmode_pool_offset = 0;
Expand Down
62 changes: 61 additions & 1 deletion ps2xRuntime/src/lib/syscalls/ps2_syscalls_system.inl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what is this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It checks whether a given numeric syscall has a runtime override registered. If found and is callable, the function forwards execution there and returns true. If no valid override exists, it returns false

{
uint32_t handler = 0u;
{
std::lock_guard<std::mutex> 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<std::mutex> 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)
Expand Down Expand Up @@ -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);
Expand Down
71 changes: 71 additions & 0 deletions ps2xTest/src/ps2_runtime_kernel_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -386,5 +396,66 @@ void register_ps2_runtime_kernel_tests()
const uint32_t setupSp = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(getRegS32(env.ctx, 2)),
baselineImr,
"fallback GsGetIMR result should match baseline built-in behavior");
});
});
}