mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-20 09:56:17 +08:00
[loader] cut import overhead (#32)
This commit is contained in:
@@ -633,25 +633,56 @@ public sealed partial class DirectExecutionBackend
|
|||||||
"ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult
|
"ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult
|
||||||
"rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer
|
"rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer
|
||||||
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
|
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
|
||||||
"qvMUCyyaCSI"; // sceKernelAprSubmitCommandBufferAndGetId
|
"qvMUCyyaCSI" or // sceKernelAprSubmitCommandBufferAndGetId
|
||||||
|
"Q2V+iqvjgC0" or // vsnprintf
|
||||||
|
"q1cHNfGycLI" or // scePadRead
|
||||||
|
"xk0AcarP3V4" or // scePadOpen
|
||||||
|
"yH17Q6NWtVg" or // sceUserServiceGetEvent
|
||||||
|
"D-CzAxQL0XI" or // sceUserServiceGetPlatformPrivacySetting
|
||||||
|
"K-jXhbt2gn4"; // scePthreadMutexTrylock
|
||||||
|
|
||||||
private bool ShouldLogImportResult(string nid, OrbisGen2Result result)
|
private bool ShouldLogImportResult(string nid, OrbisGen2Result result)
|
||||||
{
|
{
|
||||||
|
var resultValue = unchecked((int)result);
|
||||||
|
if (resultValue > 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var expectedFileProbeMiss =
|
var expectedFileProbeMiss =
|
||||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND &&
|
result == OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND &&
|
||||||
IsExpectedFileProbeNotFoundNid(nid);
|
IsExpectedFileProbeNotFoundNid(nid);
|
||||||
var expectedTimedWaitTimeout =
|
var expectedTimedWaitTimeout =
|
||||||
string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) &&
|
string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) &&
|
||||||
unchecked((int)result) == 60;
|
unchecked((int)result) == 60;
|
||||||
|
var expectedEqueueTimeout =
|
||||||
|
string.Equals(nid, "fzyMKs9kim0", StringComparison.Ordinal) &&
|
||||||
|
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
|
||||||
var expectedMutexTrylockBusy =
|
var expectedMutexTrylockBusy =
|
||||||
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
|
||||||
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
|
||||||
if (!expectedFileProbeMiss && !expectedTimedWaitTimeout && !expectedMutexTrylockBusy)
|
var expectedUserServiceNoEvent =
|
||||||
|
string.Equals(nid, "yH17Q6NWtVg", StringComparison.Ordinal) &&
|
||||||
|
resultValue == unchecked((int)0x80960007);
|
||||||
|
var expectedPrivacyInvalidParameter =
|
||||||
|
string.Equals(nid, "D-CzAxQL0XI", StringComparison.Ordinal) &&
|
||||||
|
resultValue == unchecked((int)0x80960009);
|
||||||
|
if (!expectedFileProbeMiss &&
|
||||||
|
!expectedTimedWaitTimeout &&
|
||||||
|
!expectedEqueueTimeout &&
|
||||||
|
!expectedMutexTrylockBusy &&
|
||||||
|
!expectedUserServiceNoEvent &&
|
||||||
|
!expectedPrivacyInvalidParameter)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = nid + "\0" + (int)result;
|
if (!ShouldLogExpectedImportResults())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = nid + "\0" + resultValue;
|
||||||
int count;
|
int count;
|
||||||
lock (_importResultLogSampleGate)
|
lock (_importResultLogSampleGate)
|
||||||
{
|
{
|
||||||
@@ -663,6 +694,12 @@ public sealed partial class DirectExecutionBackend
|
|||||||
return count <= 8 || count % 10000 == 0;
|
return count <= 8 || count % 10000 == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldLogExpectedImportResults() =>
|
||||||
|
string.Equals(
|
||||||
|
Environment.GetEnvironmentVariable("SHARPEMU_LOG_EXPECTED_IMPORT_RESULTS"),
|
||||||
|
"1",
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
|
||||||
private static bool IsExpectedFileProbeNotFoundNid(string nid) =>
|
private static bool IsExpectedFileProbeNotFoundNid(string nid) =>
|
||||||
nid is
|
nid is
|
||||||
"eV9wAD2riIA" or // sceKernelStat
|
"eV9wAD2riIA" or // sceKernelStat
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
{
|
{
|
||||||
private readonly ReaderWriterLockSlim _gate = new(LockRecursionPolicy.SupportsRecursion);
|
private readonly ReaderWriterLockSlim _gate = new(LockRecursionPolicy.SupportsRecursion);
|
||||||
private readonly object _guestAllocationGate = new();
|
private readonly object _guestAllocationGate = new();
|
||||||
|
private readonly object _allocationSearchHintGate = new();
|
||||||
private readonly List<MemoryRegion> _regions = new();
|
private readonly List<MemoryRegion> _regions = new();
|
||||||
|
private readonly Dictionary<(ulong DesiredAddress, ulong Alignment, bool Executable), ulong> _allocationSearchHints = new();
|
||||||
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
|
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private const ulong PageSize = 0x1000;
|
private const ulong PageSize = 0x1000;
|
||||||
@@ -19,7 +21,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
private const ulong GuestAllocationArenaSize = 0x0100_0000;
|
private const ulong GuestAllocationArenaSize = 0x0100_0000;
|
||||||
private const ulong GuestAllocationArenaStartOffset = PageSize;
|
private const ulong GuestAllocationArenaStartOffset = PageSize;
|
||||||
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
|
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
|
||||||
private const ulong LazyReservePrimeBytes = 0x5000_0000UL; // 1.25 GiB
|
private const ulong FullCommitRegionLimit = 4UL << 30;
|
||||||
|
private const ulong DefaultLazyReservePrimeBytes = 0x0400_0000UL; // 64 MiB
|
||||||
private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB
|
private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB
|
||||||
|
|
||||||
private const uint MEM_COMMIT = 0x1000;
|
private const uint MEM_COMMIT = 0x1000;
|
||||||
@@ -35,6 +38,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
|
|
||||||
private ulong _guestAllocationArenaBase;
|
private ulong _guestAllocationArenaBase;
|
||||||
private ulong _guestAllocationOffset;
|
private ulong _guestAllocationOffset;
|
||||||
|
private static readonly ulong LazyReservePrimeBytes = ResolveLazyReservePrimeBytes();
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
|
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
|
||||||
@@ -96,7 +100,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)");
|
TraceVmem($"Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +114,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||||
var allocationType = MEM_COMMIT | MEM_RESERVE;
|
var allocationType = MEM_COMMIT | MEM_RESERVE;
|
||||||
var reservedOnly = false;
|
var reservedOnly = false;
|
||||||
var preferReserveOnly = !executable && alignedSize >= LargeDataReserveThreshold;
|
var preferReserveOnly = !executable &&
|
||||||
|
alignedSize >= LargeDataReserveThreshold &&
|
||||||
|
alignedSize > FullCommitRegionLimit;
|
||||||
|
|
||||||
void* result = null;
|
void* result = null;
|
||||||
if (preferReserveOnly)
|
if (preferReserveOnly)
|
||||||
@@ -139,7 +145,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
throw new InvalidOperationException($"Failed to allocate exact mapping at 0x{desiredAddress:X16} ({alignedSize} bytes)");
|
throw new InvalidOperationException($"Failed to allocate exact mapping at 0x{desiredAddress:X16} ({alignedSize} bytes)");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine($"[VMEM] Could not allocate at 0x{desiredAddress:X16}, trying any address...");
|
TraceVmem($"Could not allocate at 0x{desiredAddress:X16}, trying any address...");
|
||||||
result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection);
|
result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection);
|
||||||
|
|
||||||
if (result == null)
|
if (result == null)
|
||||||
@@ -193,12 +199,12 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
lazyPrimeState = committedBytes == primeBytes
|
lazyPrimeState = committedBytes == primeBytes
|
||||||
? $"ok:{committedBytes:X}"
|
? $"ok:{committedBytes:X}"
|
||||||
: $"partial:{committedBytes:X}/{primeBytes:X}";
|
: $"partial:{committedBytes:X}/{primeBytes:X}";
|
||||||
Console.Error.WriteLine($"[VMEM] Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
|
TraceVmem($"Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lazyPrimeState = $"fail:{primeBytes:X}";
|
lazyPrimeState = $"fail:{primeBytes:X}";
|
||||||
Console.Error.WriteLine($"[VMEM] Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit");
|
TraceVmem($"Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -227,7 +233,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
var allocationKind = reservedOnly
|
var allocationKind = reservedOnly
|
||||||
? "reserved data memory (lazy commit)"
|
? "reserved data memory (lazy commit)"
|
||||||
: (executable ? "executable memory" : "data memory");
|
: (executable ? "executable memory" : "data memory");
|
||||||
Console.Error.WriteLine($"[VMEM] Allocated {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes) lazy_prime={lazyPrimeState}");
|
TraceVmem($"Allocated {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes) lazy_prime={lazyPrimeState}");
|
||||||
|
|
||||||
return actualAddress;
|
return actualAddress;
|
||||||
}
|
}
|
||||||
@@ -247,7 +253,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
|
|
||||||
var alignedSize = AlignUp(size, PageSize);
|
var alignedSize = AlignUp(size, PageSize);
|
||||||
var effectiveAlignment = Math.Max(PageSize, alignment == 0 ? PageSize : alignment);
|
var effectiveAlignment = Math.Max(PageSize, alignment == 0 ? PageSize : alignment);
|
||||||
var cursor = AlignUp(desiredAddress, effectiveAlignment);
|
var requestedCursor = AlignUp(desiredAddress, effectiveAlignment);
|
||||||
|
var cursor = GetAllocationSearchCursor(desiredAddress, requestedCursor, effectiveAlignment, executable);
|
||||||
|
|
||||||
for (var attempt = 0; attempt < 0x10000; attempt++)
|
for (var attempt = 0; attempt < 0x10000; attempt++)
|
||||||
{
|
{
|
||||||
@@ -267,6 +274,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
actualAddress = AllocateAt(cursor, alignedSize, executable, allowAlternative: false);
|
actualAddress = AllocateAt(cursor, alignedSize, executable, allowAlternative: false);
|
||||||
if (actualAddress == cursor)
|
if (actualAddress == cursor)
|
||||||
{
|
{
|
||||||
|
UpdateAllocationSearchCursor(desiredAddress, effectiveAlignment, executable, actualAddress + alignedSize);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +342,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
}
|
}
|
||||||
_regions.Clear();
|
_regions.Clear();
|
||||||
_pageProtections.Clear();
|
_pageProtections.Clear();
|
||||||
|
lock (_allocationSearchHintGate)
|
||||||
|
{
|
||||||
|
_allocationSearchHints.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -390,7 +402,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
|
|
||||||
ApplySegmentProtection(mapStart, mapEnd, protection);
|
ApplySegmentProtection(mapStart, mapEnd, protection);
|
||||||
|
|
||||||
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
|
TraceVmem($"Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -793,6 +805,16 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
{
|
{
|
||||||
var regionEnd = region.VirtualAddress + region.Size;
|
var regionEnd = region.VirtualAddress + region.Size;
|
||||||
|
if (region.VirtualAddress >= end)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regionEnd <= address)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (address < regionEnd && region.VirtualAddress < end)
|
if (address < regionEnd && region.VirtualAddress < end)
|
||||||
{
|
{
|
||||||
overlapEnd = Math.Max(overlapEnd, regionEnd);
|
overlapEnd = Math.Max(overlapEnd, regionEnd);
|
||||||
@@ -807,6 +829,37 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
return overlapEnd != 0;
|
return overlapEnd != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ulong GetAllocationSearchCursor(
|
||||||
|
ulong desiredAddress,
|
||||||
|
ulong requestedCursor,
|
||||||
|
ulong alignment,
|
||||||
|
bool executable)
|
||||||
|
{
|
||||||
|
lock (_allocationSearchHintGate)
|
||||||
|
{
|
||||||
|
var key = (desiredAddress, alignment, executable);
|
||||||
|
if (_allocationSearchHints.TryGetValue(key, out var hintedCursor) &&
|
||||||
|
hintedCursor > requestedCursor)
|
||||||
|
{
|
||||||
|
return AlignUp(hintedCursor, alignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestedCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateAllocationSearchCursor(
|
||||||
|
ulong desiredAddress,
|
||||||
|
ulong alignment,
|
||||||
|
bool executable,
|
||||||
|
ulong nextCursor)
|
||||||
|
{
|
||||||
|
lock (_allocationSearchHintGate)
|
||||||
|
{
|
||||||
|
_allocationSearchHints[(desiredAddress, alignment, executable)] = AlignUp(nextCursor, alignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryResolveRegionOffset(ulong address, ulong size, MemoryRegion region, out ulong offset)
|
private static bool TryResolveRegionOffset(ulong address, ulong size, MemoryRegion region, out ulong offset)
|
||||||
{
|
{
|
||||||
offset = 0;
|
offset = 0;
|
||||||
@@ -975,6 +1028,29 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
|
|||||||
return checked((value + mask) & ~mask);
|
return checked((value + mask) & ~mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong ResolveLazyReservePrimeBytes()
|
||||||
|
{
|
||||||
|
var configured = Environment.GetEnvironmentVariable("SHARPEMU_LAZY_RESERVE_PRIME_MB");
|
||||||
|
if (ulong.TryParse(configured, out var megabytes))
|
||||||
|
{
|
||||||
|
return megabytes == 0
|
||||||
|
? 0
|
||||||
|
: checked(Math.Min(megabytes, 4096UL) * 1024UL * 1024UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefaultLazyReservePrimeBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TraceVmem(string message)
|
||||||
|
{
|
||||||
|
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VMEM"), "1", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Error.WriteLine($"[VMEM] {message}");
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public static class AmprExports
|
|||||||
private const uint KernelEventQueueRecordType = 2;
|
private const uint KernelEventQueueRecordType = 2;
|
||||||
private const uint WriteAddressRecordType = 3;
|
private const uint WriteAddressRecordType = 3;
|
||||||
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
|
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
|
||||||
|
private static readonly ConcurrentDictionary<string, Lazy<CachedHostFile>> _hostFileCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static readonly bool _traceAmpr =
|
private static readonly bool _traceAmpr =
|
||||||
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal);
|
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal);
|
||||||
private static readonly bool _traceAmprReads =
|
private static readonly bool _traceAmprReads =
|
||||||
@@ -37,6 +38,23 @@ public static class AmprExports
|
|||||||
public ulong WriteOffset;
|
public ulong WriteOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class CachedHostFile
|
||||||
|
{
|
||||||
|
public CachedHostFile(string path)
|
||||||
|
{
|
||||||
|
Stream = new FileStream(
|
||||||
|
path,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.ReadWrite | FileShare.Delete,
|
||||||
|
bufferSize: 1024 * 1024,
|
||||||
|
FileOptions.RandomAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Gate { get; } = new();
|
||||||
|
public FileStream Stream { get; }
|
||||||
|
}
|
||||||
|
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "8aI7R7WaOlc",
|
Nid = "8aI7R7WaOlc",
|
||||||
ExportName = "sceAmprCommandBufferConstructor",
|
ExportName = "sceAmprCommandBufferConstructor",
|
||||||
@@ -249,7 +267,7 @@ public static class AmprExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath) || !File.Exists(hostPath))
|
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath))
|
||||||
{
|
{
|
||||||
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead: 0, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
TraceAmprRead(ctx, commandBuffer, fileId, destination, size, fileOffset, bytesRead: 0, hostPath, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||||
@@ -634,24 +652,43 @@ public static class AmprExports
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var stream = new FileStream(
|
if (!TryGetCachedHostFile(hostPath, out var cachedFile, out var openResult))
|
||||||
hostPath,
|
{
|
||||||
FileMode.Open,
|
return openResult;
|
||||||
FileAccess.Read,
|
}
|
||||||
FileShare.ReadWrite | FileShare.Delete,
|
|
||||||
ChunkSize,
|
long fileLength;
|
||||||
FileOptions.SequentialScan);
|
lock (cachedFile.Gate)
|
||||||
if (fileOffset >= (ulong)stream.Length)
|
{
|
||||||
|
fileLength = cachedFile.Stream.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileOffset >= (ulong)fileLength)
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Position = unchecked((long)fileOffset);
|
|
||||||
|
|
||||||
while (bytesRead < size)
|
while (bytesRead < size)
|
||||||
{
|
{
|
||||||
|
if (bytesRead > ulong.MaxValue - fileOffset)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
var absoluteOffset = fileOffset + bytesRead;
|
||||||
|
if (absoluteOffset > long.MaxValue)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
var request = (int)Math.Min((ulong)buffer.Length, size - bytesRead);
|
var request = (int)Math.Min((ulong)buffer.Length, size - bytesRead);
|
||||||
var read = stream.Read(buffer, 0, request);
|
int read;
|
||||||
|
lock (cachedFile.Gate)
|
||||||
|
{
|
||||||
|
cachedFile.Stream.Position = unchecked((long)absoluteOffset);
|
||||||
|
read = cachedFile.Stream.Read(buffer, 0, request);
|
||||||
|
}
|
||||||
|
|
||||||
if (read <= 0)
|
if (read <= 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -681,6 +718,44 @@ public static class AmprExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetCachedHostFile(string hostPath, out CachedHostFile file, out int result)
|
||||||
|
{
|
||||||
|
file = null!;
|
||||||
|
result = (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
|
||||||
|
string cachePath;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cachePath = Path.GetFullPath(hostPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
cachePath = hostPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lazy = _hostFileCache.GetOrAdd(
|
||||||
|
cachePath,
|
||||||
|
static path => new Lazy<CachedHostFile>(() => new CachedHostFile(path), isThreadSafe: true));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
file = lazy.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
_hostFileCache.TryRemove(cachePath, out _);
|
||||||
|
result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
_hostFileCache.TryRemove(cachePath, out _);
|
||||||
|
result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool AppendReadFileRecord(
|
private static bool AppendReadFileRecord(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
ulong commandBuffer,
|
ulong commandBuffer,
|
||||||
|
|||||||
@@ -276,6 +276,22 @@ public static class KernelEventQueueCompatExports
|
|||||||
var outCountAddress = ctx[CpuRegister.Rcx];
|
var outCountAddress = ctx[CpuRegister.Rcx];
|
||||||
var timeoutAddress = ctx[CpuRegister.R8];
|
var timeoutAddress = ctx[CpuRegister.R8];
|
||||||
|
|
||||||
|
if (!IsValidEqueue(handle))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventsAddress == 0 || eventCapacity < 1)
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint timeoutUsec = 0;
|
||||||
|
if (timeoutAddress != 0 && !TryReadUInt32(ctx, timeoutAddress, out timeoutUsec))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
|
var deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
|
||||||
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
|
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
|
||||||
{
|
{
|
||||||
@@ -300,6 +316,41 @@ public static class KernelEventQueueCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeoutAddress != 0 && ctx.TryReadUInt64(timeoutAddress, out var timeoutRaw))
|
||||||
|
{
|
||||||
|
var timeoutMicros = timeoutRaw & 0xFFFF_FFFFUL;
|
||||||
|
var deadline = Environment.TickCount64 +
|
||||||
|
Math.Max(1L, (long)Math.Min(timeoutMicros / 1000, int.MaxValue));
|
||||||
|
lock (_eventQueueGate)
|
||||||
|
{
|
||||||
|
while (!HasPendingEvents(handle))
|
||||||
|
{
|
||||||
|
var remaining = deadline - Environment.TickCount64;
|
||||||
|
if (remaining <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Monitor.Wait(_eventQueueGate, (int)Math.Min(remaining, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deliveredCount = DequeueEvents(ctx, handle, eventsAddress, eventCapacity);
|
||||||
|
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
|
||||||
|
{
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deliveredCount > 0)
|
||||||
|
{
|
||||||
|
TraceEventQueue(ctx, "wait-timed-deliver", handle);
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceEventQueue(ctx, "wait-timeout", handle);
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
|
||||||
|
}
|
||||||
|
|
||||||
TraceEventQueue(ctx, "wait", handle);
|
TraceEventQueue(ctx, "wait", handle);
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
@@ -330,6 +381,7 @@ public static class KernelEventQueueCompatExports
|
|||||||
|
|
||||||
queue.AddLast(queuedEvent);
|
queue.AddLast(queuedEvent);
|
||||||
queued = true;
|
queued = true;
|
||||||
|
Monitor.PulseAll(_eventQueueGate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queued)
|
if (queued)
|
||||||
@@ -494,7 +546,9 @@ public static class KernelEventQueueCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return deliveredCount > 0
|
||||||
|
? (int)OrbisGen2Result.ORBIS_GEN2_OK
|
||||||
|
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasPendingEvents(ulong handle)
|
private static bool HasPendingEvents(ulong handle)
|
||||||
@@ -612,4 +666,17 @@ public static class KernelEventQueueCompatExports
|
|||||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||||
return ctx.Memory.TryWrite(address, buffer);
|
return ctx.Memory.TryWrite(address, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||||
|
if (!ctx.Memory.TryRead(address, buffer))
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ using SharpEmu.HLE;
|
|||||||
using SharpEmu.Libs.Ampr;
|
using SharpEmu.Libs.Ampr;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -108,6 +108,7 @@ public static class KernelMemoryCompatExports
|
|||||||
private static readonly Dictionary<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
|
private static readonly Dictionary<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
|
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
|
||||||
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private static readonly ConcurrentDictionary<string, ulong> _aprFileSizeCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static long _nextFileDescriptor = 2;
|
private static long _nextFileDescriptor = 2;
|
||||||
private static ulong _nextPhysicalAddress;
|
private static ulong _nextPhysicalAddress;
|
||||||
private static ulong _nextVirtualAddress;
|
private static ulong _nextVirtualAddress;
|
||||||
@@ -1342,6 +1343,7 @@ public static class KernelMemoryCompatExports
|
|||||||
if (IsMutatingOpen(flags))
|
if (IsMutatingOpen(flags))
|
||||||
{
|
{
|
||||||
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
|
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
|
||||||
|
InvalidateAprFileSizeCache(hostPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
|
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
|
||||||
@@ -1550,6 +1552,7 @@ public static class KernelMemoryCompatExports
|
|||||||
|
|
||||||
File.Delete(hostPath);
|
File.Delete(hostPath);
|
||||||
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
|
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
|
||||||
|
InvalidateAprFileSizeCache(hostPath);
|
||||||
AddNegativeStatCacheForGuestPath(guestPath);
|
AddNegativeStatCacheForGuestPath(guestPath);
|
||||||
LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'");
|
LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'");
|
||||||
ctx[CpuRegister.Rax] = 0;
|
ctx[CpuRegister.Rax] = 0;
|
||||||
@@ -2596,8 +2599,11 @@ public static class KernelMemoryCompatExports
|
|||||||
var length = ctx[CpuRegister.Rsi];
|
var length = ctx[CpuRegister.Rsi];
|
||||||
var protection = unchecked((int)ctx[CpuRegister.Rdx]);
|
var protection = unchecked((int)ctx[CpuRegister.Rdx]);
|
||||||
var flags = ctx[CpuRegister.Rcx];
|
var flags = ctx[CpuRegister.Rcx];
|
||||||
Console.Error.WriteLine(
|
if (ShouldTraceDirectMemory())
|
||||||
$"[LOADER][TRACE] map_flexible: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16}");
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] map_flexible: inout=0x{inOutAddressPointer:X16} len=0x{length:X16} prot=0x{protection:X8} flags=0x{flags:X16}");
|
||||||
|
}
|
||||||
if (inOutAddressPointer == 0 || length == 0)
|
if (inOutAddressPointer == 0 || length == 0)
|
||||||
{
|
{
|
||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
@@ -2627,8 +2633,11 @@ public static class KernelMemoryCompatExports
|
|||||||
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
|
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
if (ShouldTraceDirectMemory())
|
||||||
$"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}");
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] map_flexible reserve: requested=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16}");
|
||||||
|
}
|
||||||
|
|
||||||
if (mappedAddress == 0)
|
if (mappedAddress == 0)
|
||||||
{
|
{
|
||||||
@@ -3973,118 +3982,17 @@ public static class KernelMemoryCompatExports
|
|||||||
ulong alignment,
|
ulong alignment,
|
||||||
out ulong mappedAddress)
|
out ulong mappedAddress)
|
||||||
{
|
{
|
||||||
mappedAddress = 0;
|
var executable = (protection & OrbisProtCpuExec) != 0;
|
||||||
if (length == 0)
|
return KernelVirtualRangeAllocator.TryReserve(
|
||||||
{
|
ctx,
|
||||||
return false;
|
desiredAddress,
|
||||||
}
|
length,
|
||||||
|
executable,
|
||||||
try
|
alignment,
|
||||||
{
|
allowSearch: true,
|
||||||
object memoryObject = ctx.Memory;
|
allowAllocateAtAlternative: false,
|
||||||
MethodInfo? allocateAt = null;
|
"reserve range",
|
||||||
MethodInfo? allocateAtOrAbove = null;
|
out mappedAddress);
|
||||||
var allocateAtHasAllowAlternativeArg = false;
|
|
||||||
for (var depth = 0; depth < 4; depth++)
|
|
||||||
{
|
|
||||||
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
|
|
||||||
{
|
|
||||||
var parameters = candidate.GetParameters();
|
|
||||||
if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
|
|
||||||
parameters.Length == 5 &&
|
|
||||||
parameters[0].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[1].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[2].ParameterType == typeof(bool) &&
|
|
||||||
parameters[3].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[4].ParameterType == typeof(ulong).MakeByRefType())
|
|
||||||
{
|
|
||||||
allocateAtOrAbove = candidate;
|
|
||||||
}
|
|
||||||
else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (parameters.Length == 3 &&
|
|
||||||
parameters[0].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[1].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[2].ParameterType == typeof(bool))
|
|
||||||
{
|
|
||||||
allocateAt = candidate;
|
|
||||||
allocateAtHasAllowAlternativeArg = false;
|
|
||||||
}
|
|
||||||
else if (parameters.Length == 4 &&
|
|
||||||
parameters[0].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[1].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[2].ParameterType == typeof(bool) &&
|
|
||||||
parameters[3].ParameterType == typeof(bool))
|
|
||||||
{
|
|
||||||
allocateAt = candidate;
|
|
||||||
allocateAtHasAllowAlternativeArg = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocateAtOrAbove is not null && allocateAt is not null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocateAtOrAbove is not null || allocateAt is not null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerProperty = memoryObject.GetType().GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (innerProperty is null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerValue = innerProperty.GetValue(memoryObject);
|
|
||||||
if (innerValue is null || ReferenceEquals(innerValue, memoryObject))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryObject = innerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var executable = (protection & OrbisProtCpuExec) != 0;
|
|
||||||
if (allocateAtOrAbove is not null)
|
|
||||||
{
|
|
||||||
var searchArgs = new object[] { desiredAddress, length, executable, alignment, 0UL };
|
|
||||||
var searchResult = allocateAtOrAbove.Invoke(memoryObject, searchArgs);
|
|
||||||
if (searchResult is bool trueValue && trueValue &&
|
|
||||||
searchArgs[4] is ulong searchedAddress && searchedAddress != 0)
|
|
||||||
{
|
|
||||||
mappedAddress = searchedAddress;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocateAt is null)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt missing on {ctx.Memory.GetType().FullName}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var invokeArgs = allocateAtHasAllowAlternativeArg
|
|
||||||
? new object[] { desiredAddress, length, executable, false }
|
|
||||||
: new object[] { desiredAddress, length, executable };
|
|
||||||
var result = allocateAt.Invoke(memoryObject, invokeArgs);
|
|
||||||
if (result is not ulong allocated || allocated == 0)
|
|
||||||
{
|
|
||||||
var resultType = result?.GetType().FullName ?? "null";
|
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] reserve range: AllocateAt returned {resultType} value={result ?? "null"}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedAddress = allocated;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine("[LOADER][TRACE] reserve range threw while invoking AllocateAt");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsMappedGuestRangeAvailable(
|
private static bool IsMappedGuestRangeAvailable(
|
||||||
@@ -4237,6 +4145,20 @@ public static class KernelMemoryCompatExports
|
|||||||
var app0Root = ResolveApp0Root();
|
var app0Root = ResolveApp0Root();
|
||||||
if (!string.IsNullOrWhiteSpace(app0Root))
|
if (!string.IsNullOrWhiteSpace(app0Root))
|
||||||
{
|
{
|
||||||
|
if (string.Equals(guestPath, "$", StringComparison.Ordinal) ||
|
||||||
|
string.Equals(guestPath, "$/", StringComparison.Ordinal) ||
|
||||||
|
string.Equals(guestPath, "$\\", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return app0Root;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guestPath.StartsWith("$/", StringComparison.Ordinal) ||
|
||||||
|
guestPath.StartsWith("$\\", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var relative = NormalizeMountRelativePath(guestPath[2..]);
|
||||||
|
return Path.Combine(app0Root, relative);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(guestPath, "/app0", StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase))
|
string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -5899,22 +5821,40 @@ public static class KernelMemoryCompatExports
|
|||||||
private static bool TryGetAprFileSize(string hostPath, out ulong size)
|
private static bool TryGetAprFileSize(string hostPath, out ulong size)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
|
|
||||||
|
string cachePath;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(hostPath);
|
cachePath = Path.GetFullPath(hostPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
cachePath = hostPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_aprFileSizeCache.TryGetValue(cachePath, out size))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(cachePath);
|
||||||
if (fileInfo.Exists)
|
if (fileInfo.Exists)
|
||||||
{
|
{
|
||||||
var length = fileInfo.Length;
|
var length = fileInfo.Length;
|
||||||
size = length < 0 ? 0UL : unchecked((ulong)length);
|
size = length < 0 ? 0UL : unchecked((ulong)length);
|
||||||
|
_aprFileSizeCache.TryAdd(cachePath, size);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new DirectoryInfo(hostPath).Exists)
|
if (!new DirectoryInfo(cachePath).Exists)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size = 65536;
|
size = 65536;
|
||||||
|
_aprFileSizeCache.TryAdd(cachePath, size);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -6213,6 +6153,20 @@ public static class KernelMemoryCompatExports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void InvalidateAprFileSizeCache(string hostPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
hostPath = Path.GetFullPath(hostPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// The cache key remains the original path when normalization fails.
|
||||||
|
}
|
||||||
|
|
||||||
|
_aprFileSizeCache.TryRemove(hostPath, out _);
|
||||||
|
}
|
||||||
|
|
||||||
private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes)
|
private static string PreviewIoBytes(byte[] buffer, int count, int maxBytes)
|
||||||
{
|
{
|
||||||
if (count <= 0)
|
if (count <= 0)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using System.Buffers.Binary;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Intrinsics.X86;
|
using System.Runtime.Intrinsics.X86;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
@@ -87,11 +86,11 @@ public static class KernelRuntimeCompatExports
|
|||||||
|
|
||||||
if (micros < 1000)
|
if (micros < 1000)
|
||||||
{
|
{
|
||||||
// Guest worker pools use usleep(1) as a polling backoff. Periodically
|
// Guest worker pools use usleep(1) as a polling backoff. Do not turn
|
||||||
// relinquish a full host time slice so spin workers cannot starve producers.
|
// PS microsecond waits into Windows millisecond sleeps on hot paths.
|
||||||
if ((++_shortUsleepCount & 31) == 0)
|
if ((++_shortUsleepCount & 255) == 0)
|
||||||
{
|
{
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -739,8 +738,11 @@ public static class KernelRuntimeCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
if (ShouldTraceVirtualMemory())
|
||||||
$"[LOADER][TRACE] reserve_virtual_range: req=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16} len=0x{length:X16} flags=0x{flags:X8} align=0x{effectiveAlignment:X16}");
|
{
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][TRACE] reserve_virtual_range: req=0x{requestedAddress:X16} desired=0x{desiredAddress:X16} mapped=0x{mappedAddress:X16} len=0x{length:X16} flags=0x{flags:X8} align=0x{effectiveAlignment:X16}");
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
|
if (!ctx.TryWriteUInt64(inOutAddressPointer, mappedAddress))
|
||||||
{
|
{
|
||||||
@@ -1733,117 +1735,16 @@ public static class KernelRuntimeCompatExports
|
|||||||
bool allowSearch,
|
bool allowSearch,
|
||||||
out ulong mappedAddress)
|
out ulong mappedAddress)
|
||||||
{
|
{
|
||||||
mappedAddress = 0;
|
return KernelVirtualRangeAllocator.TryReserve(
|
||||||
if (length == 0)
|
ctx,
|
||||||
{
|
desiredAddress,
|
||||||
return false;
|
length,
|
||||||
}
|
executable: false,
|
||||||
|
alignment,
|
||||||
try
|
allowSearch,
|
||||||
{
|
allowAllocateAtAlternative: allowSearch,
|
||||||
object memoryObject = ctx.Memory;
|
"reserve_virtual_range",
|
||||||
MethodInfo? allocateAt = null;
|
out mappedAddress);
|
||||||
MethodInfo? allocateAtOrAbove = null;
|
|
||||||
var allocateAtHasAllowAlternativeArg = false;
|
|
||||||
for (var depth = 0; depth < 4; depth++)
|
|
||||||
{
|
|
||||||
foreach (var candidate in memoryObject.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance))
|
|
||||||
{
|
|
||||||
var parameters = candidate.GetParameters();
|
|
||||||
if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
|
|
||||||
parameters.Length == 5 &&
|
|
||||||
parameters[0].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[1].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[2].ParameterType == typeof(bool) &&
|
|
||||||
parameters[3].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[4].ParameterType == typeof(ulong).MakeByRefType())
|
|
||||||
{
|
|
||||||
allocateAtOrAbove = candidate;
|
|
||||||
}
|
|
||||||
else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
if (parameters.Length == 3 &&
|
|
||||||
parameters[0].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[1].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[2].ParameterType == typeof(bool))
|
|
||||||
{
|
|
||||||
allocateAt = candidate;
|
|
||||||
allocateAtHasAllowAlternativeArg = false;
|
|
||||||
}
|
|
||||||
else if (parameters.Length == 4 &&
|
|
||||||
parameters[0].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[1].ParameterType == typeof(ulong) &&
|
|
||||||
parameters[2].ParameterType == typeof(bool) &&
|
|
||||||
parameters[3].ParameterType == typeof(bool))
|
|
||||||
{
|
|
||||||
allocateAt = candidate;
|
|
||||||
allocateAtHasAllowAlternativeArg = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocateAtOrAbove is not null && allocateAt is not null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocateAtOrAbove is not null || allocateAt is not null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerProperty = memoryObject.GetType().GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (innerProperty is null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerValue = innerProperty.GetValue(memoryObject);
|
|
||||||
if (innerValue is null || ReferenceEquals(innerValue, memoryObject))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryObject = innerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowSearch && allocateAtOrAbove is not null)
|
|
||||||
{
|
|
||||||
var searchArgs = new object[] { desiredAddress, length, false, alignment, 0UL };
|
|
||||||
var searchResult = allocateAtOrAbove.Invoke(memoryObject, searchArgs);
|
|
||||||
if (searchResult is bool trueValue && trueValue &&
|
|
||||||
searchArgs[4] is ulong searchedAddress && searchedAddress != 0)
|
|
||||||
{
|
|
||||||
mappedAddress = searchedAddress;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocateAt is null)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] reserve_virtual_range: AllocateAt missing on {ctx.Memory.GetType().FullName}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var invokeArgs = allocateAtHasAllowAlternativeArg
|
|
||||||
? new object[] { desiredAddress, length, false, allowSearch }
|
|
||||||
: new object[] { desiredAddress, length, false };
|
|
||||||
var result = allocateAt.Invoke(memoryObject, invokeArgs);
|
|
||||||
if (result is not ulong allocated || allocated == 0)
|
|
||||||
{
|
|
||||||
var resultType = result?.GetType().FullName ?? "null";
|
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] reserve_virtual_range: AllocateAt returned {resultType} value={result ?? "null"}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedAddress = allocated;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine("[LOADER][TRACE] reserve_virtual_range: AllocateAt invocation threw");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong AlignUp(ulong value, ulong alignment)
|
private static ulong AlignUp(ulong value, ulong alignment)
|
||||||
@@ -1856,4 +1757,9 @@ public static class KernelRuntimeCompatExports
|
|||||||
var mask = alignment - 1;
|
var mask = alignment - 1;
|
||||||
return (value + mask) & ~mask;
|
return (value + mask) & ~mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldTraceVirtualMemory()
|
||||||
|
{
|
||||||
|
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIRTUAL_MEMORY"), "1", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Kernel;
|
||||||
|
|
||||||
|
internal static class KernelVirtualRangeAllocator
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<Type, Accessor> _accessors = new();
|
||||||
|
|
||||||
|
public static bool TryReserve(
|
||||||
|
CpuContext ctx,
|
||||||
|
ulong desiredAddress,
|
||||||
|
ulong length,
|
||||||
|
bool executable,
|
||||||
|
ulong alignment,
|
||||||
|
bool allowSearch,
|
||||||
|
bool allowAllocateAtAlternative,
|
||||||
|
string traceName,
|
||||||
|
out ulong mappedAddress)
|
||||||
|
{
|
||||||
|
mappedAddress = 0;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!TryResolveAccessor(ctx.Memory, out var target, out var accessor))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt missing on {ctx.Memory.GetType().FullName}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowSearch && accessor.AllocateAtOrAbove is not null)
|
||||||
|
{
|
||||||
|
var searchArgs = new object[] { desiredAddress, length, executable, alignment, 0UL };
|
||||||
|
var searchResult = accessor.AllocateAtOrAbove.Invoke(target, searchArgs);
|
||||||
|
if (searchResult is bool trueValue && trueValue &&
|
||||||
|
searchArgs[4] is ulong searchedAddress && searchedAddress != 0)
|
||||||
|
{
|
||||||
|
mappedAddress = searchedAddress;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessor.AllocateAt is null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt missing on {target.GetType().FullName}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var invokeArgs = accessor.AllocateAtHasAllowAlternativeArg
|
||||||
|
? new object[] { desiredAddress, length, executable, allowAllocateAtAlternative }
|
||||||
|
: new object[] { desiredAddress, length, executable };
|
||||||
|
var result = accessor.AllocateAt.Invoke(target, invokeArgs);
|
||||||
|
if (result is not ulong allocated || allocated == 0)
|
||||||
|
{
|
||||||
|
var resultType = result?.GetType().FullName ?? "null";
|
||||||
|
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt returned {resultType} value={result ?? "null"}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedAddress = allocated;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt invocation threw");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveAccessor(object rootMemory, out object target, out Accessor accessor)
|
||||||
|
{
|
||||||
|
target = rootMemory;
|
||||||
|
accessor = default;
|
||||||
|
|
||||||
|
for (var depth = 0; depth < 4; depth++)
|
||||||
|
{
|
||||||
|
accessor = _accessors.GetOrAdd(target.GetType(), DiscoverAccessor);
|
||||||
|
if (accessor.AllocateAt is not null || accessor.AllocateAtOrAbove is not null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessor.InnerProperty is null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var innerValue = accessor.InnerProperty.GetValue(target);
|
||||||
|
if (innerValue is null || ReferenceEquals(innerValue, target))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = innerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Accessor DiscoverAccessor(Type type)
|
||||||
|
{
|
||||||
|
MethodInfo? allocateAt = null;
|
||||||
|
MethodInfo? allocateAtOrAbove = null;
|
||||||
|
var allocateAtHasAllowAlternativeArg = false;
|
||||||
|
|
||||||
|
foreach (var candidate in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
var parameters = candidate.GetParameters();
|
||||||
|
if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) &&
|
||||||
|
parameters.Length == 5 &&
|
||||||
|
parameters[0].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[1].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[2].ParameterType == typeof(bool) &&
|
||||||
|
parameters[3].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[4].ParameterType == typeof(ulong).MakeByRefType())
|
||||||
|
{
|
||||||
|
allocateAtOrAbove = candidate;
|
||||||
|
}
|
||||||
|
else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
if (parameters.Length == 3 &&
|
||||||
|
parameters[0].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[1].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[2].ParameterType == typeof(bool))
|
||||||
|
{
|
||||||
|
allocateAt = candidate;
|
||||||
|
allocateAtHasAllowAlternativeArg = false;
|
||||||
|
}
|
||||||
|
else if (parameters.Length == 4 &&
|
||||||
|
parameters[0].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[1].ParameterType == typeof(ulong) &&
|
||||||
|
parameters[2].ParameterType == typeof(bool) &&
|
||||||
|
parameters[3].ParameterType == typeof(bool))
|
||||||
|
{
|
||||||
|
allocateAt = candidate;
|
||||||
|
allocateAtHasAllowAlternativeArg = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var innerProperty = type.GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance);
|
||||||
|
return new Accessor(allocateAt, allocateAtOrAbove, allocateAtHasAllowAlternativeArg, innerProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct Accessor(
|
||||||
|
MethodInfo? AllocateAt,
|
||||||
|
MethodInfo? AllocateAtOrAbove,
|
||||||
|
bool AllocateAtHasAllowAlternativeArg,
|
||||||
|
PropertyInfo? InnerProperty);
|
||||||
|
}
|
||||||
@@ -879,6 +879,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private bool _swapchainRecreateDeferred;
|
private bool _swapchainRecreateDeferred;
|
||||||
private bool _tracedPresentedSwapchain;
|
private bool _tracedPresentedSwapchain;
|
||||||
private bool _swapchainReadbackPending;
|
private bool _swapchainReadbackPending;
|
||||||
|
private bool _deviceLost;
|
||||||
|
private bool _deviceLostLogged;
|
||||||
private int _directPresentationCount;
|
private int _directPresentationCount;
|
||||||
private readonly Dictionary<ulong, GuestImageResource> _guestImages = new();
|
private readonly Dictionary<ulong, GuestImageResource> _guestImages = new();
|
||||||
private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureCacheHits = new();
|
private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureCacheHits = new();
|
||||||
@@ -2816,7 +2818,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
dstSelect: texture.DstSelect,
|
dstSelect: texture.DstSelect,
|
||||||
out var view))
|
out var view))
|
||||||
{
|
{
|
||||||
if (_tracedTextureCacheHits.Add(
|
if (ShouldTraceVulkanResources() &&
|
||||||
|
_tracedTextureCacheHits.Add(
|
||||||
(texture.Address, texture.Width, texture.Height, vkFormat)))
|
(texture.Address, texture.Width, texture.Height, vkFormat)))
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
@@ -3395,7 +3398,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
out var memory);
|
out var memory);
|
||||||
var size = (ulong)Math.Max(guestBuffer.Data.Length, sizeof(uint));
|
var size = (ulong)Math.Max(guestBuffer.Data.Length, sizeof(uint));
|
||||||
|
|
||||||
if (_tracedGlobalBuffers.Add((guestBuffer.BaseAddress, guestBuffer.Data.Length)))
|
if (ShouldTraceVulkanResources() &&
|
||||||
|
_tracedGlobalBuffers.Add((guestBuffer.BaseAddress, guestBuffer.Data.Length)))
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] vk.global_buffer base=0x{guestBuffer.BaseAddress:X16} " +
|
$"[LOADER][TRACE] vk.global_buffer base=0x{guestBuffer.BaseAddress:X16} " +
|
||||||
@@ -4030,6 +4034,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private void ExecuteComputeDispatch(VulkanComputeGuestDispatch work)
|
private void ExecuteComputeDispatch(VulkanComputeGuestDispatch work)
|
||||||
{
|
{
|
||||||
|
if (_deviceLost)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (AddressListContains("SHARPEMU_SKIP_COMPUTE_CS", work.ShaderAddress))
|
if (AddressListContains("SHARPEMU_SKIP_COMPUTE_CS", work.ShaderAddress))
|
||||||
{
|
{
|
||||||
TraceVulkanShader(
|
TraceVulkanShader(
|
||||||
@@ -4132,6 +4141,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
if (TryMarkDeviceLost(exception))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][ERROR] Vulkan compute dispatch failed " +
|
$"[LOADER][ERROR] Vulkan compute dispatch failed " +
|
||||||
$"cs=0x{work.ShaderAddress:X16}: {exception.Message}");
|
$"cs=0x{work.ShaderAddress:X16}: {exception.Message}");
|
||||||
@@ -4197,6 +4211,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private void ExecuteOffscreenDraw(VulkanOffscreenGuestDraw work)
|
private void ExecuteOffscreenDraw(VulkanOffscreenGuestDraw work)
|
||||||
{
|
{
|
||||||
|
if (_deviceLost)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var format = GetRenderTargetFormat(work.Target.Format, work.Target.NumberType);
|
var format = GetRenderTargetFormat(work.Target.Format, work.Target.NumberType);
|
||||||
if (format == Format.Undefined)
|
if (format == Format.Undefined)
|
||||||
{
|
{
|
||||||
@@ -4354,6 +4373,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
if (TryMarkDeviceLost(exception))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_guestImages.TryGetValue(work.Target.Address, out var failedTarget) ||
|
if (!_guestImages.TryGetValue(work.Target.Address, out var failedTarget) ||
|
||||||
!failedTarget.Initialized)
|
!failedTarget.Initialized)
|
||||||
{
|
{
|
||||||
@@ -4809,7 +4833,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
_commandBuffer = _presentationCommandBuffer;
|
_commandBuffer = _presentationCommandBuffer;
|
||||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
if (!_deviceLost)
|
||||||
|
{
|
||||||
|
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||||
|
}
|
||||||
|
|
||||||
var completedWork = 0;
|
var completedWork = 0;
|
||||||
while (completedWork < MaxGuestWorkPerRender &&
|
while (completedWork < MaxGuestWorkPerRender &&
|
||||||
@@ -5667,6 +5694,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
string.Equals(mode, "present", StringComparison.OrdinalIgnoreCase);
|
string.Equals(mode, "present", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldTraceVulkanResources() =>
|
||||||
|
string.Equals(
|
||||||
|
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VK_RESOURCES"),
|
||||||
|
"1",
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
|
||||||
private void RecordTranslatedGraphicsPass(
|
private void RecordTranslatedGraphicsPass(
|
||||||
TranslatedDrawResources resources,
|
TranslatedDrawResources resources,
|
||||||
RenderPass renderPass,
|
RenderPass renderPass,
|
||||||
@@ -6502,6 +6535,25 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryMarkDeviceLost(Exception exception)
|
||||||
|
{
|
||||||
|
if (!exception.Message.Contains(nameof(Result.ErrorDeviceLost), StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceLost = true;
|
||||||
|
if (!_deviceLostLogged)
|
||||||
|
{
|
||||||
|
_deviceLostLogged = true;
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
"[LOADER][ERROR] Vulkan device lost; dropping subsequent guest GPU work. " +
|
||||||
|
exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void TraceVulkanShader(string message)
|
private static void TraceVulkanShader(string message)
|
||||||
{
|
{
|
||||||
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) &&
|
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) &&
|
||||||
|
|||||||
Reference in New Issue
Block a user