From de4fc1e1a805d104bb061320977ab8dedf3b95bd Mon Sep 17 00:00:00 2001 From: j92580498-max Date: Sat, 11 Jul 2026 23:42:26 +0300 Subject: [PATCH] Add sceKernelNanosleep to libKernel (#72) Implements the sceKernelNanosleep export (NID QvsZxomvUHs) for both Gen4 and Gen5 targets. Reads the requested timespec from guest memory, validates the pointer and tv_nsec range, sleeps for the requested duration, and zeroes the optional remaining-time struct on completion. Also fixes: reading rqtp as a guest pointer to a timespec (tv_sec/tv_nsec int64 pair) instead of raw register values, and keeps the optimized sceKernelUsleep short-sleep path untouched. Co-authored-by: par274 --- .../Kernel/KernelRuntimeCompatExports.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs index 6e07f41..34b8bc9 100644 --- a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs @@ -24,6 +24,7 @@ public static class KernelRuntimeCompatExports internal const int ClockRealtimeFast = 10; internal const int ClockMonotonicFast = 12; private const int Efault = 14; + private const int Einval = 22; private const ulong TlsErrnoOffset = 0x40; private const ulong TlsStackChkGuardBaseOffset = 0x800; private const ulong StackChkGuardFieldOffset = 0x10; @@ -74,6 +75,77 @@ public static class KernelRuntimeCompatExports [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong RdtscDelegate(); + [SysAbiExport( + Nid = "QvsZxomvUHs", + ExportName = "sceKernelNanosleep", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int KernelNanosleep(CpuContext ctx) + { + var requestAddress = ctx[CpuRegister.Rdi]; + var remainAddress = ctx[CpuRegister.Rsi]; + + if (requestAddress == 0) + { + ctx[CpuRegister.Rax] = Einval; + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + Span timespecBuffer = stackalloc byte[16]; + if (!ctx.Memory.TryRead(requestAddress, timespecBuffer)) + { + ctx[CpuRegister.Rax] = Efault; + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var tvSec = BinaryPrimitives.ReadInt64LittleEndian(timespecBuffer); + var tvNsec = BinaryPrimitives.ReadInt64LittleEndian(timespecBuffer[sizeof(long)..]); + + if (tvSec < 0 || tvNsec < 0 || tvNsec >= 1_000_000_000L) + { + ctx[CpuRegister.Rax] = Einval; + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (tvSec == 0 && tvNsec == 0) + { + WriteRemainingTime(ctx, remainAddress, 0, 0); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + GuestThreadExecution.Scheduler?.Pump(ctx, "sceKernelNanosleep"); + + // TimeSpan resolution is 100 ns ticks, so sub-100 ns requests round up to + // a single tick rather than collapsing to a zero-length (no-op) sleep. + var totalTicks = tvSec * TimeSpan.TicksPerSecond + Math.Max(tvNsec / 100L, 1L); + try + { + Thread.Sleep(TimeSpan.FromTicks(totalTicks)); + } + catch (ArgumentOutOfRangeException) + { + Thread.Sleep(TimeSpan.FromMilliseconds(int.MaxValue)); + } + + WriteRemainingTime(ctx, remainAddress, 0, 0); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static void WriteRemainingTime(CpuContext ctx, ulong remainAddress, long seconds, long nanoseconds) + { + if (remainAddress == 0) + { + return; + } + + Span remainBuffer = stackalloc byte[16]; + BinaryPrimitives.WriteInt64LittleEndian(remainBuffer, seconds); + BinaryPrimitives.WriteInt64LittleEndian(remainBuffer[sizeof(long)..], nanoseconds); + ctx.Memory.TryWrite(remainAddress, remainBuffer); + } + [SysAbiExport( Nid = "1jfXLRVzisc", ExportName = "sceKernelUsleep",