From 5512da9e121ac520a9c5aeb3021b09a2c37c26ff Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Sun, 21 Jun 2026 23:18:42 +0300 Subject: [PATCH] [kernel] Add thread and event primitives --- src/SharpEmu.Libs/Fiber/FiberExports.cs | 961 ++++++++++++++++++ .../Kernel/KernelEventFlagCompatExports.cs | 447 ++++++++ .../Kernel/KernelEventQueueCompatExports.cs | 70 +- .../Kernel/KernelPthreadCompatExports.cs | 208 +++- .../KernelPthreadExtendedCompatExports.cs | 31 + 5 files changed, 1668 insertions(+), 49 deletions(-) create mode 100644 src/SharpEmu.Libs/Fiber/FiberExports.cs create mode 100644 src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs diff --git a/src/SharpEmu.Libs/Fiber/FiberExports.cs b/src/SharpEmu.Libs/Fiber/FiberExports.cs new file mode 100644 index 0000000..8e7dcfc --- /dev/null +++ b/src/SharpEmu.Libs/Fiber/FiberExports.cs @@ -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 _continuations = new(); + private static readonly ConcurrentDictionary _stackRanges = new(); + private static readonly ConcurrentDictionary _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 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 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 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 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 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 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 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 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; + } +} diff --git a/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs new file mode 100644 index 0000000..3e583a2 --- /dev/null +++ b/src/SharpEmu.Libs/Kernel/KernelEventFlagCompatExports.cs @@ -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 _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 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 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 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}"); + } + } +} diff --git a/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs index eff601f..a6e21b8 100644 --- a/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs @@ -13,7 +13,7 @@ public static class KernelEventQueueCompatExports private static readonly object _eventQueueGate = new(); private static readonly HashSet _eventQueues = new(); - private static readonly Dictionary> _pendingEvents = new(); + private static readonly Dictionary> _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(); + _pendingEvents[handle] = new LinkedList(); } if (!ctx.TryWriteUInt64(outAddress, handle)) @@ -225,11 +225,70 @@ public static class KernelEventQueueCompatExports if (!_pendingEvents.TryGetValue(handle, out var queue)) { - queue = new Queue(); + queue = new LinkedList(); _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(); + _pendingEvents[handle] = events; + } + + LinkedListNode? 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(); } } diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs index a515a5c..74e0027 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs @@ -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 _mutexAttrStates = new(); private static readonly Dictionary _condStates = new(); private static readonly HashSet _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 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 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; } diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs index 72ad5f0..17e88a8 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs @@ -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",