sharpemu/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs

2150 lines
72 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Fiber;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
public static class KernelRuntimeCompatExports
{
private const ulong TlsErrnoOffset = 0x40;
private const ulong TlsStackChkGuardBaseOffset = 0x800;
private const ulong StackChkGuardFieldOffset = 0x10;
private const ulong TlsProcParamOffset = 0x100;
private const ulong TlsMallocReplaceOffset = 0x200;
private const ulong TlsNewReplaceOffset = 0x300;
private const int MallocReplaceSize = 0x70;
private const int NewReplaceSize = 0x68;
private const int OrbisTimesecSize = sizeof(long) + sizeof(uint) + sizeof(uint);
// PS4/PS5 libkernel module-info ABI. The extended form is consumed by
// sceKernelGetModuleInfoFromAddr and ends at byte 0x1A8; libc commonly
// places its stack canary immediately after that caller-owned buffer.
private const int ModuleInfoNameMaxBytes = 256;
private const int ModuleInfoSize = 0x160;
private const int ModuleInfoExSize = 0x1A8;
private const ulong ModuleInfoNameOffset = 0x08;
private const ulong ModuleInfoExHandleOffset = 0x108;
private const ulong ModuleInfoExInitProcOffset = 0x128;
private const ulong ModuleInfoExSegmentsOffset = 0x160;
private const ulong ModuleInfoExSegmentCountOffset = 0x1A0;
private const int ModuleInfoSegmentSize = 16;
private const ulong GuestExecutableAddressFloor = 0x0000000800000000UL;
private const ulong GuestExecutableAddressCeiling = 0x0000000880000000UL;
private const ulong HostUnwindBoundarySize = 0x10_0000UL;
private const ulong DefaultKernelTscFrequency = 10_000_000UL;
private const ulong PrtAreaStartAddress = 0x0000001000000000UL;
private const ulong PrtAreaSize = 0x000000EC00000000UL;
private const int AioInitParamSize = 0x3C;
private const uint MemCommit = 0x1000;
private const uint MemReserve = 0x2000;
private const uint PageExecuteReadWrite = 0x40;
private static readonly object _stateGate = new();
private static readonly long _processStartCounter = Stopwatch.GetTimestamp();
private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader();
private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency();
private static readonly ulong _stackChkGuardValue = 0xC0DEC0DECAFEBA00UL;
private static readonly nint _stackChkGuardObjectAddress =
HleDataSymbols.TryGetAddress("f7uOxY9mM1U", out var stackChkGuardAddress)
? unchecked((nint)stackChkGuardAddress)
: AllocateStackChkGuardObject();
private static ulong _applicationHeapApiAddress;
private static ulong _processProcParamAddress;
private static uint _gpoStateBits;
private static readonly HashSet<int> _loadedSysmodules = new();
private static readonly object _prtApertureGate = new();
private static readonly (ulong Base, ulong Size)[] _prtApertures = new (ulong Base, ulong Size)[3];
private static int _stackChkFailCount;
private static long _usleepTraceCount;
private static readonly bool _traceUsleep =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal);
private static readonly bool _traceGuestThreads =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUEST_THREADS"), "1", StringComparison.Ordinal);
[ThreadStatic]
private static int _shortUsleepCount;
private static readonly bool _stopwatchTicksAreNanoseconds =
Stopwatch.Frequency == 1_000_000_000L;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ulong RdtscDelegate();
[SysAbiExport(
Nid = "1jfXLRVzisc",
ExportName = "sceKernelUsleep",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelUsleep(CpuContext ctx)
{
var micros = ctx[CpuRegister.Rdi];
TraceUsleepSpin(ctx, micros);
if (micros == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
GuestThreadExecution.Scheduler?.Pump(ctx, "sceKernelUsleep");
if (micros < 1000)
{
// Guest worker pools use usleep(1) as a polling backoff. Do not turn
// PS microsecond waits into Windows millisecond sleeps on hot paths.
if ((++_shortUsleepCount & 255) == 0)
{
Thread.Sleep(0);
}
else
{
Thread.Yield();
}
}
else
{
_shortUsleepCount = 0;
// Precise sleep: rounding microseconds up to Thread.Sleep
// milliseconds (which itself overshoots by a scheduler quantum)
// hard-caps games that pace their frame loop with usleep.
HostTiming.SleepMicroseconds((long)Math.Min(micros, long.MaxValue));
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static void TraceUsleepSpin(CpuContext ctx, ulong micros)
{
if (!_traceUsleep)
{
return;
}
var count = Interlocked.Increment(ref _usleepTraceCount);
if (count > 32 && count % 10000 != 0)
{
return;
}
var rbx = ctx[CpuRegister.Rbx];
var r12 = ctx[CpuRegister.R12];
var r13 = ctx[CpuRegister.R13];
var lockAddress = rbx == 0 ? 0 : rbx + 0xF78;
var lockText = "unreadable";
if (lockAddress != 0 && ctx.TryReadUInt64(lockAddress, out var lockValue))
{
lockText = $"0x{lockValue:X16}";
}
var schedulerText = "unreadable";
if (r12 != 0 && ctx.TryReadUInt64(r12 + 8, out var schedulerAddress))
{
schedulerText = $"0x{schedulerAddress:X16}";
}
var waitValueText = "unreadable";
if (r13 != 0 && ctx.TryReadUInt64(r13, out var waitValue))
{
waitValueText = $"0x{waitValue:X16}";
}
var callerReturnText = "unreadable";
var rbp = ctx[CpuRegister.Rbp];
if (rbp != 0 && ctx.TryReadUInt64(rbp + 8, out var callerReturn))
{
callerReturnText = $"0x{callerReturn:X16}";
}
var returnRip = GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame)
? frame.ReturnRip
: 0UL;
var thread = GuestThreadExecution.CurrentGuestThreadHandle;
var fiber = FiberExports.GetCurrentFiberAddressForDiagnostics(ctx);
Console.Error.WriteLine(
$"[LOADER][TRACE] usleep#{count}: usec={micros} ret=0x{returnRip:X16} caller={callerReturnText} thread=0x{thread:X16} fiber=0x{fiber:X16} rbx=0x{rbx:X16} lock@+F78=0x{lockAddress:X16}:{lockText} r12=0x{r12:X16} scheduler@+8={schedulerText} r13=0x{r13:X16}:{waitValueText} r14=0x{ctx[CpuRegister.R14]:X16} r15=0x{ctx[CpuRegister.R15]:X16}");
if (count % 100000 == 0 &&
_traceGuestThreads &&
GuestThreadExecution.Scheduler is { } scheduler)
{
foreach (var snapshot in scheduler.SnapshotThreads())
{
Console.Error.WriteLine(
$"[LOADER][TRACE] guest_thread.snapshot handle=0x{snapshot.ThreadHandle:X16} name='{snapshot.Name}' " +
$"state={snapshot.State} imports={snapshot.ImportCount} nid={snapshot.LastImportNid ?? "none"} " +
$"ret=0x{snapshot.LastReturnRip:X16} block={snapshot.BlockReason ?? "none"}");
}
}
}
[SysAbiExport(
Nid = "QBi7HCK03hw",
ExportName = "sceKernelClockGettime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelClockGettime(CpuContext ctx)
{
var clockId = unchecked((int)ctx[CpuRegister.Rdi]);
var timeAddress = ctx[CpuRegister.Rsi];
if (timeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
GetClockTime(clockId, out var seconds, out var nanoseconds);
if (!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)nanoseconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static void GetClockTime(int clockId, out long seconds, out long nanoseconds)
{
if (clockId == 0)
{
var now = DateTimeOffset.UtcNow;
seconds = now.ToUnixTimeSeconds();
nanoseconds = (now.Ticks % TimeSpan.TicksPerSecond) * 100;
}
else
{
var processStartCounter = _processStartCounter;
var elapsedTicks = Stopwatch.GetTimestamp() - processStartCounter;
if (_stopwatchTicksAreNanoseconds)
{
// Constant divisors let the JIT strength-reduce the division;
// games call this thousands of times per second.
seconds = elapsedTicks / 1_000_000_000L;
nanoseconds = elapsedTicks - seconds * 1_000_000_000L;
}
else
{
seconds = elapsedTicks / Stopwatch.Frequency;
nanoseconds = (elapsedTicks % Stopwatch.Frequency) * 1_000_000_000L / Stopwatch.Frequency;
}
}
}
[SysAbiExport(
Nid = "ejekcaNQNq0",
ExportName = "sceKernelGettimeofday",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGettimeofday(CpuContext ctx)
{
var timeAddress = ctx[CpuRegister.Rdi];
if (timeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var now = DateTimeOffset.UtcNow;
var seconds = now.ToUnixTimeSeconds();
var microseconds = (now.Ticks % TimeSpan.TicksPerSecond) / 10;
if (!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)microseconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "n88vx3C5nW8",
ExportName = "gettimeofday",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixGettimeofday(CpuContext ctx)
{
var timeAddress = ctx[CpuRegister.Rdi];
var timezoneAddress = ctx[CpuRegister.Rsi];
var now = DateTimeOffset.UtcNow;
var seconds = now.ToUnixTimeSeconds();
var microseconds = (now.Ticks % TimeSpan.TicksPerSecond) / 10;
if (timeAddress != 0 &&
(!ctx.TryWriteUInt64(timeAddress, unchecked((ulong)seconds)) ||
!ctx.TryWriteUInt64(timeAddress + sizeof(long), unchecked((ulong)microseconds))))
{
return -1;
}
if (timezoneAddress != 0 &&
(!TryWriteInt32(ctx, timezoneAddress, 0) ||
!TryWriteInt32(ctx, timezoneAddress + sizeof(int), 0)))
{
return -1;
}
ctx[CpuRegister.Rax] = 0;
return 0;
}
[SysAbiExport(
Nid = "-2IRUCO--PM",
ExportName = "sceKernelReadTsc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelReadTsc(CpuContext ctx)
{
ctx[CpuRegister.Rax] = ReadTscCounter();
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static ulong TscFrequency => _kernelTscFrequency;
internal static ulong ReadTscCounter() =>
TryReadHostTsc(out var counter) ? counter : unchecked((ulong)Math.Max(0, Stopwatch.GetTimestamp()));
[SysAbiExport(
Nid = "1j3S3n-tTW4",
ExportName = "sceKernelGetTscFrequency",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetTscFrequency(CpuContext ctx)
{
ctx[CpuRegister.Rax] = _kernelTscFrequency;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "4J2sUJmuHZQ",
ExportName = "sceKernelGetProcessTime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetProcessTime(CpuContext ctx)
{
ctx[CpuRegister.Rax] = ReadProcessTimeMicroseconds();
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static ulong ReadProcessTimeMicroseconds()
{
var elapsedTicks = Stopwatch.GetTimestamp() - _processStartCounter;
var micros = elapsedTicks * 1_000_000L / Stopwatch.Frequency;
return unchecked((ulong)Math.Max(0, micros));
}
[SysAbiExport(
Nid = "fgxnMeTNUtY",
ExportName = "sceKernelGetProcessTimeCounter",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetProcessTimeCounter(CpuContext ctx)
{
ctx[CpuRegister.Rax] = ReadProcessTimeCounter();
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static ulong ReadProcessTimeCounter()
=> ReadProcessTimeCounterAt(Stopwatch.GetTimestamp());
internal static ulong ReadProcessTimeCounterAt(long timestamp)
{
var elapsedTicks = timestamp - _processStartCounter;
return unchecked((ulong)Math.Max(0, elapsedTicks));
}
[SysAbiExport(
Nid = "BNowx2l588E",
ExportName = "sceKernelGetProcessTimeCounterFrequency",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetProcessTimeCounterFrequency(CpuContext ctx)
{
ctx[CpuRegister.Rax] = unchecked((ulong)Stopwatch.Frequency);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6xVpy0Fdq+I",
ExportName = "_sigprocmask",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int Sigprocmask(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "959qrazPIrg",
ExportName = "sceKernelGetProcParam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetProcParam(CpuContext ctx)
{
ulong address;
lock (_stateGate)
{
address = _processProcParamAddress;
}
if (address == 0)
{
address = GetTlsScratchAddress(ctx, TlsProcParamOffset);
}
if (address != 0)
{
if (address == GetTlsScratchAddress(ctx, TlsProcParamOffset))
{
_ = ctx.Memory.TryWrite(address, new byte[0x80]);
}
}
TraceProcParam(ctx, address);
ctx[CpuRegister.Rax] = address;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
public static void ConfigureProcessProcParamAddress(ulong procParamAddress)
{
lock (_stateGate)
{
_processProcParamAddress = procParamAddress;
}
}
private static void TraceProcParam(CpuContext ctx, ulong address)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PROC_PARAM"), "1", StringComparison.Ordinal))
{
return;
}
if (address == 0)
{
Console.Error.WriteLine("[LOADER][TRACE] proc_param: address=0");
return;
}
const int dumpSize = 0x200;
var buffer = GC.AllocateUninitializedArray<byte>(dumpSize);
if (!ctx.Memory.TryRead(address, buffer))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param: address=0x{address:X16} unreadable");
return;
}
Console.Error.WriteLine($"[LOADER][TRACE] proc_param: address=0x{address:X16} size=0x{dumpSize:X}");
for (var offset = 0; offset < dumpSize; offset += 16)
{
var slice = buffer.AsSpan(offset, 16);
var hex = Convert.ToHexString(slice);
Console.Error.WriteLine($"[LOADER][TRACE] proc_param[{offset:X3}]: {hex}");
}
TraceProcParamPointers(ctx, address, buffer);
}
private static void TraceProcParamPointers(CpuContext ctx, ulong baseAddress, ReadOnlySpan<byte> buffer)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PROC_PARAM_PTRS"), "1", StringComparison.Ordinal))
{
return;
}
if (buffer.Length < 0x50)
{
return;
}
for (var offset = 0x20; offset <= 0x48; offset += 8)
{
var ptr = BinaryPrimitives.ReadUInt64LittleEndian(buffer.Slice(offset, 8));
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr@{offset:X2}: 0x{ptr:X16}");
if (ptr == 0)
{
continue;
}
TraceProcParamPointerTarget(ctx, ptr);
}
}
private static void TraceProcParamPointerTarget(CpuContext ctx, ulong address)
{
const int maxAsciiBytes = 256;
const int maxWideChars = 128;
if (TryReadUtf8CString(ctx, address, maxAsciiBytes, out var asciiValue))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target ascii@0x{address:X16}: \"{asciiValue}\"");
return;
}
if (TryReadUtf16CString(ctx, address, maxWideChars, out var wideValue))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target wide@0x{address:X16}: \"{wideValue}\"");
return;
}
var preview = GC.AllocateUninitializedArray<byte>(64);
if (ctx.Memory.TryRead(address, preview))
{
var hex = Convert.ToHexString(preview);
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target hex@0x{address:X16}: {hex}");
TraceProcParamEmbeddedPointers(ctx, address, preview);
}
else
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.target unreadable@0x{address:X16}");
}
}
private static bool TryReadUtf8CString(CpuContext ctx, ulong address, int maxBytes, out string value)
{
value = string.Empty;
var buffer = GC.AllocateUninitializedArray<byte>(maxBytes);
if (!ctx.Memory.TryRead(address, buffer))
{
return false;
}
var length = Array.IndexOf(buffer, (byte)0);
if (length < 0)
{
length = maxBytes;
}
if (length == 0)
{
return false;
}
var text = Encoding.UTF8.GetString(buffer, 0, length);
if (!IsMostlyPrintable(text))
{
return false;
}
value = text;
return true;
}
private static bool TryReadUtf16CString(CpuContext ctx, ulong address, int maxChars, out string value)
{
value = string.Empty;
var maxBytes = maxChars * 2;
var buffer = GC.AllocateUninitializedArray<byte>(maxBytes);
if (!ctx.Memory.TryRead(address, buffer))
{
return false;
}
var lengthBytes = -1;
for (var i = 0; i + 1 < buffer.Length; i += 2)
{
if (buffer[i] == 0 && buffer[i + 1] == 0)
{
lengthBytes = i;
break;
}
}
if (lengthBytes <= 0)
{
return false;
}
var text = Encoding.Unicode.GetString(buffer, 0, lengthBytes);
if (!IsMostlyPrintable(text))
{
return false;
}
value = text;
return true;
}
private static bool IsMostlyPrintable(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
var printable = 0;
for (var i = 0; i < text.Length; i++)
{
var ch = text[i];
if (ch == '\0')
{
continue;
}
if (!char.IsControl(ch) || ch == '\r' || ch == '\n' || ch == '\t')
{
printable++;
}
}
return printable >= Math.Max(4, text.Length * 3 / 4);
}
private static void TraceProcParamEmbeddedPointers(CpuContext ctx, ulong baseAddress, ReadOnlySpan<byte> data)
{
const int maxCandidates = 12;
var found = 0;
Span<byte> probe = stackalloc byte[2];
for (var offset = 0; offset + 8 <= data.Length; offset += 8)
{
var candidate = BinaryPrimitives.ReadUInt64LittleEndian(data.Slice(offset, 8));
if (candidate == 0)
{
continue;
}
if (!ctx.Memory.TryRead(candidate, probe))
{
continue;
}
if (TryReadUtf8CString(ctx, candidate, 256, out var ascii))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.embed@0x{baseAddress:X16}+0x{offset:X2} -> 0x{candidate:X16} ascii \"{ascii}\"");
if (++found >= maxCandidates)
{
return;
}
continue;
}
if (TryReadUtf16CString(ctx, candidate, 128, out var wide))
{
Console.Error.WriteLine($"[LOADER][TRACE] proc_param.ptr.embed@0x{baseAddress:X16}+0x{offset:X2} -> 0x{candidate:X16} wide \"{wide}\"");
if (++found >= maxCandidates)
{
return;
}
}
}
}
[SysAbiExport(
Nid = "9BcDykPmo1I",
ExportName = "__error",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int ErrorAddress(CpuContext ctx)
{
var address = GetTlsScratchAddress(ctx, TlsErrnoOffset);
ctx[CpuRegister.Rax] = address;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static bool TrySetErrno(CpuContext ctx, int value)
{
var address = GetTlsScratchAddress(ctx, TlsErrnoOffset);
return address != 0 && TryWriteInt32(ctx, address, value);
}
[SysAbiExport(
Nid = "bnZxYgAFeA0",
ExportName = "sceKernelGetSanitizerNewReplaceExternal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetSanitizerNewReplaceExternal(CpuContext ctx)
{
var address = GetTlsScratchAddress(ctx, TlsNewReplaceOffset);
if (address != 0)
{
if (!ctx.Memory.TryWrite(address, new byte[NewReplaceSize]) ||
!ctx.TryWriteUInt64(address, NewReplaceSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = address;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "py6L8jiVAN8",
ExportName = "sceKernelGetSanitizerMallocReplaceExternal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetSanitizerMallocReplaceExternal(CpuContext ctx)
{
var address = GetTlsScratchAddress(ctx, TlsMallocReplaceOffset);
if (address != 0)
{
if (!ctx.Memory.TryWrite(address, new byte[MallocReplaceSize]) ||
!ctx.TryWriteUInt64(address, MallocReplaceSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = address;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "jh+8XiK4LeE",
ExportName = "sceKernelIsAddressSanitizerEnabled",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelIsAddressSanitizerEnabled(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "ca7v6Cxulzs",
ExportName = "sceKernelSetGPO",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetGpo(CpuContext ctx)
{
_gpoStateBits = unchecked((uint)ctx[CpuRegister.Rdi]);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "4oXYe9Xmk0Q",
ExportName = "sceKernelGetGPI",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetGpi(CpuContext ctx)
{
var bitMask = _gpoStateBits;
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALLOC_IMPORTS"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] get_gpi: mask=0x{bitMask:X8}");
}
ctx[CpuRegister.Rax] = bitMask;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "7oxv3PPCumo",
ExportName = "sceKernelReserveVirtualRange",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelReserveVirtualRange(CpuContext ctx) =>
KernelMemoryCompatExports.ReserveBackingRange(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx], ctx[CpuRegister.Rcx]);
[SysAbiExport(
Nid = "BohYr-F7-is",
ExportName = "sceKernelSetPrtAperture",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetPrtAperture(CpuContext ctx)
{
var rawId = ctx[CpuRegister.Rdi];
var apertureBase = ctx[CpuRegister.Rsi];
var apertureSize = ctx[CpuRegister.Rdx];
var apertureId = unchecked((int)rawId);
if (apertureId < 0 || apertureId >= _prtApertures.Length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if ((apertureBase & 0xFFFUL) != 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (apertureBase < PrtAreaStartAddress)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (apertureSize > PrtAreaSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (apertureBase - PrtAreaStartAddress > PrtAreaSize - apertureSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_prtApertureGate)
{
_prtApertures[apertureId] = (apertureBase, apertureSize);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] set_prt_aperture: id={apertureId} base=0x{apertureBase:X16} size=0x{apertureSize:X16}");
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALLOC_IMPORTS"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] set_prt_aperture raw: rdi=0x{rawId:X16} rsi=0x{apertureBase:X16} rdx=0x{apertureSize:X16} rcx=0x{ctx[CpuRegister.Rcx]:X16} r8=0x{ctx[CpuRegister.R8]:X16} r9=0x{ctx[CpuRegister.R9]:X16}");
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static bool ContainsPrtRange(ulong address, ulong size)
{
if (size == 0 || address > ulong.MaxValue - size)
return false;
lock (_prtApertureGate)
{
return _prtApertures.Any(aperture => address >= aperture.Base &&
size <= aperture.Size && address - aperture.Base <= aperture.Size - size);
}
}
[SysAbiExport(
Nid = "f7KBOafysXo",
ExportName = "sceKernelGetModuleInfoFromAddr",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleInfoFromAddr(CpuContext ctx)
{
var queriedAddress = ctx[CpuRegister.Rdi];
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
var outInfoAddress = ctx[CpuRegister.Rdx];
if (outInfoAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (flags is < 0 or >= 3)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(outInfoAddress, out var callerSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (callerSize != ModuleInfoExSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!KernelModuleRegistry.TryGetModuleByAddress(queriedAddress, out var module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!TryWriteModuleInfoEx(ctx, outInfoAddress, module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "RpQJJVKTiFM",
ExportName = "sceKernelGetModuleInfoForUnwind",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleInfoForUnwind(CpuContext ctx)
{
var queriedAddress = ctx[CpuRegister.Rdi];
var flags = unchecked((int)ctx[CpuRegister.Rsi]);
var outInfoAddress = ctx[CpuRegister.Rdx];
if (outInfoAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (flags is < 0 or >= 3)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadUInt64(outInfoAddress, out var callerSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (callerSize < 0x130)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!KernelModuleRegistry.TryGetModuleByAddress(queriedAddress, out var module))
{
if (queriedAddress >= GuestExecutableAddressFloor &&
queriedAddress < GuestExecutableAddressCeiling)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!TryWriteHostBoundaryModuleInfoForUnwind(ctx, outInfoAddress, queriedAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
else if (!TryWriteModuleInfoForUnwind(ctx, outInfoAddress, module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "crb5j7mkk1c",
ExportName = "_is_signal_return",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelIsSignalReturn(CpuContext ctx)
{
// SharpEmu calls guest exception handlers without a signal-return frame.
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "kUpgrXIrz7Q",
ExportName = "sceKernelGetModuleInfo",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleInfo(CpuContext ctx)
{
return KernelGetModuleInfoByHandleCore(
ctx,
unchecked((int)ctx[CpuRegister.Rdi]),
ctx[CpuRegister.Rsi]);
}
[SysAbiExport(
Nid = "QgsKEUfkqMA",
ExportName = "sceKernelGetModuleInfo2",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleInfo2(CpuContext ctx)
{
return KernelGetModuleInfoByHandleCore(
ctx,
unchecked((int)ctx[CpuRegister.Rdi]),
ctx[CpuRegister.Rsi]);
}
[SysAbiExport(
Nid = "HZO7xOos4xc",
ExportName = "sceKernelGetModuleInfoInternal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleInfoInternal(CpuContext ctx)
{
return KernelGetModuleInfoExByHandleCore(
ctx,
unchecked((int)ctx[CpuRegister.Rdi]),
ctx[CpuRegister.Rsi]);
}
[SysAbiExport(
Nid = "IuxnUuXk6Bg",
ExportName = "sceKernelGetModuleList",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleList(CpuContext ctx)
{
return KernelGetModuleListCore(
ctx,
handlesAddress: ctx[CpuRegister.Rdi],
capacity: ctx[CpuRegister.Rsi],
outCountAddress: ctx[CpuRegister.Rdx],
includeSystemModules: true);
}
[SysAbiExport(
Nid = "ZzzC3ZGVAkc",
ExportName = "sceKernelGetModuleList2",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetModuleList2(CpuContext ctx)
{
return KernelGetModuleListCore(
ctx,
handlesAddress: ctx[CpuRegister.Rdi],
capacity: ctx[CpuRegister.Rsi],
outCountAddress: ctx[CpuRegister.Rdx],
includeSystemModules: false);
}
[SysAbiExport(
Nid = "Fjc4-n1+y2g",
ExportName = "__elf_phdr_match_addr",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int ElfPhdrMatchAddr(CpuContext ctx)
{
var moduleInfoAddress = ctx[CpuRegister.Rdi];
_ = ctx[CpuRegister.Rsi]; // dtor virtual address
if (moduleInfoAddress == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
ctx[CpuRegister.Rax] = 1;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "OMDRKKAZ8I4",
ExportName = "sceKernelDebugRaiseException",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDebugRaiseException(CpuContext ctx)
{
return KernelDebugRaiseExceptionCore(ctx, "sceKernelDebugRaiseException");
}
[SysAbiExport(
Nid = "zE-wXIZjLoM",
ExportName = "sceKernelDebugRaiseExceptionOnReleaseMode",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDebugRaiseExceptionOnReleaseMode(CpuContext ctx)
{
return KernelDebugRaiseExceptionCore(ctx, "sceKernelDebugRaiseExceptionOnReleaseMode");
}
private static int KernelDebugRaiseExceptionCore(CpuContext ctx, string exportName)
{
var error = unchecked((uint)ctx[CpuRegister.Rdi]);
var unknown = unchecked((long)ctx[CpuRegister.Rsi]);
if (unknown != 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
// libkernel treats this as a fatal debug exception. Callers commonly place
// UD2 immediately after the noreturn call, so returning normally turns the
// guest's intended termination into a host illegal-instruction fault.
Console.Error.WriteLine($"[LOADER][ERROR] {exportName}: error=0x{error:X8}");
GuestThreadExecution.RequestCurrentEntryExit(exportName, unchecked((ulong)error));
ctx[CpuRegister.Rax] = error;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "cfwBSQyr5Ys",
ExportName = "sceKernelDebugWriteCppExceptionInfo",
Target = Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDebugWriteCppExceptionInfo(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "f7uOxY9mM1U",
ExportName = "__stack_chk_guard",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int StackCheckGuard(CpuContext ctx)
{
var baseAddress = _stackChkGuardObjectAddress != 0
? unchecked((ulong)_stackChkGuardObjectAddress)
: GetTlsScratchAddress(ctx, TlsStackChkGuardBaseOffset);
if (baseAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (_stackChkGuardObjectAddress != 0)
{
try
{
Marshal.WriteInt64(_stackChkGuardObjectAddress, unchecked((long)_stackChkGuardValue));
Marshal.WriteInt64(IntPtr.Add(_stackChkGuardObjectAddress, (int)sizeof(ulong)), unchecked((long)_stackChkGuardValue));
}
catch
{
}
}
if (ctx.FsBase != 0)
{
_ = ctx.TryWriteUInt64(ctx.FsBase + 0x28, _stackChkGuardValue);
_ = ctx.TryWriteUInt64(ctx.FsBase + TlsStackChkGuardBaseOffset, _stackChkGuardValue);
_ = ctx.TryWriteUInt64(ctx.FsBase + TlsStackChkGuardBaseOffset + StackChkGuardFieldOffset, _stackChkGuardValue);
}
ctx[CpuRegister.Rax] = baseAddress;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ou3iL1abvng",
ExportName = "__stack_chk_fail",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int StackCheckFail(CpuContext ctx)
{
var count = Interlocked.Increment(ref _stackChkFailCount);
Console.Error.WriteLine(
$"[LOADER][ERROR] __stack_chk_fail#{count}: rip=0x{ctx.Rip:X16} rdi=0x{ctx[CpuRegister.Rdi]:X16}");
var result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP;
GuestThreadExecution.RequestCurrentEntryExit("__stack_chk_fail", result);
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
[SysAbiExport(
Nid = "p5EcQeEeJAE",
ExportName = "_sceKernelRtldSetApplicationHeapAPI",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelRtldSetApplicationHeapApi(CpuContext ctx)
{
lock (_stateGate)
{
_applicationHeapApiAddress = ctx[CpuRegister.Rdi];
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "QKd0qM58Qes",
ExportName = "sceKernelStopUnloadModule",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelStopUnloadModule(CpuContext ctx)
{
var resultAddress = ctx[CpuRegister.R9];
if (resultAddress != 0 && !TryWriteInt32(ctx, resultAddress, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "wzvqT4UqKX8",
ExportName = "sceKernelLoadStartModule",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelLoadStartModule(CpuContext ctx)
{
var modulePathAddress = ctx[CpuRegister.Rdi];
var argumentSize = ctx[CpuRegister.Rsi];
var argumentAddress = ctx[CpuRegister.Rdx];
var resultAddress = ctx[CpuRegister.R9];
if (resultAddress != 0 && !TryWriteInt32(ctx, resultAddress, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryReadUtf8Z(ctx, modulePathAddress, 512, out var modulePath) ||
string.IsNullOrWhiteSpace(modulePath))
{
return ReturnModuleLoadError(
ctx,
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
int handle;
var hasDirectory = modulePath.Contains('/') || modulePath.Contains('\\');
var isApp0Path = modulePath.StartsWith("/app0/", StringComparison.OrdinalIgnoreCase) ||
modulePath.StartsWith("app0/", StringComparison.OrdinalIgnoreCase);
var loadResult = hasDirectory
? KernelModuleRegistry.LoadModule(modulePath)
: default;
if (loadResult.Succeeded)
{
handle = loadResult.Handle;
}
else if (!isApp0Path &&
KernelModuleRegistry.TryFindByPathOrName(modulePath, out var moduleByPath))
{
handle = moduleByPath.Handle;
}
else
{
loadResult = hasDirectory
? loadResult
: KernelModuleRegistry.LoadModule(modulePath);
if (!loadResult.Succeeded)
{
return ReturnModuleLoadError(ctx, loadResult.Error);
}
handle = loadResult.Handle;
}
if (KernelModuleRegistry.TryBeginModuleStart(handle, out var moduleToStart))
{
var scheduler = GuestThreadExecution.Scheduler;
string? startError = null;
ulong startResult = 0;
var started = scheduler is not null && scheduler.TryCallGuestFunction(
ctx,
moduleToStart.InitEntryPoint,
argumentSize,
argumentAddress,
0,
0,
0,
$"sceKernelLoadStartModule:{moduleToStart.Name}",
out startResult,
out startError);
KernelModuleRegistry.CompleteModuleStart(handle, started);
if (!started)
{
Console.Error.WriteLine(
$"[LOADER][ERROR] sceKernelLoadStartModule failed to start '{moduleToStart.Name}' " +
$"at 0x{moduleToStart.InitEntryPoint:X16}: {startError ?? "guest scheduler unavailable"}");
return ReturnModuleLoadError(
ctx,
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP);
}
if (resultAddress != 0 && !TryWriteInt32(ctx, resultAddress, unchecked((int)startResult)))
{
return ReturnModuleLoadError(
ctx,
(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
Console.Error.WriteLine(
$"[LOADER][INFO] sceKernelLoadStartModule started '{moduleToStart.Name}' " +
$"at 0x{moduleToStart.InitEntryPoint:X16}");
}
ctx[CpuRegister.Rax] = unchecked((uint)handle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int ReturnModuleLoadError(CpuContext ctx, int error)
{
ctx[CpuRegister.Rax] = unchecked((ulong)(long)error);
return error;
}
[SysAbiExport(
Nid = "g8cM39EUZ6o",
ExportName = "sceSysmoduleLoadModule",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSysmodule")]
public static int SysmoduleLoadModule(CpuContext ctx)
{
var moduleId = unchecked((int)ctx[CpuRegister.Rdi]);
_ = KernelModuleRegistry.MarkSysmoduleLoaded(moduleId);
lock (_stateGate)
{
_loadedSysmodules.Add(moduleId);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "nu4a0-arQis",
ExportName = "sceKernelAioInitializeParam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAioInitializeParam(CpuContext ctx)
{
var paramAddress = ctx[CpuRegister.Rdi];
if (paramAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> zero = stackalloc byte[AioInitParamSize];
zero.Clear();
if (!ctx.Memory.TryWrite(paramAddress, zero))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "-o5uEDpN+oY",
ExportName = "sceKernelConvertUtcToLocaltime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelConvertUtcToLocaltime(CpuContext ctx)
{
var utcSeconds = unchecked((long)ctx[CpuRegister.Rdi]);
var localTimeAddress = ctx[CpuRegister.Rsi];
var timesecAddress = ctx[CpuRegister.Rdx];
var dstSecondsAddress = ctx[CpuRegister.Rcx];
var timezone = GetStandardTimezone();
var localSeconds = ConvertUtcToLocaltimeSeconds(
utcSeconds,
timezone.MinutesWest,
timezone.DstSeconds);
var westSeconds = unchecked((uint)(-timezone.MinutesWest * 60));
if (localTimeAddress != 0 &&
!ctx.TryWriteUInt64(localTimeAddress, unchecked((ulong)localSeconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (timesecAddress != 0)
{
Span<byte> timesec = stackalloc byte[OrbisTimesecSize];
BinaryPrimitives.WriteInt64LittleEndian(timesec, utcSeconds);
BinaryPrimitives.WriteUInt32LittleEndian(timesec.Slice(sizeof(long), sizeof(uint)), westSeconds);
BinaryPrimitives.WriteUInt32LittleEndian(
timesec.Slice(sizeof(long) + sizeof(uint), sizeof(uint)),
unchecked((uint)timezone.DstSeconds));
if (!ctx.Memory.TryWrite(timesecAddress, timesec))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
if (dstSecondsAddress != 0 &&
!ctx.TryWriteUInt64(dstSecondsAddress, unchecked((ulong)timezone.DstSeconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "0NTHN1NKONI",
ExportName = "sceKernelConvertLocaltimeToUtc",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelConvertLocaltimeToUtc(CpuContext ctx)
{
var localSeconds = unchecked((long)ctx[CpuRegister.Rdi]);
var utcTimeAddress = ctx[CpuRegister.Rdx];
var timeResultAddress = ctx[CpuRegister.Rcx];
var dstSecondsAddress = ctx[CpuRegister.R8];
if (timeResultAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var timezone = GetStandardTimezone();
var utcSeconds = ConvertLocaltimeToUtcSeconds(
localSeconds,
timezone.MinutesWest,
timezone.DstSeconds);
// Both conversion directions return seconds and UTC offsets in the same layout.
Span<byte> timeResult = stackalloc byte[OrbisTimesecSize];
BinaryPrimitives.WriteInt64LittleEndian(timeResult, utcSeconds);
BinaryPrimitives.WriteInt32LittleEndian(timeResult.Slice(sizeof(long), sizeof(int)), -timezone.MinutesWest * 60);
BinaryPrimitives.WriteInt32LittleEndian(
timeResult.Slice(sizeof(long) + sizeof(int), sizeof(int)),
timezone.DstSeconds);
if (!ctx.Memory.TryWrite(timeResultAddress, timeResult))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (utcTimeAddress != 0 && !ctx.TryWriteUInt64(utcTimeAddress, unchecked((ulong)utcSeconds)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (dstSecondsAddress != 0 && !TryWriteInt32(ctx, dstSecondsAddress, timezone.DstSeconds))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static long ConvertLocaltimeToUtcSeconds(long localSeconds, int minutesWest, int dstSeconds) =>
unchecked(localSeconds + (long)minutesWest * 60 - dstSeconds);
internal static long ConvertUtcToLocaltimeSeconds(long utcSeconds, int minutesWest, int dstSeconds) =>
unchecked(utcSeconds - (long)minutesWest * 60 + dstSeconds);
private static (int MinutesWest, int DstSeconds) GetStandardTimezone()
{
// Use the standard UTC offset; daylight-saving settings are not modeled.
var timezone = TimeZoneInfo.Local;
var baseOffset = timezone.BaseUtcOffset;
return (unchecked((int)-baseOffset.TotalMinutes), 0);
}
[SysAbiExport(
Nid = "vYU8P9Td2Zo",
ExportName = "sceKernelAioInitializeImpl",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelAioInitializeImpl(CpuContext ctx)
{
var paramAddress = ctx[CpuRegister.Rdi];
var size = unchecked((int)ctx[CpuRegister.Rsi]);
if (paramAddress == 0 || size < AioInitParamSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "eR2bZFAAU0Q",
ExportName = "sceSysmoduleUnloadModule",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSysmodule")]
public static int SysmoduleUnloadModule(CpuContext ctx)
{
var moduleId = unchecked((int)ctx[CpuRegister.Rdi]);
KernelModuleRegistry.MarkSysmoduleUnloaded(moduleId);
lock (_stateGate)
{
_loadedSysmodules.Remove(moduleId);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "hHrGoGoNf+s",
ExportName = "sceSysmoduleLoadModuleInternalWithArg",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSysmodule")]
public static int SysmoduleLoadModuleInternalWithArg(CpuContext ctx)
{
var moduleId = unchecked((int)ctx[CpuRegister.Rdi]);
var resultAddress = ctx[CpuRegister.R8];
_ = KernelModuleRegistry.MarkSysmoduleLoaded(moduleId);
lock (_stateGate)
{
_loadedSysmodules.Add(moduleId);
}
if (resultAddress != 0 && !TryWriteInt32(ctx, resultAddress, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "4fU5yvOkVG4",
ExportName = "sceSysmoduleGetModuleInfoForUnwind",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSysmodule")]
public static int SysmoduleGetModuleInfoForUnwind(CpuContext ctx) =>
KernelGetModuleInfoForUnwind(ctx);
[SysAbiExport(
Nid = "fMP5NHUOaMk",
ExportName = "sceSysmoduleIsLoaded",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSysmodule")]
public static int SysmoduleIsLoaded(CpuContext ctx)
{
var moduleId = unchecked((int)ctx[CpuRegister.Rdi]);
var loaded = KernelModuleRegistry.IsSysmoduleLoaded(moduleId);
lock (_stateGate)
{
loaded |= _loadedSysmodules.Contains(moduleId);
}
ctx[CpuRegister.Rax] = loaded ? 0UL : unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "WslcK1FQcGI",
ExportName = "sceKernelIsNeoMode",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelIsNeoMode(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Xjoosiw+XPI",
ExportName = "sceKernelUuidCreate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelUuidCreate(CpuContext ctx)
{
var uuidAddress = ctx[CpuRegister.Rdi];
if (uuidAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
Span<byte> uuid = stackalloc byte[16];
RandomNumberGenerator.Fill(uuid);
if (!ctx.Memory.TryWrite(uuidAddress, uuid))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int KernelGetModuleInfoByHandleCore(CpuContext ctx, int handle, ulong outInfoAddress)
{
if (outInfoAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!KernelModuleRegistry.TryGetModuleByHandle(handle, out var module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!ctx.TryReadUInt64(outInfoAddress, out var callerSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (callerSize != ModuleInfoSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryWriteModuleInfo(ctx, outInfoAddress, module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int KernelGetModuleInfoExByHandleCore(CpuContext ctx, int handle, ulong outInfoAddress)
{
if (outInfoAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!KernelModuleRegistry.TryGetModuleByHandle(handle, out var module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (!ctx.TryReadUInt64(outInfoAddress, out var callerSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (callerSize != ModuleInfoExSize)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryWriteModuleInfoEx(ctx, outInfoAddress, module))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int KernelGetModuleListCore(
CpuContext ctx,
ulong handlesAddress,
ulong capacity,
ulong outCountAddress,
bool includeSystemModules)
{
if (outCountAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var handles = KernelModuleRegistry.GetModuleHandles(includeSystemModules);
if (!ctx.TryWriteUInt64(outCountAddress, unchecked((ulong)handles.Length)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (handlesAddress == 0 || capacity == 0 || handles.Length == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var writableCount = (int)Math.Min(Math.Min(capacity, (ulong)int.MaxValue), (ulong)handles.Length);
for (var i = 0; i < writableCount; i++)
{
if (!TryWriteInt32(ctx, handlesAddress + (ulong)(i * sizeof(int)), handles[i]))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
if ((ulong)handles.Length > capacity)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryWriteModuleInfo(
CpuContext ctx,
ulong outInfoAddress,
KernelModuleRegistry.ModuleEntry module)
{
var payload = new byte[ModuleInfoSize];
BinaryPrimitives.WriteUInt64LittleEndian(payload, ModuleInfoSize);
WriteModuleName(payload, module.Name);
WriteModuleSegment(payload, 0x108, module);
BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(0x148), 1);
return ctx.Memory.TryWrite(outInfoAddress, payload);
}
private static bool TryWriteModuleInfoEx(
CpuContext ctx,
ulong outInfoAddress,
KernelModuleRegistry.ModuleEntry module)
{
var payload = new byte[ModuleInfoExSize];
BinaryPrimitives.WriteUInt64LittleEndian(payload, ModuleInfoExSize);
WriteModuleName(payload, module.Name);
BinaryPrimitives.WriteInt32LittleEndian(
payload.AsSpan((int)ModuleInfoExHandleOffset),
module.Handle);
BinaryPrimitives.WriteUInt64LittleEndian(
payload.AsSpan((int)ModuleInfoExInitProcOffset),
module.EntryPoint);
WriteModuleSegment(payload, (int)ModuleInfoExSegmentsOffset, module);
BinaryPrimitives.WriteUInt32LittleEndian(
payload.AsSpan((int)ModuleInfoExSegmentCountOffset),
1);
return ctx.Memory.TryWrite(outInfoAddress, payload);
}
private static bool TryWriteModuleInfoForUnwind(
CpuContext ctx,
ulong outInfoAddress,
KernelModuleRegistry.ModuleEntry module)
{
const int unwindInfoSize = 0x130;
var payload = new byte[unwindInfoSize];
BinaryPrimitives.WriteUInt64LittleEndian(payload, unwindInfoSize);
WriteModuleName(payload, module.Name);
BinaryPrimitives.WriteUInt64LittleEndian(payload.AsSpan(0x108), module.EhFrameHeaderAddress);
BinaryPrimitives.WriteUInt64LittleEndian(payload.AsSpan(0x110), module.EhFrameAddress);
BinaryPrimitives.WriteUInt64LittleEndian(payload.AsSpan(0x118), module.EhFrameSize);
BinaryPrimitives.WriteUInt64LittleEndian(payload.AsSpan(0x120), module.BaseAddress);
BinaryPrimitives.WriteUInt64LittleEndian(
payload.AsSpan(0x128),
module.EndAddress - module.BaseAddress);
return ctx.Memory.TryWrite(outInfoAddress, payload);
}
private static bool TryWriteHostBoundaryModuleInfoForUnwind(
CpuContext ctx,
ulong outInfoAddress,
ulong queriedAddress)
{
var boundaryBase = queriedAddress & ~(HostUnwindBoundarySize - 1);
var module = new KernelModuleRegistry.ModuleEntry(
Handle: 0,
Name: "SharpEmuHostBoundary",
Path: string.Empty,
BaseAddress: boundaryBase,
EndAddress: boundaryBase + HostUnwindBoundarySize,
EntryPoint: 0,
InitEntryPoint: 0,
EhFrameHeaderAddress: 0,
EhFrameAddress: 0,
EhFrameSize: 0,
StartState: KernelModuleRegistry.ModuleStartState.Started,
IsMain: false,
IsSystemModule: false);
return TryWriteModuleInfoForUnwind(ctx, outInfoAddress, module);
}
private static void WriteModuleName(Span<byte> payload, string moduleName)
{
if (string.IsNullOrWhiteSpace(moduleName))
{
return;
}
var encoded = Encoding.UTF8.GetBytes(moduleName);
var payloadLength = Math.Min(encoded.Length, ModuleInfoNameMaxBytes - 1);
if (payloadLength > 0)
{
encoded.AsSpan(0, payloadLength).CopyTo(
payload.Slice((int)ModuleInfoNameOffset, payloadLength));
}
}
private static void WriteModuleSegment(
Span<byte> payload,
int offset,
KernelModuleRegistry.ModuleEntry module)
{
Debug.Assert(offset >= 0 && offset + ModuleInfoSegmentSize <= payload.Length);
BinaryPrimitives.WriteUInt64LittleEndian(payload.Slice(offset), module.BaseAddress);
BinaryPrimitives.WriteUInt32LittleEndian(
payload.Slice(offset + sizeof(ulong)),
(uint)Math.Min(module.EndAddress - module.BaseAddress, uint.MaxValue));
// Loaded modules are represented as a single aggregate readable and
// executable range until per-program-header registry data is exposed.
BinaryPrimitives.WriteInt32LittleEndian(payload.Slice(offset + 12), 5);
}
private static bool TryReadUtf8Z(CpuContext ctx, ulong address, int maxLength, out string value)
{
value = string.Empty;
if (address == 0 || maxLength <= 0)
{
return false;
}
var bytes = new List<byte>(Math.Min(maxLength, 64));
Span<byte> one = stackalloc byte[1];
for (var i = 0; i < maxLength; i++)
{
if (!ctx.Memory.TryRead(address + (ulong)i, one))
{
return false;
}
if (one[0] == 0)
{
value = Encoding.UTF8.GetString(bytes.ToArray());
return true;
}
bytes.Add(one[0]);
}
value = Encoding.UTF8.GetString(bytes.ToArray());
return true;
}
private static ulong GetTlsScratchAddress(CpuContext ctx, ulong offset)
{
if (ctx.FsBase == 0)
{
return 0;
}
return unchecked(ctx.FsBase + offset);
}
private static bool TryWriteInt32(CpuContext ctx, ulong address, int value)
{
Span<byte> bytes = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
return ctx.Memory.TryWrite(address, bytes);
}
private static nint AllocateStackChkGuardObject()
{
try
{
var memory = Marshal.AllocHGlobal(sizeof(ulong) * 2);
Marshal.WriteInt64(memory, unchecked((long)_stackChkGuardValue));
Marshal.WriteInt64(IntPtr.Add(memory, sizeof(ulong)), unchecked((long)_stackChkGuardValue));
return memory;
}
catch
{
return 0;
}
}
private static ulong ResolveKernelTscFrequency()
{
var (frequencyHz, source) = SelectKernelTscFrequency(
_rdtscReader is not null,
Environment.GetEnvironmentVariable("SHARPEMU_TSC_FREQ_HZ"),
TryCalibrateHostTscFrequency,
TryResolveCpuidTscFrequency,
Stopwatch.Frequency);
TraceKernelTscFrequency(source, frequencyHz);
return frequencyHz;
}
internal delegate bool TryGetFrequency(out ulong frequencyHz);
// sceKernelReadTsc only returns the CPU's RDTSC when the host RDTSC reader is available
// (currently 64-bit Windows); otherwise it falls back to the QPC-based Stopwatch. The
// calibrated and CPUID frequencies both describe RDTSC, so reporting either while ReadTsc is
// actually returning Stopwatch ticks makes sceKernelGetTscFrequency disagree with the counter,
// and a guest computing elapsed = readTscDelta / frequency gets the wrong time on Linux and
// macOS. Gate those on rdtscAvailable; otherwise report Stopwatch's own frequency so the pair
// stays consistent.
internal static (ulong FrequencyHz, string Source) SelectKernelTscFrequency(
bool rdtscAvailable,
string? overrideHzText,
TryGetFrequency tryCalibrate,
TryGetFrequency tryResolveCpuid,
long stopwatchFrequency)
{
const ulong minSane = 1_000_000UL;
if (!string.IsNullOrWhiteSpace(overrideHzText) &&
ulong.TryParse(overrideHzText, out var overrideHz) &&
overrideHz >= minSane)
{
return (overrideHz, "env");
}
if (rdtscAvailable)
{
if (tryCalibrate(out ulong calibratedHz) && calibratedHz >= minSane)
{
return (calibratedHz, "calibrated-rdtsc");
}
if (tryResolveCpuid(out ulong cpuidHz) && cpuidHz >= minSane)
{
return (cpuidHz, "cpuid");
}
}
var hostQpc = stopwatchFrequency > 0
? unchecked((ulong)stopwatchFrequency)
: DefaultKernelTscFrequency;
return hostQpc >= minSane ? (hostQpc, "qpc") : (DefaultKernelTscFrequency, "default");
}
private static void TraceKernelTscFrequency(string source, ulong frequencyHz)
{
Console.Error.WriteLine($"[LOADER][INFO] Kernel TSC frequency: {frequencyHz} Hz ({source})");
}
private static bool TryResolveCpuidTscFrequency(out ulong frequencyHz)
{
frequencyHz = 0;
if (!X86Base.IsSupported)
{
return false;
}
try
{
var leaf15 = X86Base.CpuId(unchecked((int)0x15), 0);
uint denominator = unchecked((uint)leaf15.Eax);
uint numerator = unchecked((uint)leaf15.Ebx);
uint crystalHz = unchecked((uint)leaf15.Ecx);
if (denominator != 0 && numerator != 0 && crystalHz != 0)
{
frequencyHz = ((ulong)crystalHz * numerator) / denominator;
if (frequencyHz != 0)
{
return true;
}
}
var leaf16 = X86Base.CpuId(unchecked((int)0x16), 0);
uint baseMHz = unchecked((uint)leaf16.Eax);
if (baseMHz != 0)
{
frequencyHz = (ulong)baseMHz * 1_000_000UL;
return true;
}
}
catch
{
}
return false;
}
private static bool TryCalibrateHostTscFrequency(out ulong frequencyHz)
{
frequencyHz = 0;
if (!TryReadHostTsc(out _))
{
return false;
}
const int sampleCount = 3;
Span<ulong> estimates = stackalloc ulong[sampleCount];
int validSamples = 0;
for (int i = 0; i < sampleCount; i++)
{
if (!TryCalibrateHostTscFrequencySample(out ulong estimate))
{
continue;
}
estimates[validSamples++] = estimate;
}
if (validSamples == 0)
{
return false;
}
estimates[..validSamples].Sort();
frequencyHz = estimates[validSamples / 2];
return frequencyHz != 0;
}
private static bool TryCalibrateHostTscFrequencySample(out ulong frequencyHz)
{
frequencyHz = 0;
if (!TryReadHostTsc(out ulong startTsc))
{
return false;
}
long startQpc = Stopwatch.GetTimestamp();
long minimumQpcDelta = Math.Max(Stopwatch.Frequency / 20, 1);
long endQpc;
ulong endTsc;
do
{
Thread.SpinWait(256);
endQpc = Stopwatch.GetTimestamp();
if (!TryReadHostTsc(out endTsc))
{
return false;
}
}
while (endQpc - startQpc < minimumQpcDelta);
ulong deltaTsc = endTsc - startTsc;
long deltaQpc = endQpc - startQpc;
if (deltaTsc == 0 || deltaQpc <= 0)
{
return false;
}
frequencyHz = unchecked((deltaTsc * (ulong)Stopwatch.Frequency) / (ulong)deltaQpc);
return frequencyHz != 0;
}
private static bool TryReadHostTsc(out ulong counter)
{
counter = 0;
if (_rdtscReader is null)
{
return false;
}
try
{
counter = _rdtscReader();
return counter != 0;
}
catch
{
counter = 0;
return false;
}
}
private static RdtscDelegate? CreateRdtscReader()
{
if (!Environment.Is64BitProcess ||
RuntimeInformation.ProcessArchitecture != Architecture.X64)
{
return null;
}
try
{
nint stubAddress = VirtualAlloc(nint.Zero, (nuint)16, MemCommit | MemReserve, PageExecuteReadWrite);
if (stubAddress == 0)
{
return null;
}
Span<byte> stub = stackalloc byte[]
{
0x0F, 0x31,
0x48, 0xC1, 0xE2, 0x20,
0x48, 0x09, 0xD0,
0xC3,
};
Marshal.Copy(stub.ToArray(), 0, stubAddress, stub.Length);
return Marshal.GetDelegateForFunctionPointer<RdtscDelegate>(stubAddress);
}
catch
{
return null;
}
}
private static unsafe nint VirtualAlloc(nint lpAddress, nuint dwSize, uint flAllocationType, uint flProtect) =>
(nint)HostMemory.Alloc((void*)lpAddress, dwSize, flAllocationType, flProtect);
private static ulong AlignUp(ulong value, ulong alignment)
{
if (alignment <= 1)
{
return value;
}
var mask = alignment - 1;
return (value + mask) & ~mask;
}
[SysAbiExport(
Nid = "QvsZxomvUHs",
ExportName = "sceKernelNanosleep",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelNanosleep(CpuContext ctx) => NanosleepCore(ctx, posix: false);
[SysAbiExport(
Nid = "NhpspxdjEKU",
ExportName = "_nanosleep",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixNanosleepUnderscore(CpuContext ctx) => NanosleepCore(ctx, posix: true);
[SysAbiExport(
Nid = "yS8U2TGCe1A",
ExportName = "nanosleep",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixNanosleep(CpuContext ctx) => NanosleepCore(ctx, posix: true);
private static int NanosleepCore(CpuContext ctx, bool posix)
{
const int eFault = 14;
const int eInvalid = 22;
var requestAddress = ctx[CpuRegister.Rdi];
var remainAddress = ctx[CpuRegister.Rsi];
if (requestAddress == 0)
{
return NanosleepFailure(ctx, posix, eInvalid, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
Span<byte> timespecBuffer = stackalloc byte[16];
if (!ctx.Memory.TryRead(requestAddress, timespecBuffer))
{
return NanosleepFailure(ctx, posix, eFault, 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)
{
return NanosleepFailure(ctx, posix, eInvalid, 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, posix ? "nanosleep" : "sceKernelNanosleep");
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 int NanosleepFailure(
CpuContext ctx,
bool posix,
int errnoValue,
OrbisGen2Result sceResult)
{
if (posix)
{
TrySetErrno(ctx, errnoValue);
ctx[CpuRegister.Rax] = unchecked((ulong)(-1L));
}
else
{
ctx[CpuRegister.Rax] = unchecked((ulong)errnoValue);
}
return (int)sceResult;
}
private static void WriteRemainingTime(CpuContext ctx, ulong remainAddress, long seconds, long nanoseconds)
{
if (remainAddress == 0)
{
return;
}
Span<byte> remainBuffer = stackalloc byte[16];
BinaryPrimitives.WriteInt64LittleEndian(remainBuffer, seconds);
BinaryPrimitives.WriteInt64LittleEndian(remainBuffer[sizeof(long)..], nanoseconds);
ctx.Memory.TryWrite(remainAddress, remainBuffer);
}
[SysAbiExport(
Nid = "QcteRwbsnV0",
ExportName = "usleep",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixUsleep(CpuContext ctx) => KernelUsleep(ctx);
[SysAbiExport(
Nid = "HoLVWNanBBc",
ExportName = "getpid",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int GetProcessId(CpuContext ctx)
{
var processId = Environment.ProcessId;
ctx[CpuRegister.Rax] = unchecked((uint)processId);
return processId;
}
}