mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35736e4740 | |||
| 92526ecacf | |||
| 0d89b2488b | |||
| c3019b78d6 | |||
| dd5e524879 | |||
| df9d0e6aaf | |||
| 253d0fae3f | |||
| 78d719ef9e | |||
| be3806cdf6 |
@@ -126,7 +126,7 @@ jobs:
|
||||
needs:
|
||||
- init
|
||||
- build
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -482,6 +482,8 @@ public sealed partial class DirectExecutionBackend
|
||||
catch (Exception ex)
|
||||
{
|
||||
LastError = $"HLE dispatch error for {importStubEntry.Nid}: {ex.GetType().Name}: {ex.Message}";
|
||||
Console.Error.WriteLine($"[LOADER][ERROR] {LastError}");
|
||||
Console.Error.WriteLine($"[LOADER][ERROR] {ex.StackTrace}");
|
||||
cpuContext[CpuRegister.Rax] = 18446744071562199298uL;
|
||||
return 18446744071562199298uL;
|
||||
}
|
||||
@@ -533,12 +535,8 @@ public sealed partial class DirectExecutionBackend
|
||||
$"ret=0x{returnRip:X16}");
|
||||
}
|
||||
|
||||
var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(
|
||||
returnRip,
|
||||
(ulong)argPackPtr + 104uL,
|
||||
ActiveGuestReturnSlotAddress);
|
||||
int returnValue;
|
||||
try
|
||||
if (IsNoBlockLeafImport(importStubEntry.Nid))
|
||||
{
|
||||
cpuContext.ClearRaxWriteFlag();
|
||||
returnValue = export.Function(cpuContext);
|
||||
@@ -547,9 +545,25 @@ public sealed partial class DirectExecutionBackend
|
||||
cpuContext[CpuRegister.Rax] = unchecked((ulong)returnValue);
|
||||
}
|
||||
}
|
||||
finally
|
||||
else
|
||||
{
|
||||
GuestThreadExecution.RestoreImportCallFrame(previousImportCallFrame);
|
||||
var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame(
|
||||
returnRip,
|
||||
(ulong)argPackPtr + 104uL,
|
||||
ActiveGuestReturnSlotAddress);
|
||||
try
|
||||
{
|
||||
cpuContext.ClearRaxWriteFlag();
|
||||
returnValue = export.Function(cpuContext);
|
||||
if (!cpuContext.WasRaxWritten)
|
||||
{
|
||||
cpuContext[CpuRegister.Rax] = unchecked((ulong)returnValue);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GuestThreadExecution.RestoreImportCallFrame(previousImportCallFrame);
|
||||
}
|
||||
}
|
||||
|
||||
if (returnValue != (int)OrbisGen2Result.ORBIS_GEN2_OK)
|
||||
@@ -594,6 +608,28 @@ public sealed partial class DirectExecutionBackend
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsNoBlockLeafImport(string nid) =>
|
||||
nid is
|
||||
"8aI7R7WaOlc" or // sceAmprCommandBufferConstructor
|
||||
"a8uLzYY--tM" or // sceAmprAprCommandBufferConstructor
|
||||
"Qs1xtplKo0U" or // sceAmprAprCommandBufferDestructor
|
||||
"GuchCTefuZw" or // sceAmprCommandBufferDestructor
|
||||
"N-FSPA4S3nI" or // sceAmprCommandBufferSetBuffer
|
||||
"baQO9ez2gL4" or // sceAmprCommandBufferReset
|
||||
"ULvXMDz56po" or // sceAmprCommandBufferClearBuffer
|
||||
"mQ16-QdKv7k" or // sceAmprAprCommandBufferReadFile
|
||||
"vWU-odnS+fU" or // sceAmprMeasureCommandSizeReadFile
|
||||
"sSAUCCU1dv4" or // sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00
|
||||
"C+IEj+BsAFM" or // sceAmprMeasureCommandSizeWriteAddressOnCompletion
|
||||
"tZDDEo2tE5k" or // sceAmprCommandBufferGetSize
|
||||
"GnxKOHEawhk" or // sceAmprCommandBufferGetCurrentOffset
|
||||
"H896Pt-yB4I" or // sceAmprCommandBufferWriteKernelEventQueue_04_00
|
||||
"sJXyWHjP-F8" or // sceAmprCommandBufferWriteAddressOnCompletion
|
||||
"ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult
|
||||
"rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer
|
||||
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
|
||||
"qvMUCyyaCSI"; // sceKernelAprSubmitCommandBufferAndGetId
|
||||
|
||||
private bool ShouldLogImportResult(string nid, OrbisGen2Result result)
|
||||
{
|
||||
var expectedFileProbeMiss =
|
||||
@@ -602,7 +638,10 @@ public sealed partial class DirectExecutionBackend
|
||||
var expectedTimedWaitTimeout =
|
||||
string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) &&
|
||||
unchecked((int)result) == 60;
|
||||
if (!expectedFileProbeMiss && !expectedTimedWaitTimeout)
|
||||
var expectedMutexTrylockBusy =
|
||||
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||
if (!expectedFileProbeMiss && !expectedTimedWaitTimeout && !expectedMutexTrylockBusy)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -684,7 +723,12 @@ public sealed partial class DirectExecutionBackend
|
||||
"Vo5V8KAwCmk" or // sceSystemServiceHideSplashScreen
|
||||
"TywrFKCoLGY" or // sceSaveDataInitialize3
|
||||
"dyIhnXq-0SM" or // sceSaveDataDirNameSearch
|
||||
"ZP4e7rlzOUk" or // sceSaveDataMount3
|
||||
"ERKzksauAJA" or // sceSaveDataDialogGetStatus
|
||||
"KK3Bdg1RWK0" or // sceSaveDataDialogUpdateStatus
|
||||
"en7gNVnh878" or // sceSaveDataDialogIsReadyToDisplay
|
||||
"jO8DM8oyego" or // sceNpEntitlementAccessInitialize
|
||||
"TFyU+KFBv54" or // sceNpEntitlementAccessGetAddcontEntitlementInfoList
|
||||
"27bAgiJmOh0" or // pthread_cond_timedwait
|
||||
"iQw3iQPhvUQ" or // sceNetCtlCheckCallback
|
||||
"Q2V+iqvjgC0" or // vsnprintf
|
||||
|
||||
@@ -355,6 +355,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
public GuestThreadRunState State { get; set; }
|
||||
|
||||
public ulong ExitValue { get; set; }
|
||||
|
||||
public string? BlockReason { get; set; }
|
||||
|
||||
public bool HasBlockedContinuation { get; set; }
|
||||
@@ -1266,7 +1268,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
libraryName.IndexOf("Kernel", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private static bool PreferLleForLibcExport(string exportName)
|
||||
private bool PreferLleForLibcExport(string exportName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(exportName))
|
||||
{
|
||||
@@ -1287,6 +1289,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (IsLibcAllocatorExport(exportName))
|
||||
{
|
||||
return CanUseLleLibcAllocatorFamily();
|
||||
}
|
||||
if (string.Equals(value, "0", StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
@@ -1298,6 +1304,51 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
return IsSafeLleLibcExport(exportName);
|
||||
}
|
||||
|
||||
private bool CanUseLleLibcAllocatorFamily()
|
||||
{
|
||||
return HasUsableLleLibcExport("gQX+4GDQjpM", "malloc") &&
|
||||
HasUsableLleLibcExport("tIhsqj0qsFE", "free") &&
|
||||
HasUsableLleLibcExport("2X5agFjKxMc", "calloc") &&
|
||||
HasUsableLleLibcExport("Y7aJ1uydPMo", "realloc") &&
|
||||
HasUsableLleLibcExport("Ujf3KzMvRmI", "memalign") &&
|
||||
HasUsableLleLibcExport("2Btkg8k24Zg", "aligned_alloc") &&
|
||||
HasUsableLleLibcExport("cVSk9y8URbc", "posix_memalign");
|
||||
}
|
||||
|
||||
private bool HasUsableLleLibcExport(string nid, string exportName)
|
||||
{
|
||||
if (TryResolveRuntimeSymbolAddress(nid, out var address) && IsDirectImportTargetUsable(address))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var candidate in EnumerateRuntimeSymbolCandidates(exportName))
|
||||
{
|
||||
if (TryResolveRuntimeSymbolAddress(candidate, out address) && IsDirectImportTargetUsable(address))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsLibcAllocatorExport(string exportName)
|
||||
{
|
||||
return exportName switch
|
||||
{
|
||||
"malloc" or
|
||||
"free" or
|
||||
"calloc" or
|
||||
"realloc" or
|
||||
"memalign" or
|
||||
"aligned_alloc" or
|
||||
"posix_memalign" or
|
||||
"malloc_usable_size" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsSafeLleLibcExport(string exportName)
|
||||
{
|
||||
return exportName switch
|
||||
@@ -2406,6 +2457,69 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
|
||||
public bool SupportsGuestContextTransfer => true;
|
||||
|
||||
public bool TryJoinThread(
|
||||
CpuContext callerContext,
|
||||
ulong threadHandle,
|
||||
out ulong returnValue,
|
||||
out string? error)
|
||||
{
|
||||
returnValue = 0;
|
||||
error = null;
|
||||
if (threadHandle == 0)
|
||||
{
|
||||
error = "thread handle is zero";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (threadHandle == GuestThreadExecution.CurrentGuestThreadHandle)
|
||||
{
|
||||
error = "thread cannot join itself";
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!ActiveForcedGuestExit)
|
||||
{
|
||||
Thread? hostThread;
|
||||
lock (_guestThreadGate)
|
||||
{
|
||||
if (!_guestThreads.TryGetValue(threadHandle, out var thread))
|
||||
{
|
||||
error = $"unknown guest thread 0x{threadHandle:X16}";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (thread.State == GuestThreadRunState.Exited)
|
||||
{
|
||||
returnValue = thread.ExitValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (thread.State == GuestThreadRunState.Faulted)
|
||||
{
|
||||
error =
|
||||
$"guest thread 0x{threadHandle:X16} faulted: " +
|
||||
(thread.BlockReason ?? "unknown error");
|
||||
return false;
|
||||
}
|
||||
|
||||
hostThread = thread.HostThread;
|
||||
}
|
||||
|
||||
if (hostThread is not null &&
|
||||
!ReferenceEquals(hostThread, Thread.CurrentThread))
|
||||
{
|
||||
hostThread.Join(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
error = "guest execution stopped while joining thread";
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Pump(CpuContext callerContext, string reason)
|
||||
{
|
||||
_ = callerContext;
|
||||
@@ -3254,6 +3368,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
||||
switch (exitReason)
|
||||
{
|
||||
case GuestNativeCallExitReason.Returned:
|
||||
thread.ExitValue = thread.Context[CpuRegister.Rax];
|
||||
thread.State = GuestThreadRunState.Exited;
|
||||
break;
|
||||
case GuestNativeCallExitReason.Blocked:
|
||||
|
||||
@@ -29,6 +29,12 @@ public interface IGuestThreadScheduler
|
||||
|
||||
bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error);
|
||||
|
||||
bool TryJoinThread(
|
||||
CpuContext callerContext,
|
||||
ulong threadHandle,
|
||||
out ulong returnValue,
|
||||
out string? error);
|
||||
|
||||
void Pump(CpuContext callerContext, string reason);
|
||||
|
||||
int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
@@ -25,6 +26,9 @@ public static class AmprExports
|
||||
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
|
||||
private static readonly bool _traceAmpr =
|
||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal);
|
||||
private static readonly bool _traceAmprReads =
|
||||
_traceAmpr ||
|
||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR_READS"), "1", StringComparison.Ordinal);
|
||||
|
||||
private sealed class CommandBufferState
|
||||
{
|
||||
@@ -625,12 +629,18 @@ public static class AmprExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
const int ChunkSize = 64 * 1024;
|
||||
var buffer = new byte[(int)Math.Min((ulong)ChunkSize, size)];
|
||||
const int ChunkSize = 1024 * 1024;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent((int)Math.Min((ulong)ChunkSize, size));
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = new FileStream(hostPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||
using var stream = new FileStream(
|
||||
hostPath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite | FileShare.Delete,
|
||||
ChunkSize,
|
||||
FileOptions.SequentialScan);
|
||||
if (fileOffset >= (ulong)stream.Length)
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
@@ -663,6 +673,10 @@ public static class AmprExports
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
@@ -831,7 +845,7 @@ public static class AmprExports
|
||||
string? hostPath,
|
||||
int result)
|
||||
{
|
||||
if (!_traceAmpr)
|
||||
if (!_traceAmprReads)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -306,10 +306,6 @@ public static class KernelExports
|
||||
{
|
||||
var threadId = ctx[CpuRegister.Rdi];
|
||||
var returnValueAddress = ctx[CpuRegister.Rsi];
|
||||
if (returnValueAddress != 0 && !ctx.TryWriteUInt64(returnValueAddress, 0))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
if (ShouldTracePthread())
|
||||
{
|
||||
@@ -317,6 +313,30 @@ public static class KernelExports
|
||||
$"[LOADER][TRACE] pthread_join: thread=0x{threadId:X16} retval_out=0x{returnValueAddress:X16}");
|
||||
}
|
||||
|
||||
var returnValue = 0UL;
|
||||
if (GuestThreadExecution.Scheduler is { } scheduler &&
|
||||
!scheduler.TryJoinThread(ctx, threadId, out returnValue, out var error))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][ERROR] pthread_join: thread=0x{threadId:X16}: {error}");
|
||||
var result = string.Equals(
|
||||
error,
|
||||
"thread cannot join itself",
|
||||
StringComparison.Ordinal)
|
||||
? OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
|
||||
: OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(int)result);
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
if (returnValueAddress != 0 &&
|
||||
!ctx.TryWriteUInt64(returnValueAddress, returnValue))
|
||||
{
|
||||
ctx[CpuRegister.Rax] =
|
||||
unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
ctx[CpuRegister.Rax] = 0;
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
@@ -99,10 +99,12 @@ public static class KernelMemoryCompatExports
|
||||
private static readonly object _tlsGate = new();
|
||||
private static readonly object _ioTraceGate = new();
|
||||
private static readonly object _statCacheGate = new();
|
||||
private static readonly object _guestMountGate = new();
|
||||
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
|
||||
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
||||
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
|
||||
private static readonly Dictionary<ulong, ulong> _tlsModuleBlocks = new();
|
||||
private static readonly Dictionary<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
|
||||
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
private static long _nextFileDescriptor = 2;
|
||||
@@ -153,6 +155,32 @@ public static class KernelMemoryCompatExports
|
||||
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, bool IsDirect, ulong DirectStart);
|
||||
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
|
||||
|
||||
public static void RegisterGuestPathMount(string guestMountPoint, string hostRoot)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(guestMountPoint);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(hostRoot);
|
||||
|
||||
var normalizedMountPoint = NormalizeGuestStatCachePath(guestMountPoint);
|
||||
if (normalizedMountPoint is null || normalizedMountPoint == "/")
|
||||
{
|
||||
throw new ArgumentException("Guest mount point must name a directory.", nameof(guestMountPoint));
|
||||
}
|
||||
|
||||
var normalizedHostRoot = Path.GetFullPath(hostRoot);
|
||||
Directory.CreateDirectory(normalizedHostRoot);
|
||||
lock (_guestMountGate)
|
||||
{
|
||||
_guestMounts[normalizedMountPoint] = normalizedHostRoot;
|
||||
}
|
||||
|
||||
lock (_statCacheGate)
|
||||
{
|
||||
_negativeStatCache.RemoveWhere(path =>
|
||||
string.Equals(path, normalizedMountPoint, StringComparison.OrdinalIgnoreCase) ||
|
||||
path.StartsWith(normalizedMountPoint + "/", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryAllocateHleData(
|
||||
CpuContext ctx,
|
||||
ulong length,
|
||||
@@ -4033,6 +4061,11 @@ public static class KernelMemoryCompatExports
|
||||
return guestPath;
|
||||
}
|
||||
|
||||
if (TryResolveRegisteredGuestMount(guestPath, out var mountedPath))
|
||||
{
|
||||
return mountedPath;
|
||||
}
|
||||
|
||||
if (guestPath.StartsWith("/devlog/app/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var relative = NormalizeMountRelativePath(guestPath["/devlog/app/".Length..]);
|
||||
@@ -4131,6 +4164,51 @@ public static class KernelMemoryCompatExports
|
||||
return guestPath;
|
||||
}
|
||||
|
||||
private static bool TryResolveRegisteredGuestMount(string guestPath, out string hostPath)
|
||||
{
|
||||
hostPath = string.Empty;
|
||||
var normalizedGuestPath = NormalizeGuestStatCachePath(guestPath);
|
||||
if (normalizedGuestPath is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string? matchedMountPoint = null;
|
||||
string? matchedHostRoot = null;
|
||||
lock (_guestMountGate)
|
||||
{
|
||||
foreach (var (mountPoint, hostRoot) in _guestMounts)
|
||||
{
|
||||
if ((string.Equals(normalizedGuestPath, mountPoint, StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizedGuestPath.StartsWith(mountPoint + "/", StringComparison.OrdinalIgnoreCase)) &&
|
||||
(matchedMountPoint is null || mountPoint.Length > matchedMountPoint.Length))
|
||||
{
|
||||
matchedMountPoint = mountPoint;
|
||||
matchedHostRoot = hostRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedMountPoint is null || matchedHostRoot is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var relativePath = normalizedGuestPath[matchedMountPoint.Length..].TrimStart('/');
|
||||
var candidate = Path.GetFullPath(Path.Combine(
|
||||
matchedHostRoot,
|
||||
NormalizeMountRelativePath(relativePath)));
|
||||
var rootWithSeparator = Path.TrimEndingDirectorySeparator(matchedHostRoot) + Path.DirectorySeparatorChar;
|
||||
if (!string.Equals(candidate, matchedHostRoot, StringComparison.OrdinalIgnoreCase) &&
|
||||
!candidate.StartsWith(rootWithSeparator, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
hostPath = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? ResolveApp0Root()
|
||||
{
|
||||
var cached = Volatile.Read(ref _cachedApp0Root);
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace SharpEmu.Libs.Kernel;
|
||||
|
||||
public static class KernelPthreadCompatExports
|
||||
{
|
||||
private const int MutexTypeDefault = 0;
|
||||
private const int MutexTypeErrorCheck = 1;
|
||||
private const int MutexTypeRecursive = 2;
|
||||
private const int MutexTypeNormal = 3;
|
||||
@@ -44,7 +43,7 @@ public static class KernelPthreadCompatExports
|
||||
public SemaphoreSlim Semaphore { get; } = new(1, 1);
|
||||
public ulong OwnerThreadId { get; set; }
|
||||
public int RecursionCount { get; set; }
|
||||
public int Type { get; set; } = MutexTypeDefault;
|
||||
public int Type { get; set; } = MutexTypeErrorCheck;
|
||||
public int Protocol { get; set; }
|
||||
}
|
||||
|
||||
@@ -541,7 +540,7 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||
}
|
||||
|
||||
if (state.Type is MutexTypeDefault or MutexTypeNormal or MutexTypeAdaptiveNp)
|
||||
if (state.Type is MutexTypeNormal or MutexTypeAdaptiveNp)
|
||||
{
|
||||
if (tryOnly)
|
||||
{
|
||||
@@ -676,7 +675,7 @@ public static class KernelPthreadCompatExports
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
}
|
||||
|
||||
var initialState = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
var initialState = new PthreadMutexAttrState(MutexTypeErrorCheck, 0);
|
||||
if (!WriteMutexAttrObject(ctx, handle, initialState))
|
||||
{
|
||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||
@@ -735,7 +734,7 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
|
||||
{
|
||||
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
state = new PthreadMutexAttrState(MutexTypeErrorCheck, 0);
|
||||
}
|
||||
|
||||
updatedState = state with { Type = NormalizeMutexType(type) };
|
||||
@@ -764,7 +763,7 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
if (!_mutexAttrStates.TryGetValue(resolvedAddress, out var state))
|
||||
{
|
||||
state = new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
state = new PthreadMutexAttrState(MutexTypeErrorCheck, 0);
|
||||
}
|
||||
|
||||
updatedState = state with { Protocol = protocol };
|
||||
@@ -847,7 +846,7 @@ public static class KernelPthreadCompatExports
|
||||
return false;
|
||||
}
|
||||
|
||||
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeDefault, out resolvedAddress, out state);
|
||||
return CreateImplicitMutexState(ctx, mutexAddress, MutexTypeErrorCheck, out resolvedAddress, out state);
|
||||
}
|
||||
|
||||
private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress)
|
||||
@@ -891,7 +890,7 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
return _mutexAttrStates.TryGetValue(resolvedAddress, out var state)
|
||||
? state
|
||||
: new PthreadMutexAttrState(MutexTypeDefault, 0);
|
||||
: new PthreadMutexAttrState(MutexTypeErrorCheck, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1293,12 +1292,12 @@ public static class KernelPthreadCompatExports
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
0 => MutexTypeDefault,
|
||||
0 => MutexTypeErrorCheck,
|
||||
1 => MutexTypeErrorCheck,
|
||||
2 => MutexTypeRecursive,
|
||||
3 => MutexTypeNormal,
|
||||
4 => MutexTypeAdaptiveNp,
|
||||
_ => MutexTypeDefault,
|
||||
_ => MutexTypeErrorCheck,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace SharpEmu.Libs.Np;
|
||||
public static class NpEntitlementAccessExports
|
||||
{
|
||||
private const int BootParamClearSize = 0x20;
|
||||
private const int EmptyAddcontInfoListSize = 0x10;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "jO8DM8oyego",
|
||||
@@ -33,6 +34,30 @@ public static class NpEntitlementAccessExports
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "TFyU+KFBv54",
|
||||
ExportName = "sceNpEntitlementAccessGetAddcontEntitlementInfoList",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceNpEntitlementAccess")]
|
||||
public static int NpEntitlementAccessGetAddcontEntitlementInfoList(CpuContext ctx)
|
||||
{
|
||||
var listAddress = ctx[CpuRegister.Rsi];
|
||||
if (listAddress != 0)
|
||||
{
|
||||
Span<byte> emptyList = stackalloc byte[EmptyAddcontInfoListSize];
|
||||
emptyList.Clear();
|
||||
if (!ctx.Memory.TryWrite(listAddress, emptyList))
|
||||
{
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
}
|
||||
|
||||
TraceNpEntitlementAccess(
|
||||
$"get_addcont_info_list service=0x{ctx[CpuRegister.Rdi]:X16} list=0x{listAddress:X16} " +
|
||||
$"max={ctx[CpuRegister.Rdx]} flags=0x{ctx[CpuRegister.Rcx]:X16} -> empty");
|
||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, OrbisGen2Result result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)(int)result);
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.HLE;
|
||||
using System.Buffers.Binary;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpEmu.Libs.SaveData;
|
||||
|
||||
public static class SaveDataDialogExports
|
||||
{
|
||||
private const int StatusNone = 0;
|
||||
private const int StatusInitialized = 1;
|
||||
private const int StatusRunning = 2;
|
||||
private const int StatusFinished = 3;
|
||||
|
||||
private const int ErrorOk = 0;
|
||||
private const int ErrorNotInitialized = unchecked((int)0x80B80003);
|
||||
private const int ErrorAlreadyInitialized = unchecked((int)0x80B80004);
|
||||
private const int ErrorNotFinished = unchecked((int)0x80B80005);
|
||||
private const int ErrorNotRunning = unchecked((int)0x80B8000B);
|
||||
private const int ErrorArgNull = unchecked((int)0x80B8000D);
|
||||
|
||||
private const int ResultSize = 0x48;
|
||||
private static int _status;
|
||||
private static int _lastMode;
|
||||
private static ulong _lastUserData;
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "s9e3+YpRnzw",
|
||||
ExportName = "sceSaveDataDialogInitialize",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogInitialize(CpuContext ctx)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _status, StatusInitialized, StatusNone) != StatusNone)
|
||||
{
|
||||
return SetReturn(ctx, ErrorAlreadyInitialized);
|
||||
}
|
||||
|
||||
return SetReturn(ctx, ErrorOk);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "4tPhsP6FpDI",
|
||||
ExportName = "sceSaveDataDialogOpen",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogOpen(CpuContext ctx)
|
||||
{
|
||||
var paramAddress = ctx[CpuRegister.Rdi];
|
||||
if (paramAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, ErrorArgNull);
|
||||
}
|
||||
|
||||
if (_status is not (StatusInitialized or StatusFinished))
|
||||
{
|
||||
return SetReturn(ctx, ErrorNotInitialized);
|
||||
}
|
||||
|
||||
_lastMode = TryReadInt32(ctx, paramAddress, out var mode) ? mode : 0;
|
||||
_lastUserData = TryReadUInt64(ctx, paramAddress + 0xC8, out var userData) ? userData : 0;
|
||||
|
||||
// There is no host save dialog yet. Complete immediately with OK so
|
||||
// guest polling sees a finished dialog instead of spinning forever.
|
||||
Interlocked.Exchange(ref _status, StatusFinished);
|
||||
TraceSaveDataDialog($"open mode={_lastMode} userData=0x{_lastUserData:X16} -> finished");
|
||||
return SetReturn(ctx, ErrorOk);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "ERKzksauAJA",
|
||||
ExportName = "sceSaveDataDialogGetStatus",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogGetStatus(CpuContext ctx) => SetReturn(ctx, Volatile.Read(ref _status));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "KK3Bdg1RWK0",
|
||||
ExportName = "sceSaveDataDialogUpdateStatus",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogUpdateStatus(CpuContext ctx) => SetReturn(ctx, Volatile.Read(ref _status));
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "en7gNVnh878",
|
||||
ExportName = "sceSaveDataDialogIsReadyToDisplay",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogIsReadyToDisplay(CpuContext ctx) => SetReturn(ctx, 1);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "yEiJ-qqr6Cg",
|
||||
ExportName = "sceSaveDataDialogGetResult",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogGetResult(CpuContext ctx)
|
||||
{
|
||||
var resultAddress = ctx[CpuRegister.Rdi];
|
||||
if (resultAddress == 0)
|
||||
{
|
||||
return SetReturn(ctx, ErrorArgNull);
|
||||
}
|
||||
|
||||
if (Volatile.Read(ref _status) != StatusFinished)
|
||||
{
|
||||
return SetReturn(ctx, ErrorNotFinished);
|
||||
}
|
||||
|
||||
Span<byte> result = stackalloc byte[ResultSize];
|
||||
result.Clear();
|
||||
BinaryPrimitives.WriteInt32LittleEndian(result[0x00..], _lastMode);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(result[0x04..], 0);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(result[0x08..], 0);
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(result[0x20..], _lastUserData);
|
||||
|
||||
if (!ctx.Memory.TryWrite(resultAddress, result))
|
||||
{
|
||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||
}
|
||||
|
||||
return SetReturn(ctx, ErrorOk);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "fH46Lag88XY",
|
||||
ExportName = "sceSaveDataDialogClose",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogClose(CpuContext ctx)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _status, StatusFinished, StatusRunning) != StatusRunning)
|
||||
{
|
||||
return SetReturn(ctx, ErrorNotRunning);
|
||||
}
|
||||
|
||||
return SetReturn(ctx, ErrorOk);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "YuH2FA7azqQ",
|
||||
ExportName = "sceSaveDataDialogTerminate",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogTerminate(CpuContext ctx)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _status, StatusNone) == StatusNone)
|
||||
{
|
||||
return SetReturn(ctx, ErrorNotInitialized);
|
||||
}
|
||||
|
||||
_lastMode = 0;
|
||||
_lastUserData = 0;
|
||||
return SetReturn(ctx, ErrorOk);
|
||||
}
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "V-uEeFKARJU",
|
||||
ExportName = "sceSaveDataDialogProgressBarInc",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogProgressBarInc(CpuContext ctx) => SetReturn(ctx, ErrorOk);
|
||||
|
||||
[SysAbiExport(
|
||||
Nid = "hay1CfTmLyA",
|
||||
ExportName = "sceSaveDataDialogProgressBarSetValue",
|
||||
Target = Generation.Gen4 | Generation.Gen5,
|
||||
LibraryName = "libSceSaveDataDialog")]
|
||||
public static int SaveDataDialogProgressBarSetValue(CpuContext ctx) => SetReturn(ctx, ErrorOk);
|
||||
|
||||
private static bool TryReadInt32(CpuContext ctx, ulong address, out int value)
|
||||
{
|
||||
value = 0;
|
||||
Span<byte> bytes = stackalloc byte[sizeof(int)];
|
||||
if (!ctx.Memory.TryRead(address, bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadInt32LittleEndian(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value)
|
||||
{
|
||||
value = 0;
|
||||
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
|
||||
if (!ctx.Memory.TryRead(address, bytes))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadUInt64LittleEndian(bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int SetReturn(CpuContext ctx, int result)
|
||||
{
|
||||
ctx[CpuRegister.Rax] = unchecked((ulong)result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void TraceSaveDataDialog(string message)
|
||||
{
|
||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SAVEDATA"), "1", StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"[LOADER][TRACE] save_data_dialog.{message}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user