mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-01 23:49:44 +08:00
[kernel] Add thread and event primitives
This commit is contained in:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user