mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-27 05:01:07 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 873f473b65 | |||
| 9a1a3789ef | |||
| 0f3d4032cb | |||
| 0c4a757695 | |||
| 5fcc8eaa37 | |||
| 21105e0346 | |||
| b424df5a64 | |||
| 7f85971e39 | |||
| e28259a99d |
@@ -527,7 +527,10 @@ public sealed partial class DirectExecutionBackend
|
|||||||
if (dispatchIndex % 100000 == 0)
|
if (dispatchIndex % 100000 == 0)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] Import#{dispatchIndex}: {export.LibraryName}:{export.Name} ({importStubEntry.Nid})");
|
$"[LOADER][TRACE] Import#{dispatchIndex}: {export.LibraryName}:{export.Name} ({importStubEntry.Nid}) " +
|
||||||
|
$"rdi=0x{arg0:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} " +
|
||||||
|
$"rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16} " +
|
||||||
|
$"ret=0x{returnRip:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(
|
var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(
|
||||||
@@ -653,7 +656,12 @@ public sealed partial class DirectExecutionBackend
|
|||||||
"rqwFKI4PAiM" or
|
"rqwFKI4PAiM" or
|
||||||
"eE4Szl8sil8" or
|
"eE4Szl8sil8" or
|
||||||
"qvMUCyyaCSI" or
|
"qvMUCyyaCSI" or
|
||||||
|
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
||||||
|
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
||||||
|
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
||||||
"27bAgiJmOh0" or // pthread_cond_timedwait
|
"27bAgiJmOh0" or // pthread_cond_timedwait
|
||||||
|
"iQw3iQPhvUQ" or // sceNetCtlCheckCallback
|
||||||
|
"Q2V+iqvjgC0" or // vsnprintf
|
||||||
"j4ViWNHEgww" or // strlen
|
"j4ViWNHEgww" or // strlen
|
||||||
"5jNubw4vlAA" or // strnlen
|
"5jNubw4vlAA" or // strnlen
|
||||||
"LHMrG7e8G78" or // wcslen
|
"LHMrG7e8G78" or // wcslen
|
||||||
|
|||||||
@@ -347,6 +347,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
public string Name { get; init; } = string.Empty;
|
public string Name { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public int Priority { get; init; }
|
||||||
|
|
||||||
|
public ulong AffinityMask { get; init; }
|
||||||
|
|
||||||
public CpuContext Context { get; init; } = null!;
|
public CpuContext Context { get; init; } = null!;
|
||||||
|
|
||||||
public GuestThreadRunState State { get; set; }
|
public GuestThreadRunState State { get; set; }
|
||||||
@@ -388,14 +392,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
private Action? _work;
|
private Action? _work;
|
||||||
private volatile bool _stopping;
|
private volatile bool _stopping;
|
||||||
|
|
||||||
public GuestContinuationRunner(ulong guestThreadHandle)
|
public GuestContinuationRunner(ulong guestThreadHandle, ThreadPriority priority)
|
||||||
{
|
{
|
||||||
_guestThreadHandle = guestThreadHandle;
|
_guestThreadHandle = guestThreadHandle;
|
||||||
_thread = new Thread(ThreadMain)
|
_thread = new Thread(ThreadMain)
|
||||||
{
|
{
|
||||||
IsBackground = true,
|
IsBackground = true,
|
||||||
Name = $"GuestContinuation-{guestThreadHandle:X}",
|
Name = $"GuestContinuation-{guestThreadHandle:X}",
|
||||||
Priority = ThreadPriority.BelowNormal,
|
Priority = priority,
|
||||||
};
|
};
|
||||||
_thread.Start();
|
_thread.Start();
|
||||||
}
|
}
|
||||||
@@ -2393,7 +2397,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
Interlocked.Increment(ref _readyGuestThreadCount);
|
Interlocked.Increment(ref _readyGuestThreadCount);
|
||||||
}
|
}
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][INFO] Scheduled guest thread '{thread.Name}' handle=0x{thread.ThreadHandle:X16} entry=0x{thread.EntryPoint:X16} arg=0x{thread.Argument:X16}");
|
$"[LOADER][INFO] Scheduled guest thread '{thread.Name}' handle=0x{thread.ThreadHandle:X16} " +
|
||||||
|
$"entry=0x{thread.EntryPoint:X16} arg=0x{thread.Argument:X16} priority={thread.Priority} " +
|
||||||
|
$"host_priority={MapGuestThreadPriority(thread.Priority)} affinity=0x{thread.AffinityMask:X}");
|
||||||
Pump(creatorContext, "pthread_create");
|
Pump(creatorContext, "pthread_create");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2448,7 +2454,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
IsBackground = true,
|
IsBackground = true,
|
||||||
Name = $"SharpEmu-{thread.Name}",
|
Name = $"SharpEmu-{thread.Name}",
|
||||||
Priority = ThreadPriority.BelowNormal,
|
Priority = MapGuestThreadPriority(thread.Priority),
|
||||||
};
|
};
|
||||||
lock (_guestThreadGate)
|
lock (_guestThreadGate)
|
||||||
{
|
{
|
||||||
@@ -2851,7 +2857,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
if (_guestThreads.TryGetValue(currentGuestThreadHandle, out var guestThread))
|
if (_guestThreads.TryGetValue(currentGuestThreadHandle, out var guestThread))
|
||||||
{
|
{
|
||||||
runner = guestThread.ContinuationRunner ??= new GuestContinuationRunner(currentGuestThreadHandle);
|
runner = guestThread.ContinuationRunner ??= new GuestContinuationRunner(
|
||||||
|
currentGuestThreadHandle,
|
||||||
|
MapGuestThreadPriority(guestThread.Priority));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2990,6 +2998,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
EntryPoint = request.EntryPoint,
|
EntryPoint = request.EntryPoint,
|
||||||
Argument = request.Argument,
|
Argument = request.Argument,
|
||||||
Name = string.IsNullOrWhiteSpace(request.Name) ? $"Thread-{request.ThreadHandle:X}" : request.Name,
|
Name = string.IsNullOrWhiteSpace(request.Name) ? $"Thread-{request.ThreadHandle:X}" : request.Name,
|
||||||
|
Priority = request.Priority,
|
||||||
|
AffinityMask = request.AffinityMask,
|
||||||
Context = context,
|
Context = context,
|
||||||
State = GuestThreadRunState.Ready,
|
State = GuestThreadRunState.Ready,
|
||||||
};
|
};
|
||||||
@@ -3129,11 +3139,77 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
context.TryWriteUInt64(tlsBase + 0x60, tlsBase);
|
context.TryWriteUInt64(tlsBase + 0x60, tlsBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ThreadPriority MapGuestThreadPriority(int priority)
|
||||||
|
{
|
||||||
|
if (priority <= 478)
|
||||||
|
{
|
||||||
|
return ThreadPriority.Highest;
|
||||||
|
}
|
||||||
|
if (priority >= 733)
|
||||||
|
{
|
||||||
|
return ThreadPriority.Lowest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ThreadPriority.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyGuestThreadAffinity(ulong guestAffinityMask)
|
||||||
|
{
|
||||||
|
var hostAffinityMask = MapGuestThreadAffinity(guestAffinityMask);
|
||||||
|
if (hostAffinityMask == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SetThreadAffinityMask(GetCurrentThread(), (nuint)hostAffinityMask) == 0 && _logGuestThreads)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][WARN] Failed to set guest thread affinity guest=0x{guestAffinityMask:X} " +
|
||||||
|
$"host=0x{hostAffinityMask:X} error={Marshal.GetLastWin32Error()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong MapGuestThreadAffinity(ulong guestAffinityMask)
|
||||||
|
{
|
||||||
|
if (guestAffinityMask == 0 || guestAffinityMask == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var processorCount = Math.Min(Environment.ProcessorCount, 64);
|
||||||
|
if (processorCount == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong hostAffinityMask = 0;
|
||||||
|
for (var guestCpu = 0; guestCpu < 64; guestCpu++)
|
||||||
|
{
|
||||||
|
if ((guestAffinityMask & (1UL << guestCpu)) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostCpu = processorCount < 8
|
||||||
|
? guestCpu % processorCount
|
||||||
|
: processorCount >= 16
|
||||||
|
? guestCpu * 2
|
||||||
|
: guestCpu;
|
||||||
|
if (hostCpu < processorCount)
|
||||||
|
{
|
||||||
|
hostAffinityMask |= 1UL << hostCpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hostAffinityMask;
|
||||||
|
}
|
||||||
|
|
||||||
private void RunGuestThread(GuestThreadState thread, string reason)
|
private void RunGuestThread(GuestThreadState thread, string reason)
|
||||||
{
|
{
|
||||||
var previousLastError = LastError;
|
var previousLastError = LastError;
|
||||||
var previousGuestThreadHandle = GuestThreadExecution.EnterGuestThread(thread.ThreadHandle);
|
var previousGuestThreadHandle = GuestThreadExecution.EnterGuestThread(thread.ThreadHandle);
|
||||||
var previousGuestThreadState = _activeGuestThreadState;
|
var previousGuestThreadState = _activeGuestThreadState;
|
||||||
|
ApplyGuestThreadAffinity(thread.AffinityMask);
|
||||||
Volatile.Write(ref thread.HostThreadId, unchecked((int)GetCurrentThreadId()));
|
Volatile.Write(ref thread.HostThreadId, unchecked((int)GetCurrentThreadId()));
|
||||||
_activeGuestThreadState = thread;
|
_activeGuestThreadState = thread;
|
||||||
try
|
try
|
||||||
@@ -3183,6 +3259,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
case GuestNativeCallExitReason.Blocked:
|
case GuestNativeCallExitReason.Blocked:
|
||||||
thread.State = GuestThreadRunState.Blocked;
|
thread.State = GuestThreadRunState.Blocked;
|
||||||
thread.BlockReason = blockReason;
|
thread.BlockReason = blockReason;
|
||||||
|
if (thread.HasBlockedContinuation &&
|
||||||
|
thread.BlockWakeHandler is not null &&
|
||||||
|
thread.BlockWakeHandler())
|
||||||
|
{
|
||||||
|
thread.State = GuestThreadRunState.Ready;
|
||||||
|
thread.BlockReason = null;
|
||||||
|
thread.BlockWakeHandler = null;
|
||||||
|
thread.BlockDeadlineTimestamp = 0;
|
||||||
|
_readyGuestThreads.Enqueue(thread);
|
||||||
|
Interlocked.Increment(ref _readyGuestThreadCount);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
thread.State = GuestThreadRunState.Faulted;
|
thread.State = GuestThreadRunState.Faulted;
|
||||||
@@ -4144,6 +4231,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
[DllImport("kernel32.dll")]
|
[DllImport("kernel32.dll")]
|
||||||
private static extern uint GetCurrentThreadId();
|
private static extern uint GetCurrentThreadId();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern nint GetCurrentThread();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern nuint SetThreadAffinityMask(nint hThread, nuint dwThreadAffinityMask);
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
private static extern nint OpenThread(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId);
|
private static extern nint OpenThread(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace SharpEmu.Core.Memory;
|
|||||||
|
|
||||||
public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryAllocator, IDisposable
|
public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryAllocator, IDisposable
|
||||||
{
|
{
|
||||||
private readonly object _gate = new();
|
private readonly ReaderWriterLockSlim _gate = new(LockRecursionPolicy.SupportsRecursion);
|
||||||
private readonly object _guestAllocationGate = new();
|
private readonly object _guestAllocationGate = new();
|
||||||
private readonly List<MemoryRegion> _regions = new();
|
private readonly List<MemoryRegion> _regions = new();
|
||||||
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
|
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
|
||||||
@@ -78,7 +78,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_gate)
|
_gate.EnterWriteLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_regions.Add(new MemoryRegion
|
_regions.Add(new MemoryRegion
|
||||||
{
|
{
|
||||||
@@ -89,6 +90,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
Protection = protection
|
Protection = protection
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
var allocationKind = executable ? "executable memory" : "data memory";
|
var allocationKind = executable ? "executable memory" : "data memory";
|
||||||
Console.Error.WriteLine($"[VMEM] Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
|
Console.Error.WriteLine($"[VMEM] Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
|
||||||
@@ -202,7 +207,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_gate)
|
_gate.EnterWriteLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_regions.Add(new MemoryRegion
|
_regions.Add(new MemoryRegion
|
||||||
{
|
{
|
||||||
@@ -213,6 +219,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
Protection = protection
|
Protection = protection
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
var allocationKind = reservedOnly
|
var allocationKind = reservedOnly
|
||||||
? "reserved data memory (lazy commit)"
|
? "reserved data memory (lazy commit)"
|
||||||
@@ -315,7 +325,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
{
|
{
|
||||||
lock (_guestAllocationGate)
|
lock (_guestAllocationGate)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
_gate.EnterWriteLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
@@ -324,6 +335,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
_regions.Clear();
|
_regions.Clear();
|
||||||
_pageProtections.Clear();
|
_pageProtections.Clear();
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
_guestAllocationArenaBase = 0;
|
_guestAllocationArenaBase = 0;
|
||||||
_guestAllocationOffset = 0;
|
_guestAllocationOffset = 0;
|
||||||
@@ -343,7 +358,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
var mapEnd = AlignUp(segmentEnd, PageSize);
|
var mapEnd = AlignUp(segmentEnd, PageSize);
|
||||||
var mapSize = checked(mapEnd - mapStart);
|
var mapSize = checked(mapEnd - mapStart);
|
||||||
|
|
||||||
lock (_gate)
|
_gate.EnterWriteLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var existingRegion = FindRegion(mapStart, mapSize);
|
var existingRegion = FindRegion(mapStart, mapSize);
|
||||||
if (existingRegion == null)
|
if (existingRegion == null)
|
||||||
@@ -376,6 +392,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
|
|
||||||
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
|
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitWriteLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplySegmentProtection(ulong mapStart, ulong mapEnd, ProgramHeaderFlags flags)
|
private void ApplySegmentProtection(ulong mapStart, ulong mapEnd, ProgramHeaderFlags flags)
|
||||||
@@ -425,7 +445,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
|
|
||||||
public IReadOnlyList<VirtualMemoryRegion> SnapshotRegions()
|
public IReadOnlyList<VirtualMemoryRegion> SnapshotRegions()
|
||||||
{
|
{
|
||||||
lock (_gate)
|
_gate.EnterReadLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var snapshot = new VirtualMemoryRegion[_regions.Count];
|
var snapshot = new VirtualMemoryRegion[_regions.Count];
|
||||||
for (var i = 0; i < _regions.Count; i++)
|
for (var i = 0; i < _regions.Count; i++)
|
||||||
@@ -440,11 +461,17 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitReadLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryRead(ulong virtualAddress, Span<byte> destination)
|
public bool TryRead(ulong virtualAddress, Span<byte> destination)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
var requiresExclusiveAccess = false;
|
||||||
|
_gate.EnterReadLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
@@ -456,48 +483,55 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
if (region.IsReservedOnly)
|
||||||
{
|
{
|
||||||
return false;
|
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
||||||
}
|
|
||||||
|
|
||||||
if (CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
|
|
||||||
{
|
|
||||||
fixed (byte* destPtr = destination)
|
|
||||||
{
|
{
|
||||||
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
fixed (byte* destPtr = destination)
|
|
||||||
{
|
|
||||||
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
|
||||||
{
|
{
|
||||||
RestorePageProtections(touchedPages);
|
requiresExclusiveAccess = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (byte* destPtr = destination)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitReadLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requiresExclusiveAccess)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_gate.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return TryReadExclusive(virtualAddress, destination);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitWriteLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
|
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
var requiresExclusiveAccess = false;
|
||||||
|
_gate.EnterReadLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
@@ -509,47 +543,148 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
if (region.IsReservedOnly)
|
||||||
{
|
{
|
||||||
return false;
|
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
||||||
}
|
|
||||||
|
|
||||||
if (CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
|
|
||||||
{
|
|
||||||
fixed (byte* srcPtr = source)
|
|
||||||
{
|
{
|
||||||
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
fixed (byte* srcPtr = source)
|
|
||||||
{
|
|
||||||
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
|
||||||
{
|
{
|
||||||
VirtualProtect(destPtr, (nuint)source.Length, oldProtect, out _);
|
requiresExclusiveAccess = true;
|
||||||
if (IsExecutableProtection(oldProtect))
|
break;
|
||||||
{
|
}
|
||||||
FlushInstructionCache(null, destPtr, (nuint)source.Length);
|
|
||||||
}
|
fixed (byte* srcPtr = source)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitReadLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requiresExclusiveAccess)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_gate.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return TryWriteExclusive(virtualAddress, source);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryReadExclusive(ulong virtualAddress, Span<byte> destination)
|
||||||
|
{
|
||||||
|
foreach (var region in _regions)
|
||||||
|
{
|
||||||
|
if (!TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcPtr = (void*)(region.VirtualAddress + offset);
|
||||||
|
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
|
||||||
|
{
|
||||||
|
fixed (byte* destPtr = destination)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fixed (byte* destPtr = destination)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RestorePageProtections(touchedPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
foreach (var region in _regions)
|
||||||
|
{
|
||||||
|
if (!TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var destPtr = (void*)(region.VirtualAddress + offset);
|
||||||
|
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
|
||||||
|
{
|
||||||
|
fixed (byte* srcPtr = source)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fixed (byte* srcPtr = source)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
VirtualProtect(destPtr, (nuint)source.Length, oldProtect, out _);
|
||||||
|
if (IsExecutableProtection(oldProtect))
|
||||||
|
{
|
||||||
|
FlushInstructionCache(null, destPtr, (nuint)source.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryWriteUInt64(ulong virtualAddress, ulong value)
|
public bool TryWriteUInt64(ulong virtualAddress, ulong value)
|
||||||
@@ -561,7 +696,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
|
|
||||||
public void* GetPointer(ulong virtualAddress)
|
public void* GetPointer(ulong virtualAddress)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
_gate.EnterReadLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
@@ -573,11 +709,16 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitReadLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAccessible(ulong virtualAddress, ulong size)
|
public bool IsAccessible(ulong virtualAddress, ulong size)
|
||||||
{
|
{
|
||||||
lock (_gate)
|
_gate.EnterReadLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
@@ -588,6 +729,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitReadLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryRegion? FindRegion(ulong address, ulong size)
|
private MemoryRegion? FindRegion(ulong address, ulong size)
|
||||||
@@ -611,7 +756,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
|
|
||||||
var end = address + size;
|
var end = address + size;
|
||||||
lock (_gate)
|
_gate.EnterReadLock();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
@@ -622,6 +768,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_gate.ExitReadLock();
|
||||||
|
}
|
||||||
|
|
||||||
return overlapEnd != 0;
|
return overlapEnd != 0;
|
||||||
}
|
}
|
||||||
@@ -707,15 +857,26 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
var endPage = AlignUp(address + size, PageSize);
|
var endPage = AlignUp(address + size, PageSize);
|
||||||
var commitProtection = GetCommitProtection(region);
|
var commitProtection = GetCommitProtection(region);
|
||||||
|
|
||||||
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
|
var pageAddress = startPage;
|
||||||
|
while (pageAddress < endPage)
|
||||||
{
|
{
|
||||||
if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0)
|
if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var queriedEnd = info.RegionSize > ulong.MaxValue - info.BaseAddress
|
||||||
|
? ulong.MaxValue
|
||||||
|
: info.BaseAddress + info.RegionSize;
|
||||||
|
var rangeEnd = Math.Min(endPage, queriedEnd);
|
||||||
|
if (rangeEnd <= pageAddress)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (info.State == MEM_COMMIT)
|
if (info.State == MEM_COMMIT)
|
||||||
{
|
{
|
||||||
|
pageAddress = rangeEnd;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,10 +885,13 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VirtualAlloc((void*)pageAddress, (nuint)PageSize, MEM_COMMIT, commitProtection) == null)
|
var commitSize = rangeEnd - pageAddress;
|
||||||
|
if (VirtualAlloc((void*)pageAddress, (nuint)commitSize, MEM_COMMIT, commitProtection) == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageAddress = rangeEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using SharpEmu.HLE;
|
|||||||
using SharpEmu.Libs.VideoOut;
|
using SharpEmu.Libs.VideoOut;
|
||||||
using SharpEmu.Libs.Kernel;
|
using SharpEmu.Libs.Kernel;
|
||||||
using SharpEmu.Libs.AppContent;
|
using SharpEmu.Libs.AppContent;
|
||||||
|
using SharpEmu.Libs.SaveData;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -136,6 +137,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
|
|||||||
KernelModuleRegistry.Reset();
|
KernelModuleRegistry.Reset();
|
||||||
var image = LoadImage(normalizedEbootPath);
|
var image = LoadImage(normalizedEbootPath);
|
||||||
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version);
|
VideoOutExports.ConfigureApplicationInfo(image.Title, image.TitleId, image.Version);
|
||||||
|
SaveDataExports.ConfigureApplicationInfo(image.TitleId);
|
||||||
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
RegisterLoadedModule(normalizedEbootPath, image, isMain: true, isSystemModule: false);
|
||||||
KernelRuntimeCompatExports.ConfigureProcessProcParamAddress(image.ProcParamAddress);
|
KernelRuntimeCompatExports.ConfigureProcessProcParamAddress(image.ProcParamAddress);
|
||||||
Console.Error.WriteLine($"[RUNTIME] Entry: 0x{image.EntryPoint:X16}");
|
Console.Error.WriteLine($"[RUNTIME] Entry: 0x{image.EntryPoint:X16}");
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ public readonly record struct GuestThreadStartRequest(
|
|||||||
ulong EntryPoint,
|
ulong EntryPoint,
|
||||||
ulong Argument,
|
ulong Argument,
|
||||||
ulong AttributeAddress,
|
ulong AttributeAddress,
|
||||||
string Name);
|
string Name,
|
||||||
|
int Priority,
|
||||||
|
ulong AffinityMask);
|
||||||
|
|
||||||
public readonly record struct GuestThreadSnapshot(
|
public readonly record struct GuestThreadSnapshot(
|
||||||
ulong ThreadHandle,
|
ulong ThreadHandle,
|
||||||
|
|||||||
@@ -77,12 +77,7 @@ public static class AmprExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = 0UL;
|
if (!InitializeCommandBuffer(ctx, commandBuffer, buffer: 0, size: 0, aux0, aux1, clear: false))
|
||||||
var size = 0UL;
|
|
||||||
_ = ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out buffer);
|
|
||||||
_ = ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out size);
|
|
||||||
|
|
||||||
if (!InitializeCommandBuffer(ctx, commandBuffer, buffer, size, aux0, aux1, clear: false))
|
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
@@ -106,8 +101,9 @@ public static class AmprExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.TryWriteUInt64(commandBuffer + CommandBufferAux0Offset, 0) ||
|
Span<byte> auxiliaryPointers = stackalloc byte[sizeof(ulong) * 2];
|
||||||
!ctx.TryWriteUInt64(commandBuffer + CommandBufferAux1Offset, 0))
|
auxiliaryPointers.Clear();
|
||||||
|
if (!ctx.Memory.TryWrite(commandBuffer + CommandBufferAux0Offset, auxiliaryPointers))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
@@ -181,8 +177,15 @@ public static class AmprExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out var buffer) ||
|
Span<byte> bufferPointers = stackalloc byte[sizeof(ulong) * 2];
|
||||||
!ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out var size) ||
|
if (!ctx.Memory.TryRead(commandBuffer + CommandBufferDataOffset, bufferPointers))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = BinaryPrimitives.ReadUInt64LittleEndian(bufferPointers);
|
||||||
|
var size = BinaryPrimitives.ReadUInt64LittleEndian(bufferPointers[sizeof(ulong)..]);
|
||||||
|
if (
|
||||||
!WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0))
|
!WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
@@ -476,20 +479,34 @@ public static class AmprExports
|
|||||||
ulong aux1,
|
ulong aux1,
|
||||||
bool clear)
|
bool clear)
|
||||||
{
|
{
|
||||||
|
Span<byte> header = stackalloc byte[CommandBufferHeaderSize];
|
||||||
if (clear)
|
if (clear)
|
||||||
{
|
{
|
||||||
Span<byte> header = stackalloc byte[CommandBufferHeaderSize];
|
|
||||||
header.Clear();
|
header.Clear();
|
||||||
if (!ctx.Memory.TryWrite(commandBuffer, header))
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ctx.Memory.TryRead(commandBuffer, header))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer = BinaryPrimitives.ReadUInt64LittleEndian(header[(int)CommandBufferDataOffset..]);
|
||||||
|
size = BinaryPrimitives.ReadUInt64LittleEndian(header[(int)CommandBufferSizeOffset..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.TryWriteUInt64(commandBuffer + CommandBufferSelfOffset, commandBuffer) &&
|
BinaryPrimitives.WriteUInt64LittleEndian(header[(int)CommandBufferSelfOffset..], commandBuffer);
|
||||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferAux0Offset, aux0) &&
|
BinaryPrimitives.WriteUInt64LittleEndian(header[(int)CommandBufferDataOffset..], buffer);
|
||||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferAux1Offset, aux1) &&
|
BinaryPrimitives.WriteUInt64LittleEndian(header[(int)CommandBufferSizeOffset..], size);
|
||||||
WriteCommandBufferPointers(ctx, commandBuffer, buffer, size, writeOffset: 0);
|
BinaryPrimitives.WriteUInt64LittleEndian(header[(int)CommandBufferAux0Offset..], aux0);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(header[(int)CommandBufferAux1Offset..], aux1);
|
||||||
|
if (!ctx.Memory.TryWrite(commandBuffer, header))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCommandBufferState(commandBuffer, buffer, size, writeOffset: 0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool WriteCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
|
private static bool WriteCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
|
||||||
@@ -504,6 +521,26 @@ public static class AmprExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateCommandBufferState(commandBuffer, buffer, size, writeOffset);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool WriteVisibleCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
|
||||||
|
{
|
||||||
|
Span<byte> pointers = stackalloc byte[sizeof(ulong) * 3];
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(pointers, commandBuffer);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(pointers[sizeof(ulong)..], buffer);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(pointers[(sizeof(ulong) * 2)..], size);
|
||||||
|
return ctx.Memory.TryWrite(commandBuffer + CommandBufferSelfOffset, pointers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateCommandBufferState(
|
||||||
|
ulong commandBuffer,
|
||||||
|
ulong buffer,
|
||||||
|
ulong size,
|
||||||
|
ulong writeOffset)
|
||||||
|
{
|
||||||
var state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
|
var state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
|
||||||
lock (state)
|
lock (state)
|
||||||
{
|
{
|
||||||
@@ -511,15 +548,6 @@ public static class AmprExports
|
|||||||
state.Size = size;
|
state.Size = size;
|
||||||
state.WriteOffset = writeOffset;
|
state.WriteOffset = writeOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool WriteVisibleCommandBufferPointers(CpuContext ctx, ulong commandBuffer, ulong buffer, ulong size)
|
|
||||||
{
|
|
||||||
return ctx.TryWriteUInt64(commandBuffer + CommandBufferSelfOffset, commandBuffer) &&
|
|
||||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferDataOffset, buffer) &&
|
|
||||||
ctx.TryWriteUInt64(commandBuffer + CommandBufferSizeOffset, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetCommandBufferState(
|
private static bool TryGetCommandBufferState(
|
||||||
@@ -540,9 +568,11 @@ public static class AmprExports
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.TryReadUInt64(commandBuffer + CommandBufferDataOffset, out buffer) &&
|
Span<byte> pointers = stackalloc byte[sizeof(ulong) * 2];
|
||||||
ctx.TryReadUInt64(commandBuffer + CommandBufferSizeOffset, out size))
|
if (ctx.Memory.TryRead(commandBuffer + CommandBufferDataOffset, pointers))
|
||||||
{
|
{
|
||||||
|
buffer = BinaryPrimitives.ReadUInt64LittleEndian(pointers);
|
||||||
|
size = BinaryPrimitives.ReadUInt64LittleEndian(pointers[sizeof(ulong)..]);
|
||||||
state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
|
state = _commandBuffers.GetOrAdd(commandBuffer, static _ => new CommandBufferState());
|
||||||
lock (state)
|
lock (state)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -204,6 +204,16 @@ public static class KernelExports
|
|||||||
var nameAddress = ctx[CpuRegister.R8];
|
var nameAddress = ctx[CpuRegister.R8];
|
||||||
var name = nameAddress == 0 ? string.Empty : ReadCString(ctx, nameAddress, 256);
|
var name = nameAddress == 0 ? string.Empty : ReadCString(ctx, nameAddress, 256);
|
||||||
var threadHandle = KernelPthreadState.CreateThreadHandle(name);
|
var threadHandle = KernelPthreadState.CreateThreadHandle(name);
|
||||||
|
KernelPthreadExtendedCompatExports.GetThreadStartScheduling(
|
||||||
|
ctx,
|
||||||
|
attrAddress,
|
||||||
|
out var priority,
|
||||||
|
out var affinityMask);
|
||||||
|
KernelPthreadExtendedCompatExports.RegisterThreadStart(
|
||||||
|
threadHandle,
|
||||||
|
name,
|
||||||
|
priority,
|
||||||
|
affinityMask);
|
||||||
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, threadHandle))
|
if (threadIdAddress != 0 && !ctx.TryWriteUInt64(threadIdAddress, threadHandle))
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
@@ -212,13 +222,22 @@ public static class KernelExports
|
|||||||
if (ShouldTracePthread())
|
if (ShouldTracePthread())
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} name='{name}' -> thread=0x{threadHandle:X16}");
|
$"[LOADER][TRACE] pthread_create: out=0x{threadIdAddress:X16} attr=0x{attrAddress:X16} " +
|
||||||
|
$"entry=0x{entryAddress:X16} arg=0x{argument:X16} name_ptr=0x{nameAddress:X16} " +
|
||||||
|
$"name='{name}' priority={priority} affinity=0x{affinityMask:X} -> thread=0x{threadHandle:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var scheduler = GuestThreadExecution.Scheduler;
|
var scheduler = GuestThreadExecution.Scheduler;
|
||||||
if (scheduler is not null && entryAddress != 0)
|
if (scheduler is not null && entryAddress != 0)
|
||||||
{
|
{
|
||||||
var request = new GuestThreadStartRequest(threadHandle, entryAddress, argument, attrAddress, name);
|
var request = new GuestThreadStartRequest(
|
||||||
|
threadHandle,
|
||||||
|
entryAddress,
|
||||||
|
argument,
|
||||||
|
attrAddress,
|
||||||
|
name,
|
||||||
|
priority,
|
||||||
|
affinityMask);
|
||||||
if (!scheduler.TryStartThread(ctx, request, out var error))
|
if (!scheduler.TryStartThread(ctx, request, out var error))
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
|
|||||||
@@ -602,9 +602,7 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ulong NextGpArg() => vaCursor.NextGpArg();
|
rendered = FormatString(ctx, format, ref vaCursor);
|
||||||
double NextFloatArg() => vaCursor.NextFloatArg();
|
|
||||||
rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Write(rendered);
|
Console.Write(rendered);
|
||||||
@@ -2908,9 +2906,8 @@ public static class KernelMemoryCompatExports
|
|||||||
|
|
||||||
var format = Encoding.UTF8.GetString(formatBytes);
|
var format = Encoding.UTF8.GetString(formatBytes);
|
||||||
var result = FormatString(ctx, format);
|
var result = FormatString(ctx, format);
|
||||||
var outputBytes = Encoding.UTF8.GetBytes(result);
|
|
||||||
|
|
||||||
return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes);
|
return WriteSnprintfOutput(ctx, destination, bufferSize, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int VsnprintfCore(CpuContext ctx)
|
private static int VsnprintfCore(CpuContext ctx)
|
||||||
@@ -2931,12 +2928,9 @@ public static class KernelMemoryCompatExports
|
|||||||
return WriteSnprintfOutput(ctx, destination, bufferSize, formatBytes);
|
return WriteSnprintfOutput(ctx, destination, bufferSize, formatBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong NextGpArg() => vaCursor.NextGpArg();
|
var rendered = FormatString(ctx, format, ref vaCursor);
|
||||||
double NextFloatArg() => vaCursor.NextFloatArg();
|
|
||||||
var rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
|
|
||||||
|
|
||||||
var outputBytes = Encoding.UTF8.GetBytes(rendered);
|
return WriteSnprintfOutput(ctx, destination, bufferSize, rendered);
|
||||||
return WriteSnprintfOutput(ctx, destination, bufferSize, outputBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int SwprintfCore(CpuContext ctx)
|
private static int SwprintfCore(CpuContext ctx)
|
||||||
@@ -2977,9 +2971,7 @@ public static class KernelMemoryCompatExports
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
TraceWidePrintfVaList(ctx, "vswprintf", format, vaListAddress, vaCursor);
|
TraceWidePrintfVaList(ctx, "vswprintf", format, vaListAddress, vaCursor);
|
||||||
ulong NextGpArg() => vaCursor.NextGpArg();
|
rendered = FormatString(ctx, format, ref vaCursor);
|
||||||
double NextFloatArg() => vaCursor.NextFloatArg();
|
|
||||||
rendered = FormatString(ctx, format, NextGpArg, NextFloatArg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceWidePrintf(ctx, "vswprintf", destination, bufferSize, format, rendered);
|
TraceWidePrintf(ctx, "vswprintf", destination, bufferSize, format, rendered);
|
||||||
@@ -2994,10 +2986,8 @@ public static class KernelMemoryCompatExports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryReadUInt32Compat(ctx, vaListAddress + 0, out var gpOffset) ||
|
Span<byte> vaList = stackalloc byte[24];
|
||||||
!TryReadUInt32Compat(ctx, vaListAddress + 4, out var fpOffset) ||
|
if (!TryReadCompat(ctx, vaListAddress, vaList))
|
||||||
!TryReadUInt64Compat(ctx, vaListAddress + 8, out var overflowArgArea) ||
|
|
||||||
!TryReadUInt64Compat(ctx, vaListAddress + 16, out var regSaveArea))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3005,10 +2995,10 @@ public static class KernelMemoryCompatExports
|
|||||||
cursor = new SysVAmd64VaListCursor(
|
cursor = new SysVAmd64VaListCursor(
|
||||||
ctx,
|
ctx,
|
||||||
vaListAddress,
|
vaListAddress,
|
||||||
gpOffset,
|
BinaryPrimitives.ReadUInt32LittleEndian(vaList),
|
||||||
fpOffset,
|
BinaryPrimitives.ReadUInt32LittleEndian(vaList[4..]),
|
||||||
overflowArgArea,
|
BinaryPrimitives.ReadUInt64LittleEndian(vaList[8..]),
|
||||||
regSaveArea);
|
BinaryPrimitives.ReadUInt64LittleEndian(vaList[16..]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3038,6 +3028,21 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int WriteSnprintfOutput(
|
||||||
|
CpuContext ctx,
|
||||||
|
ulong destination,
|
||||||
|
ulong bufferSize,
|
||||||
|
string output)
|
||||||
|
{
|
||||||
|
if (bufferSize == 0 || destination == 0)
|
||||||
|
{
|
||||||
|
ctx[CpuRegister.Rax] = unchecked((ulong)Encoding.UTF8.GetByteCount(output));
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WriteSnprintfOutput(ctx, destination, bufferSize, Encoding.UTF8.GetBytes(output));
|
||||||
|
}
|
||||||
|
|
||||||
private static int WriteSwprintfOutput(
|
private static int WriteSwprintfOutput(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong destination,
|
ulong destination,
|
||||||
@@ -3329,30 +3334,8 @@ public static class KernelMemoryCompatExports
|
|||||||
|
|
||||||
internal static string FormatStringFromVarArgs(CpuContext ctx, string format, int firstGpArgIndex)
|
internal static string FormatStringFromVarArgs(CpuContext ctx, string format, int firstGpArgIndex)
|
||||||
{
|
{
|
||||||
var gpIndex = Math.Max(0, firstGpArgIndex);
|
var argumentSource = new RegisterPrintfArgumentSource(ctx, Math.Max(0, firstGpArgIndex));
|
||||||
|
return FormatString(ctx, format, ref argumentSource);
|
||||||
ulong GetGpArg(int index)
|
|
||||||
{
|
|
||||||
return index switch
|
|
||||||
{
|
|
||||||
0 => ctx[CpuRegister.Rdi],
|
|
||||||
1 => ctx[CpuRegister.Rsi],
|
|
||||||
2 => ctx[CpuRegister.Rdx],
|
|
||||||
3 => ctx[CpuRegister.Rcx],
|
|
||||||
4 => ctx[CpuRegister.R8],
|
|
||||||
5 => ctx[CpuRegister.R9],
|
|
||||||
_ => ReadStackArg(ctx, (ulong)(index - 6) * 8)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong NextGpArg() => GetGpArg(gpIndex++);
|
|
||||||
double NextFloatArg()
|
|
||||||
{
|
|
||||||
var rawBits = NextGpArg();
|
|
||||||
return BitConverter.Int64BitsToDouble(unchecked((long)rawBits));
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormatString(ctx, format, NextGpArg, NextFloatArg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatString(CpuContext ctx, string format)
|
private static string FormatString(CpuContext ctx, string format)
|
||||||
@@ -3360,13 +3343,13 @@ public static class KernelMemoryCompatExports
|
|||||||
return FormatStringFromVarArgs(ctx, format, firstGpArgIndex: 3);
|
return FormatStringFromVarArgs(ctx, format, firstGpArgIndex: 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatString(
|
private static string FormatString<TArgumentSource>(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
string format,
|
string format,
|
||||||
Func<ulong> nextGpArg,
|
ref TArgumentSource argumentSource)
|
||||||
Func<double> nextFloatArg)
|
where TArgumentSource : struct, IPrintfArgumentSource
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder(format.Length + 32);
|
||||||
|
|
||||||
for (var i = 0; i < format.Length; i++)
|
for (var i = 0; i < format.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -3405,7 +3388,7 @@ public static class KernelMemoryCompatExports
|
|||||||
var width = 0;
|
var width = 0;
|
||||||
if (i < format.Length && format[i] == '*')
|
if (i < format.Length && format[i] == '*')
|
||||||
{
|
{
|
||||||
width = unchecked((int)nextGpArg());
|
width = unchecked((int)argumentSource.NextGpArg());
|
||||||
i++;
|
i++;
|
||||||
if (width < 0)
|
if (width < 0)
|
||||||
{
|
{
|
||||||
@@ -3428,7 +3411,7 @@ public static class KernelMemoryCompatExports
|
|||||||
i++;
|
i++;
|
||||||
if (i < format.Length && format[i] == '*')
|
if (i < format.Length && format[i] == '*')
|
||||||
{
|
{
|
||||||
precision = unchecked((int)nextGpArg());
|
precision = unchecked((int)argumentSource.NextGpArg());
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
else if (i < format.Length && char.IsDigit(format[i]))
|
else if (i < format.Length && char.IsDigit(format[i]))
|
||||||
@@ -3482,14 +3465,14 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
long value = lengthMod switch
|
long value = lengthMod switch
|
||||||
{
|
{
|
||||||
"hh" => unchecked((sbyte)nextGpArg()),
|
"hh" => unchecked((sbyte)argumentSource.NextGpArg()),
|
||||||
"h" => unchecked((short)nextGpArg()),
|
"h" => unchecked((short)argumentSource.NextGpArg()),
|
||||||
"l" => unchecked((long)nextGpArg()),
|
"l" => unchecked((long)argumentSource.NextGpArg()),
|
||||||
"ll" => unchecked((long)nextGpArg()),
|
"ll" => unchecked((long)argumentSource.NextGpArg()),
|
||||||
"j" => unchecked((long)nextGpArg()),
|
"j" => unchecked((long)argumentSource.NextGpArg()),
|
||||||
"z" => unchecked((long)nextGpArg()),
|
"z" => unchecked((long)argumentSource.NextGpArg()),
|
||||||
"t" => unchecked((long)nextGpArg()),
|
"t" => unchecked((long)argumentSource.NextGpArg()),
|
||||||
_ => unchecked((int)nextGpArg())
|
_ => unchecked((int)argumentSource.NextGpArg())
|
||||||
};
|
};
|
||||||
|
|
||||||
var formatted = value.ToString();
|
var formatted = value.ToString();
|
||||||
@@ -3506,14 +3489,14 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
ulong value = lengthMod switch
|
ulong value = lengthMod switch
|
||||||
{
|
{
|
||||||
"hh" => (byte)nextGpArg(),
|
"hh" => (byte)argumentSource.NextGpArg(),
|
||||||
"h" => (ushort)nextGpArg(),
|
"h" => (ushort)argumentSource.NextGpArg(),
|
||||||
"l" => nextGpArg(),
|
"l" => argumentSource.NextGpArg(),
|
||||||
"ll" => nextGpArg(),
|
"ll" => argumentSource.NextGpArg(),
|
||||||
"j" => nextGpArg(),
|
"j" => argumentSource.NextGpArg(),
|
||||||
"z" => nextGpArg(),
|
"z" => argumentSource.NextGpArg(),
|
||||||
"t" => nextGpArg(),
|
"t" => argumentSource.NextGpArg(),
|
||||||
_ => (uint)nextGpArg()
|
_ => (uint)argumentSource.NextGpArg()
|
||||||
};
|
};
|
||||||
|
|
||||||
var formatted = value.ToString();
|
var formatted = value.ToString();
|
||||||
@@ -3526,14 +3509,14 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
ulong value = lengthMod switch
|
ulong value = lengthMod switch
|
||||||
{
|
{
|
||||||
"hh" => (byte)nextGpArg(),
|
"hh" => (byte)argumentSource.NextGpArg(),
|
||||||
"h" => (ushort)nextGpArg(),
|
"h" => (ushort)argumentSource.NextGpArg(),
|
||||||
"l" => nextGpArg(),
|
"l" => argumentSource.NextGpArg(),
|
||||||
"ll" => nextGpArg(),
|
"ll" => argumentSource.NextGpArg(),
|
||||||
"j" => nextGpArg(),
|
"j" => argumentSource.NextGpArg(),
|
||||||
"z" => nextGpArg(),
|
"z" => argumentSource.NextGpArg(),
|
||||||
"t" => nextGpArg(),
|
"t" => argumentSource.NextGpArg(),
|
||||||
_ => (uint)nextGpArg()
|
_ => (uint)argumentSource.NextGpArg()
|
||||||
};
|
};
|
||||||
|
|
||||||
var formatted = specifier == 'x'
|
var formatted = specifier == 'x'
|
||||||
@@ -3551,14 +3534,14 @@ public static class KernelMemoryCompatExports
|
|||||||
{
|
{
|
||||||
ulong value = lengthMod switch
|
ulong value = lengthMod switch
|
||||||
{
|
{
|
||||||
"hh" => (byte)nextGpArg(),
|
"hh" => (byte)argumentSource.NextGpArg(),
|
||||||
"h" => (ushort)nextGpArg(),
|
"h" => (ushort)argumentSource.NextGpArg(),
|
||||||
"l" => nextGpArg(),
|
"l" => argumentSource.NextGpArg(),
|
||||||
"ll" => nextGpArg(),
|
"ll" => argumentSource.NextGpArg(),
|
||||||
"j" => nextGpArg(),
|
"j" => argumentSource.NextGpArg(),
|
||||||
"z" => nextGpArg(),
|
"z" => argumentSource.NextGpArg(),
|
||||||
"t" => nextGpArg(),
|
"t" => argumentSource.NextGpArg(),
|
||||||
_ => (uint)nextGpArg()
|
_ => (uint)argumentSource.NextGpArg()
|
||||||
};
|
};
|
||||||
|
|
||||||
var formatted = Convert.ToString((long)value, 8);
|
var formatted = Convert.ToString((long)value, 8);
|
||||||
@@ -3571,7 +3554,7 @@ public static class KernelMemoryCompatExports
|
|||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
{
|
{
|
||||||
var value = nextGpArg();
|
var value = argumentSource.NextGpArg();
|
||||||
var formatted = value == 0
|
var formatted = value == 0
|
||||||
? "(nil)"
|
? "(nil)"
|
||||||
: $"0x{value:X}";
|
: $"0x{value:X}";
|
||||||
@@ -3581,7 +3564,7 @@ public static class KernelMemoryCompatExports
|
|||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
{
|
{
|
||||||
var strAddr = nextGpArg();
|
var strAddr = argumentSource.NextGpArg();
|
||||||
TracePrintfStringArgument(ctx, lengthMod, strAddr);
|
TracePrintfStringArgument(ctx, lengthMod, strAddr);
|
||||||
if (strAddr == 0)
|
if (strAddr == 0)
|
||||||
{
|
{
|
||||||
@@ -3620,14 +3603,14 @@ public static class KernelMemoryCompatExports
|
|||||||
string renderedChar;
|
string renderedChar;
|
||||||
if (lengthMod == "l")
|
if (lengthMod == "l")
|
||||||
{
|
{
|
||||||
var scalar = unchecked((ushort)nextGpArg());
|
var scalar = unchecked((ushort)argumentSource.NextGpArg());
|
||||||
renderedChar = TryConvertWideScalarToString(scalar, out var wideCharText)
|
renderedChar = TryConvertWideScalarToString(scalar, out var wideCharText)
|
||||||
? wideCharText
|
? wideCharText
|
||||||
: "?";
|
: "?";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
renderedChar = ((char)(byte)nextGpArg()).ToString();
|
renderedChar = ((char)(byte)argumentSource.NextGpArg()).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Append(PadString(renderedChar, width, leftAlign, false));
|
sb.Append(PadString(renderedChar, width, leftAlign, false));
|
||||||
@@ -3641,7 +3624,7 @@ public static class KernelMemoryCompatExports
|
|||||||
case 'g':
|
case 'g':
|
||||||
case 'G':
|
case 'G':
|
||||||
{
|
{
|
||||||
var value = nextFloatArg();
|
var value = argumentSource.NextFloatArg();
|
||||||
|
|
||||||
var formatStr = precision >= 0
|
var formatStr = precision >= 0
|
||||||
? $"{{0:{specifier}{precision}}}"
|
? $"{{0:{specifier}{precision}}}"
|
||||||
@@ -3659,7 +3642,7 @@ public static class KernelMemoryCompatExports
|
|||||||
|
|
||||||
case 'n':
|
case 'n':
|
||||||
{
|
{
|
||||||
var addr = nextGpArg();
|
var addr = argumentSource.NextGpArg();
|
||||||
if (addr != 0)
|
if (addr != 0)
|
||||||
{
|
{
|
||||||
_ = TryWriteInt32(ctx, addr, sb.Length);
|
_ = TryWriteInt32(ctx, addr, sb.Length);
|
||||||
@@ -3699,7 +3682,46 @@ public static class KernelMemoryCompatExports
|
|||||||
return leftAlign ? str + padding : padding + str;
|
return leftAlign ? str + padding : padding + str;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SysVAmd64VaListCursor
|
private interface IPrintfArgumentSource
|
||||||
|
{
|
||||||
|
ulong NextGpArg();
|
||||||
|
|
||||||
|
double NextFloatArg();
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct RegisterPrintfArgumentSource : IPrintfArgumentSource
|
||||||
|
{
|
||||||
|
private readonly CpuContext _ctx;
|
||||||
|
private int _gpIndex;
|
||||||
|
|
||||||
|
public RegisterPrintfArgumentSource(CpuContext ctx, int gpIndex)
|
||||||
|
{
|
||||||
|
_ctx = ctx;
|
||||||
|
_gpIndex = gpIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong NextGpArg()
|
||||||
|
{
|
||||||
|
var index = _gpIndex++;
|
||||||
|
return index switch
|
||||||
|
{
|
||||||
|
0 => _ctx[CpuRegister.Rdi],
|
||||||
|
1 => _ctx[CpuRegister.Rsi],
|
||||||
|
2 => _ctx[CpuRegister.Rdx],
|
||||||
|
3 => _ctx[CpuRegister.Rcx],
|
||||||
|
4 => _ctx[CpuRegister.R8],
|
||||||
|
5 => _ctx[CpuRegister.R9],
|
||||||
|
_ => ReadStackArg(_ctx, (ulong)(index - 6) * 8)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public double NextFloatArg()
|
||||||
|
{
|
||||||
|
return BitConverter.Int64BitsToDouble(unchecked((long)NextGpArg()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SysVAmd64VaListCursor : IPrintfArgumentSource
|
||||||
{
|
{
|
||||||
private const uint GpSaveAreaLimit = 48;
|
private const uint GpSaveAreaLimit = 48;
|
||||||
private const uint FpSaveAreaLimit = 176;
|
private const uint FpSaveAreaLimit = 176;
|
||||||
@@ -4297,9 +4319,32 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int maxChunkSize = 4096;
|
const int maxChunkSize = 4096;
|
||||||
|
const int inlineChunkSize = 256;
|
||||||
|
Span<byte> inlineChunk = stackalloc byte[inlineChunkSize];
|
||||||
|
var firstPageRemaining = maxChunkSize - (int)(address & (maxChunkSize - 1));
|
||||||
|
var firstReadLength = Math.Min(limit, Math.Min(inlineChunkSize, firstPageRemaining));
|
||||||
|
var firstSpan = inlineChunk[..firstReadLength];
|
||||||
|
ulong offset = 0;
|
||||||
|
if (TryReadCompat(ctx, address, firstSpan))
|
||||||
|
{
|
||||||
|
var nulIndex = firstSpan.IndexOf((byte)0);
|
||||||
|
if (nulIndex >= 0)
|
||||||
|
{
|
||||||
|
bytes = firstSpan[..nulIndex].ToArray();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = unchecked((ulong)firstReadLength);
|
||||||
|
}
|
||||||
|
|
||||||
var chunk = GC.AllocateUninitializedArray<byte>(Math.Min(maxChunkSize, limit));
|
var chunk = GC.AllocateUninitializedArray<byte>(Math.Min(maxChunkSize, limit));
|
||||||
var writer = new ArrayBufferWriter<byte>(Math.Min(limit, 256));
|
var writer = new ArrayBufferWriter<byte>(Math.Min(limit, 256));
|
||||||
ulong offset = 0;
|
if (offset != 0)
|
||||||
|
{
|
||||||
|
firstSpan.CopyTo(writer.GetSpan(firstReadLength));
|
||||||
|
writer.Advance(firstReadLength);
|
||||||
|
}
|
||||||
|
|
||||||
while (offset < (ulong)limit)
|
while (offset < (ulong)limit)
|
||||||
{
|
{
|
||||||
var current = address + offset;
|
var current = address + offset;
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ namespace SharpEmu.Libs.Kernel;
|
|||||||
public static class KernelPthreadExtendedCompatExports
|
public static class KernelPthreadExtendedCompatExports
|
||||||
{
|
{
|
||||||
private const int DefaultThreadPriority = 700;
|
private const int DefaultThreadPriority = 700;
|
||||||
private const ulong DefaultThreadAffinityMask = ulong.MaxValue;
|
private const ulong DefaultThreadAffinityMask = 0x7FUL;
|
||||||
private const int DefaultDetachState = 0;
|
private const int DefaultDetachState = 0;
|
||||||
private const ulong DefaultGuardSize = 0x1000UL;
|
private const ulong DefaultGuardSize = 0x1000UL;
|
||||||
private const ulong DefaultStackSize = 0x1_00000UL;
|
private const ulong DefaultStackSize = 0x1_00000UL;
|
||||||
private const int DefaultInheritSched = 0;
|
private const int DefaultInheritSched = 4;
|
||||||
private const int DefaultSchedPolicy = 0;
|
private const int DefaultSchedPolicy = 1;
|
||||||
private const int DefaultSchedPriority = 0;
|
private const int DefaultSchedPriority = DefaultThreadPriority;
|
||||||
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
|
||||||
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
|
private const ulong SyntheticPthreadAttrHandleBase = 0x00006004_0000_0000;
|
||||||
|
|
||||||
@@ -34,6 +34,48 @@ public static class KernelPthreadExtendedCompatExports
|
|||||||
|
|
||||||
private static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<int, ulong>> _threadLocalSpecific = new();
|
private static readonly ConcurrentDictionary<ulong, ConcurrentDictionary<int, ulong>> _threadLocalSpecific = new();
|
||||||
|
|
||||||
|
internal static void GetThreadStartScheduling(
|
||||||
|
CpuContext ctx,
|
||||||
|
ulong attrAddress,
|
||||||
|
out int priority,
|
||||||
|
out ulong affinityMask)
|
||||||
|
{
|
||||||
|
if (attrAddress == 0)
|
||||||
|
{
|
||||||
|
priority = DefaultThreadPriority;
|
||||||
|
affinityMask = DefaultThreadAffinityMask;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedAddress = ResolvePthreadAttrHandle(ctx, attrAddress);
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
var attributes = GetOrCreateAttrStateLocked(resolvedAddress);
|
||||||
|
priority = attributes.SchedPriority;
|
||||||
|
affinityMask = attributes.AffinityMask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void RegisterThreadStart(
|
||||||
|
ulong thread,
|
||||||
|
string name,
|
||||||
|
int priority,
|
||||||
|
ulong affinityMask)
|
||||||
|
{
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
var state = GetOrCreateThreadStateLocked(thread);
|
||||||
|
state.Name = name;
|
||||||
|
state.Priority = priority;
|
||||||
|
state.AffinityMask = affinityMask;
|
||||||
|
state.Attributes = state.Attributes with
|
||||||
|
{
|
||||||
|
SchedPriority = priority,
|
||||||
|
AffinityMask = affinityMask,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class ThreadState
|
private sealed class ThreadState
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -0,0 +1,444 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.SaveData;
|
||||||
|
|
||||||
|
public static class SaveDataExports
|
||||||
|
{
|
||||||
|
private const int OrbisSaveDataErrorParameter = unchecked((int)0x809F0000);
|
||||||
|
private const int OrbisSaveDataErrorInternal = unchecked((int)0x809F000B);
|
||||||
|
private const int SaveDataTitleIdSize = 10;
|
||||||
|
private const int SaveDataDirNameSize = 32;
|
||||||
|
private const int SaveDataParamSize = 0x530;
|
||||||
|
private const int SaveDataSearchInfoSize = 0x30;
|
||||||
|
private const ulong ResultHitNumOffset = 0x00;
|
||||||
|
private const ulong ResultDirNamesOffset = 0x08;
|
||||||
|
private const ulong ResultDirNamesNumOffset = 0x10;
|
||||||
|
private const ulong ResultSetNumOffset = 0x14;
|
||||||
|
private const ulong ResultParamsOffset = 0x18;
|
||||||
|
private const ulong ResultInfosOffset = 0x20;
|
||||||
|
private const uint SortKeyFreeBlocks = 5;
|
||||||
|
private const uint SortOrderDescent = 1;
|
||||||
|
private static readonly object _stateGate = new();
|
||||||
|
private static string? _titleId;
|
||||||
|
|
||||||
|
public static void ConfigureApplicationInfo(string? titleId)
|
||||||
|
{
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
_titleId = string.IsNullOrWhiteSpace(titleId) ? null : SanitizePathSegment(titleId.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "TywrFKCoLGY",
|
||||||
|
ExportName = "sceSaveDataInitialize3",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libSceSaveData")]
|
||||||
|
public static int SaveDataInitialize3(CpuContext ctx)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(ResolveSaveDataRoot());
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "dyIhnXq-0SM",
|
||||||
|
ExportName = "sceSaveDataDirNameSearch",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libSceSaveData")]
|
||||||
|
public static int SaveDataDirNameSearch(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var condAddress = ctx[CpuRegister.Rdi];
|
||||||
|
var resultAddress = ctx[CpuRegister.Rsi];
|
||||||
|
if (condAddress == 0 || resultAddress == 0)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryReadSearchCond(ctx, condAddress, out var cond) ||
|
||||||
|
!TryReadSearchResult(ctx, resultAddress, out var result))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cond.UserId < 0 || cond.SortKey > SortKeyFreeBlocks || cond.SortOrder > SortOrderDescent)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string titleId;
|
||||||
|
if (cond.TitleIdAddress == 0)
|
||||||
|
{
|
||||||
|
titleId = ResolveConfiguredTitleId();
|
||||||
|
}
|
||||||
|
else if (!TryReadFixedAscii(ctx, cond.TitleIdAddress, SaveDataTitleIdSize, out titleId))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = ResolveTitleSaveRoot(cond.UserId, titleId);
|
||||||
|
var entries = Directory.Exists(root)
|
||||||
|
? EnumerateSaveDirectories(root, cond.Pattern)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
entries = SortEntries(entries, cond.SortKey, cond.SortOrder);
|
||||||
|
var setNum = result.DirNamesNum == 0
|
||||||
|
? 0
|
||||||
|
: Math.Min(result.DirNamesNum, entries.Count);
|
||||||
|
if (!TryWriteUInt32(ctx, resultAddress + ResultHitNumOffset, checked((uint)entries.Count)) ||
|
||||||
|
!TryWriteUInt32(ctx, resultAddress + ResultSetNumOffset, checked((uint)setNum)))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setNum == 0)
|
||||||
|
{
|
||||||
|
TraceSaveData($"dir_name_search user={cond.UserId} title={titleId} hits={entries.Count} set=0 root='{root}'");
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.DirNamesAddress == 0)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < setNum; i++)
|
||||||
|
{
|
||||||
|
var entry = entries[i];
|
||||||
|
if (!TryWriteFixedAscii(
|
||||||
|
ctx,
|
||||||
|
result.DirNamesAddress + ((ulong)i * SaveDataDirNameSize),
|
||||||
|
SaveDataDirNameSize,
|
||||||
|
entry.Name) ||
|
||||||
|
(result.ParamsAddress != 0 &&
|
||||||
|
!TryWriteParam(ctx, result.ParamsAddress + ((ulong)i * SaveDataParamSize), entry)) ||
|
||||||
|
(result.InfosAddress != 0 &&
|
||||||
|
!TryWriteSearchInfo(ctx, result.InfosAddress + ((ulong)i * SaveDataSearchInfoSize), entry)))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceSaveData($"dir_name_search user={cond.UserId} title={titleId} hits={entries.Count} set={setNum} root='{root}'");
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, OrbisSaveDataErrorInternal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadSearchCond(CpuContext ctx, ulong address, out SearchCond cond)
|
||||||
|
{
|
||||||
|
cond = default;
|
||||||
|
if (!TryReadInt32(ctx, address, out var userId) ||
|
||||||
|
!ctx.TryReadUInt64(address + 0x08, out var titleIdAddress) ||
|
||||||
|
!ctx.TryReadUInt64(address + 0x10, out var dirNameAddress) ||
|
||||||
|
!TryReadUInt32(ctx, address + 0x18, out var sortKey) ||
|
||||||
|
!TryReadUInt32(ctx, address + 0x1C, out var sortOrder))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string pattern;
|
||||||
|
if (dirNameAddress == 0)
|
||||||
|
{
|
||||||
|
pattern = string.Empty;
|
||||||
|
}
|
||||||
|
else if (!TryReadFixedAscii(ctx, dirNameAddress, SaveDataDirNameSize, out pattern))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cond = new SearchCond(userId, titleIdAddress, pattern, sortKey, sortOrder);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadSearchResult(CpuContext ctx, ulong address, out SearchResult result)
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
if (!ctx.TryReadUInt64(address + ResultDirNamesOffset, out var dirNamesAddress) ||
|
||||||
|
!TryReadUInt32(ctx, address + ResultDirNamesNumOffset, out var dirNamesNum) ||
|
||||||
|
!ctx.TryReadUInt64(address + ResultParamsOffset, out var paramsAddress) ||
|
||||||
|
!ctx.TryReadUInt64(address + ResultInfosOffset, out var infosAddress))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new SearchResult(dirNamesAddress, dirNamesNum, paramsAddress, infosAddress);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SaveEntry> EnumerateSaveDirectories(string root, string pattern)
|
||||||
|
{
|
||||||
|
var entries = new List<SaveEntry>();
|
||||||
|
foreach (var directory in Directory.EnumerateDirectories(root))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(directory);
|
||||||
|
if (string.IsNullOrWhiteSpace(name) ||
|
||||||
|
name.StartsWith("sce_", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
(!string.IsNullOrEmpty(pattern) && !MatchPattern(name, pattern)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = new DirectoryInfo(directory);
|
||||||
|
entries.Add(new SaveEntry(name, directory, info.LastWriteTimeUtc));
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<SaveEntry> SortEntries(List<SaveEntry> entries, uint sortKey, uint sortOrder)
|
||||||
|
{
|
||||||
|
IOrderedEnumerable<SaveEntry> sorted = sortKey switch
|
||||||
|
{
|
||||||
|
3 => entries.OrderBy(entry => entry.LastWriteUtc),
|
||||||
|
_ => entries.OrderBy(entry => entry.Name, StringComparer.OrdinalIgnoreCase),
|
||||||
|
};
|
||||||
|
|
||||||
|
var list = sorted.ToList();
|
||||||
|
if (sortOrder == SortOrderDescent)
|
||||||
|
{
|
||||||
|
list.Reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryWriteParam(CpuContext ctx, ulong address, SaveEntry entry)
|
||||||
|
{
|
||||||
|
var param = new byte[SaveDataParamSize];
|
||||||
|
WriteAscii(param.AsSpan(0x00, 128), "Saved Data");
|
||||||
|
WriteAscii(param.AsSpan(0x100, 1024), entry.Name);
|
||||||
|
BinaryPrimitives.WriteInt64LittleEndian(
|
||||||
|
param.AsSpan(0x508, sizeof(long)),
|
||||||
|
new DateTimeOffset(entry.LastWriteUtc).ToUnixTimeSeconds());
|
||||||
|
return ctx.Memory.TryWrite(address, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryWriteSearchInfo(CpuContext ctx, ulong address, SaveEntry entry)
|
||||||
|
{
|
||||||
|
var size = GetDirectorySize(entry.Path);
|
||||||
|
var usedBlocks = checked((ulong)((size + 32767) / 32768));
|
||||||
|
var blocks = Math.Max(96UL, usedBlocks);
|
||||||
|
Span<byte> info = stackalloc byte[SaveDataSearchInfoSize];
|
||||||
|
info.Clear();
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(info[0x00..], blocks);
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(info[0x08..], blocks - usedBlocks);
|
||||||
|
return ctx.Memory.TryWrite(address, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long GetDirectorySize(string root)
|
||||||
|
{
|
||||||
|
long total = 0;
|
||||||
|
foreach (var file in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
total += new FileInfo(file).Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchPattern(string value, string pattern) =>
|
||||||
|
MatchPattern(value.AsSpan(), pattern.AsSpan());
|
||||||
|
|
||||||
|
private static bool MatchPattern(ReadOnlySpan<char> value, ReadOnlySpan<char> pattern)
|
||||||
|
{
|
||||||
|
if (pattern.IsEmpty)
|
||||||
|
{
|
||||||
|
return value.IsEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern[0] == '%')
|
||||||
|
{
|
||||||
|
for (var i = 0; i <= value.Length; i++)
|
||||||
|
{
|
||||||
|
if (MatchPattern(value[i..], pattern[1..]))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern[0] == '_' ||
|
||||||
|
char.ToUpperInvariant(pattern[0]) == char.ToUpperInvariant(value[0]))
|
||||||
|
{
|
||||||
|
return MatchPattern(value[1..], pattern[1..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveTitleSaveRoot(int userId, string titleId) =>
|
||||||
|
Path.Combine(ResolveSaveDataRoot(), userId.ToString(), SanitizePathSegment(titleId));
|
||||||
|
|
||||||
|
private static string ResolveSaveDataRoot()
|
||||||
|
{
|
||||||
|
var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR");
|
||||||
|
var root = string.IsNullOrWhiteSpace(configured)
|
||||||
|
? Path.Combine(Environment.CurrentDirectory, "user", "savedata")
|
||||||
|
: configured;
|
||||||
|
return Path.GetFullPath(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveConfiguredTitleId()
|
||||||
|
{
|
||||||
|
lock (_stateGate)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(_titleId))
|
||||||
|
{
|
||||||
|
return _titleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var app0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||||
|
var app0Name = string.IsNullOrWhiteSpace(app0Root)
|
||||||
|
? null
|
||||||
|
: Path.GetFileName(Path.TrimEndingDirectorySeparator(app0Root));
|
||||||
|
if (!string.IsNullOrWhiteSpace(app0Name))
|
||||||
|
{
|
||||||
|
var candidate = app0Name.Split('-', StringSplitOptions.RemoveEmptyEntries)[0];
|
||||||
|
if (!string.IsNullOrWhiteSpace(candidate))
|
||||||
|
{
|
||||||
|
return SanitizePathSegment(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizePathSegment(string value)
|
||||||
|
{
|
||||||
|
var invalid = Path.GetInvalidFileNameChars();
|
||||||
|
var sanitized = new string(value.Select(ch => invalid.Contains(ch) ? '_' : ch).ToArray());
|
||||||
|
return string.IsNullOrWhiteSpace(sanitized) ? "default" : sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadFixedAscii(CpuContext ctx, ulong address, int length, out string value)
|
||||||
|
{
|
||||||
|
value = string.Empty;
|
||||||
|
Span<byte> buffer = stackalloc byte[length];
|
||||||
|
if (!ctx.Memory.TryRead(address, buffer))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringLength = buffer.IndexOf((byte)0);
|
||||||
|
if (stringLength < 0)
|
||||||
|
{
|
||||||
|
stringLength = buffer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = Encoding.ASCII.GetString(buffer[..stringLength]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryWriteFixedAscii(CpuContext ctx, ulong address, int length, string value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[length];
|
||||||
|
buffer.Clear();
|
||||||
|
WriteAscii(buffer, value);
|
||||||
|
return ctx.Memory.TryWrite(address, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteAscii(Span<byte> destination, string value)
|
||||||
|
{
|
||||||
|
var count = Math.Min(value.Length, Math.Max(0, destination.Length - 1));
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var ch = value[i];
|
||||||
|
destination[i] = ch <= 0x7F ? (byte)ch : (byte)'?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadInt32(CpuContext ctx, ulong address, out int value)
|
||||||
|
{
|
||||||
|
Span<byte> bytes = stackalloc byte[sizeof(int)];
|
||||||
|
if (!ctx.Memory.TryRead(address, bytes))
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = BinaryPrimitives.ReadInt32LittleEndian(bytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
|
||||||
|
{
|
||||||
|
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
||||||
|
if (!ctx.Memory.TryRead(address, bytes))
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = BinaryPrimitives.ReadUInt32LittleEndian(bytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryWriteUInt32(CpuContext ctx, ulong address, uint value)
|
||||||
|
{
|
||||||
|
Span<byte> bytes = stackalloc byte[sizeof(uint)];
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
|
||||||
|
return ctx.Memory.TryWrite(address, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SetReturn(CpuContext ctx, int result)
|
||||||
|
{
|
||||||
|
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TraceSaveData(string message)
|
||||||
|
{
|
||||||
|
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SAVEDATA"), "1", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[LOADER][TRACE] savedata.{message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct SearchCond(
|
||||||
|
int UserId,
|
||||||
|
ulong TitleIdAddress,
|
||||||
|
string Pattern,
|
||||||
|
uint SortKey,
|
||||||
|
uint SortOrder);
|
||||||
|
|
||||||
|
private readonly record struct SearchResult(
|
||||||
|
ulong DirNamesAddress,
|
||||||
|
uint DirNamesNum,
|
||||||
|
ulong ParamsAddress,
|
||||||
|
ulong InfosAddress);
|
||||||
|
|
||||||
|
private readonly record struct SaveEntry(string Name, string Path, DateTime LastWriteUtc);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using SharpEmu.HLE;
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.Libs.VideoOut;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
|
|
||||||
namespace SharpEmu.Libs.SystemService;
|
namespace SharpEmu.Libs.SystemService;
|
||||||
@@ -85,6 +86,17 @@ public static class SystemServiceExports
|
|||||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "Vo5V8KAwCmk",
|
||||||
|
ExportName = "sceSystemServiceHideSplashScreen",
|
||||||
|
Target = Generation.Gen4 | Generation.Gen5,
|
||||||
|
LibraryName = "libSceSystemService")]
|
||||||
|
public static int SystemServiceHideSplashScreen(CpuContext ctx)
|
||||||
|
{
|
||||||
|
VulkanVideoPresenter.HideSplashScreen();
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
private static int SetReturn(CpuContext ctx, int result)
|
private static int SetReturn(CpuContext ctx, int result)
|
||||||
{
|
{
|
||||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private static uint _windowWidth;
|
private static uint _windowWidth;
|
||||||
private static uint _windowHeight;
|
private static uint _windowHeight;
|
||||||
private static bool _closed;
|
private static bool _closed;
|
||||||
|
private static bool _splashHidden;
|
||||||
|
|
||||||
public static void EnsureStarted(uint width, uint height)
|
public static void EnsureStarted(uint width, uint height)
|
||||||
{
|
{
|
||||||
@@ -55,7 +56,15 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
_windowWidth = width;
|
_windowWidth = width;
|
||||||
_windowHeight = height;
|
_windowHeight = height;
|
||||||
_latestPresentation ??= hasSplash
|
_latestPresentation ??= _splashHidden
|
||||||
|
? new Presentation(
|
||||||
|
CreateBlackFrame(width, height),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
1,
|
||||||
|
GuestDrawKind.None,
|
||||||
|
IsSplash: false)
|
||||||
|
: hasSplash
|
||||||
? new Presentation(
|
? new Presentation(
|
||||||
splashPixels,
|
splashPixels,
|
||||||
splashWidth,
|
splashWidth,
|
||||||
@@ -79,6 +88,28 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void HideSplashScreen()
|
||||||
|
{
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
_splashHidden = true;
|
||||||
|
if (_closed || _latestPresentation is not { IsSplash: true } latest)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sequence = latest.Sequence + 1;
|
||||||
|
_latestPresentation = new Presentation(
|
||||||
|
CreateBlackFrame(latest.Width, latest.Height),
|
||||||
|
latest.Width,
|
||||||
|
latest.Height,
|
||||||
|
sequence,
|
||||||
|
GuestDrawKind.None,
|
||||||
|
IsSplash: false);
|
||||||
|
Console.Error.WriteLine("[LOADER][INFO] Vulkan VideoOut hid splash");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void Submit(byte[] bgraFrame, uint width, uint height)
|
public static void Submit(byte[] bgraFrame, uint width, uint height)
|
||||||
{
|
{
|
||||||
if (bgraFrame.Length != checked((int)(width * height * 4)))
|
if (bgraFrame.Length != checked((int)(width * height * 4)))
|
||||||
@@ -159,6 +190,24 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] CreateBlackFrame(uint width, uint height)
|
||||||
|
{
|
||||||
|
if (width == 0 || height == 0 || width > 8192 || height > 8192)
|
||||||
|
{
|
||||||
|
width = 1;
|
||||||
|
height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixels = GC.AllocateUninitializedArray<byte>(checked((int)(width * height * 4)));
|
||||||
|
pixels.AsSpan().Clear();
|
||||||
|
for (var offset = 3; offset < pixels.Length; offset += 4)
|
||||||
|
{
|
||||||
|
pixels[offset] = 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
|
||||||
private static void Run()
|
private static void Run()
|
||||||
{
|
{
|
||||||
uint width;
|
uint width;
|
||||||
@@ -259,6 +308,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
options.Title = VideoOutExports.GetWindowTitle();
|
options.Title = VideoOutExports.GetWindowTitle();
|
||||||
options.WindowBorder = WindowBorder.Fixed;
|
options.WindowBorder = WindowBorder.Fixed;
|
||||||
options.VSync = true;
|
options.VSync = true;
|
||||||
|
options.FramesPerSecond = 60;
|
||||||
|
options.UpdatesPerSecond = 60;
|
||||||
_window = Window.Create(options);
|
_window = Window.Create(options);
|
||||||
_window.Load += Initialize;
|
_window.Load += Initialize;
|
||||||
_window.Render += Render;
|
_window.Render += Render;
|
||||||
|
|||||||
Reference in New Issue
Block a user