mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-15 15:36:11 +08:00
initial commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dependencies": {
|
||||
"net10.0": {
|
||||
"sharpemu.hle": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user