initial commit

This commit is contained in:
ParantezTech
2026-03-11 15:48:28 +03:00
commit 4d73f469bc
92 changed files with 166746 additions and 0 deletions
@@ -0,0 +1,20 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
namespace SharpEmu.Libs.AppContent;
public static class AppContentExports
{
[SysAbiExport(
Nid = "xnd8BJzAxmk",
ExportName = "sceAppContentGetAddcontInfoList",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAppContent")]
public static int AppContentGetAddcontInfoList(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
+205
View File
@@ -0,0 +1,205 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System;
using System.Collections.Concurrent;
using System.Threading;
using SharpEmu.HLE;
namespace SharpEmu.Libs.CxxAbi;
public static class CxaGuardExports
{
private sealed class GuardState
{
public int OwnerThreadId { get; set; }
}
private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new();
[SysAbiExport(
Nid = "3GPpjQdAMTw",
ExportName = "__cxa_guard_acquire",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaGuardAcquire(CpuContext ctx)
{
var guardPtr = ctx[CpuRegister.Rdi];
if (guardPtr == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var currentThreadId = Environment.CurrentManagedThreadId;
var spinner = new SpinWait();
while (true)
{
if (!TryReadGuardInitialized(ctx, guardPtr, out var initialized))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
LogGuardState(ctx, "guard_acquire", guardPtr, initialized);
if (initialized)
{
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_acquire", guardPtr, result: 0, initialized, inProgress: false, ownerThreadId: 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var newState = new GuardState
{
OwnerThreadId = currentThreadId,
};
if (_inProgress.TryAdd(guardPtr, newState))
{
ctx[CpuRegister.Rax] = 1;
LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (_inProgress.TryGetValue(guardPtr, out var state))
{
if (state.OwnerThreadId == currentThreadId)
{
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_acquire", guardPtr, result: 0, initialized, inProgress: true, ownerThreadId: state.OwnerThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
spinner.SpinOnce();
if (spinner.Count % 32 == 0)
{
Thread.Yield();
}
}
}
[SysAbiExport(
Nid = "",
ExportName = "__cxa_guard_release",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaGuardRelease(CpuContext ctx)
{
var guardPtr = ctx[CpuRegister.Rdi];
if (guardPtr == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (_inProgress.TryGetValue(guardPtr, out var state) &&
state.OwnerThreadId != Environment.CurrentManagedThreadId)
{
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_release", guardPtr, result: 0, initialized: false, inProgress: true, ownerThreadId: state.OwnerThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
_inProgress.TryRemove(guardPtr, out _);
LogGuardState(ctx, "guard_release", guardPtr, initialized: true);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "",
ExportName = "__cxa_guard_abort",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaGuardAbort(CpuContext ctx)
{
var guardPtr = ctx[CpuRegister.Rdi];
if (guardPtr == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (_inProgress.TryGetValue(guardPtr, out var state) &&
state.OwnerThreadId != Environment.CurrentManagedThreadId)
{
ctx[CpuRegister.Rax] = 0;
LogGuardResult("guard_abort", guardPtr, result: 0, initialized: false, inProgress: true, ownerThreadId: state.OwnerThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
_ = TryWriteGuardInitialized(ctx, guardPtr, initialized: false);
_inProgress.TryRemove(guardPtr, out _);
LogGuardState(ctx, "guard_abort", guardPtr, initialized: false);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryReadGuardInitialized(CpuContext ctx, ulong guardPtr, out bool initialized)
{
initialized = false;
var aligned = guardPtr & ~7UL;
var shift = (int)((guardPtr & 7UL) * 8);
if (!ctx.TryReadUInt64(aligned, out var word))
{
return false;
}
var b0 = (byte)((word >> shift) & 0xFF);
initialized = (b0 & 0x01) != 0;
return true;
}
private static bool TryWriteGuardInitialized(CpuContext ctx, ulong guardPtr, bool initialized)
{
var aligned = guardPtr & ~7UL;
var shift = (int)((guardPtr & 7UL) * 8);
var mask = 0xFFUL << shift;
if (!ctx.TryReadUInt64(aligned, out var word))
{
return false;
}
var b0 = (byte)((word >> shift) & 0xFF);
b0 = initialized ? (byte)(b0 | 0x01) : (byte)(b0 & ~0x01);
var newWord = (word & ~mask) | ((ulong)b0 << shift);
return ctx.TryWriteUInt64(aligned, newWord);
}
private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal))
{
return;
}
var aligned = guardPtr & ~7UL;
var readable = ctx.TryReadUInt64(aligned, out var word);
Console.Error.WriteLine(
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} aligned=0x{aligned:X16} init={initialized} word={(readable ? $"0x{word:X16}" : "<unreadable>")}");
}
private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal))
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} result={result} init={initialized} in_progress={inProgress} owner_thread={ownerThreadId}");
}
}
+373
View File
@@ -0,0 +1,373 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
public static class KernelExports
{
private static long _nextThreadId = 1;
private static int _nextFileDescriptor = 2;
private static readonly object _cxaGate = new();
private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
private readonly record struct CxaDestructorEntry(
ulong Function,
ulong Argument,
ulong ModuleHandle);
[SysAbiExport(
Nid = "WB66evu8bsU",
ExportName = "sceKernelGetCompiledSdkVersion",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelGetCompiledSdkVersion(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "uMei1W9uyNo",
ExportName = "exit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int Exit(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "XKRegsFpEpk",
ExportName = "catchReturnFromMain",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CatchReturnFromMain(CpuContext ctx)
{
var status = unchecked((int)ctx[CpuRegister.Rdi]);
Console.Error.WriteLine($"[LOADER][INFO] catchReturnFromMain(status={status})");
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "bzQExy189ZI",
ExportName = "_init_env",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int InitEnv(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "8G2LB+A3rzg",
ExportName = "atexit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Atexit(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "tsvEmnenz48",
ExportName = "__cxa_atexit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaAtexit(CpuContext ctx)
{
var destructorFunction = ctx[CpuRegister.Rdi];
var destructorArgument = ctx[CpuRegister.Rsi];
var moduleHandle = ctx[CpuRegister.Rdx];
if (destructorFunction == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
lock (_cxaGate)
{
_cxaDestructors.Add(new CxaDestructorEntry(
destructorFunction,
destructorArgument,
moduleHandle));
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "H2e8t5ScQGc",
ExportName = "__cxa_finalize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaFinalize(CpuContext ctx)
{
var moduleHandle = ctx[CpuRegister.Rdi];
lock (_cxaGate)
{
if (moduleHandle == 0)
{
_cxaDestructors.Clear();
}
else
{
for (var i = _cxaDestructors.Count - 1; i >= 0; i--)
{
if (_cxaDestructors[i].ModuleHandle == moduleHandle)
{
_cxaDestructors.RemoveAt(i);
}
}
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "kbw4UHHSYy0",
ExportName = "__pthread_cxa_finalize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCxaFinalize(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6Z83sYWFlA8",
ExportName = "_exit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int UnderscoreExit(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ac86z8q7T8A",
ExportName = "sceKernelExitSblock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelExitSblock(CpuContext ctx)
{
_ = ctx;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6UgtwV+0zb4",
ExportName = "scePthreadCreate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCreate(CpuContext ctx)
{
var threadIdAddress = ctx[CpuRegister.Rdi];
var nextThreadId = unchecked((ulong)Interlocked.Increment(ref _nextThreadId));
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, nextThreadId))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "OxhIB8LB-PQ",
ExportName = "pthread_create",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadCreate(CpuContext ctx)
{
return PthreadCreate(ctx);
}
[SysAbiExport(
Nid = "onNY9Byn-W8",
ExportName = "scePthreadJoin",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadJoin(CpuContext ctx)
{
var returnValueAddress = ctx[CpuRegister.Rsi];
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "h9CcP3J0oVM",
ExportName = "pthread_join",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadJoin(CpuContext ctx)
{
return PthreadJoin(ctx);
}
[SysAbiExport(
Nid = "wuCroIGjt2g",
ExportName = "open",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int Open(CpuContext ctx)
{
ctx[CpuRegister.Rax] = unchecked((ulong)Interlocked.Increment(ref _nextFileDescriptor));
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "1G3lF1Gg1k8",
ExportName = "sceKernelOpen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelOpen(CpuContext ctx)
{
ctx[CpuRegister.Rax] = unchecked((ulong)Interlocked.Increment(ref _nextFileDescriptor));
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "hcuQgD53UxM",
ExportName = "printf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Printf(CpuContext ctx)
{
ulong fmtPtr = ctx[CpuRegister.Rdi];
string fmt = ReadCString(ctx, fmtPtr, 4096);
ulong[] args =
{
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
ctx[CpuRegister.Rcx],
ctx[CpuRegister.R8],
ctx[CpuRegister.R9],
};
int argIndex = 0;
var sb = new System.Text.StringBuilder(fmt.Length + 64);
for (int i = 0; i < fmt.Length; i++)
{
char ch = fmt[i];
if (ch != '%')
{
sb.Append(ch);
continue;
}
if (i + 1 < fmt.Length && fmt[i + 1] == '%')
{
sb.Append('%');
i++;
continue;
}
int j = i + 1;
while (j < fmt.Length && "-+ #0".IndexOf(fmt[j]) >= 0) j++;
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
if (j < fmt.Length && fmt[j] == '.')
{
j++;
while (j < fmt.Length && char.IsDigit(fmt[j])) j++;
}
if (j >= fmt.Length) break;
char spec = fmt[j];
i = j;
ulong a = argIndex < args.Length ? args[argIndex++] : 0;
switch (spec)
{
case 's':
sb.Append(a == 0 ? "(null)" : ReadCString(ctx, a, 4096));
break;
case 'd':
case 'i':
sb.Append(unchecked((int)a));
break;
case 'u':
sb.Append(unchecked((uint)a));
break;
case 'x':
sb.Append(unchecked((uint)a).ToString("x"));
break;
case 'p':
sb.Append("0x").Append(a.ToString("x16"));
break;
default:
sb.Append('%').Append(spec);
break;
}
}
string outStr = sb.ToString();
Console.WriteLine($"[DEBUG][PRINF] {outStr}");
ctx[CpuRegister.Rax] = (ulong)outStr.Length;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "EMutwaQ34Jo",
ExportName = "perror",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Perror(CpuContext ctx)
{
ulong sPtr = ctx[CpuRegister.Rdi];
string msg;
if (sPtr == 0)
{
msg = "perror(NULL)";
}
else
{
msg = ReadCString(ctx, sPtr, 2048);
msg = $"perror(\"{msg}\")";
}
Console.WriteLine(msg);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static string ReadCString(CpuContext ctx, ulong address, int maxLen)
{
Span<byte> buf = stackalloc byte[maxLen];
if (!ctx.Memory.TryRead(address, buf))
return $"<unreadable 0x{address:X16}>";
int len = 0;
while (len < buf.Length && buf[len] != 0) len++;
try { return System.Text.Encoding.UTF8.GetString(buf.Slice(0, len)); }
catch { return System.Text.Encoding.ASCII.GetString(buf.Slice(0, len)); }
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,347 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Linq;
using System.Collections.Generic;
using System.IO;
namespace SharpEmu.Libs.Kernel;
public static class KernelModuleRegistry
{
private static readonly object _gate = new();
private static readonly Dictionary<int, ModuleEntry> _modulesByHandle = new();
private static readonly Dictionary<string, int> _handleByPath = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, int> _handleByName = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<int, int> _sysmoduleHandleById = new();
private static int _nextHandle = 1;
private static readonly Dictionary<int, string[]> KnownSysmoduleNames = new()
{
[0x0006] = new[] { "libSceFiber.sprx", "libSceFiber.prx" },
[0x0021] = new[] { "libSceRudp.sprx", "libSceRudp.prx" },
[0x0095] = new[] { "libSceIme.sprx", "libSceIme.prx" },
[0x0096] = new[] { "libSceImeDialog.sprx", "libSceImeDialog.prx" },
[0x00A9] = new[] { "libSceMouse.sprx", "libSceMouse.prx" },
};
public readonly record struct ModuleEntry(
int Handle,
string Name,
string Path,
ulong BaseAddress,
ulong EndAddress,
ulong EntryPoint,
bool IsMain,
bool IsSystemModule);
public static void Reset()
{
lock (_gate)
{
_modulesByHandle.Clear();
_handleByPath.Clear();
_handleByName.Clear();
_sysmoduleHandleById.Clear();
_nextHandle = 1;
}
}
public static int RegisterModule(
string? modulePath,
ulong baseAddress,
ulong size,
ulong entryPoint,
bool isMain,
bool isSystemModule = false)
{
var normalizedPath = NormalizePath(modulePath);
lock (_gate)
{
if (!string.IsNullOrWhiteSpace(normalizedPath) &&
_handleByPath.TryGetValue(normalizedPath, out var existingHandle) &&
_modulesByHandle.TryGetValue(existingHandle, out var existing))
{
var updated = existing with
{
BaseAddress = baseAddress,
EndAddress = ComputeEnd(baseAddress, size),
EntryPoint = entryPoint,
IsMain = existing.IsMain || isMain,
IsSystemModule = existing.IsSystemModule || isSystemModule,
};
_modulesByHandle[existingHandle] = updated;
_handleByName[updated.Name] = existingHandle;
return existingHandle;
}
var handle = _nextHandle++;
var name = ResolveName(normalizedPath, handle);
var entry = new ModuleEntry(
Handle: handle,
Name: name,
Path: normalizedPath,
BaseAddress: baseAddress,
EndAddress: ComputeEnd(baseAddress, size),
EntryPoint: entryPoint,
IsMain: isMain,
IsSystemModule: isSystemModule);
_modulesByHandle[handle] = entry;
if (!string.IsNullOrWhiteSpace(normalizedPath))
{
_handleByPath[normalizedPath] = handle;
}
_handleByName[name] = handle;
return handle;
}
}
public static int RegisterSyntheticModule(string moduleName, bool isSystemModule)
{
lock (_gate)
{
if (TryResolveHandleByNameLocked(moduleName, out var existingHandle))
{
var existing = _modulesByHandle[existingHandle];
_modulesByHandle[existingHandle] = existing with { IsSystemModule = existing.IsSystemModule || isSystemModule };
return existingHandle;
}
var handle = _nextHandle++;
var fallbackName = string.IsNullOrWhiteSpace(moduleName)
? $"module_{handle:X4}.sprx"
: moduleName;
var entry = new ModuleEntry(
Handle: handle,
Name: fallbackName,
Path: string.Empty,
BaseAddress: 0,
EndAddress: 0,
EntryPoint: 0,
IsMain: false,
IsSystemModule: isSystemModule);
_modulesByHandle[handle] = entry;
_handleByName[fallbackName] = handle;
return handle;
}
}
public static int MarkSysmoduleLoaded(int sysmoduleId)
{
lock (_gate)
{
if (_sysmoduleHandleById.TryGetValue(sysmoduleId, out var existingHandle) &&
_modulesByHandle.ContainsKey(existingHandle))
{
return existingHandle;
}
var handle = TryResolveKnownSysmoduleHandleLocked(sysmoduleId, out var knownHandle)
? knownHandle
: RegisterSyntheticModule($"sysmodule_0x{sysmoduleId:X4}.sprx", isSystemModule: true);
_sysmoduleHandleById[sysmoduleId] = handle;
return handle;
}
}
public static void MarkSysmoduleUnloaded(int sysmoduleId)
{
lock (_gate)
{
_sysmoduleHandleById.Remove(sysmoduleId);
}
}
public static bool IsSysmoduleLoaded(int sysmoduleId)
{
lock (_gate)
{
return _sysmoduleHandleById.ContainsKey(sysmoduleId);
}
}
public static bool TryGetModuleByAddress(ulong address, out ModuleEntry module)
{
lock (_gate)
{
ModuleEntry? best = null;
foreach (var entry in _modulesByHandle.Values)
{
if (entry.BaseAddress == 0 || entry.EndAddress <= entry.BaseAddress)
{
continue;
}
if (address < entry.BaseAddress || address >= entry.EndAddress)
{
continue;
}
if (best is null || (entry.EndAddress - entry.BaseAddress) < (best.Value.EndAddress - best.Value.BaseAddress))
{
best = entry;
}
}
if (best is null)
{
module = default;
return false;
}
module = best.Value;
return true;
}
}
public static bool TryGetModuleByHandle(int handle, out ModuleEntry module)
{
lock (_gate)
{
return _modulesByHandle.TryGetValue(handle, out module);
}
}
public static bool TryFindByPathOrName(string? modulePathOrName, out ModuleEntry module)
{
module = default;
if (string.IsNullOrWhiteSpace(modulePathOrName))
{
return false;
}
var normalizedPath = NormalizePath(modulePathOrName);
var fileName = ResolveName(normalizedPath, 0);
lock (_gate)
{
if (!string.IsNullOrWhiteSpace(normalizedPath) &&
_handleByPath.TryGetValue(normalizedPath, out var handleByPath) &&
_modulesByHandle.TryGetValue(handleByPath, out module))
{
return true;
}
if (TryResolveHandleByNameLocked(fileName, out var handleByName) &&
_modulesByHandle.TryGetValue(handleByName, out module))
{
return true;
}
}
return false;
}
public static int[] GetModuleHandles(bool includeSystemModules)
{
lock (_gate)
{
return _modulesByHandle.Values
.Where(entry => includeSystemModules || !entry.IsSystemModule)
.OrderBy(entry => entry.Handle)
.Select(entry => entry.Handle)
.ToArray();
}
}
public static bool TryGetFirstModule(out ModuleEntry module)
{
lock (_gate)
{
if (_modulesByHandle.Count == 0)
{
module = default;
return false;
}
var first = _modulesByHandle.Values.OrderBy(entry => entry.Handle).First();
module = first;
return true;
}
}
private static bool TryResolveKnownSysmoduleHandleLocked(int sysmoduleId, out int handle)
{
handle = 0;
if (!KnownSysmoduleNames.TryGetValue(sysmoduleId, out var candidates))
{
return false;
}
foreach (var candidate in candidates)
{
if (TryResolveHandleByNameLocked(candidate, out handle))
{
var existing = _modulesByHandle[handle];
_modulesByHandle[handle] = existing with { IsSystemModule = true };
return true;
}
}
return false;
}
private static bool TryResolveHandleByNameLocked(string moduleName, out int handle)
{
handle = 0;
if (string.IsNullOrWhiteSpace(moduleName))
{
return false;
}
if (_handleByName.TryGetValue(moduleName, out handle))
{
return true;
}
var altName = moduleName.EndsWith(".sprx", StringComparison.OrdinalIgnoreCase)
? moduleName[..^5] + ".prx"
: (moduleName.EndsWith(".prx", StringComparison.OrdinalIgnoreCase)
? moduleName[..^4] + ".sprx"
: string.Empty);
return !string.IsNullOrWhiteSpace(altName) && _handleByName.TryGetValue(altName, out handle);
}
private static ulong ComputeEnd(ulong baseAddress, ulong size)
{
if (size == 0)
{
return baseAddress;
}
return unchecked(baseAddress + size);
}
private static string NormalizePath(string? modulePath)
{
if (string.IsNullOrWhiteSpace(modulePath))
{
return string.Empty;
}
try
{
return Path.GetFullPath(modulePath).Replace('\\', '/');
}
catch
{
return modulePath.Replace('\\', '/');
}
}
private static string ResolveName(string normalizedPath, int handleHint)
{
if (!string.IsNullOrWhiteSpace(normalizedPath))
{
var fileName = Path.GetFileName(normalizedPath);
if (!string.IsNullOrWhiteSpace(fileName))
{
return fileName;
}
}
return handleHint <= 0
? "module.sprx"
: $"module_{handleHint:X4}.sprx";
}
}
@@ -0,0 +1,815 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
public static class KernelPthreadCompatExports
{
private const int MutexTypeNormal = 0;
private const int MutexTypeRecursive = 1;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
private static readonly Dictionary<ulong, PthreadMutexAttrState> _mutexAttrStates = new();
private static readonly Dictionary<ulong, PthreadCondState> _condStates = new();
private static readonly HashSet<ulong> _condAttrStates = new();
private static long _nextSyntheticThreadId = 1;
private static long _nextSyntheticMutexHandleId = 1;
private static long _nextSyntheticMutexAttrHandleId = 1;
[ThreadStatic]
private static ulong _currentThreadId;
private sealed class PthreadMutexState
{
public SemaphoreSlim Semaphore { get; } = new(1, 1);
public ulong OwnerThreadId { get; set; }
public int RecursionCount { get; set; }
public int Type { get; set; } = MutexTypeNormal;
public int Protocol { get; set; }
}
private sealed class PthreadCondState
{
public int PendingSignals { get; set; }
public int Waiters { get; set; }
}
private readonly record struct PthreadMutexAttrState(int Type, int Protocol);
[SysAbiExport(
Nid = "aI+OeCz8xrQ",
ExportName = "scePthreadSelf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSelf(CpuContext ctx)
{
var currentThreadId = GetCurrentThreadId();
ctx[CpuRegister.Rax] = currentThreadId;
TracePthreadSelf(ctx, currentThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "3PtV6p3QNX4",
ExportName = "scePthreadEqual",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadEqual(CpuContext ctx)
{
var left = ctx[CpuRegister.Rdi];
var right = ctx[CpuRegister.Rsi];
ctx[CpuRegister.Rax] = left == right ? 1UL : 0UL;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "T72hz6ffq08",
ExportName = "scePthreadYield",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadYield(CpuContext ctx)
{
_ = ctx;
Thread.Yield();
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "EI-5-jlq2dE",
ExportName = "scePthreadGetthreadid",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadGetthreadid(CpuContext ctx)
{
var currentThreadId = GetCurrentThreadId();
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress != 0 && !ctx.TryWriteUInt64(outAddress, currentThreadId))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = currentThreadId;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "cmo1RIYva9o",
ExportName = "scePthreadMutexInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexInit(CpuContext ctx) => PthreadMutexInitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi]);
[SysAbiExport(
Nid = "2Of0f+3mhhE",
ExportName = "scePthreadMutexDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexDestroy(CpuContext ctx) => PthreadMutexDestroyCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "9UK1vLZQft4",
ExportName = "scePthreadMutexLock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexLock(CpuContext ctx) => PthreadMutexLockCore(ctx, ctx[CpuRegister.Rdi], tryOnly: false);
[SysAbiExport(
Nid = "upoVrzMHFeE",
ExportName = "scePthreadMutexTrylock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexTrylock(CpuContext ctx) => PthreadMutexLockCore(ctx, ctx[CpuRegister.Rdi], tryOnly: true);
[SysAbiExport(
Nid = "tn3VlD0hG60",
ExportName = "scePthreadMutexUnlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexUnlock(CpuContext ctx) => PthreadMutexUnlockCore(ctx, ctx[CpuRegister.Rdi], requireOwner: true);
[SysAbiExport(
Nid = "ttHNfU+qDBU",
ExportName = "pthread_mutex_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexInit(CpuContext ctx) => PthreadMutexInitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi]);
[SysAbiExport(
Nid = "ltCfaGr2JGE",
ExportName = "pthread_mutex_destroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexDestroy(CpuContext ctx) => PthreadMutexDestroyCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "7H0iTOciTLo",
ExportName = "pthread_mutex_lock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexLock(CpuContext ctx) => PthreadMutexLockCore(ctx, ctx[CpuRegister.Rdi], tryOnly: false);
[SysAbiExport(
Nid = "K-jXhbt2gn4",
ExportName = "pthread_mutex_trylock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexTrylock(CpuContext ctx) => PthreadMutexLockCore(ctx, ctx[CpuRegister.Rdi], tryOnly: true);
[SysAbiExport(
Nid = "2Z+PpY6CaJg",
ExportName = "pthread_mutex_unlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexUnlock(CpuContext ctx) => PthreadMutexUnlockCore(ctx, ctx[CpuRegister.Rdi], requireOwner: true);
[SysAbiExport(
Nid = "F8bUHwAG284",
ExportName = "scePthreadMutexattrInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexattrInit(CpuContext ctx) => PthreadMutexattrInitCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "smWEktiyyG0",
ExportName = "scePthreadMutexattrDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexattrDestroy(CpuContext ctx) => PthreadMutexattrDestroyCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "iMp8QpE+XO4",
ExportName = "scePthreadMutexattrSettype",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "1FGvU0i9saQ",
ExportName = "scePthreadMutexattrSetprotocol",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadMutexattrSetprotocol(CpuContext ctx) => PthreadMutexattrSetprotocolCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "dQHWEsJtoE4",
ExportName = "pthread_mutexattr_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrInit(CpuContext ctx) => PthreadMutexattrInitCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "HF7lK46xzjY",
ExportName = "pthread_mutexattr_destroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrDestroy(CpuContext ctx) => PthreadMutexattrDestroyCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "mDmgMOGVUqg",
ExportName = "pthread_mutexattr_settype",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadMutexattrSettype(CpuContext ctx) => PthreadMutexattrSettypeCore(ctx, ctx[CpuRegister.Rdi], unchecked((int)ctx[CpuRegister.Rsi]));
[SysAbiExport(
Nid = "2Tb92quprl0",
ExportName = "scePthreadCondInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "g+PZd2hiacg",
ExportName = "scePthreadCondDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx[CpuRegister.Rdi]);
[SysAbiExport(
Nid = "WKAXJ4XBPQ4",
ExportName = "scePthreadCondWait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondWait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: false);
[SysAbiExport(
Nid = "BmMjYxmew1w",
ExportName = "scePthreadCondTimedwait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondTimedwait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: true);
[SysAbiExport(
Nid = "kDh-NfxgMtE",
ExportName = "scePthreadCondSignal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondSignal(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: false);
[SysAbiExport(
Nid = "JGgj7Uvrl+A",
ExportName = "scePthreadCondBroadcast",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport(
Nid = "Op8TBGY5KHg",
ExportName = "pthread_cond_wait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadCondWait(CpuContext ctx) => PthreadCondWaitCore(ctx, ctx[CpuRegister.Rdi], ctx[CpuRegister.Rsi], timed: false);
[SysAbiExport(
Nid = "mkx2fVhNMsg",
ExportName = "pthread_cond_broadcast",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadCondBroadcast(CpuContext ctx) => PthreadCondSignalCore(ctx[CpuRegister.Rdi], broadcast: true);
[SysAbiExport(
Nid = "m5-2bsNfv7s",
ExportName = "scePthreadCondattrInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondattrInit(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
_condAttrStates.Add(attrAddress);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "waPcxYiR3WA",
ExportName = "scePthreadCondattrDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadCondattrDestroy(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
_condAttrStates.Remove(attrAddress);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexInitCore(CpuContext ctx, ulong mutexAddress, ulong attrAddress)
{
if (mutexAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var attr = ResolveMutexAttrState(ctx, attrAddress);
var state = new PthreadMutexState
{
Type = attr.Type,
Protocol = attr.Protocol,
};
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
lock (_stateGate)
{
_mutexStates[mutexAddress] = state;
_mutexStates[syntheticHandle] = state;
}
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexDestroyCore(CpuContext ctx, ulong mutexAddress)
{
if (mutexAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
PthreadMutexState? state;
lock (_stateGate)
{
_mutexStates.TryGetValue(resolvedAddress, out state);
_mutexStates.Remove(resolvedAddress);
if (resolvedAddress != mutexAddress)
{
_mutexStates.Remove(mutexAddress);
}
}
if (state is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexLockCore(CpuContext ctx, ulong mutexAddress, bool tryOnly)
{
if (mutexAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
PthreadMutexState state;
lock (_stateGate)
{
if (!_mutexStates.TryGetValue(resolvedAddress, out state!))
{
state = new PthreadMutexState();
_mutexStates[resolvedAddress] = state;
}
if (resolvedAddress != mutexAddress)
{
_mutexStates[mutexAddress] = state;
}
}
var currentThreadId = GetCurrentThreadId();
lock (state)
{
if (state.OwnerThreadId == currentThreadId)
{
if (state.Type == MutexTypeRecursive)
{
state.RecursionCount++;
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
}
var acquired = true;
if (tryOnly)
{
acquired = state.Semaphore.Wait(0);
}
else
{
state.Semaphore.Wait();
}
if (!acquired)
{
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
lock (state)
{
state.OwnerThreadId = currentThreadId;
state.RecursionCount = 1;
}
TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexUnlockCore(CpuContext ctx, ulong mutexAddress, bool requireOwner)
{
if (mutexAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress);
PthreadMutexState? state;
lock (_stateGate)
{
_mutexStates.TryGetValue(resolvedAddress, out state);
}
if (state is null)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, GetCurrentThreadId(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var currentThreadId = GetCurrentThreadId();
var shouldRelease = false;
lock (state)
{
if (state.RecursionCount <= 0)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (requireOwner && state.OwnerThreadId != currentThreadId)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
state.RecursionCount--;
if (state.RecursionCount == 0)
{
state.OwnerThreadId = 0;
shouldRelease = true;
}
}
if (shouldRelease)
{
try
{
state.Semaphore.Release();
}
catch (SemaphoreFullException)
{
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
}
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, state, currentThreadId, (int)OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexattrInitCore(CpuContext ctx, ulong attrAddress)
{
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
lock (_stateGate)
{
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeNormal, 0);
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeNormal, 0);
}
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexattrDestroyCore(CpuContext ctx, ulong attrAddress)
{
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
lock (_stateGate)
{
_mutexAttrStates.Remove(resolvedAddress);
if (resolvedAddress != attrAddress)
{
_mutexAttrStates.Remove(attrAddress);
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexattrSettypeCore(CpuContext ctx, ulong attrAddress, int type)
{
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
lock (_stateGate)
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{
state = new PthreadMutexAttrState(MutexTypeNormal, 0);
}
_mutexAttrStates[resolvedAddress] = state with { Type = type };
if (resolvedAddress != attrAddress)
{
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadMutexattrSetprotocolCore(CpuContext ctx, ulong attrAddress, int protocol)
{
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
lock (_stateGate)
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
{
state = new PthreadMutexAttrState(MutexTypeNormal, 0);
}
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
if (resolvedAddress != attrAddress)
{
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static ulong ResolveMutexHandle(CpuContext ctx, ulong mutexAddress)
{
if (mutexAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_mutexStates.ContainsKey(mutexAddress))
{
return mutexAddress;
}
}
if (ctx.TryReadUInt64(mutexAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_mutexStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return mutexAddress;
}
private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress)
{
if (attrAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_mutexAttrStates.ContainsKey(attrAddress))
{
return attrAddress;
}
}
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_mutexAttrStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return attrAddress;
}
private static PthreadMutexAttrState ResolveMutexAttrState(CpuContext ctx, ulong attrAddress)
{
if (attrAddress == 0)
{
return default;
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
lock (_stateGate)
{
return _mutexAttrStates.TryGetValue(resolvedAddress, out var state)
? state
: default;
}
}
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
{
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
return baseAddress + (id << 4);
}
private static int PthreadCondInitCore(ulong condAddress)
{
if (condAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
_condStates[condAddress] = new PthreadCondState();
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadCondDestroyCore(ulong condAddress)
{
if (condAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
_condStates.Remove(condAddress);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadCondWaitCore(CpuContext ctx, ulong condAddress, ulong mutexAddress, bool timed)
{
if (condAddress == 0 || mutexAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
if (!_condStates.TryGetValue(condAddress, out var state))
{
state = new PthreadCondState();
_condStates[condAddress] = state;
}
state.Waiters++;
if (state.PendingSignals > 0)
{
state.PendingSignals--;
state.Waiters--;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
var unlockResult = PthreadMutexUnlockCore(ctx, mutexAddress, requireOwner: true);
if (unlockResult != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
return unlockResult;
}
if (timed)
{
Thread.Sleep(1);
}
else
{
Thread.Yield();
}
var lockResult = PthreadMutexLockCore(ctx, mutexAddress, tryOnly: false);
lock (_stateGate)
{
if (_condStates.TryGetValue(condAddress, out var state))
{
state.Waiters = Math.Max(0, state.Waiters - 1);
}
}
return lockResult;
}
private static int PthreadCondSignalCore(ulong condAddress, bool broadcast)
{
if (condAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
if (!_condStates.TryGetValue(condAddress, out var state))
{
state = new PthreadCondState();
_condStates[condAddress] = state;
}
if (broadcast)
{
state.PendingSignals += Math.Max(1, state.Waiters);
}
else
{
state.PendingSignals++;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static ulong GetCurrentThreadId()
{
if (_currentThreadId != 0)
{
return _currentThreadId;
}
_currentThreadId = unchecked((ulong)Interlocked.Increment(ref _nextSyntheticThreadId));
return _currentThreadId;
}
private static void TracePthreadSelf(CpuContext ctx, ulong currentThreadId)
{
if (!ShouldTracePthread())
{
return;
}
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_self: stale_rdi=0x{ctx[CpuRegister.Rdi]:X16} thread=0x{currentThreadId:X16}");
}
private static void TracePthreadMutex(CpuContext ctx, string operation, ulong mutexAddress, ulong resolvedAddress, PthreadMutexState? state, ulong currentThreadId, int result)
{
if (!ShouldTracePthread())
{
return;
}
_ = ctx.TryReadUInt64(mutexAddress, out var guestWord0);
_ = ctx.TryReadUInt64(mutexAddress + 8, out var guestWord1);
Console.Error.WriteLine(
$"[LOADER][TRACE] pthread_{operation}: mutex=0x{mutexAddress:X16} resolved=0x{resolvedAddress:X16} " +
$"guest[0]=0x{guestWord0:X16} guest[8]=0x{guestWord1:X16} " +
$"current=0x{currentThreadId:X16} owner=0x{(state?.OwnerThreadId ?? 0):X16} " +
$"recursion={(state?.RecursionCount ?? 0)} type={(state?.Type ?? 0)} result=0x{unchecked((uint)result):X8}");
}
private static bool ShouldTracePthread()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_PTHREADS"), "1", StringComparison.Ordinal);
}
}
@@ -0,0 +1,902 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Text;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
public static class KernelPthreadExtendedCompatExports
{
private const int DefaultThreadPriority = 700;
private const ulong DefaultThreadAffinityMask = ulong.MaxValue;
private const int DefaultDetachState = 0;
private const ulong DefaultGuardSize = 0x1000UL;
private const ulong DefaultStackSize = 0x1_00000UL;
private const int DefaultInheritSched = 0;
private const int DefaultSchedPolicy = 0;
private const int DefaultSchedPriority = 0;
private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
private static readonly Dictionary<ulong, PthreadAttrState> _attrStates = new();
private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new();
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
private static int _nextTlsKey = 1;
[ThreadStatic]
private static Dictionary<int, ulong>? _threadLocalSpecific;
private sealed class ThreadState
{
public string Name { get; set; } = string.Empty;
public int Priority { get; set; } = DefaultThreadPriority;
public ulong AffinityMask { get; set; } = DefaultThreadAffinityMask;
public int DetachState { get; set; } = DefaultDetachState;
public PthreadAttrState Attributes { get; set; } = PthreadAttrState.Default;
}
private readonly record struct TlsKeyState(ulong Destructor);
private readonly record struct PthreadAttrState(
ulong AffinityMask,
int DetachState,
ulong StackAddress,
ulong StackSize,
ulong GuardSize,
int InheritSched,
int SchedPolicy,
int SchedPriority)
{
public static PthreadAttrState Default =>
new(
DefaultThreadAffinityMask,
DefaultDetachState,
0,
DefaultStackSize,
DefaultGuardSize,
DefaultInheritSched,
DefaultSchedPolicy,
DefaultSchedPriority);
}
[SysAbiExport(
Nid = "4qGrR6eoP9Y",
ExportName = "scePthreadDetach",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadDetach(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
if (thread == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateThreadStateLocked(thread);
state.DetachState = 1;
state.Attributes = state.Attributes with { DetachState = 1 };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "How7B8Oet6k",
ExportName = "scePthreadGetname",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadGetname(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
var outNameAddress = ctx[CpuRegister.Rsi];
if (thread == 0 || outNameAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
string name;
lock (_stateGate)
{
name = GetOrCreateThreadStateLocked(thread).Name;
}
if (!TryWriteFixedUtf8CString(ctx, outNameAddress, name, 32))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "bt3CTBKmGyI",
ExportName = "scePthreadSetaffinity",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSetaffinity(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
var mask = ctx[CpuRegister.Rsi];
if (thread == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateThreadStateLocked(thread);
state.AffinityMask = mask;
state.Attributes = state.Attributes with { AffinityMask = mask };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "1tKyG7RlMJo",
ExportName = "scePthreadGetprio",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadGetprio(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
var outPriorityAddress = ctx[CpuRegister.Rsi];
if (thread == 0 || outPriorityAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
int priority;
lock (_stateGate)
{
priority = GetOrCreateThreadStateLocked(thread).Priority;
}
if (!TryWriteInt32(ctx, outPriorityAddress, priority))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((uint)priority);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "W0Hpm2X0uPE",
ExportName = "scePthreadSetprio",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSetprio(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
var priority = unchecked((int)ctx[CpuRegister.Rsi]);
if (thread == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
GetOrCreateThreadStateLocked(thread).Priority = priority;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "nsYoNRywwNg",
ExportName = "scePthreadAttrInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrInit(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
_attrStates[attrAddress] = PthreadAttrState.Default;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "62KCwEMmzcM",
ExportName = "scePthreadAttrDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrDestroy(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
_attrStates.Remove(attrAddress);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "x1X76arYMxU",
ExportName = "scePthreadAttrGet",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGet(CpuContext ctx)
{
var thread = ctx[CpuRegister.Rdi];
var outAttrAddress = ctx[CpuRegister.Rsi];
if (thread == 0 || outAttrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var threadState = GetOrCreateThreadStateLocked(thread);
_attrStates[outAttrAddress] = threadState.Attributes;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "8+s5BzZjxSg",
ExportName = "scePthreadAttrGetaffinity",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetaffinity(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var outMaskAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || outMaskAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!ctx.TryWriteUInt64(outMaskAddress, state.AffinityMask))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = state.AffinityMask;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "JaRMy+QcpeU",
ExportName = "scePthreadAttrGetdetachstate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetdetachstate(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var outStateAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || outStateAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!TryWriteInt32(ctx, outStateAddress, state.DetachState))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((uint)state.DetachState);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "txHtngJ+eyc",
ExportName = "scePthreadAttrGetguardsize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetguardsize(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var outGuardSizeAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || outGuardSizeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!ctx.TryWriteUInt64(outGuardSizeAddress, state.GuardSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = state.GuardSize;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ru36fiTtJzA",
ExportName = "scePthreadAttrGetstackaddr",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetstackaddr(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var outStackAddressPointer = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || outStackAddressPointer == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!ctx.TryWriteUInt64(outStackAddressPointer, state.StackAddress))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = state.StackAddress;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "-fA+7ZlGDQs",
ExportName = "scePthreadAttrGetstacksize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetstacksize(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var outStackSizeAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || outStackSizeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!ctx.TryWriteUInt64(outStackSizeAddress, state.StackSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = state.StackSize;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "3qxgM4ezETA",
ExportName = "scePthreadAttrSetaffinity",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetaffinity(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var mask = ctx[CpuRegister.Rsi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { AffinityMask = mask };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "-Wreprtu0Qs",
ExportName = "scePthreadAttrSetdetachstate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetdetachstate(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var detachState = unchecked((int)ctx[CpuRegister.Rsi]);
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { DetachState = detachState };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "El+cQ20DynU",
ExportName = "scePthreadAttrSetguardsize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetguardsize(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var guardSize = ctx[CpuRegister.Rsi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { GuardSize = guardSize };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "eXbUSpEaTsA",
ExportName = "scePthreadAttrSetinheritsched",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetinheritsched(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var inheritSched = unchecked((int)ctx[CpuRegister.Rsi]);
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { InheritSched = inheritSched };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "DzES9hQF4f4",
ExportName = "scePthreadAttrSetschedparam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetschedparam(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var schedParamAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || schedParamAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadInt32(ctx, schedParamAddress, out var schedPriority))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { SchedPriority = schedPriority };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "4+h9EzwKF4I",
ExportName = "scePthreadAttrSetschedpolicy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetschedpolicy(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var policy = unchecked((int)ctx[CpuRegister.Rsi]);
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { SchedPolicy = policy };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "UTXzJbWhhTE",
ExportName = "scePthreadAttrSetstacksize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrSetstacksize(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var stackSize = ctx[CpuRegister.Rsi];
if (attrAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
var state = GetOrCreateAttrStateLocked(attrAddress);
_attrStates[attrAddress] = state with { StackSize = stackSize };
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6ULAa0fq4jA",
ExportName = "scePthreadRwlockInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadRwlockInit(CpuContext ctx)
{
var rwlockAddress = ctx[CpuRegister.Rdi];
if (rwlockAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_stateGate)
{
if (_rwlockStates.Remove(rwlockAddress, out var existing))
{
existing.Dispose();
}
_rwlockStates[rwlockAddress] = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "BB+kb08Tl9A",
ExportName = "scePthreadRwlockDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadRwlockDestroy(CpuContext ctx)
{
var rwlockAddress = ctx[CpuRegister.Rdi];
if (rwlockAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ReaderWriterLockSlim? state;
lock (_stateGate)
{
_rwlockStates.Remove(rwlockAddress, out state);
}
if (state is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
state.Dispose();
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Ox9i0c7L5w0",
ExportName = "scePthreadRwlockRdlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: false);
[SysAbiExport(
Nid = "mqdNorrB+gI",
ExportName = "scePthreadRwlockWrlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: true);
[SysAbiExport(
Nid = "+L98PIbGttk",
ExportName = "scePthreadRwlockUnlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadRwlockUnlock(CpuContext ctx)
{
var rwlockAddress = ctx[CpuRegister.Rdi];
if (rwlockAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ReaderWriterLockSlim? rwlock;
lock (_stateGate)
{
_rwlockStates.TryGetValue(rwlockAddress, out rwlock);
}
if (rwlock is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
try
{
if (rwlock.IsWriteLockHeld)
{
rwlock.ExitWriteLock();
}
else if (rwlock.IsReadLockHeld)
{
rwlock.ExitReadLock();
}
else
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
}
catch (SynchronizationLockException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "mqULNdimTn0",
ExportName = "pthread_key_create",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadKeyCreate(CpuContext ctx)
{
var outKeyAddress = ctx[CpuRegister.Rdi];
var destructor = ctx[CpuRegister.Rsi];
if (outKeyAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
int key;
lock (_stateGate)
{
while (_tlsKeys.ContainsKey(_nextTlsKey))
{
_nextTlsKey++;
}
key = _nextTlsKey++;
_tlsKeys[key] = new TlsKeyState(destructor);
}
if (!TryWriteInt32(ctx, outKeyAddress, key))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((uint)key);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "6BpEZuDT7YI",
ExportName = "pthread_key_delete",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadKeyDelete(CpuContext ctx)
{
var key = unchecked((int)ctx[CpuRegister.Rdi]);
lock (_stateGate)
{
if (!_tlsKeys.Remove(key))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
_threadLocalSpecific?.Remove(key);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "WrOLvHU0yQM",
ExportName = "pthread_setspecific",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadSetspecific(CpuContext ctx)
{
var key = unchecked((int)ctx[CpuRegister.Rdi]);
var value = ctx[CpuRegister.Rsi];
lock (_stateGate)
{
if (!_tlsKeys.TryGetValue(key, out _))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
}
_threadLocalSpecific ??= new Dictionary<int, ulong>();
_threadLocalSpecific[key] = value;
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "0-KXaS70xy4",
ExportName = "pthread_getspecific",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadGetspecific(CpuContext ctx)
{
var key = unchecked((int)ctx[CpuRegister.Rdi]);
lock (_stateGate)
{
if (!_tlsKeys.TryGetValue(key, out _))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
ctx[CpuRegister.Rax] =
_threadLocalSpecific is not null && _threadLocalSpecific.TryGetValue(key, out var value)
? value
: 0UL;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int PthreadRwlockLockCore(ulong rwlockAddress, bool write)
{
if (rwlockAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ReaderWriterLockSlim rwlock;
lock (_stateGate)
{
if (!_rwlockStates.TryGetValue(rwlockAddress, out rwlock!))
{
rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_rwlockStates[rwlockAddress] = rwlock;
}
}
try
{
if (write)
{
rwlock.EnterWriteLock();
}
else
{
rwlock.EnterReadLock();
}
}
catch (LockRecursionException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static ThreadState GetOrCreateThreadStateLocked(ulong thread)
{
if (_threadStates.TryGetValue(thread, out var state))
{
return state;
}
state = new ThreadState
{
Name = $"Thread-{thread:X}",
Priority = DefaultThreadPriority,
AffinityMask = DefaultThreadAffinityMask,
DetachState = DefaultDetachState,
Attributes = PthreadAttrState.Default,
};
_threadStates[thread] = state;
return state;
}
private static PthreadAttrState GetOrCreateAttrStateLocked(ulong attrAddress)
{
if (_attrStates.TryGetValue(attrAddress, out var state))
{
return state;
}
state = PthreadAttrState.Default;
_attrStates[attrAddress] = state;
return state;
}
private static bool TryWriteFixedUtf8CString(CpuContext ctx, ulong address, string value, int maxBytes)
{
if (maxBytes <= 0)
{
return false;
}
var utf8 = Encoding.UTF8.GetBytes(value);
var payloadLength = Math.Min(utf8.Length, maxBytes - 1);
var payload = new byte[payloadLength + 1];
utf8.AsSpan(0, payloadLength).CopyTo(payload);
payload[^1] = 0;
return ctx.Memory.TryWrite(address, payload);
}
private static bool TryReadInt32(CpuContext ctx, ulong address, out int value)
{
Span<byte> bytes = stackalloc byte[sizeof(int)];
if (!ctx.Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadInt32LittleEndian(bytes);
return true;
}
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);
}
}
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
<!--
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\SharpEmu.HLE\SharpEmu.HLE.csproj" />
</ItemGroup>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
@@ -0,0 +1,10 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
namespace SharpEmu.Libs.VideoOut;
public static class VideoOutExports
{
}
+10
View File
@@ -0,0 +1,10 @@
{
"version": 2,
"dependencies": {
"net10.0": {
"sharpemu.hle": {
"type": "Project"
}
}
}
}