[kernel] Add thread and event primitives

This commit is contained in:
ParantezTech
2026-06-21 23:18:42 +03:00
parent 2a52ccfe6a
commit 5512da9e12
5 changed files with 1668 additions and 49 deletions
+961
View File
@@ -0,0 +1,961 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Fiber;
public static class FiberExports
{
private const int MaxNameLength = 31;
private const int FiberInfoSize = 128;
private const int FiberContextMinimumSize = 512;
private const uint FiberSignature0 = 0xDEF1649C;
private const uint FiberSignature1 = 0xB37592A0;
private const uint FiberOptSignature = 0xBB40E64D;
private const ulong FiberStackSignature = 0x7149F2CA7149F2CAUL;
private const ulong FiberStackSizeCheck = 0xDEADBEEFDEADBEEFUL;
private const uint FiberStateRun = 1;
private const uint FiberStateIdle = 2;
private const uint FiberStateTerminated = 3;
private const uint FiberFlagContextSizeCheck = 0x10;
private const int FiberErrorNull = unchecked((int)0x80590001);
private const int FiberErrorAlignment = unchecked((int)0x80590002);
private const int FiberErrorRange = unchecked((int)0x80590003);
private const int FiberErrorInvalid = unchecked((int)0x80590004);
private const int FiberErrorPermission = unchecked((int)0x80590005);
private const int FiberErrorState = unchecked((int)0x80590006);
private const int FiberMagicStartOffset = 0;
private const int FiberStateOffset = 4;
private const int FiberEntryOffset = 8;
private const int FiberArgOnInitializeOffset = 16;
private const int FiberContextAddressOffset = 24;
private const int FiberContextSizeOffset = 32;
private const int FiberNameOffset = 40;
private const int FiberContextPointerOffset = 72;
private const int FiberFlagsOffset = 80;
private const int FiberContextStartOffset = 88;
private const int FiberContextEndOffset = 96;
private const int FiberMagicEndOffset = 104;
private static int _contextSizeCheck;
[ThreadStatic]
private static ulong _currentFiberAddress;
[ThreadStatic]
private static bool _fiberReturnRequested;
[ThreadStatic]
private static ulong _fiberReturnArgument;
private static readonly ConcurrentDictionary<ulong, FiberContinuation> _continuations = new();
private static readonly ConcurrentDictionary<ulong, FiberStackRange> _stackRanges = new();
private static readonly ConcurrentDictionary<ulong, FiberRunSession> _runSessions = new();
[SysAbiExport(
Nid = "hVYD7Ou2pCQ",
ExportName = "_sceFiberInitializeImpl",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberInitialize(CpuContext ctx)
{
var optParam = ReadStackArg64(ctx, 0);
return FiberInitializeCore(
ctx,
ctx[CpuRegister.Rdi],
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
ctx[CpuRegister.Rcx],
ctx[CpuRegister.R8],
ctx[CpuRegister.R9],
optParam,
flags: 0);
}
[SysAbiExport(
Nid = "7+OJIpko9RY",
ExportName = "_sceFiberInitializeWithInternalOptionImpl",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberInitializeWithInternalOption(CpuContext ctx)
{
var optParam = ReadStackArg64(ctx, 0);
var flags = unchecked((uint)ReadStackArg64(ctx, 1));
return FiberInitializeCore(
ctx,
ctx[CpuRegister.Rdi],
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
ctx[CpuRegister.Rcx],
ctx[CpuRegister.R8],
ctx[CpuRegister.R9],
optParam,
flags);
}
[SysAbiExport(
Nid = "asjUJJ+aa8s",
ExportName = "sceFiberOptParamInitialize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberOptParamInitialize(CpuContext ctx)
{
var optParam = ctx[CpuRegister.Rdi];
if (optParam == 0)
{
return SetReturn(ctx, FiberErrorNull);
}
if ((optParam & 7) != 0)
{
return SetReturn(ctx, FiberErrorAlignment);
}
return TryWriteUInt32(ctx, optParam, FiberOptSignature)
? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorInvalid);
}
[SysAbiExport(
Nid = "JeNX5F-NzQU",
ExportName = "sceFiberFinalize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberFinalize(CpuContext ctx)
{
var fiber = ctx[CpuRegister.Rdi];
if (!TryValidateFiber(ctx, fiber, out var error))
{
return SetReturn(ctx, error);
}
if (!TryReadUInt32(ctx, fiber + FiberStateOffset, out var state))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (state != FiberStateIdle)
{
return SetReturn(ctx, FiberErrorState);
}
_continuations.TryRemove(fiber, out _);
_stackRanges.TryRemove(fiber, out _);
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateTerminated);
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "a0LLrZWac0M",
ExportName = "sceFiberRun",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberRun(CpuContext ctx)
{
return FiberRunCore(
ctx,
ctx[CpuRegister.Rdi],
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
attachContextAddress: 0,
attachContextSize: 0,
reason: "sceFiberRun",
isSwitch: false);
}
[SysAbiExport(
Nid = "avfGJ94g36Q",
ExportName = "_sceFiberAttachContextAndRun",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberAttachContextAndRun(CpuContext ctx)
{
return FiberRunCore(
ctx,
ctx[CpuRegister.Rdi],
ctx[CpuRegister.Rcx],
ctx[CpuRegister.R8],
attachContextAddress: ctx[CpuRegister.Rsi],
attachContextSize: ctx[CpuRegister.Rdx],
reason: "_sceFiberAttachContextAndRun",
isSwitch: false);
}
[SysAbiExport(
Nid = "PFT2S-tJ7Uk",
ExportName = "sceFiberSwitch",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberSwitch(CpuContext ctx)
{
return FiberRunCore(
ctx,
ctx[CpuRegister.Rdi],
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdx],
attachContextAddress: 0,
attachContextSize: 0,
reason: "sceFiberSwitch",
isSwitch: true);
}
[SysAbiExport(
Nid = "ZqhZFuzKT6U",
ExportName = "_sceFiberAttachContextAndSwitch",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberAttachContextAndSwitch(CpuContext ctx)
{
return FiberRunCore(
ctx,
ctx[CpuRegister.Rdi],
ctx[CpuRegister.Rcx],
ctx[CpuRegister.R8],
attachContextAddress: ctx[CpuRegister.Rsi],
attachContextSize: ctx[CpuRegister.Rdx],
reason: "_sceFiberAttachContextAndSwitch",
isSwitch: true);
}
[SysAbiExport(
Nid = "B0ZX2hx9DMw",
ExportName = "sceFiberReturnToThread",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberReturnToThread(CpuContext ctx)
{
var fiberAddress = _currentFiberAddress;
var inferredFiber = false;
if (fiberAddress == 0 && TryFindFiberByStack(ctx, out fiberAddress))
{
inferredFiber = true;
}
if (fiberAddress == 0)
{
return SetReturn(ctx, FiberErrorPermission);
}
var argOnRunAddress = ctx[CpuRegister.Rsi];
if (argOnRunAddress != 0 && !TryWriteUInt64(ctx, argOnRunAddress, 0))
{
return SetReturn(ctx, FiberErrorInvalid);
}
_fiberReturnRequested = true;
_fiberReturnArgument = ctx[CpuRegister.Rdi];
if (_runSessions.TryGetValue(fiberAddress, out var session))
{
session.SetReturn(ctx[CpuRegister.Rdi]);
}
if (GuestThreadExecution.TryGetCurrentImportCallFrame(out var frame))
{
_continuations[fiberAddress] = new FiberContinuation(
CaptureContinuation(ctx, frame.ReturnRip, frame.ResumeRsp),
argOnRunAddress);
TraceFiber($"yield{(inferredFiber ? "-inferred" : string.Empty)} fiber=0x{fiberAddress:X16} resume=0x{frame.ReturnRip:X16} rsp=0x{frame.ResumeRsp:X16} arg_out=0x{argOnRunAddress:X16}");
}
else
{
TraceFiber($"yield-no-frame{(inferredFiber ? "-inferred" : string.Empty)} fiber=0x{fiberAddress:X16} arg_out=0x{argOnRunAddress:X16}");
}
GuestThreadExecution.RequestCurrentEntryExit("sceFiberReturnToThread", 0);
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "p+zLIOg27zU",
ExportName = "sceFiberGetSelf",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberGetSelf(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress == 0)
{
return SetReturn(ctx, FiberErrorNull);
}
if (_currentFiberAddress == 0)
{
return SetReturn(ctx, FiberErrorPermission);
}
return TryWriteUInt64(ctx, outAddress, _currentFiberAddress)
? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorInvalid);
}
[SysAbiExport(
Nid = "uq2Y5BFz0PE",
ExportName = "sceFiberGetInfo",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberGetInfo(CpuContext ctx)
{
var fiber = ctx[CpuRegister.Rdi];
var info = ctx[CpuRegister.Rsi];
if (info == 0)
{
return SetReturn(ctx, FiberErrorNull);
}
if (!TryValidateFiber(ctx, fiber, out var error))
{
return SetReturn(ctx, error);
}
if (!TryReadUInt64(ctx, info, out var size) || size != FiberInfoSize)
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (!TryReadFiberFields(ctx, fiber, out var fields))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (!TryWriteUInt64(ctx, info + 8, fields.Entry) ||
!TryWriteUInt64(ctx, info + 16, fields.ArgOnInitialize) ||
!TryWriteUInt64(ctx, info + 24, fields.ContextAddress) ||
!TryWriteUInt64(ctx, info + 32, fields.ContextSize) ||
!TryWriteName(ctx, info + 40, fields.Name) ||
!TryWriteUInt64(ctx, info + 72, ulong.MaxValue))
{
return SetReturn(ctx, FiberErrorInvalid);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "JzyT91ucGDc",
ExportName = "sceFiberRename",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberRename(CpuContext ctx)
{
var fiber = ctx[CpuRegister.Rdi];
var nameAddress = ctx[CpuRegister.Rsi];
if (!TryValidateFiber(ctx, fiber, out var error))
{
return SetReturn(ctx, error);
}
if (nameAddress == 0)
{
return SetReturn(ctx, FiberErrorNull);
}
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxNameLength + 1, out var name))
{
return SetReturn(ctx, FiberErrorInvalid);
}
return TryWriteName(ctx, fiber + FiberNameOffset, name)
? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorInvalid);
}
[SysAbiExport(
Nid = "Lcqty+QNWFc",
ExportName = "sceFiberStartContextSizeCheck",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberStartContextSizeCheck(CpuContext ctx)
{
if (ctx[CpuRegister.Rdi] != 0)
{
return SetReturn(ctx, FiberErrorInvalid);
}
return Interlocked.Exchange(ref _contextSizeCheck, 1) == 0
? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorState);
}
[SysAbiExport(
Nid = "Kj4nXMpnM8Y",
ExportName = "sceFiberStopContextSizeCheck",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberStopContextSizeCheck(CpuContext ctx)
{
return Interlocked.Exchange(ref _contextSizeCheck, 0) == 1
? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorState);
}
[SysAbiExport(
Nid = "0dy4JtMUcMQ",
ExportName = "_sceFiberGetThreadFramePointerAddress",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceFiber")]
public static int FiberGetThreadFramePointerAddress(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress == 0)
{
return SetReturn(ctx, FiberErrorNull);
}
if (_currentFiberAddress == 0)
{
return SetReturn(ctx, FiberErrorPermission);
}
return TryWriteUInt64(ctx, outAddress, ctx[CpuRegister.Rbp])
? SetReturn(ctx, 0)
: SetReturn(ctx, FiberErrorInvalid);
}
private static int FiberInitializeCore(
CpuContext ctx,
ulong fiber,
ulong nameAddress,
ulong entry,
ulong argOnInitialize,
ulong contextAddress,
ulong contextSize,
ulong optParam,
uint flags)
{
if (fiber == 0 || nameAddress == 0 || entry == 0)
{
return SetReturn(ctx, FiberErrorNull);
}
if ((fiber & 7) != 0 ||
(contextAddress & 15) != 0 ||
(optParam & 7) != 0)
{
return SetReturn(ctx, FiberErrorAlignment);
}
if (contextSize != 0 && contextSize < FiberContextMinimumSize)
{
return SetReturn(ctx, FiberErrorRange);
}
if ((contextSize & 15) != 0 ||
(contextAddress == 0 && contextSize != 0) ||
(contextAddress != 0 && contextSize == 0))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (optParam != 0 &&
(!TryReadUInt32(ctx, optParam, out var optMagic) || optMagic != FiberOptSignature))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxNameLength + 1, out var name))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (Volatile.Read(ref _contextSizeCheck) != 0)
{
flags |= FiberFlagContextSizeCheck;
}
if (!TryWriteUInt32(ctx, fiber + FiberMagicStartOffset, FiberSignature0) ||
!TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle) ||
!TryWriteUInt64(ctx, fiber + FiberEntryOffset, entry) ||
!TryWriteUInt64(ctx, fiber + FiberArgOnInitializeOffset, argOnInitialize) ||
!TryWriteUInt64(ctx, fiber + FiberContextAddressOffset, contextAddress) ||
!TryWriteUInt64(ctx, fiber + FiberContextSizeOffset, contextSize) ||
!TryWriteName(ctx, fiber + FiberNameOffset, name) ||
!TryWriteUInt64(ctx, fiber + FiberContextPointerOffset, 0) ||
!TryWriteUInt32(ctx, fiber + FiberFlagsOffset, flags) ||
!TryWriteUInt64(ctx, fiber + FiberContextStartOffset, contextAddress) ||
!TryWriteUInt64(ctx, fiber + FiberContextEndOffset, contextAddress == 0 ? 0 : contextAddress + contextSize) ||
!TryWriteUInt32(ctx, fiber + FiberMagicEndOffset, FiberSignature1))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (contextAddress != 0)
{
if (!TryWriteUInt64(ctx, contextAddress, FiberStackSignature))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if ((flags & FiberFlagContextSizeCheck) != 0)
{
FillContextSizeCheck(ctx, contextAddress + sizeof(ulong), contextSize - sizeof(ulong));
}
}
if (contextAddress != 0 && contextSize != 0)
{
_stackRanges[fiber] = new FiberStackRange(contextAddress, contextSize);
}
TraceFiber($"init fiber=0x{fiber:X16} entry=0x{entry:X16} ctx=0x{contextAddress:X16} size=0x{contextSize:X} name='{name}'");
return SetReturn(ctx, 0);
}
private static int FiberRunCore(
CpuContext ctx,
ulong fiber,
ulong argOnRun,
ulong outArgumentAddress,
ulong attachContextAddress,
ulong attachContextSize,
string reason,
bool isSwitch)
{
if (!TryValidateFiber(ctx, fiber, out var error))
{
return SetReturn(ctx, error);
}
if (!TryReadFiberFields(ctx, fiber, out var fields))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (attachContextAddress != 0 || attachContextSize != 0)
{
var attachResult = AttachContext(ctx, fiber, attachContextAddress, attachContextSize, ref fields);
if (attachResult != 0)
{
return SetReturn(ctx, attachResult);
}
}
if (fields.State != FiberStateIdle)
{
return SetReturn(ctx, FiberErrorState);
}
var previousFiber = _currentFiberAddress;
var switchingFromFiber = isSwitch && previousFiber != 0 && previousFiber != fiber;
if (isSwitch && previousFiber == 0)
{
return SetReturn(ctx, FiberErrorPermission);
}
var scheduler = GuestThreadExecution.Scheduler;
if (scheduler is null)
{
return SetReturn(ctx, FiberErrorPermission);
}
if (!TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateRun))
{
return SetReturn(ctx, FiberErrorInvalid);
}
if (switchingFromFiber && !TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateIdle))
{
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle);
return SetReturn(ctx, FiberErrorInvalid);
}
var previousReturnRequested = _fiberReturnRequested;
var previousReturnArgument = _fiberReturnArgument;
var session = new FiberRunSession();
_runSessions[fiber] = session;
_currentFiberAddress = fiber;
_fiberReturnRequested = false;
_fiberReturnArgument = 0;
var hasContinuation = _continuations.TryGetValue(fiber, out var continuation);
bool callbackOk;
string? callbackError;
if (hasContinuation)
{
if (continuation.ArgOnRunAddress != 0 &&
!TryWriteUInt64(ctx, continuation.ArgOnRunAddress, argOnRun))
{
callbackOk = false;
callbackError = $"failed to write resumed argOnRun to 0x{continuation.ArgOnRunAddress:X16}";
}
else
{
callbackOk = scheduler.TryCallGuestContinuation(
ctx,
continuation.Context,
reason,
out callbackError);
}
}
else
{
callbackOk = scheduler.TryCallGuestFunction(
ctx,
fields.Entry,
fields.ArgOnInitialize,
argOnRun,
fields.ContextAddress,
fields.ContextSize,
reason,
out callbackError);
}
var returnRequested = _fiberReturnRequested;
var returnArgument = _fiberReturnArgument;
if (!returnRequested && session.TryGetReturn(out var sessionReturnArgument))
{
returnRequested = true;
returnArgument = sessionReturnArgument;
}
if (!returnRequested)
{
_continuations.TryRemove(fiber, out _);
}
_runSessions.TryRemove(fiber, out _);
_currentFiberAddress = previousFiber;
_fiberReturnRequested = previousReturnRequested;
_fiberReturnArgument = previousReturnArgument;
_ = TryWriteUInt32(ctx, fiber + FiberStateOffset, FiberStateIdle);
if (switchingFromFiber)
{
_ = TryWriteUInt32(ctx, previousFiber + FiberStateOffset, FiberStateRun);
}
if (!callbackOk)
{
TraceFiber($"run-failed fiber=0x{fiber:X16} entry=0x{fields.Entry:X16} error={callbackError}");
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_CPU_TRAP);
}
if (outArgumentAddress != 0 && !TryWriteUInt64(ctx, outArgumentAddress, returnArgument))
{
return SetReturn(ctx, FiberErrorInvalid);
}
TraceFiber($"run fiber=0x{fiber:X16} entry=0x{fields.Entry:X16} resume={hasContinuation} arg=0x{argOnRun:X16} ret=0x{returnArgument:X16}");
return SetReturn(ctx, 0);
}
private static GuestCpuContinuation CaptureContinuation(CpuContext ctx, ulong resumeRip, ulong resumeRsp) =>
new(
resumeRip,
resumeRsp,
ctx.Rflags == 0 ? 0x202UL : ctx.Rflags,
ctx.FsBase,
ctx.GsBase,
0,
ctx[CpuRegister.Rcx],
ctx[CpuRegister.Rdx],
ctx[CpuRegister.Rbx],
ctx[CpuRegister.Rbp],
ctx[CpuRegister.Rsi],
ctx[CpuRegister.Rdi],
ctx[CpuRegister.R8],
ctx[CpuRegister.R9],
ctx[CpuRegister.R12],
ctx[CpuRegister.R13],
ctx[CpuRegister.R14],
ctx[CpuRegister.R15]);
private static int AttachContext(
CpuContext ctx,
ulong fiber,
ulong contextAddress,
ulong contextSize,
ref FiberFields fields)
{
if ((contextAddress & 15) != 0)
{
return FiberErrorAlignment;
}
if (contextSize != 0 && contextSize < FiberContextMinimumSize)
{
return FiberErrorRange;
}
if ((contextSize & 15) != 0 ||
contextAddress == 0 ||
contextSize == 0 ||
fields.ContextAddress != 0)
{
return FiberErrorInvalid;
}
if (!TryWriteUInt64(ctx, fiber + FiberContextAddressOffset, contextAddress) ||
!TryWriteUInt64(ctx, fiber + FiberContextSizeOffset, contextSize) ||
!TryWriteUInt64(ctx, fiber + FiberContextStartOffset, contextAddress) ||
!TryWriteUInt64(ctx, fiber + FiberContextEndOffset, contextAddress + contextSize) ||
!TryWriteUInt64(ctx, contextAddress, FiberStackSignature))
{
return FiberErrorInvalid;
}
fields = fields with
{
ContextAddress = contextAddress,
ContextSize = contextSize,
};
_stackRanges[fiber] = new FiberStackRange(contextAddress, contextSize);
return 0;
}
private static bool TryFindFiberByStack(CpuContext ctx, out ulong fiber)
{
if (TryFindFiberByStackAddress(ctx[CpuRegister.Rsp], out fiber))
{
return true;
}
return TryFindFiberByStackAddress(ctx[CpuRegister.Rbp], out fiber);
}
private static bool TryFindFiberByStackAddress(ulong address, out ulong fiber)
{
if (address != 0)
{
foreach (var (candidate, range) in _stackRanges)
{
if (range.Contains(address))
{
fiber = candidate;
return true;
}
}
}
fiber = 0;
return false;
}
private static bool TryValidateFiber(CpuContext ctx, ulong fiber, out int error)
{
if (fiber == 0)
{
error = FiberErrorNull;
return false;
}
if ((fiber & 7) != 0)
{
error = FiberErrorAlignment;
return false;
}
if (!TryReadUInt32(ctx, fiber + FiberMagicStartOffset, out var magicStart) ||
!TryReadUInt32(ctx, fiber + FiberMagicEndOffset, out var magicEnd) ||
magicStart != FiberSignature0 ||
magicEnd != FiberSignature1)
{
error = FiberErrorInvalid;
return false;
}
error = 0;
return true;
}
private static bool TryReadFiberFields(CpuContext ctx, ulong fiber, out FiberFields fields)
{
fields = default;
if (!TryReadUInt32(ctx, fiber + FiberStateOffset, out var state) ||
!TryReadUInt64(ctx, fiber + FiberEntryOffset, out var entry) ||
!TryReadUInt64(ctx, fiber + FiberArgOnInitializeOffset, out var argOnInitialize) ||
!TryReadUInt64(ctx, fiber + FiberContextAddressOffset, out var contextAddress) ||
!TryReadUInt64(ctx, fiber + FiberContextSizeOffset, out var contextSize) ||
!TryReadUInt32(ctx, fiber + FiberFlagsOffset, out var flags) ||
!TryReadInlineName(ctx, fiber + FiberNameOffset, out var name))
{
return false;
}
fields = new FiberFields(
state,
entry,
argOnInitialize,
contextAddress,
contextSize,
flags,
name);
return true;
}
private static void FillContextSizeCheck(CpuContext ctx, ulong address, ulong size)
{
Span<byte> value = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(value, FiberStackSizeCheck);
var end = address + size;
for (var current = address; current + sizeof(ulong) <= end; current += sizeof(ulong))
{
_ = ctx.Memory.TryWrite(current, value);
}
}
private static ulong ReadStackArg64(CpuContext ctx, int index)
{
if (ctx.TryReadUInt64(ctx[CpuRegister.Rsp] + sizeof(ulong) + ((ulong)index * sizeof(ulong)), out var value))
{
return value;
}
return 0;
}
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (!ctx.Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return true;
}
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return ctx.Memory.TryWrite(address, buffer);
}
private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
if (!ctx.Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
return true;
}
private static bool TryWriteUInt64(CpuContext ctx, ulong address, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
return ctx.Memory.TryWrite(address, buffer);
}
private static bool TryWriteName(CpuContext ctx, ulong address, string name)
{
Span<byte> buffer = stackalloc byte[MaxNameLength + 1];
var bytes = Encoding.UTF8.GetBytes(name);
bytes.AsSpan(0, Math.Min(bytes.Length, MaxNameLength)).CopyTo(buffer);
return ctx.Memory.TryWrite(address, buffer);
}
private static bool TryReadInlineName(CpuContext ctx, ulong address, out string value)
{
Span<byte> buffer = stackalloc byte[MaxNameLength + 1];
if (!ctx.Memory.TryRead(address, buffer))
{
value = string.Empty;
return false;
}
var length = buffer.IndexOf((byte)0);
if (length < 0)
{
length = buffer.Length;
}
value = Encoding.UTF8.GetString(buffer[..length]);
return true;
}
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int capacity, out string value)
{
var bytes = new byte[capacity];
for (var index = 0; index < bytes.Length; index++)
{
Span<byte> current = stackalloc byte[1];
if (!ctx.Memory.TryRead(address + (ulong)index, current))
{
value = string.Empty;
return false;
}
if (current[0] == 0)
{
value = Encoding.UTF8.GetString(bytes, 0, index);
return true;
}
bytes[index] = current[0];
}
value = Encoding.UTF8.GetString(bytes);
return true;
}
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
private static void TraceFiber(string message)
{
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine($"[LOADER][TRACE] fiber.{message}");
}
}
private readonly record struct FiberFields(
uint State,
ulong Entry,
ulong ArgOnInitialize,
ulong ContextAddress,
ulong ContextSize,
uint Flags,
string Name);
private readonly record struct FiberContinuation(
GuestCpuContinuation Context,
ulong ArgOnRunAddress);
private sealed class FiberRunSession
{
private int _returnRequested;
private ulong _returnArgument;
public void SetReturn(ulong argument)
{
_returnArgument = argument;
Volatile.Write(ref _returnRequested, 1);
}
public bool TryGetReturn(out ulong argument)
{
if (Volatile.Read(ref _returnRequested) == 0)
{
argument = 0;
return false;
}
argument = _returnArgument;
return true;
}
}
private readonly record struct FiberStackRange(ulong Start, ulong Size)
{
public bool Contains(ulong address) =>
Size != 0 && address >= Start && address < Start + Size;
}
}
@@ -0,0 +1,447 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text;
using SharpEmu.HLE;
namespace SharpEmu.Libs.Kernel;
public static class KernelEventFlagCompatExports
{
private const int MaxEventFlagNameLength = 31;
private const int HostWaitPumpMilliseconds = 1;
private const uint AttrThreadFifo = 0x01;
private const uint AttrThreadPriority = 0x02;
private const uint AttrSingle = 0x10;
private const uint AttrMulti = 0x20;
private const uint WaitAnd = 0x01;
private const uint WaitOr = 0x02;
private const uint ClearAll = 0x10;
private const uint ClearPattern = 0x20;
private static readonly ConcurrentDictionary<ulong, EventFlagState> _eventFlags = new();
private static long _nextEventFlagHandle = 1;
private sealed class EventFlagState
{
public required string Name { get; init; }
public required uint Attributes { get; init; }
public ulong Bits { get; set; }
public int WaitingThreads { get; set; }
public object Gate { get; } = new();
}
[SysAbiExport(
Nid = "BpFoboUJoZU",
ExportName = "sceKernelCreateEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCreateEventFlag(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
var nameAddress = ctx[CpuRegister.Rsi];
var attributes = unchecked((uint)ctx[CpuRegister.Rdx]);
var initialPattern = ctx[CpuRegister.Rcx];
var optionAddress = ctx[CpuRegister.R8];
if (outAddress == 0 ||
nameAddress == 0 ||
optionAddress != 0 ||
!IsValidAttributes(attributes))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryReadNullTerminatedUtf8(ctx, nameAddress, MaxEventFlagNameLength + 1, out var name))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (Encoding.UTF8.GetByteCount(name) > MaxEventFlagNameLength)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var handle = unchecked((ulong)Interlocked.Increment(ref _nextEventFlagHandle));
_eventFlags[handle] = new EventFlagState
{
Name = name,
Attributes = attributes,
Bits = initialPattern,
};
if (!ctx.TryWriteUInt64(outAddress, handle))
{
_eventFlags.TryRemove(handle, out _);
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceEventFlag($"create handle=0x{handle:X16} name='{name}' attr=0x{attributes:X2} bits=0x{initialPattern:X16}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "8mql9OcQnd4",
ExportName = "sceKernelDeleteEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelDeleteEventFlag(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
if (!_eventFlags.TryRemove(handle, out var state))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
lock (state.Gate)
{
Monitor.PulseAll(state.Gate);
}
TraceEventFlag($"delete handle=0x{handle:X16} name='{state.Name}'");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "IOnSvHzqu6A",
ExportName = "sceKernelSetEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetEventFlag(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var pattern = ctx[CpuRegister.Rsi];
if (!_eventFlags.TryGetValue(handle, out var state))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
lock (state.Gate)
{
state.Bits |= pattern;
Monitor.PulseAll(state.Gate);
TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "7uhBFWRAS60",
ExportName = "sceKernelClearEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelClearEventFlag(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var pattern = ctx[CpuRegister.Rsi];
if (!_eventFlags.TryGetValue(handle, out var state))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
lock (state.Gate)
{
state.Bits &= pattern;
TraceEventFlag($"clear handle=0x{handle:X16} mask=0x{pattern:X16} bits=0x{state.Bits:X16}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "9lvj5DjHZiA",
ExportName = "sceKernelPollEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelPollEventFlag(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var pattern = ctx[CpuRegister.Rsi];
var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]);
var resultAddress = ctx[CpuRegister.Rcx];
if (!_eventFlags.TryGetValue(handle, out var state))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (pattern == 0 || !IsValidWaitMode(waitMode))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
lock (state.Gate)
{
if (!TryWriteResultPattern(ctx, resultAddress, state.Bits))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!IsSatisfied(state.Bits, pattern, waitMode))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY);
}
ApplyClearMode(state, pattern, waitMode);
TraceEventFlag($"poll handle=0x{handle:X16} pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
}
[SysAbiExport(
Nid = "JTvBflhYazQ",
ExportName = "sceKernelWaitEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelWaitEventFlag(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var pattern = ctx[CpuRegister.Rsi];
var waitMode = unchecked((uint)ctx[CpuRegister.Rdx]);
var resultAddress = ctx[CpuRegister.Rcx];
var timeoutAddress = ctx[CpuRegister.R8];
if (!_eventFlags.TryGetValue(handle, out var state))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
if (pattern == 0 || !IsValidWaitMode(waitMode))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
uint timeoutUsec = 0;
if (timeoutAddress != 0 && !TryReadUInt32(ctx, timeoutAddress, out timeoutUsec))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
Monitor.Enter(state.Gate);
try
{
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var immediateWaitResult))
{
return SetReturn(ctx, immediateWaitResult);
}
if (timeoutAddress != 0)
{
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits);
TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} timeout={timeoutUsec}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
}
if (!GuestThreadExecution.RequestCurrentThreadBlock("sceKernelWaitEventFlag"))
{
var scheduler = GuestThreadExecution.Scheduler;
if (scheduler is null)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
}
state.WaitingThreads++;
TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads}");
var releaseWaiter = true;
try
{
while (true)
{
Monitor.Exit(state.Gate);
try
{
scheduler.Pump(ctx, "sceKernelWaitEventFlag");
}
finally
{
Monitor.Enter(state.Gate);
}
if (TryCompleteSatisfiedWait(ctx, state, pattern, waitMode, out var pumpedWaitResult))
{
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
releaseWaiter = false;
TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}");
return SetReturn(ctx, pumpedWaitResult);
}
Monitor.Wait(state.Gate, HostWaitPumpMilliseconds);
}
}
finally
{
if (releaseWaiter)
{
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
}
}
}
state.WaitingThreads++;
TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
finally
{
Monitor.Exit(state.Gate);
}
}
[SysAbiExport(
Nid = "PZku4ZrXJqg",
ExportName = "sceKernelCancelEventFlag",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCancelEventFlag(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var setPattern = ctx[CpuRegister.Rsi];
var waiterCountAddress = ctx[CpuRegister.Rdx];
if (!_eventFlags.TryGetValue(handle, out var state))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
}
lock (state.Gate)
{
if (waiterCountAddress != 0 &&
!TryWriteUInt32(ctx, waiterCountAddress, unchecked((uint)state.WaitingThreads)))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
state.Bits = setPattern;
state.WaitingThreads = 0;
Monitor.PulseAll(state.Gate);
TraceEventFlag($"cancel handle=0x{handle:X16} bits=0x{setPattern:X16}");
}
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
private static bool IsValidAttributes(uint attributes)
{
var queueMode = attributes & 0x0F;
var threadMode = attributes & 0xF0;
return (queueMode is 0 or AttrThreadFifo or AttrThreadPriority) &&
(threadMode is 0 or AttrSingle or AttrMulti) &&
(attributes & ~0x33u) == 0;
}
private static bool IsValidWaitMode(uint waitMode)
{
var condition = waitMode & 0x0F;
var clearMode = waitMode & 0xF0;
return condition is WaitAnd or WaitOr &&
clearMode is 0 or ClearAll or ClearPattern &&
(waitMode & ~0x33u) == 0;
}
private static bool IsSatisfied(ulong bits, ulong pattern, uint waitMode) =>
(waitMode & 0x0F) == WaitAnd
? (bits & pattern) == pattern
: (bits & pattern) != 0;
private static void ApplyClearMode(EventFlagState state, ulong pattern, uint waitMode)
{
switch (waitMode & 0xF0)
{
case ClearAll:
state.Bits = 0;
break;
case ClearPattern:
state.Bits &= ~pattern;
break;
}
}
private static bool TryCompleteSatisfiedWait(
CpuContext ctx,
EventFlagState state,
ulong pattern,
uint waitMode,
out OrbisGen2Result result)
{
result = OrbisGen2Result.ORBIS_GEN2_OK;
if (!IsSatisfied(state.Bits, pattern, waitMode))
{
return false;
}
if (!TryWriteResultPattern(ctx, ctx[CpuRegister.Rcx], state.Bits))
{
result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
return true;
}
ApplyClearMode(state, pattern, waitMode);
return true;
}
private static bool TryWriteResultPattern(CpuContext ctx, ulong address, ulong bits) =>
address == 0 || ctx.TryWriteUInt64(address, bits);
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (!ctx.Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return true;
}
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return ctx.Memory.TryWrite(address, buffer);
}
private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int capacity, out string value)
{
var bytes = new byte[capacity];
for (var index = 0; index < bytes.Length; index++)
{
Span<byte> current = stackalloc byte[1];
if (!ctx.Memory.TryRead(address + (ulong)index, current))
{
value = string.Empty;
return false;
}
if (current[0] == 0)
{
value = Encoding.UTF8.GetString(bytes, 0, index);
return true;
}
bytes[index] = current[0];
}
value = Encoding.UTF8.GetString(bytes);
return true;
}
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
{
var value = (int)result;
ctx[CpuRegister.Rax] = unchecked((ulong)value);
return value;
}
private static void TraceEventFlag(string message)
{
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal))
{
Console.Error.WriteLine($"[LOADER][TRACE] event_flag.{message}");
}
}
}
@@ -13,7 +13,7 @@ public static class KernelEventQueueCompatExports
private static readonly object _eventQueueGate = new();
private static readonly HashSet<ulong> _eventQueues = new();
private static readonly Dictionary<ulong, Queue<KernelQueuedEvent>> _pendingEvents = new();
private static readonly Dictionary<ulong, LinkedList<KernelQueuedEvent>> _pendingEvents = new();
private static long _nextEventQueueHandle = 1;
public readonly record struct KernelQueuedEvent(
@@ -41,7 +41,7 @@ public static class KernelEventQueueCompatExports
lock (_eventQueueGate)
{
_eventQueues.Add(handle);
_pendingEvents[handle] = new Queue<KernelQueuedEvent>();
_pendingEvents[handle] = new LinkedList<KernelQueuedEvent>();
}
if (!ctx.TryWriteUInt64(outAddress, handle))
@@ -225,11 +225,70 @@ public static class KernelEventQueueCompatExports
if (!_pendingEvents.TryGetValue(handle, out var queue))
{
queue = new Queue<KernelQueuedEvent>();
queue = new LinkedList<KernelQueuedEvent>();
_pendingEvents[handle] = queue;
}
queue.Enqueue(queuedEvent);
queue.AddLast(queuedEvent);
return true;
}
}
public static bool TriggerDisplayEvent(
ulong handle,
ulong ident,
short filter,
ulong eventHint,
ulong userData)
{
lock (_eventQueueGate)
{
if (!_eventQueues.Contains(handle))
{
return false;
}
if (!_pendingEvents.TryGetValue(handle, out var events))
{
events = new LinkedList<KernelQueuedEvent>();
_pendingEvents[handle] = events;
}
LinkedListNode<KernelQueuedEvent>? pendingNode = null;
for (var node = events.First; node is not null; node = node.Next)
{
if (node.Value.Ident == ident && node.Value.Filter == filter)
{
pendingNode = node;
break;
}
}
var count = 1UL;
if (pendingNode is not null)
{
count = Math.Min(((pendingNode.Value.Data >> 12) & 0xFUL) + 1, 0xFUL);
}
var timeBits = unchecked((ulong)Environment.TickCount64) & 0xFFFUL;
var eventData = timeBits | (count << 12) | (eventHint & 0xFFFF_FFFF_FFFF_0000UL);
var triggeredEvent = new KernelQueuedEvent(
ident,
filter,
0x20,
0,
eventData,
userData);
if (pendingNode is not null)
{
pendingNode.Value = triggeredEvent;
}
else
{
events.AddLast(triggeredEvent);
}
return true;
}
}
@@ -253,7 +312,8 @@ public static class KernelEventQueueCompatExports
events = new KernelQueuedEvent[count];
for (var i = 0; i < count; i++)
{
events[i] = queue.Dequeue();
events[i] = queue.First!.Value;
queue.RemoveFirst();
}
}
@@ -15,9 +15,9 @@ public static class KernelPthreadCompatExports
private const int MutexTypeNormal = 3;
private const int MutexTypeAdaptiveNp = 4;
private const ulong StaticAdaptiveMutexInitializer = 1;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
private const int MutexObjectSize = 0x100;
private const int MutexAttrObjectSize = 0x40;
private const int CondObjectSize = 0x100;
private const int DefaultSpuriousCondWakeMilliseconds = 1;
private static readonly object _stateGate = new();
@@ -25,9 +25,6 @@ public static class KernelPthreadCompatExports
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 _nextSyntheticMutexHandleId = 1;
private static long _nextSyntheticMutexAttrHandleId = 1;
private static long _nextSyntheticCondHandleId = 1;
private sealed class PthreadMutexState
{
@@ -357,14 +354,34 @@ public static class KernelPthreadCompatExports
Protocol = attr.Protocol,
};
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
if (!TryAllocateOpaqueObject(ctx, MutexObjectSize, out var handle))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!InitializeMutexObject(ctx, handle, state))
{
state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
lock (_stateGate)
{
_mutexStates[mutexAddress] = state;
_mutexStates[syntheticHandle] = state;
_mutexStates[handle] = state;
}
if (!ctx.TryWriteUInt64(mutexAddress, handle))
{
lock (_stateGate)
{
_mutexStates.Remove(mutexAddress);
_mutexStates.Remove(handle);
}
state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -533,14 +550,34 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexAttrHandleBase, ref _nextSyntheticMutexAttrHandleId);
lock (_stateGate)
if (!TryAllocateOpaqueObject(ctx, MutexAttrObjectSize, out var handle))
{
_mutexAttrStates[attrAddress] = new PthreadMutexAttrState(MutexTypeDefault, 0);
_mutexAttrStates[syntheticHandle] = new PthreadMutexAttrState(MutexTypeDefault, 0);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var initialState = new PthreadMutexAttrState(MutexTypeDefault, 0);
if (!WriteMutexAttrObject(ctx, handle, initialState))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
lock (_stateGate)
{
_mutexAttrStates[attrAddress] = initialState;
_mutexAttrStates[handle] = initialState;
}
if (!ctx.TryWriteUInt64(attrAddress, handle))
{
lock (_stateGate)
{
_mutexAttrStates.Remove(attrAddress);
_mutexAttrStates.Remove(handle);
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
_ = ctx.TryWriteUInt64(attrAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -572,6 +609,7 @@ public static class KernelPthreadCompatExports
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
PthreadMutexAttrState updatedState;
lock (_stateGate)
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
@@ -579,14 +617,17 @@ public static class KernelPthreadCompatExports
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
}
_mutexAttrStates[resolvedAddress] = state with { Type = NormalizeMutexType(type) };
updatedState = state with { Type = NormalizeMutexType(type) };
_mutexAttrStates[resolvedAddress] = updatedState;
if (resolvedAddress != attrAddress)
{
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
_mutexAttrStates[attrAddress] = updatedState;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
return WriteMutexAttrObject(ctx, resolvedAddress, updatedState)
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
private static int PthreadMutexattrSetprotocolCore(CpuContext ctx, ulong attrAddress, int protocol)
@@ -597,6 +638,7 @@ public static class KernelPthreadCompatExports
}
var resolvedAddress = ResolveMutexAttrHandle(ctx, attrAddress);
PthreadMutexAttrState updatedState;
lock (_stateGate)
{
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
@@ -604,14 +646,17 @@ public static class KernelPthreadCompatExports
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
}
_mutexAttrStates[resolvedAddress] = state with { Protocol = protocol };
updatedState = state with { Protocol = protocol };
_mutexAttrStates[resolvedAddress] = updatedState;
if (resolvedAddress != attrAddress)
{
_mutexAttrStates[attrAddress] = _mutexAttrStates[resolvedAddress];
_mutexAttrStates[attrAddress] = updatedState;
}
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
return WriteMutexAttrObject(ctx, resolvedAddress, updatedState)
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
private static ulong ResolveMutexHandle(CpuContext ctx, ulong mutexAddress)
@@ -703,14 +748,6 @@ public static class KernelPthreadCompatExports
return 0;
}
lock (_stateGate)
{
if (_mutexAttrStates.ContainsKey(attrAddress))
{
return attrAddress;
}
}
if (ctx.TryReadUInt64(attrAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
@@ -722,6 +759,14 @@ public static class KernelPthreadCompatExports
}
}
lock (_stateGate)
{
if (_mutexAttrStates.ContainsKey(attrAddress))
{
return attrAddress;
}
}
return attrAddress;
}
@@ -816,23 +861,60 @@ public static class KernelPthreadCompatExports
}
var createdState = new PthreadCondState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
if (!TryAllocateOpaqueObject(ctx, CondObjectSize, out var handle))
{
return false;
}
lock (_stateGate)
{
_condStates[condAddress] = createdState;
_condStates[syntheticHandle] = createdState;
_condStates[handle] = createdState;
}
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
if (!ctx.TryWriteUInt64(condAddress, handle))
{
lock (_stateGate)
{
_condStates.Remove(condAddress);
_condStates.Remove(handle);
}
return false;
}
resolvedAddress = handle;
state = createdState;
return true;
}
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
private static bool TryAllocateOpaqueObject(CpuContext ctx, int size, out ulong address)
{
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
return baseAddress + (id << 4);
address = 0;
if (ctx.Memory is not IGuestMemoryAllocator allocator ||
!allocator.TryAllocateGuestMemory((ulong)size, alignment: 0x10, out address))
{
return false;
}
Span<byte> initialData = stackalloc byte[size];
initialData.Clear();
return ctx.Memory.TryWrite(address, initialData);
}
private static bool InitializeMutexObject(CpuContext ctx, ulong address, PthreadMutexState state) =>
TryWriteUInt32(ctx, address + 0x20, unchecked((uint)state.Type)) &&
TryWriteUInt32(ctx, address + 0x3C, unchecked((uint)state.Protocol));
private static bool WriteMutexAttrObject(CpuContext ctx, ulong address, PthreadMutexAttrState state) =>
TryWriteUInt32(ctx, address, unchecked((uint)state.Type)) &&
TryWriteUInt32(ctx, address + 4, unchecked((uint)state.Protocol));
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
BitConverter.TryWriteBytes(bytes, value);
return ctx.Memory.TryWrite(address, bytes);
}
private static int PthreadCondInitCore(CpuContext ctx, ulong condAddress)
@@ -842,15 +924,29 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
if (!TryAllocateOpaqueObject(ctx, CondObjectSize, out var handle))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
lock (_stateGate)
{
var state = new PthreadCondState();
_condStates[condAddress] = state;
_condStates[syntheticHandle] = state;
_condStates[handle] = state;
}
if (!ctx.TryWriteUInt64(condAddress, handle))
{
lock (_stateGate)
{
_condStates.Remove(condAddress);
_condStates.Remove(handle);
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -1047,7 +1143,19 @@ public static class KernelPthreadCompatExports
Type = type,
};
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
if (!TryAllocateOpaqueObject(ctx, MutexObjectSize, out var handle))
{
resolvedAddress = 0;
state = null;
return false;
}
if (!InitializeMutexObject(ctx, handle, createdState))
{
resolvedAddress = 0;
state = null;
return false;
}
lock (_stateGate)
{
if (_mutexStates.TryGetValue(mutexAddress, out state))
@@ -1056,18 +1164,30 @@ public static class KernelPthreadCompatExports
return true;
}
if (_mutexStates.TryGetValue(syntheticHandle, out state))
if (_mutexStates.TryGetValue(handle, out state))
{
resolvedAddress = syntheticHandle;
resolvedAddress = handle;
return true;
}
_mutexStates[mutexAddress] = createdState;
_mutexStates[syntheticHandle] = createdState;
_mutexStates[handle] = createdState;
}
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
if (!ctx.TryWriteUInt64(mutexAddress, handle))
{
lock (_stateGate)
{
_mutexStates.Remove(mutexAddress);
_mutexStates.Remove(handle);
}
resolvedAddress = 0;
state = null;
return false;
}
resolvedAddress = handle;
state = createdState;
return true;
}
@@ -489,6 +489,37 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "-quPa4SEJUw",
ExportName = "scePthreadAttrGetstack",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetstack(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var outStackAddressPointer = ctx[CpuRegister.Rsi];
var outStackSizeAddress = ctx[CpuRegister.Rdx];
if (attrAddress == 0 || outStackAddressPointer == 0 || outStackSizeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!ctx.TryWriteUInt64(outStackAddressPointer, state.StackAddress) ||
!ctx.TryWriteUInt64(outStackSizeAddress, state.StackSize))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "-fA+7ZlGDQs",
ExportName = "scePthreadAttrGetstacksize",