Compare commits

..

1 Commits

Author SHA1 Message Date
ParantezTech 35736e4740 [ci] fix workflow dispatch and push triggers for main branch 2026-07-04 13:45:44 +03:00
45 changed files with 460 additions and 19891 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ indent_size = 4
indent_style = space
tab_width = 4
[*.{md,json,yml,xml,props,csproj}]
[*.{md,json,yml,props,csproj}]
indent_size = 2
tab_width = 2
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

-1
View File
@@ -26,7 +26,6 @@ arm64/
*.userosscache
*.sln.docstates
.packages
packages/
*.nupkg
.nuget/
+2 -2
View File
@@ -2,6 +2,7 @@
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
@@ -9,8 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup>
<PackageVersion Include="Iced" Version="1.21.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Windowing" Version="2.23.0" />
</ItemGroup>
</Project>
</Project>
BIN
View File
Binary file not shown.
+1 -3
View File
@@ -3,14 +3,12 @@ version = 1
[[annotations]]
path = [
"REUSE.toml",
"nuget.config",
"global.json",
"**/packages.lock.json",
"scripts/ps5_names.txt",
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
"_logs/**",
".github/images/**",
"assets/images/**"
".github/images/**"
]
precedence = "aggregate"
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

-18
View File
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="globalPackagesFolder" value=".packages" />
</config>
<packageSources>
<clear />
<add key="Nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="Nuget">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
+1 -6
View File
@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<!-- <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> -->
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -30,11 +30,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == ''">
<ApplicationIcon>..\..\assets\images\SharpEmu.ico</ApplicationIcon>
<Win32Icon>..\..\assets\images\SharpEmu.ico</Win32Icon>
</PropertyGroup>
<ItemGroup>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
-11
View File
@@ -81,7 +81,6 @@
"dependencies": {
"SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )"
}
@@ -104,16 +103,6 @@
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
@@ -338,15 +338,10 @@ public sealed partial class DirectExecutionBackend
{
orbisGen2Result = DispatchBootstrapBridge();
}
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal) ||
string.Equals(importStubEntry.Nid, "LwG8g3niqwA", StringComparison.Ordinal))
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal))
{
orbisGen2Result = DispatchKernelDynlibDlsym();
}
else if (string.Equals(importStubEntry.Nid, "r8mvOaWdi28", StringComparison.Ordinal))
{
orbisGen2Result = DispatchIl2CppApiLookupSymbol();
}
else if (importStubEntry.Export is { } cachedExport &&
(cachedExport.Target & cpuContext.TargetGeneration) != 0)
{
@@ -633,56 +628,25 @@ public sealed partial class DirectExecutionBackend
"ASoW5WE-UPo" or // sceKernelAprSubmitCommandBufferAndGetResult
"rqwFKI4PAiM" or // sceKernelAprWaitCommandBuffer
"eE4Szl8sil8" or // sceKernelAprSubmitCommandBuffer
"qvMUCyyaCSI" or // sceKernelAprSubmitCommandBufferAndGetId
"Q2V+iqvjgC0" or // vsnprintf
"q1cHNfGycLI" or // scePadRead
"xk0AcarP3V4" or // scePadOpen
"yH17Q6NWtVg" or // sceUserServiceGetEvent
"D-CzAxQL0XI" or // sceUserServiceGetPlatformPrivacySetting
"K-jXhbt2gn4"; // scePthreadMutexTrylock
"qvMUCyyaCSI"; // sceKernelAprSubmitCommandBufferAndGetId
private bool ShouldLogImportResult(string nid, OrbisGen2Result result)
{
var resultValue = unchecked((int)result);
if (resultValue > 0)
{
return false;
}
var expectedFileProbeMiss =
result == OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND &&
IsExpectedFileProbeNotFoundNid(nid);
var expectedTimedWaitTimeout =
string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) &&
unchecked((int)result) == 60;
var expectedEqueueTimeout =
string.Equals(nid, "fzyMKs9kim0", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
var expectedMutexTrylockBusy =
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
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)
if (!expectedFileProbeMiss && !expectedTimedWaitTimeout && !expectedMutexTrylockBusy)
{
return true;
}
if (!ShouldLogExpectedImportResults())
{
return false;
}
var key = nid + "\0" + resultValue;
var key = nid + "\0" + (int)result;
int count;
lock (_importResultLogSampleGate)
{
@@ -694,12 +658,6 @@ public sealed partial class DirectExecutionBackend
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) =>
nid is
"eV9wAD2riIA" or // sceKernelStat
@@ -1222,11 +1180,8 @@ public sealed partial class DirectExecutionBackend
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress) &&
!TryResolveRuntimeSymbolAlias(symbolName, out resolvedAddress))
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress))
{
Console.Error.WriteLine(
$"[LOADER][WARN] sceKernelDlsym failed: handle=0x{cpuContext[CpuRegister.Rdi]:X} symbol='{symbolName}'");
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -1239,62 +1194,6 @@ public sealed partial class DirectExecutionBackend
return OrbisGen2Result.ORBIS_GEN2_OK;
}
private bool TryResolveRuntimeSymbolAlias(string symbolName, out ulong address)
{
address = 0;
var alias = symbolName switch
{
"scriptingGetMem" => "malloc",
"scriptingFreeMem" => "free",
"scriptingRealloc" => "realloc",
"scriptingCalloc" => "calloc",
_ => null,
};
return alias != null && TryResolveRuntimeSymbolAddress(alias, out address);
}
private OrbisGen2Result DispatchIl2CppApiLookupSymbol()
{
var cpuContext = ActiveCpuContext;
if (cpuContext == null)
{
return OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var symbolNameAddress = cpuContext[CpuRegister.Rdi];
var outputAddress = cpuContext[CpuRegister.Rsi];
if (!TryReadAsciiZ(symbolNameAddress, 512, out var symbolName) ||
outputAddress == 0 ||
!TryResolveIl2CppApiAddress(symbolName, out var resolvedAddress) ||
!TryWriteUInt64Compat(outputAddress, resolvedAddress))
{
Console.Error.WriteLine(
$"[LOADER][WARN] il2cpp_api_lookup_symbol failed: name='{symbolName}' out=0x{outputAddress:X16}");
if (outputAddress != 0)
{
_ = TryWriteUInt64Compat(outputAddress, 0);
}
cpuContext[CpuRegister.Rax] = ulong.MaxValue;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
cpuContext[CpuRegister.Rax] = 0;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
private bool TryResolveIl2CppApiAddress(string symbolName, out ulong address)
{
if (TryResolveRuntimeSymbolAddress(symbolName, out address))
{
return true;
}
return Aerolib.Instance.TryGetByExportName(symbolName, out var symbol) &&
TryResolveRuntimeSymbolAddress(symbol.Nid, out address);
}
private OrbisGen2Result DispatchBootstrapBridge()
{
var cpuContext = ActiveCpuContext;
+82 -189
View File
@@ -11,9 +11,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
{
private readonly ReaderWriterLockSlim _gate = new(LockRecursionPolicy.SupportsRecursion);
private readonly object _guestAllocationGate = new();
private readonly object _allocationSearchHintGate = 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 bool _disposed;
private const ulong PageSize = 0x1000;
@@ -21,8 +19,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private const ulong GuestAllocationArenaSize = 0x0100_0000;
private const ulong GuestAllocationArenaStartOffset = PageSize;
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
private const ulong FullCommitRegionLimit = 4UL << 30;
private const ulong DefaultLazyReservePrimeBytes = 0x0400_0000UL; // 64 MiB
private const ulong LazyReservePrimeBytes = 0x5000_0000UL; // 1.25 GiB
private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB
private const uint MEM_COMMIT = 0x1000;
@@ -38,7 +35,6 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private ulong _guestAllocationArenaBase;
private ulong _guestAllocationOffset;
private static readonly ulong LazyReservePrimeBytes = ResolveLazyReservePrimeBytes();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
@@ -85,7 +81,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock();
try
{
InsertRegionSorted(new MemoryRegion
_regions.Add(new MemoryRegion
{
VirtualAddress = actualAddress,
Size = alignedSize,
@@ -100,7 +96,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
}
var allocationKind = executable ? "executable memory" : "data memory";
TraceVmem($"Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
Console.Error.WriteLine($"[VMEM] Allocated exact {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes)");
return true;
}
@@ -114,9 +110,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
var allocationType = MEM_COMMIT | MEM_RESERVE;
var reservedOnly = false;
var preferReserveOnly = !executable &&
alignedSize >= LargeDataReserveThreshold &&
alignedSize > FullCommitRegionLimit;
var preferReserveOnly = !executable && alignedSize >= LargeDataReserveThreshold;
void* result = null;
if (preferReserveOnly)
@@ -145,7 +139,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
throw new InvalidOperationException($"Failed to allocate exact mapping at 0x{desiredAddress:X16} ({alignedSize} bytes)");
}
TraceVmem($"Could not allocate at 0x{desiredAddress:X16}, trying any address...");
Console.Error.WriteLine($"[VMEM] Could not allocate at 0x{desiredAddress:X16}, trying any address...");
result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection);
if (result == null)
@@ -199,12 +193,12 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
lazyPrimeState = committedBytes == primeBytes
? $"ok:{committedBytes:X}"
: $"partial:{committedBytes:X}/{primeBytes:X}";
TraceVmem($"Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
Console.Error.WriteLine($"[VMEM] Primed lazy region: 0x{actualAddress:X16} - 0x{actualAddress + committedBytes:X16} ({committedBytes} bytes)");
}
else
{
lazyPrimeState = $"fail:{primeBytes:X}";
TraceVmem($"Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit");
Console.Error.WriteLine($"[VMEM] Failed to prime lazy region at 0x{actualAddress:X16} ({primeBytes} bytes), continuing with on-demand commit");
}
}
else
@@ -216,7 +210,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterWriteLock();
try
{
InsertRegionSorted(new MemoryRegion
_regions.Add(new MemoryRegion
{
VirtualAddress = actualAddress,
Size = alignedSize,
@@ -233,7 +227,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var allocationKind = reservedOnly
? "reserved data memory (lazy commit)"
: (executable ? "executable memory" : "data memory");
TraceVmem($"Allocated {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes) lazy_prime={lazyPrimeState}");
Console.Error.WriteLine($"[VMEM] Allocated {allocationKind}: 0x{actualAddress:X16} - 0x{actualAddress + alignedSize:X16} ({alignedSize} bytes) lazy_prime={lazyPrimeState}");
return actualAddress;
}
@@ -253,8 +247,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
var alignedSize = AlignUp(size, PageSize);
var effectiveAlignment = Math.Max(PageSize, alignment == 0 ? PageSize : alignment);
var requestedCursor = AlignUp(desiredAddress, effectiveAlignment);
var cursor = GetAllocationSearchCursor(desiredAddress, requestedCursor, effectiveAlignment, executable);
var cursor = AlignUp(desiredAddress, effectiveAlignment);
for (var attempt = 0; attempt < 0x10000; attempt++)
{
@@ -274,7 +267,6 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
actualAddress = AllocateAt(cursor, alignedSize, executable, allowAlternative: false);
if (actualAddress == cursor)
{
UpdateAllocationSearchCursor(desiredAddress, effectiveAlignment, executable, actualAddress + alignedSize);
return true;
}
@@ -342,10 +334,6 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
}
_regions.Clear();
_pageProtections.Clear();
lock (_allocationSearchHintGate)
{
_allocationSearchHints.Clear();
}
}
finally
{
@@ -402,7 +390,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
ApplySegmentProtection(mapStart, mapEnd, protection);
TraceVmem($"Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
Console.Error.WriteLine($"[VMEM] Mapped segment: 0x{virtualAddress:X16} - 0x{virtualAddress + memorySize:X16} (file: {fileData.Length} bytes, prot: {protection})");
}
finally
{
@@ -485,34 +473,30 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
try
{
var region = FindRegion(virtualAddress, (ulong)destination.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)destination.Length,
region,
out var offset))
foreach (var region in _regions)
{
var srcPtr = (void*)(region.VirtualAddress + offset);
if (destination.IsEmpty)
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
{
return true;
}
if (region.IsReservedOnly)
{
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
var srcPtr = (void*)(region.VirtualAddress + offset);
if (destination.IsEmpty)
{
return false;
return true;
}
if (region.IsReservedOnly)
{
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{
return false;
}
}
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{
requiresExclusiveAccess = true;
break;
}
}
if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region))
{
requiresExclusiveAccess = true;
}
else
{
fixed (byte* destPtr = destination)
{
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
@@ -549,34 +533,30 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
try
{
var region = FindRegion(virtualAddress, (ulong)source.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)source.Length,
region,
out var offset))
foreach (var region in _regions)
{
var destPtr = (void*)(region.VirtualAddress + offset);
if (source.IsEmpty)
if (TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset))
{
return true;
}
if (region.IsReservedOnly)
{
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
var destPtr = (void*)(region.VirtualAddress + offset);
if (source.IsEmpty)
{
return false;
return true;
}
if (region.IsReservedOnly)
{
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{
return false;
}
}
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{
requiresExclusiveAccess = true;
break;
}
}
if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region))
{
requiresExclusiveAccess = true;
}
else
{
fixed (byte* srcPtr = source)
{
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length);
@@ -609,14 +589,13 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private bool TryReadExclusive(ulong virtualAddress, Span<byte> destination)
{
var region = FindRegion(virtualAddress, (ulong)destination.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)destination.Length,
region,
out var offset))
foreach (var region in _regions)
{
if (!TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
{
continue;
}
var srcPtr = (void*)(region.VirtualAddress + offset);
if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region))
{
@@ -658,14 +637,13 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan<byte> source)
{
var region = FindRegion(virtualAddress, (ulong)source.Length);
if (region is not null &&
TryResolveRegionOffset(
virtualAddress,
(ulong)source.Length,
region,
out var offset))
foreach (var region in _regions)
{
if (!TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset))
{
continue;
}
var destPtr = (void*)(region.VirtualAddress + offset);
if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region))
{
@@ -721,9 +699,15 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
try
{
return FindRegion(virtualAddress, 1) is not null
? (void*)virtualAddress
: null;
foreach (var region in _regions)
{
if (virtualAddress >= region.VirtualAddress &&
virtualAddress < region.VirtualAddress + region.Size)
{
return (void*)virtualAddress;
}
}
return null;
}
finally
{
@@ -736,7 +720,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
_gate.EnterReadLock();
try
{
return FindRegion(virtualAddress, size) is not null;
foreach (var region in _regions)
{
if (TryResolveRegionOffset(virtualAddress, size, region, out _))
{
return true;
}
}
return false;
}
finally
{
@@ -746,48 +737,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private MemoryRegion? FindRegion(ulong address, ulong size)
{
var low = 0;
var high = _regions.Count - 1;
MemoryRegion? candidate = null;
while (low <= high)
foreach (var region in _regions)
{
var middle = low + ((high - low) >> 1);
var region = _regions[middle];
if (region.VirtualAddress <= address)
if (TryResolveRegionOffset(address, size, region, out _))
{
candidate = region;
low = middle + 1;
}
else
{
high = middle - 1;
return region;
}
}
return candidate is not null &&
TryResolveRegionOffset(address, size, candidate, out _)
? candidate
: null;
}
private void InsertRegionSorted(MemoryRegion region)
{
var low = 0;
var high = _regions.Count;
while (low < high)
{
var middle = low + ((high - low) >> 1);
if (_regions[middle].VirtualAddress < region.VirtualAddress)
{
low = middle + 1;
}
else
{
high = middle;
}
}
_regions.Insert(low, region);
return null;
}
private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd)
@@ -805,16 +762,6 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
foreach (var region in _regions)
{
var regionEnd = region.VirtualAddress + region.Size;
if (region.VirtualAddress >= end)
{
break;
}
if (regionEnd <= address)
{
continue;
}
if (address < regionEnd && region.VirtualAddress < end)
{
overlapEnd = Math.Max(overlapEnd, regionEnd);
@@ -829,37 +776,6 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
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)
{
offset = 0;
@@ -1028,29 +944,6 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
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()
{
if (!_disposed)
+2 -4
View File
@@ -543,7 +543,6 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
{
Path.Combine(ebootDirectory, "sce_module"),
Path.Combine(ebootDirectory, "sce_modules"),
Path.Combine(ebootDirectory, "Media", "Modules"),
}
.Distinct(StringComparer.OrdinalIgnoreCase)
.Where(Directory.Exists)
@@ -555,9 +554,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
}
var allModulePaths = moduleDirectories
.SelectMany(directory => Directory
.EnumerateFiles(directory)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase))
.SelectMany(Directory.EnumerateFiles)
.Where(path =>
{
var extension = Path.GetExtension(path);
@@ -565,6 +562,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
string.Equals(extension, ".sprx", StringComparison.OrdinalIgnoreCase);
})
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
.ToArray();
var modulePaths = allModulePaths
-11
View File
@@ -72,7 +72,6 @@
"dependencies": {
"SharpEmu.HLE": "[1.0.0, )",
"Silk.NET.Vulkan": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.EXT": "[2.23.0, )",
"Silk.NET.Vulkan.Extensions.KHR": "[2.23.0, )",
"Silk.NET.Windowing": "[2.23.0, )"
}
@@ -89,16 +88,6 @@
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "CentralTransitive",
"requested": "[2.23.0, )",
File diff suppressed because it is too large Load Diff
-313
View File
@@ -1,313 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Libs.Agc;
internal enum Gen5ShaderEncoding
{
Sop1,
Sop2,
Sopc,
Sopp,
Sopk,
Smrd,
Smem,
Mubuf,
Mtbuf,
Vop1,
Vop2,
Vopc,
Vop3,
Vintrp,
Ds,
Flat,
Vop3p,
Mimg,
Exp,
}
internal enum Gen5OperandKind
{
ScalarRegister,
VectorRegister,
EncodedConstant,
LiteralConstant,
}
internal enum Gen5ShaderResourceKind
{
ReadOnlyTexture,
ReadWriteTexture,
Sampler,
ConstantBuffer,
}
internal enum Gen5PixelOutputKind
{
Float,
Uint,
Sint,
}
internal enum Gen5SpirvStage
{
Vertex,
Pixel,
Compute,
}
internal sealed record Gen5SpirvShader(
byte[] Spirv,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
IReadOnlyList<Gen5ImageBinding> ImageBindings,
uint AttributeCount,
IReadOnlyList<Gen5VertexInputBinding> VertexInputs);
internal readonly record struct Gen5ShaderResourceMapping(
Gen5ShaderResourceKind Kind,
uint Slot,
uint OffsetDwords,
bool SizeFlag);
internal sealed record Gen5ShaderMetadata(
uint ExtendedUserDataSizeDwords,
uint ShaderResourceTableSizeDwords,
IReadOnlyDictionary<uint, uint> DirectResources,
IReadOnlyList<Gen5ShaderResourceMapping> Resources);
internal readonly record struct Gen5ComputeSystemRegisters(
uint? WorkGroupXRegister,
uint? WorkGroupYRegister,
uint? WorkGroupZRegister,
uint? ThreadGroupSizeRegister)
{
public bool TryGetExpression(uint scalarRegister, out string expression)
{
if (WorkGroupXRegister == scalarRegister)
{
expression = "gl_WorkGroupID.x";
return true;
}
if (WorkGroupYRegister == scalarRegister)
{
expression = "gl_WorkGroupID.y";
return true;
}
if (WorkGroupZRegister == scalarRegister)
{
expression = "gl_WorkGroupID.z";
return true;
}
if (ThreadGroupSizeRegister == scalarRegister)
{
expression = "(gl_WorkGroupSize.x * gl_WorkGroupSize.y * gl_WorkGroupSize.z)";
return true;
}
expression = string.Empty;
return false;
}
public void ClearStaticValues(Span<uint> scalarRegisters)
{
ClearStaticValue(scalarRegisters, WorkGroupXRegister);
ClearStaticValue(scalarRegisters, WorkGroupYRegister);
ClearStaticValue(scalarRegisters, WorkGroupZRegister);
ClearStaticValue(scalarRegisters, ThreadGroupSizeRegister);
}
private static void ClearStaticValue(Span<uint> scalarRegisters, uint? scalarRegister)
{
if (scalarRegister is { } register && register < scalarRegisters.Length)
{
scalarRegisters[(int)register] = 0;
}
}
}
internal sealed record Gen5ShaderState(
Gen5ShaderProgram Program,
IReadOnlyList<uint> UserData,
Gen5ShaderMetadata? Metadata,
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
uint UserDataScalarRegisterBase = 0);
internal readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
{
public static Gen5Operand Scalar(uint index) =>
new(Gen5OperandKind.ScalarRegister, index);
public static Gen5Operand Vector(uint index) =>
new(Gen5OperandKind.VectorRegister, index);
public static Gen5Operand Source(uint encoded, uint? literal = null)
{
if (encoded >= 256)
{
return Vector(encoded - 256);
}
if (encoded is 249 or 255 && literal.HasValue)
{
return new(Gen5OperandKind.LiteralConstant, literal.Value);
}
if (encoded <= 105 || encoded is 106 or 107 or 124 or 126 or 127)
{
return Scalar(encoded);
}
return new(Gen5OperandKind.EncodedConstant, encoded);
}
public override string ToString() => Kind switch
{
Gen5OperandKind.ScalarRegister => $"s{Value}",
Gen5OperandKind.VectorRegister => $"v{Value}",
Gen5OperandKind.LiteralConstant => $"0x{Value:X8}",
_ => $"src[{Value}]",
};
}
internal abstract record Gen5InstructionControl;
internal sealed record Gen5ImageControl(
uint Dmask,
uint VectorAddress,
IReadOnlyList<uint> AddressRegisters,
uint VectorData,
uint ScalarResource,
uint ScalarSampler,
uint Dimension,
bool IsArray,
bool Glc,
bool Slc) : Gen5InstructionControl
{
public uint GetAddressRegister(int component) =>
component < AddressRegisters.Count
? AddressRegisters[component]
: VectorAddress + (uint)component;
}
internal sealed record Gen5GlobalMemoryControl(
uint DwordCount,
uint VectorAddress,
uint VectorData,
uint ScalarAddress,
int OffsetBytes,
bool Glc,
bool Slc) : Gen5InstructionControl;
internal sealed record Gen5BufferMemoryControl(
uint DwordCount,
uint VectorAddress,
uint VectorData,
uint ScalarResource,
int OffsetBytes,
bool IndexEnabled,
bool OffsetEnabled,
bool Glc,
bool Slc) : Gen5InstructionControl;
internal sealed record Gen5ExportControl(
uint Target,
uint EnableMask,
bool Compressed,
bool Done,
bool ValidMask) : Gen5InstructionControl;
internal sealed record Gen5InterpolationControl(
uint Attribute,
uint Channel) : Gen5InstructionControl;
internal sealed record Gen5Vop3Control(
uint AbsoluteMask,
uint NegateMask,
uint OutputModifier,
bool Clamp,
uint? ScalarDestination) : Gen5InstructionControl;
internal sealed record Gen5SdwaControl(
uint DestinationSelect,
uint Source0Select,
uint Source1Select,
uint AbsoluteMask,
uint NegateMask,
uint OutputModifier,
bool Clamp) : Gen5InstructionControl;
internal sealed record Gen5DppControl(
uint Control,
bool FetchInactive,
bool BoundControl,
uint AbsoluteMask,
uint NegateMask,
uint BankMask,
uint RowMask) : Gen5InstructionControl;
internal sealed record Gen5ScalarMemoryControl(
uint DestinationCount,
int ImmediateOffsetBytes,
uint? DynamicOffsetRegister) : Gen5InstructionControl;
internal sealed record Gen5DataShareControl(
uint Offset0,
uint Offset1,
bool Gds) : Gen5InstructionControl;
internal sealed record Gen5ImageBinding(
uint Pc,
string Opcode,
Gen5ImageControl Control,
IReadOnlyList<uint> ResourceDescriptor,
IReadOnlyList<uint> SamplerDescriptor,
uint? MipLevel);
internal sealed record Gen5GlobalMemoryBinding(
uint ScalarAddress,
ulong BaseAddress,
IReadOnlyList<uint> InstructionPcs,
byte[] Data);
internal sealed record Gen5VertexInputBinding(
uint Pc,
uint Location,
uint ComponentCount,
uint DataFormat,
uint NumberFormat,
ulong BaseAddress,
uint Stride,
uint OffsetBytes,
byte[] Data);
internal sealed record Gen5ShaderEvaluation(
IReadOnlyList<uint> InitialScalarRegisters,
IReadOnlyList<uint> ScalarRegisters,
IReadOnlyDictionary<uint, IReadOnlyList<uint>> ScalarRegistersByPc,
IReadOnlyList<Gen5ImageBinding> ImageBindings,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
IReadOnlySet<uint>? RuntimeScalarRegisters = null,
IReadOnlyList<Gen5VertexInputBinding>? VertexInputs = null);
internal sealed record Gen5ShaderInstruction(
uint Pc,
Gen5ShaderEncoding Encoding,
string Opcode,
IReadOnlyList<uint> Words,
IReadOnlyList<Gen5Operand> Sources,
IReadOnlyList<Gen5Operand> Destinations,
Gen5InstructionControl? Control);
internal sealed record Gen5ShaderProgram(
ulong Address,
IReadOnlyList<Gen5ShaderInstruction> Instructions)
{
public IEnumerable<Gen5ImageControl> ImageResources =>
Instructions
.Select(instruction => instruction.Control)
.OfType<Gen5ImageControl>();
}
@@ -1,154 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.Agc;
internal static class Gen5ShaderMetadataReader
{
private const ulong ShaderUserDataOffset = 0x08;
private const int ResourceClassCount = 4;
private const int MaxMetadataEntries = 4096;
public static bool TryRead(
CpuContext ctx,
ulong shaderHeaderAddress,
out Gen5ShaderMetadata metadata)
{
metadata = default!;
if (!TryReadUInt64(ctx, shaderHeaderAddress + ShaderUserDataOffset, out var userDataAddress) ||
userDataAddress == 0 ||
!TryReadUInt64(ctx, userDataAddress, out var directResourceOffsetsAddress))
{
return false;
}
var resourceOffsets = new ulong[ResourceClassCount];
for (var resourceClass = 0; resourceClass < ResourceClassCount; resourceClass++)
{
if (!TryReadUInt64(
ctx,
userDataAddress + 0x08 + (ulong)(resourceClass * sizeof(ulong)),
out resourceOffsets[resourceClass]))
{
return false;
}
}
if (!TryReadUInt16(ctx, userDataAddress + 0x28, out var extendedUserDataSize) ||
!TryReadUInt16(ctx, userDataAddress + 0x2A, out var shaderResourceTableSize) ||
!TryReadUInt16(ctx, userDataAddress + 0x2C, out var directResourceCount) ||
directResourceCount > MaxMetadataEntries)
{
return false;
}
var resourceCounts = new ushort[ResourceClassCount];
for (var resourceClass = 0; resourceClass < ResourceClassCount; resourceClass++)
{
if (!TryReadUInt16(
ctx,
userDataAddress + 0x2E + (ulong)(resourceClass * sizeof(ushort)),
out resourceCounts[resourceClass]) ||
resourceCounts[resourceClass] > MaxMetadataEntries)
{
return false;
}
}
var directResources = new Dictionary<uint, uint>();
if (directResourceCount != 0)
{
if (directResourceOffsetsAddress == 0)
{
return false;
}
for (uint type = 0; type < directResourceCount; type++)
{
if (!TryReadUInt16(ctx, directResourceOffsetsAddress + type * sizeof(ushort), out var offset))
{
return false;
}
if (offset != ushort.MaxValue)
{
directResources[type] = offset;
}
}
}
var resources = new List<Gen5ShaderResourceMapping>();
for (var resourceClass = 0; resourceClass < ResourceClassCount; resourceClass++)
{
var count = resourceCounts[resourceClass];
if (count == 0)
{
continue;
}
if (resourceOffsets[resourceClass] == 0)
{
return false;
}
for (uint slot = 0; slot < count; slot++)
{
if (!TryReadUInt16(
ctx,
resourceOffsets[resourceClass] + slot * sizeof(ushort),
out var sharp))
{
return false;
}
var offset = (uint)(sharp & 0x7FFF);
if (offset == 0x7FFF)
{
continue;
}
resources.Add(new Gen5ShaderResourceMapping(
(Gen5ShaderResourceKind)resourceClass,
slot,
offset,
(sharp & 0x8000) != 0));
}
}
metadata = new Gen5ShaderMetadata(
extendedUserDataSize,
shaderResourceTableSize,
directResources,
resources);
return true;
}
private static bool TryReadUInt16(CpuContext ctx, ulong address, out ushort value)
{
Span<byte> bytes = stackalloc byte[sizeof(ushort)];
if (!ctx.Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt16LittleEndian(bytes);
return true;
}
private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
if (!ctx.Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt64LittleEndian(bytes);
return true;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-167
View File
@@ -1,167 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Libs.Agc;
internal static class SpirvFixedShaders
{
public static byte[] CreateFullscreenVertex(uint attributeCount)
{
var module = new SpirvModuleBuilder();
module.AddCapability(SpirvCapability.Shader);
var voidType = module.TypeVoid();
var boolType = module.TypeBool();
var uintType = module.TypeInt(32, signed: false);
var floatType = module.TypeFloat(32);
var vec4Type = module.TypeVector(floatType, 4);
var inputUintPointer = module.TypePointer(SpirvStorageClass.Input, uintType);
var outputVec4Pointer = module.TypePointer(SpirvStorageClass.Output, vec4Type);
var vertexIndex = module.AddGlobalVariable(inputUintPointer, SpirvStorageClass.Input);
module.AddName(vertexIndex, "vertexIndex");
module.AddDecoration(
vertexIndex,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.VertexIndex);
var position = module.AddGlobalVariable(outputVec4Pointer, SpirvStorageClass.Output);
module.AddName(position, "position");
module.AddDecoration(position, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.Position);
var attributes = new uint[attributeCount];
for (uint index = 0; index < attributeCount; index++)
{
attributes[index] =
module.AddGlobalVariable(outputVec4Pointer, SpirvStorageClass.Output);
module.AddName(attributes[index], $"attr{index}");
module.AddDecoration(attributes[index], SpirvDecoration.Location, index);
module.AddDecoration(attributes[index], SpirvDecoration.NoPerspective);
}
var functionType = module.TypeFunction(voidType);
var main = module.BeginFunction(voidType, functionType);
module.AddName(main, "main");
module.AddLabel();
var indexValue = module.AddInstruction(SpirvOp.Load, uintType, vertexIndex);
var one = module.Constant(uintType, 1);
var two = module.Constant(uintType, 2);
var shifted = module.AddInstruction(SpirvOp.ShiftLeftLogical, uintType, indexValue, one);
var xBits = module.AddInstruction(SpirvOp.BitwiseAnd, uintType, shifted, two);
var yBits = module.AddInstruction(SpirvOp.BitwiseAnd, uintType, indexValue, two);
var x = module.AddInstruction(SpirvOp.ConvertUToF, floatType, xBits);
var y = module.AddInstruction(SpirvOp.ConvertUToF, floatType, yBits);
var zero = module.ConstantFloat(floatType, 0f);
var oneFloat = module.ConstantFloat(floatType, 1f);
var twoFloat = module.ConstantFloat(floatType, 2f);
var xPosition = module.AddInstruction(SpirvOp.FMul, floatType, x, twoFloat);
xPosition = module.AddInstruction(SpirvOp.FSub, floatType, xPosition, oneFloat);
var yPosition = module.AddInstruction(SpirvOp.FMul, floatType, y, twoFloat);
yPosition = module.AddInstruction(SpirvOp.FSub, floatType, yPosition, oneFloat);
var positionValue = module.AddInstruction(
SpirvOp.CompositeConstruct,
vec4Type,
xPosition,
yPosition,
zero,
oneFloat);
module.AddStatement(SpirvOp.Store, position, positionValue);
var attributeValue = module.AddInstruction(
SpirvOp.CompositeConstruct,
vec4Type,
x,
y,
zero,
oneFloat);
foreach (var attribute in attributes)
{
module.AddStatement(SpirvOp.Store, attribute, attributeValue);
}
module.AddStatement(SpirvOp.Return);
module.EndFunction();
var interfaces = new uint[2 + attributes.Length];
interfaces[0] = vertexIndex;
interfaces[1] = position;
attributes.CopyTo(interfaces, 2);
module.AddEntryPoint(SpirvExecutionModel.Vertex, main, "main", interfaces);
_ = boolType;
return module.Build();
}
public static byte[] CreateCopyFragment()
{
var module = new SpirvModuleBuilder();
module.AddCapability(SpirvCapability.Shader);
var voidType = module.TypeVoid();
var floatType = module.TypeFloat(32);
var vec2Type = module.TypeVector(floatType, 2);
var vec4Type = module.TypeVector(floatType, 4);
var inputVec4Pointer = module.TypePointer(SpirvStorageClass.Input, vec4Type);
var outputVec4Pointer = module.TypePointer(SpirvStorageClass.Output, vec4Type);
var imageType = module.TypeImage(
floatType,
SpirvImageDim.Dim2D,
depth: false,
arrayed: false,
multisampled: false,
sampled: 1,
SpirvImageFormat.Unknown);
var sampledImageType = module.TypeSampledImage(imageType);
var sampledImagePointer =
module.TypePointer(SpirvStorageClass.UniformConstant, sampledImageType);
var attribute = module.AddGlobalVariable(inputVec4Pointer, SpirvStorageClass.Input);
module.AddName(attribute, "attr0");
module.AddDecoration(attribute, SpirvDecoration.Location, 0);
var texture = module.AddGlobalVariable(
sampledImagePointer,
SpirvStorageClass.UniformConstant);
module.AddName(texture, "tex0");
module.AddDecoration(texture, SpirvDecoration.DescriptorSet, 0);
module.AddDecoration(texture, SpirvDecoration.Binding, 1);
var output = module.AddGlobalVariable(outputVec4Pointer, SpirvStorageClass.Output);
module.AddName(output, "outColor");
module.AddDecoration(output, SpirvDecoration.Location, 0);
var functionType = module.TypeFunction(voidType);
var main = module.BeginFunction(voidType, functionType);
module.AddName(main, "main");
module.AddLabel();
var attributeValue = module.AddInstruction(SpirvOp.Load, vec4Type, attribute);
var coordinates = module.AddInstruction(
SpirvOp.VectorShuffle,
vec2Type,
attributeValue,
attributeValue,
0,
1);
var sampledImage = module.AddInstruction(SpirvOp.Load, sampledImageType, texture);
var lod = module.ConstantFloat(floatType, 0f);
var color = module.AddInstruction(
SpirvOp.ImageSampleExplicitLod,
vec4Type,
sampledImage,
coordinates,
2,
lod);
module.AddStatement(SpirvOp.Store, output, color);
module.AddStatement(SpirvOp.Return);
module.EndFunction();
module.AddEntryPoint(
SpirvExecutionModel.Fragment,
main,
"main",
[attribute, texture, output]);
module.AddExecutionMode(main, SpirvExecutionMode.OriginUpperLeft);
return module.Build();
}
}
-885
View File
@@ -1,885 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Text;
namespace SharpEmu.Libs.Agc;
internal enum SpirvOp : ushort
{
Nop = 0,
Name = 5,
Extension = 10,
ExtInstImport = 11,
ExtInst = 12,
MemoryModel = 14,
EntryPoint = 15,
ExecutionMode = 16,
Capability = 17,
TypeVoid = 19,
TypeBool = 20,
TypeInt = 21,
TypeFloat = 22,
TypeVector = 23,
TypeImage = 25,
TypeSampler = 26,
TypeSampledImage = 27,
TypeArray = 28,
TypeRuntimeArray = 29,
TypeStruct = 30,
TypePointer = 32,
TypeFunction = 33,
ConstantTrue = 41,
ConstantFalse = 42,
Constant = 43,
ConstantComposite = 44,
ConstantNull = 46,
Function = 54,
FunctionParameter = 55,
FunctionEnd = 56,
FunctionCall = 57,
Variable = 59,
Load = 61,
Store = 62,
AccessChain = 65,
ArrayLength = 68,
Decorate = 71,
VectorExtractDynamic = 77,
VectorInsertDynamic = 78,
VectorShuffle = 79,
CompositeConstruct = 80,
CompositeExtract = 81,
CompositeInsert = 82,
CopyObject = 83,
SampledImage = 86,
ImageSampleImplicitLod = 87,
ImageSampleExplicitLod = 88,
ImageSampleDrefImplicitLod = 89,
ImageSampleDrefExplicitLod = 90,
ImageFetch = 95,
ImageGather = 96,
ImageDrefGather = 97,
ImageRead = 98,
ImageWrite = 99,
Image = 100,
ImageQuerySizeLod = 103,
ImageQuerySize = 104,
ImageQueryLod = 105,
ImageQueryLevels = 106,
ImageQuerySamples = 107,
ConvertFToU = 109,
ConvertFToS = 110,
ConvertSToF = 111,
ConvertUToF = 112,
UConvert = 113,
SConvert = 114,
FConvert = 115,
Bitcast = 124,
SNegate = 126,
FNegate = 127,
IAdd = 128,
FAdd = 129,
ISub = 130,
FSub = 131,
IMul = 132,
FMul = 133,
UDiv = 134,
SDiv = 135,
FDiv = 136,
UMod = 137,
SRem = 138,
SMod = 139,
FRem = 140,
FMod = 141,
IAddCarry = 149,
ISubBorrow = 150,
UMulExtended = 151,
SMulExtended = 152,
Any = 154,
All = 155,
IsNan = 156,
IsInf = 157,
LogicalEqual = 164,
LogicalNotEqual = 165,
LogicalOr = 166,
LogicalAnd = 167,
LogicalNot = 168,
Select = 169,
IEqual = 170,
INotEqual = 171,
UGreaterThan = 172,
SGreaterThan = 173,
UGreaterThanEqual = 174,
SGreaterThanEqual = 175,
ULessThan = 176,
SLessThan = 177,
ULessThanEqual = 178,
SLessThanEqual = 179,
FOrdEqual = 180,
FUnordEqual = 181,
FOrdNotEqual = 182,
FUnordNotEqual = 183,
FOrdLessThan = 184,
FUnordLessThan = 185,
FOrdGreaterThan = 186,
FUnordGreaterThan = 187,
FOrdLessThanEqual = 188,
FUnordLessThanEqual = 189,
FOrdGreaterThanEqual = 190,
FUnordGreaterThanEqual = 191,
ShiftRightLogical = 194,
ShiftRightArithmetic = 195,
ShiftLeftLogical = 196,
BitwiseOr = 197,
BitwiseXor = 198,
BitwiseAnd = 199,
Not = 200,
BitFieldInsert = 201,
BitFieldSExtract = 202,
BitFieldUExtract = 203,
BitReverse = 204,
BitCount = 205,
ControlBarrier = 224,
MemoryBarrier = 225,
AtomicIAdd = 234,
Phi = 245,
LoopMerge = 246,
SelectionMerge = 247,
Label = 248,
Branch = 249,
BranchConditional = 250,
Switch = 251,
Kill = 252,
Return = 253,
ReturnValue = 254,
Unreachable = 255,
GroupNonUniformElect = 333,
GroupNonUniformAll = 334,
GroupNonUniformAny = 335,
GroupNonUniformAllEqual = 336,
GroupNonUniformBroadcast = 337,
GroupNonUniformBroadcastFirst = 338,
GroupNonUniformBallot = 339,
GroupNonUniformShuffle = 345,
GroupNonUniformShuffleXor = 346,
GroupNonUniformShuffleUp = 347,
GroupNonUniformShuffleDown = 348,
}
internal enum SpirvCapability : uint
{
Shader = 1,
Float16 = 9,
Float64 = 10,
Int64 = 11,
Int16 = 22,
ImageGatherExtended = 25,
StorageImageExtendedFormats = 49,
ImageQuery = 50,
StorageImageReadWithoutFormat = 55,
StorageImageWriteWithoutFormat = 56,
GroupNonUniform = 61,
GroupNonUniformVote = 62,
GroupNonUniformBallot = 64,
GroupNonUniformShuffle = 65,
RuntimeDescriptorArray = 5302,
}
internal enum SpirvStorageClass : uint
{
UniformConstant = 0,
Input = 1,
Uniform = 2,
Output = 3,
Workgroup = 4,
Private = 6,
Function = 7,
PushConstant = 9,
Image = 11,
StorageBuffer = 12,
}
internal enum SpirvExecutionModel : uint
{
Vertex = 0,
Fragment = 4,
GLCompute = 5,
}
internal enum SpirvExecutionMode : uint
{
OriginUpperLeft = 7,
DepthReplacing = 12,
LocalSize = 17,
}
internal enum SpirvDecoration : uint
{
Block = 2,
ArrayStride = 6,
BuiltIn = 11,
NoPerspective = 13,
Flat = 14,
Location = 30,
Binding = 33,
DescriptorSet = 34,
Offset = 35,
}
internal enum SpirvBuiltIn : uint
{
Position = 0,
VertexIndex = 42,
InstanceIndex = 43,
FragCoord = 15,
FrontFacing = 17,
WorkgroupId = 26,
LocalInvocationId = 27,
GlobalInvocationId = 28,
LocalInvocationIndex = 29,
SubgroupLocalInvocationId = 41,
}
internal enum SpirvImageDim : uint
{
Dim1D = 0,
Dim2D = 1,
Dim3D = 2,
Cube = 3,
Buffer = 5,
}
internal enum SpirvImageFormat : uint
{
Unknown = 0,
Rgba32f = 1,
Rgba16f = 2,
R32f = 3,
Rgba8 = 4,
Rgba8Snorm = 5,
Rg32f = 6,
Rg16f = 7,
R11fG11fB10f = 8,
R16f = 9,
Rgba16 = 10,
Rgb10A2 = 11,
Rg16 = 12,
Rg8 = 13,
R16 = 14,
R8 = 15,
Rgba16Snorm = 16,
Rg16Snorm = 17,
Rg8Snorm = 18,
R16Snorm = 19,
R8Snorm = 20,
Rgba32i = 21,
Rgba16i = 22,
Rgba8i = 23,
R32i = 24,
Rg32i = 25,
Rg16i = 26,
Rg8i = 27,
R16i = 28,
R8i = 29,
Rgba32ui = 30,
Rgba16ui = 31,
Rgba8ui = 32,
R32ui = 33,
Rgb10A2ui = 34,
Rg32ui = 35,
Rg16ui = 36,
Rg8ui = 37,
R16ui = 38,
R8ui = 39,
}
internal sealed class SpirvModuleBuilder
{
private const uint Magic = 0x07230203;
private const uint Version15 = 0x00010500;
private const uint Generator = 0x53504500; // "SPE"
private readonly List<uint> _capabilities = [];
private readonly List<uint> _extensions = [];
private readonly List<uint> _imports = [];
private readonly List<uint> _memoryModel = [];
private readonly List<uint> _entryPoints = [];
private readonly List<uint> _executionModes = [];
private readonly List<uint> _debug = [];
private readonly List<uint> _annotations = [];
private readonly List<uint> _typesConstantsGlobals = [];
private readonly List<uint> _functions = [];
private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = [];
private readonly Dictionary<uint, uint> _floatTypes = [];
private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = [];
private readonly Dictionary<
(
uint SampledType,
SpirvImageDim Dimension,
bool Depth,
bool Arrayed,
bool Multisampled,
uint Sampled,
SpirvImageFormat Format
),
uint> _imageTypes = [];
private readonly Dictionary<uint, uint> _sampledImageTypes = [];
private readonly Dictionary<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = [];
private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = [];
private readonly Dictionary<uint, uint> _runtimeArrayTypes = [];
private readonly Dictionary<string, uint> _functionTypes = [];
private readonly Dictionary<(uint Type, ulong Value), uint> _constants = [];
private readonly HashSet<SpirvCapability> _declaredCapabilities = [];
private readonly Dictionary<string, uint> _extInstImports = [];
private uint _nextId = 1;
private uint? _voidType;
private uint? _boolType;
public uint AllocateId() => _nextId++;
public void AddCapability(SpirvCapability capability)
{
if (_declaredCapabilities.Add(capability))
{
Emit(_capabilities, SpirvOp.Capability, (uint)capability);
}
}
public void AddExtension(string extension) =>
EmitWithString(_extensions, SpirvOp.Extension, [], extension);
public uint ImportExtInst(string name)
{
if (_extInstImports.TryGetValue(name, out var existing))
{
return existing;
}
var id = AllocateId();
EmitWithString(_imports, SpirvOp.ExtInstImport, [id], name);
_extInstImports.Add(name, id);
return id;
}
public void SetLogicalGlsl450MemoryModel() =>
Emit(_memoryModel, SpirvOp.MemoryModel, 0, 1);
public void AddEntryPoint(
SpirvExecutionModel model,
uint function,
string name,
IReadOnlyList<uint> interfaces)
{
var prefix = new uint[2 + interfaces.Count];
prefix[0] = (uint)model;
prefix[1] = function;
for (var index = 0; index < interfaces.Count; index++)
{
prefix[index + 2] = interfaces[index];
}
EmitWithString(_entryPoints, SpirvOp.EntryPoint, prefix, name, stringBeforeTailCount: 2);
}
public void AddExecutionMode(uint function, SpirvExecutionMode mode, params uint[] operands)
{
var values = new uint[2 + operands.Length];
values[0] = function;
values[1] = (uint)mode;
operands.CopyTo(values, 2);
Emit(_executionModes, SpirvOp.ExecutionMode, values);
}
public void AddName(uint target, string name) =>
EmitWithString(_debug, SpirvOp.Name, [target], name);
public void AddDecoration(uint target, SpirvDecoration decoration, params uint[] operands)
{
var values = new uint[2 + operands.Length];
values[0] = target;
values[1] = (uint)decoration;
operands.CopyTo(values, 2);
Emit(_annotations, SpirvOp.Decorate, values);
}
public void AddMemberDecoration(
uint target,
uint member,
SpirvDecoration decoration,
params uint[] operands)
{
var values = new uint[3 + operands.Length];
values[0] = target;
values[1] = member;
values[2] = (uint)decoration;
operands.CopyTo(values, 3);
EmitRaw(_annotations, 72, values);
}
public uint TypeVoid()
{
if (_voidType is { } existing)
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeVoid, id);
_voidType = id;
return id;
}
public uint TypeBool()
{
if (_boolType is { } existing)
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeBool, id);
_boolType = id;
return id;
}
public uint TypeInt(uint width, bool signed)
{
var key = (width, signed);
if (_integerTypes.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeInt, id, width, signed ? 1u : 0u);
_integerTypes.Add(key, id);
return id;
}
public uint TypeFloat(uint width)
{
if (_floatTypes.TryGetValue(width, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeFloat, id, width);
_floatTypes.Add(width, id);
return id;
}
public uint TypeVector(uint componentType, uint count)
{
var key = (componentType, count);
if (_vectorTypes.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeVector, id, componentType, count);
_vectorTypes.Add(key, id);
return id;
}
public uint TypeImage(
uint sampledType,
SpirvImageDim dimension,
bool depth,
bool arrayed,
bool multisampled,
uint sampled,
SpirvImageFormat format)
{
var key = (
sampledType,
dimension,
depth,
arrayed,
multisampled,
sampled,
format);
if (_imageTypes.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(
_typesConstantsGlobals,
SpirvOp.TypeImage,
id,
sampledType,
(uint)dimension,
depth ? 1u : 0u,
arrayed ? 1u : 0u,
multisampled ? 1u : 0u,
sampled,
(uint)format);
_imageTypes.Add(key, id);
return id;
}
public uint TypeSampledImage(uint imageType)
{
if (_sampledImageTypes.TryGetValue(imageType, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType);
_sampledImageTypes.Add(imageType, id);
return id;
}
public uint TypeArray(uint elementType, uint count)
{
var key = (elementType, count);
if (_arrayTypes.TryGetValue(key, out var existing))
{
return existing;
}
var length = Constant(TypeInt(32, false), count);
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeArray, id, elementType, length);
_arrayTypes.Add(key, id);
return id;
}
public uint TypeRuntimeArray(uint elementType)
{
if (_runtimeArrayTypes.TryGetValue(elementType, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypeRuntimeArray, id, elementType);
_runtimeArrayTypes.Add(elementType, id);
return id;
}
public uint TypeStruct(params uint[] memberTypes)
{
var id = AllocateId();
var operands = new uint[memberTypes.Length + 1];
operands[0] = id;
memberTypes.CopyTo(operands, 1);
Emit(_typesConstantsGlobals, SpirvOp.TypeStruct, operands);
return id;
}
public uint TypePointer(SpirvStorageClass storageClass, uint type)
{
var key = (storageClass, type);
if (_pointerTypes.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.TypePointer, id, (uint)storageClass, type);
_pointerTypes.Add(key, id);
return id;
}
public uint TypeFunction(uint returnType, params uint[] parameterTypes)
{
var key = returnType + ":" + string.Join(',', parameterTypes);
if (_functionTypes.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
var operands = new uint[parameterTypes.Length + 2];
operands[0] = id;
operands[1] = returnType;
parameterTypes.CopyTo(operands, 2);
Emit(_typesConstantsGlobals, SpirvOp.TypeFunction, operands);
_functionTypes.Add(key, id);
return id;
}
public uint ConstantBool(bool value)
{
var type = TypeBool();
var key = (type, value ? 1UL : 0UL);
if (_constants.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(
_typesConstantsGlobals,
value ? SpirvOp.ConstantTrue : SpirvOp.ConstantFalse,
type,
id);
_constants.Add(key, id);
return id;
}
public uint Constant(uint type, uint value)
{
var key = (type, (ulong)value);
if (_constants.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.Constant, type, id, value);
_constants.Add(key, id);
return id;
}
public uint Constant64(uint type, ulong value)
{
var key = (type, value);
if (_constants.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(
_typesConstantsGlobals,
SpirvOp.Constant,
type,
id,
(uint)value,
(uint)(value >> 32));
_constants.Add(key, id);
return id;
}
public uint ConstantFloat(uint type, float value) =>
Constant(type, BitConverter.SingleToUInt32Bits(value));
public uint ConstantComposite(uint type, params uint[] constituents)
{
var id = AllocateId();
var operands = new uint[constituents.Length + 2];
operands[0] = type;
operands[1] = id;
constituents.CopyTo(operands, 2);
Emit(_typesConstantsGlobals, SpirvOp.ConstantComposite, operands);
return id;
}
public uint ConstantNull(uint type)
{
var key = (type, ulong.MaxValue);
if (_constants.TryGetValue(key, out var existing))
{
return existing;
}
var id = AllocateId();
Emit(_typesConstantsGlobals, SpirvOp.ConstantNull, type, id);
_constants.Add(key, id);
return id;
}
public uint AddGlobalVariable(
uint pointerType,
SpirvStorageClass storageClass,
uint? initializer = null)
{
var id = AllocateId();
if (initializer.HasValue)
{
Emit(
_typesConstantsGlobals,
SpirvOp.Variable,
pointerType,
id,
(uint)storageClass,
initializer.Value);
}
else
{
Emit(_typesConstantsGlobals, SpirvOp.Variable, pointerType, id, (uint)storageClass);
}
return id;
}
public uint BeginFunction(uint returnType, uint functionType)
{
var id = AllocateId();
Emit(_functions, SpirvOp.Function, returnType, id, 0, functionType);
return id;
}
public uint AddFunctionParameter(uint type) =>
EmitResult(_functions, SpirvOp.FunctionParameter, type);
public uint AddLabel(uint? id = null)
{
var result = id ?? AllocateId();
Emit(_functions, SpirvOp.Label, result);
return result;
}
public uint AddFunctionVariable(uint pointerType, uint? initializer = null)
{
var id = AllocateId();
if (initializer.HasValue)
{
Emit(
_functions,
SpirvOp.Variable,
pointerType,
id,
(uint)SpirvStorageClass.Function,
initializer.Value);
}
else
{
Emit(
_functions,
SpirvOp.Variable,
pointerType,
id,
(uint)SpirvStorageClass.Function);
}
return id;
}
public uint AddInstruction(SpirvOp opcode, uint resultType, params uint[] operands) =>
EmitResult(_functions, opcode, resultType, operands);
public void AddStatement(SpirvOp opcode, params uint[] operands) =>
Emit(_functions, opcode, operands);
public void EndFunction() => Emit(_functions, SpirvOp.FunctionEnd);
public byte[] Build()
{
if (_memoryModel.Count == 0)
{
SetLogicalGlsl450MemoryModel();
}
var wordCount =
5 +
_capabilities.Count +
_extensions.Count +
_imports.Count +
_memoryModel.Count +
_entryPoints.Count +
_executionModes.Count +
_debug.Count +
_annotations.Count +
_typesConstantsGlobals.Count +
_functions.Count;
var words = new uint[wordCount];
var offset = 0;
WriteWord(Magic);
WriteWord(Version15);
WriteWord(Generator);
WriteWord(_nextId);
WriteWord(0);
WriteSection(_capabilities);
WriteSection(_extensions);
WriteSection(_imports);
WriteSection(_memoryModel);
WriteSection(_entryPoints);
WriteSection(_executionModes);
WriteSection(_debug);
WriteSection(_annotations);
WriteSection(_typesConstantsGlobals);
WriteSection(_functions);
var bytes = new byte[wordCount * sizeof(uint)];
Buffer.BlockCopy(words, 0, bytes, 0, bytes.Length);
return bytes;
void WriteWord(uint value)
{
words[offset++] = value;
}
void WriteSection(List<uint> section)
{
foreach (var value in section)
{
WriteWord(value);
}
}
}
private uint EmitResult(
List<uint> section,
SpirvOp opcode,
uint resultType,
params uint[] operands)
{
var result = AllocateId();
var values = new uint[operands.Length + 2];
values[0] = resultType;
values[1] = result;
operands.CopyTo(values, 2);
Emit(section, opcode, values);
return result;
}
private static void Emit(List<uint> section, SpirvOp opcode, params uint[] operands) =>
EmitRaw(section, (ushort)opcode, operands);
private static void EmitRaw(List<uint> section, ushort opcode, params uint[] operands)
{
section.Add(((uint)(operands.Length + 1) << 16) | opcode);
section.AddRange(operands);
}
private static void EmitWithString(
List<uint> section,
SpirvOp opcode,
IReadOnlyList<uint> prefix,
string value,
int stringBeforeTailCount = -1)
{
var encoded = EncodeString(value);
if (stringBeforeTailCount < 0)
{
var operands = new uint[prefix.Count + encoded.Length];
for (var index = 0; index < prefix.Count; index++)
{
operands[index] = prefix[index];
}
encoded.CopyTo(operands, prefix.Count);
Emit(section, opcode, operands);
return;
}
var result = new uint[prefix.Count + encoded.Length];
for (var index = 0; index < stringBeforeTailCount; index++)
{
result[index] = prefix[index];
}
encoded.CopyTo(result, stringBeforeTailCount);
for (var index = stringBeforeTailCount; index < prefix.Count; index++)
{
result[index + encoded.Length] = prefix[index];
}
Emit(section, opcode, result);
}
private static uint[] EncodeString(string value)
{
var byteCount = Encoding.UTF8.GetByteCount(value) + 1;
var words = new uint[(byteCount + 3) / 4];
Encoding.UTF8.GetBytes(value, System.Runtime.InteropServices.MemoryMarshal.AsBytes(words.AsSpan()));
return words;
}
}
+12 -87
View File
@@ -24,7 +24,6 @@ public static class AmprExports
private const uint KernelEventQueueRecordType = 2;
private const uint WriteAddressRecordType = 3;
private static readonly ConcurrentDictionary<ulong, CommandBufferState> _commandBuffers = new();
private static readonly ConcurrentDictionary<string, Lazy<CachedHostFile>> _hostFileCache = new(StringComparer.OrdinalIgnoreCase);
private static readonly bool _traceAmpr =
string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AMPR"), "1", StringComparison.Ordinal);
private static readonly bool _traceAmprReads =
@@ -38,23 +37,6 @@ public static class AmprExports
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(
Nid = "8aI7R7WaOlc",
ExportName = "sceAmprCommandBufferConstructor",
@@ -267,7 +249,7 @@ public static class AmprExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath))
if (!AmprFileRegistry.TryGetHostPath(fileId, out var hostPath) || !File.Exists(hostPath))
{
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;
@@ -652,43 +634,24 @@ public static class AmprExports
try
{
if (!TryGetCachedHostFile(hostPath, out var cachedFile, out var openResult))
{
return openResult;
}
long fileLength;
lock (cachedFile.Gate)
{
fileLength = cachedFile.Stream.Length;
}
if (fileOffset >= (ulong)fileLength)
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;
}
stream.Position = unchecked((long)fileOffset);
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);
int read;
lock (cachedFile.Gate)
{
cachedFile.Stream.Position = unchecked((long)absoluteOffset);
read = cachedFile.Stream.Read(buffer, 0, request);
}
var read = stream.Read(buffer, 0, request);
if (read <= 0)
{
break;
@@ -718,44 +681,6 @@ public static class AmprExports
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(
CpuContext ctx,
ulong commandBuffer,
@@ -1,92 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Collections.Concurrent;
using System.Threading;
namespace SharpEmu.Libs.Audio;
public static class AudioOutExports
{
private static readonly ConcurrentDictionary<int, PortState> Ports = new();
private static int _nextPortHandle;
private sealed record PortState(int UserId, int Type, uint BufferLength, uint Frequency, int Format);
[SysAbiExport(
Nid = "JfEPXVxhFqA",
ExportName = "sceAudioOutInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutInit(CpuContext ctx) => SetReturn(ctx, 0);
[SysAbiExport(
Nid = "ekNvsT22rsY",
ExportName = "sceAudioOutOpen",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutOpen(CpuContext ctx)
{
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
var type = unchecked((int)ctx[CpuRegister.Rsi]);
var bufferLength = unchecked((uint)ctx[CpuRegister.Rcx]);
var frequency = unchecked((uint)ctx[CpuRegister.R8]);
var format = unchecked((int)ctx[CpuRegister.R9]);
if (bufferLength == 0 || frequency == 0)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
var handle = Interlocked.Increment(ref _nextPortHandle);
Ports[handle] = new PortState(userId, type, bufferLength, frequency, format);
return SetReturn(ctx, handle);
}
[SysAbiExport(
Nid = "s1--uE9mBFw",
ExportName = "sceAudioOutClose",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutClose(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
return SetReturn(
ctx,
Ports.TryRemove(handle, out _)
? 0
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
[SysAbiExport(
Nid = "QOQtbeDqsT4",
ExportName = "sceAudioOutOutput",
Target = Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutOutput(CpuContext ctx)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "b+uAV89IlxE",
ExportName = "sceAudioOutSetVolume",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAudioOut")]
public static int AudioOutSetVolume(CpuContext ctx)
{
var handle = unchecked((int)ctx[CpuRegister.Rdi]);
return SetReturn(
ctx,
Ports.ContainsKey(handle)
? 0
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
@@ -1,77 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
namespace SharpEmu.Libs.AvPlayer;
public static class AvPlayerExports
{
private const int InvalidParameters = unchecked((int)0x806A0001);
private static readonly object StateGate = new();
private static readonly HashSet<ulong> Players = new();
[SysAbiExport(
Nid = "aS66RI0gGgo",
ExportName = "sceAvPlayerInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAvPlayer")]
public static int AvPlayerInit(CpuContext ctx)
{
if (ctx[CpuRegister.Rdi] == 0 ||
!KernelMemoryCompatExports.TryAllocateHleData(ctx, 0x40, 16, out var handle))
{
ctx[CpuRegister.Rax] = 0;
return 0;
}
lock (StateGate)
{
Players.Add(handle);
}
ctx[CpuRegister.Rax] = handle;
return unchecked((int)handle);
}
[SysAbiExport(
Nid = "HD1YKVU26-M",
ExportName = "sceAvPlayerPostInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAvPlayer")]
public static int AvPlayerPostInit(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
var dataAddress = ctx[CpuRegister.Rsi];
lock (StateGate)
{
return SetReturn(
ctx,
handle != 0 && dataAddress != 0 && Players.Contains(handle)
? 0
: InvalidParameters);
}
}
[SysAbiExport(
Nid = "NkJwDzKmIlw",
ExportName = "sceAvPlayerClose",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceAvPlayer")]
public static int AvPlayerClose(CpuContext ctx)
{
lock (StateGate)
{
return SetReturn(
ctx,
Players.Remove(ctx[CpuRegister.Rdi]) ? 0 : InvalidParameters);
}
}
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
@@ -1,27 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Threading;
namespace SharpEmu.Libs.CommonDialog;
public static class MsgDialogExports
{
private const int AlreadyInitialized = unchecked((int)0x80B80002);
private static int _initialized;
[SysAbiExport(
Nid = "lDqxaY1UbEo",
ExportName = "sceMsgDialogInitialize",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceMsgDialog")]
public static int MsgDialogInitialize(CpuContext ctx)
{
var result = Interlocked.Exchange(ref _initialized, 1) == 0
? 0
: AlreadyInitialized;
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
@@ -276,22 +276,6 @@ public static class KernelEventQueueCompatExports
var outCountAddress = ctx[CpuRegister.Rcx];
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);
if (outCountAddress != 0 && !TryWriteUInt32(ctx, outCountAddress, (uint)deliveredCount))
{
@@ -316,41 +300,6 @@ public static class KernelEventQueueCompatExports
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);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -381,7 +330,6 @@ public static class KernelEventQueueCompatExports
queue.AddLast(queuedEvent);
queued = true;
Monitor.PulseAll(_eventQueueGate);
}
if (queued)
@@ -546,9 +494,7 @@ public static class KernelEventQueueCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return deliveredCount > 0
? (int)OrbisGen2Result.ORBIS_GEN2_OK
: (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool HasPendingEvents(ulong handle)
@@ -666,17 +612,4 @@ public static class KernelEventQueueCompatExports
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
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;
}
}
@@ -1,65 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
namespace SharpEmu.Libs.Kernel;
public static class KernelExceptionCompatExports
{
private static readonly HashSet<int> AllowedSignals = new() { 1, 4, 8, 10, 11, 30 };
private static readonly Dictionary<int, ulong> _installedHandlers = new();
private static readonly object _gate = new();
[SysAbiExport(
Nid = "WkwEd3N7w0Y",
ExportName = "sceKernelInstallExceptionHandler",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int InstallExceptionHandler(CpuContext ctx)
{
var signum = unchecked((int)ctx[CpuRegister.Rdi]);
var handler = ctx[CpuRegister.Rsi];
if (!AllowedSignals.Contains(signum))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_gate)
{
if (_installedHandlers.ContainsKey(signum))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_ALREADY_EXISTS;
}
_installedHandlers[signum] = handler;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "Qhv5ARAoOEc",
ExportName = "sceKernelRemoveExceptionHandler",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int RemoveExceptionHandler(CpuContext ctx)
{
var signum = unchecked((int)ctx[CpuRegister.Rdi]);
if (!AllowedSignals.Contains(signum))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_gate)
{
_installedHandlers.Remove(signum);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
@@ -365,13 +365,6 @@ public static class KernelExports
LibraryName = "libKernel")]
public static int KernelOpen(CpuContext ctx) => KernelMemoryCompatExports.KernelOpenUnderscore(ctx);
[SysAbiExport(
Nid = "mqQMh1zPPT8",
ExportName = "fstat",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int Fstat(CpuContext ctx) => KernelMemoryCompatExports.KernelFstat(ctx);
[SysAbiExport(
Nid = "hcuQgD53UxM",
ExportName = "printf",
@@ -5,9 +5,9 @@ using SharpEmu.HLE;
using SharpEmu.Libs.Ampr;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Linq;
using System.Globalization;
@@ -103,12 +103,10 @@ public static class KernelMemoryCompatExports
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, string> _mappedRegionNames = 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 readonly ConcurrentDictionary<string, ulong> _aprFileSizeCache = new(StringComparer.OrdinalIgnoreCase);
private static long _nextFileDescriptor = 2;
private static ulong _nextPhysicalAddress;
private static ulong _nextVirtualAddress;
@@ -233,25 +231,6 @@ public static class KernelMemoryCompatExports
return true;
}
internal static void RegisterReservedVirtualRange(ulong address, ulong length)
{
if (address == 0 || length == 0)
{
return;
}
lock (_memoryGate)
{
_mappedRegions[address] = new MappedRegion(
address,
length,
Protection: 0,
IsFlexible: false,
IsDirect: false,
DirectStart: 0);
}
}
[SysAbiExport(
Nid = "8zTFvBIAIN8",
ExportName = "memset",
@@ -1343,7 +1322,6 @@ public static class KernelMemoryCompatExports
if (IsMutatingOpen(flags))
{
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
InvalidateAprFileSizeCache(hostPath);
}
LogOpenTrace($"_open file path='{guestPath}' host='{hostPath}' flags=0x{flags:X8} fd={fd}");
@@ -1552,7 +1530,6 @@ public static class KernelMemoryCompatExports
File.Delete(hostPath);
InvalidateNegativeStatCacheForPathAndAncestors(guestPath);
InvalidateAprFileSizeCache(hostPath);
AddNegativeStatCacheForGuestPath(guestPath);
LogOpenTrace($"unlink path='{guestPath}' host='{hostPath}'");
ctx[CpuRegister.Rax] = 0;
@@ -2458,36 +2435,6 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "hwVSPCmp5tM",
ExportName = "sceKernelCheckedReleaseDirectMemory",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelCheckedReleaseDirectMemory(CpuContext ctx)
{
var start = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
if (length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!_directAllocations.TryGetValue(start, out var allocation) ||
allocation.Length != length)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_directAllocations.Remove(start);
_nextPhysicalAddress = GetDirectMemoryHighWaterMarkLocked();
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "L-Q3LEjIbgA",
ExportName = "sceKernelMapDirectMemory",
@@ -2599,11 +2546,8 @@ public static class KernelMemoryCompatExports
var length = ctx[CpuRegister.Rsi];
var protection = unchecked((int)ctx[CpuRegister.Rdx]);
var flags = ctx[CpuRegister.Rcx];
if (ShouldTraceDirectMemory())
{
Console.Error.WriteLine(
$"[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)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
@@ -2633,11 +2577,8 @@ public static class KernelMemoryCompatExports
: AllocateMappedGuestAddress(ctx, length, 0x1000UL);
}
if (ShouldTraceDirectMemory())
{
Console.Error.WriteLine(
$"[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)
{
@@ -2673,16 +2614,6 @@ public static class KernelMemoryCompatExports
return KernelMapNamedFlexibleMemory(ctx);
}
[SysAbiExport(
Nid = "4h6F1LLbTiw",
ExportName = "sceKernelMapFlexibleMemoryInternal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMapFlexibleMemoryInternal(CpuContext ctx)
{
return KernelMapNamedFlexibleMemory(ctx);
}
[SysAbiExport(
Nid = "2SKEx6bSq-4",
ExportName = "sceKernelBatchMap",
@@ -2736,44 +2667,6 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "DGMG3JshrZU",
ExportName = "sceKernelSetVirtualRangeName",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelSetVirtualRangeName(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var nameAddress = ctx[CpuRegister.Rdx];
if (nameAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (!TryReadCString(ctx, nameAddress, OrbisKernelMaximumNameLength, out var nameBytes))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var name = Encoding.UTF8.GetString(nameBytes);
lock (_memoryGate)
{
if (!TryFindVirtualQueryRegionLocked(address, findNext: false, out var region) ||
length > region.Length ||
address < region.Address ||
length > region.Address + region.Length - address)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
_mappedRegionNames[region.Address] = name;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "rVjRvHJ0X6c",
ExportName = "sceKernelVirtualQuery",
@@ -2831,16 +2724,11 @@ public static class KernelMemoryCompatExports
BinaryPrimitives.WriteInt32LittleEndian(payload[28..32], memoryType);
payload[32] = unchecked((byte)stateFlags);
string name;
lock (_memoryGate)
{
name = _mappedRegionNames.GetValueOrDefault(region.Address)
?? (region.IsDirect
? "direct"
: region.IsFlexible
? "flexible"
: string.Empty);
}
var name = region.IsDirect
? "direct"
: region.IsFlexible
? "flexible"
: string.Empty;
if (!string.IsNullOrEmpty(name))
{
var nameBytes = Encoding.ASCII.GetBytes(name);
@@ -3982,17 +3870,118 @@ public static class KernelMemoryCompatExports
ulong alignment,
out ulong mappedAddress)
{
var executable = (protection & OrbisProtCpuExec) != 0;
return KernelVirtualRangeAllocator.TryReserve(
ctx,
desiredAddress,
length,
executable,
alignment,
allowSearch: true,
allowAllocateAtAlternative: false,
"reserve range",
out mappedAddress);
mappedAddress = 0;
if (length == 0)
{
return false;
}
try
{
object memoryObject = ctx.Memory;
MethodInfo? allocateAt = null;
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;
}
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(
@@ -4145,20 +4134,6 @@ public static class KernelMemoryCompatExports
var app0Root = ResolveApp0Root();
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) ||
string.Equals(guestPath, "app0", StringComparison.OrdinalIgnoreCase))
{
@@ -5413,37 +5388,6 @@ public static class KernelMemoryCompatExports
}
}
internal static bool TryReadTrackedLibcHeap(
ulong address,
Span<byte> destination)
{
if (destination.IsEmpty)
{
return true;
}
var length = (ulong)destination.Length;
lock (_libcAllocGate)
{
foreach (var (allocationAddress, allocation) in _libcAllocations)
{
var allocationSize = (ulong)allocation.Size;
var offset = address >= allocationAddress
? address - allocationAddress
: ulong.MaxValue;
if (offset > allocationSize ||
length > allocationSize - offset)
{
continue;
}
return TryReadHostMemory(address, destination);
}
}
return false;
}
private static bool TryAllocateLibcHeap(ulong requestedSize, nuint alignment, bool zeroFill, out ulong address)
{
address = 0;
@@ -5821,40 +5765,22 @@ public static class KernelMemoryCompatExports
private static bool TryGetAprFileSize(string hostPath, out ulong size)
{
size = 0;
string cachePath;
try
{
cachePath = Path.GetFullPath(hostPath);
}
catch
{
cachePath = hostPath;
}
if (_aprFileSizeCache.TryGetValue(cachePath, out size))
{
return true;
}
try
{
var fileInfo = new FileInfo(cachePath);
var fileInfo = new FileInfo(hostPath);
if (fileInfo.Exists)
{
var length = fileInfo.Length;
size = length < 0 ? 0UL : unchecked((ulong)length);
_aprFileSizeCache.TryAdd(cachePath, size);
return true;
}
if (!new DirectoryInfo(cachePath).Exists)
if (!new DirectoryInfo(hostPath).Exists)
{
return false;
}
size = 65536;
_aprFileSizeCache.TryAdd(cachePath, size);
return true;
}
catch
@@ -6153,20 +6079,6 @@ 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)
{
if (count <= 0)
@@ -95,13 +95,6 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "7Xl257M4VNI",
ExportName = "pthread_equal",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int PosixPthreadEqual(CpuContext ctx) => PthreadEqual(ctx);
[SysAbiExport(
Nid = "T72hz6ffq08",
ExportName = "scePthreadYield",
@@ -717,35 +717,6 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "FXPWHNk8Of0",
ExportName = "scePthreadAttrGetschedparam",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadAttrGetschedparam(CpuContext ctx)
{
var attrAddress = ctx[CpuRegister.Rdi];
var schedParamAddress = ctx[CpuRegister.Rsi];
if (attrAddress == 0 || schedParamAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
PthreadAttrState state;
lock (_stateGate)
{
state = GetOrCreateAttrStateLocked(attrAddress);
}
if (!TryWriteInt32(ctx, schedParamAddress, state.SchedPriority))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "DzES9hQF4f4",
ExportName = "scePthreadAttrSetschedparam",
@@ -7,6 +7,7 @@ using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Security.Cryptography;
@@ -86,11 +87,11 @@ public static class KernelRuntimeCompatExports
if (micros < 1000)
{
// Guest worker pools use usleep(1) as a polling backoff. Do not turn
// PS microsecond waits into Windows millisecond sleeps on hot paths.
if ((++_shortUsleepCount & 255) == 0)
// Guest worker pools use usleep(1) as a polling backoff. Periodically
// relinquish a full host time slice so spin workers cannot starve producers.
if ((++_shortUsleepCount & 31) == 0)
{
Thread.Sleep(0);
Thread.Sleep(1);
}
else
{
@@ -738,11 +739,8 @@ public static class KernelRuntimeCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (ShouldTraceVirtualMemory())
{
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}");
}
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))
{
@@ -754,7 +752,6 @@ public static class KernelRuntimeCompatExports
_nextReservedVirtualBase = Math.Max(_nextReservedVirtualBase, mappedAddress + length);
}
KernelMemoryCompatExports.RegisterReservedVirtualRange(mappedAddress, length);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
@@ -1735,16 +1732,117 @@ public static class KernelRuntimeCompatExports
bool allowSearch,
out ulong mappedAddress)
{
return KernelVirtualRangeAllocator.TryReserve(
ctx,
desiredAddress,
length,
executable: false,
alignment,
allowSearch,
allowAllocateAtAlternative: allowSearch,
"reserve_virtual_range",
out mappedAddress);
mappedAddress = 0;
if (length == 0)
{
return false;
}
try
{
object memoryObject = ctx.Memory;
MethodInfo? allocateAt = null;
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)
@@ -1757,9 +1855,4 @@ public static class KernelRuntimeCompatExports
var mask = alignment - 1;
return (value + mask) & ~mask;
}
private static bool ShouldTraceVirtualMemory()
{
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIRTUAL_MEMORY"), "1", StringComparison.Ordinal);
}
}
@@ -1,159 +0,0 @@
// 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);
}
-322
View File
@@ -1,322 +0,0 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Threading;
namespace SharpEmu.Libs.Ngs2;
public static class Ngs2Exports
{
private const int OrbisNgs2ErrorInvalidOutAddress = unchecked((int)0x804A0053);
private const int OrbisNgs2ErrorInvalidSystemHandle = unchecked((int)0x804A0230);
private const int OrbisNgs2ErrorInvalidRackHandle = unchecked((int)0x804A0261);
private const int OrbisNgs2ErrorInvalidVoiceHandle = unchecked((int)0x804A0300);
private const ulong HandleStorageSize = 0x20;
private const int RenderBufferInfoSize = 0x18;
private const ulong MaximumRenderBufferSize = 16 * 1024 * 1024;
private static readonly object StateGate = new();
private static readonly Dictionary<ulong, SystemState> Systems = new();
private static readonly Dictionary<ulong, RackState> Racks = new();
private static readonly Dictionary<ulong, VoiceState> Voices = new();
private static long _nextUid;
private static long _renderCount;
private sealed record SystemState(uint Uid);
private sealed record RackState(ulong SystemHandle, uint RackId);
private sealed record VoiceState(ulong RackHandle, uint VoiceIndex);
[SysAbiExport(
Nid = "mPYgU4oYpuY",
ExportName = "sceNgs2SystemCreateWithAllocator",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2SystemCreateWithAllocator(CpuContext ctx)
{
var outHandleAddress = ctx[CpuRegister.Rdx];
if (outHandleAddress == 0)
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidOutAddress);
}
if (!TryCreateHandle(ctx, type: 1, ownerHandle: 0, out var handle) ||
!ctx.TryWriteUInt64(outHandleAddress, handle))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (StateGate)
{
Systems[handle] = new SystemState(unchecked((uint)Interlocked.Increment(ref _nextUid)));
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "u-WrYDaJA3k",
ExportName = "sceNgs2SystemDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2SystemDestroy(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
lock (StateGate)
{
if (!Systems.Remove(handle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidSystemHandle);
}
var rackHandles = Racks
.Where(pair => pair.Value.SystemHandle == handle)
.Select(pair => pair.Key)
.ToArray();
foreach (var rackHandle in rackHandles)
{
RemoveRackLocked(rackHandle);
}
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "U546k6orxQo",
ExportName = "sceNgs2RackCreateWithAllocator",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2RackCreateWithAllocator(CpuContext ctx)
{
var systemHandle = ctx[CpuRegister.Rdi];
var rackId = unchecked((uint)ctx[CpuRegister.Rsi]);
var outHandleAddress = ctx[CpuRegister.R8];
lock (StateGate)
{
if (!Systems.ContainsKey(systemHandle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidSystemHandle);
}
}
if (outHandleAddress == 0)
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidOutAddress);
}
if (!TryCreateHandle(ctx, type: 2, systemHandle, out var handle) ||
!ctx.TryWriteUInt64(outHandleAddress, handle))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (StateGate)
{
Racks[handle] = new RackState(systemHandle, rackId);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "lCqD7oycmIM",
ExportName = "sceNgs2RackDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2RackDestroy(CpuContext ctx)
{
var handle = ctx[CpuRegister.Rdi];
lock (StateGate)
{
if (!Racks.ContainsKey(handle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidRackHandle);
}
RemoveRackLocked(handle);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "MwmHz8pAdAo",
ExportName = "sceNgs2RackGetVoiceHandle",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2RackGetVoiceHandle(CpuContext ctx)
{
var rackHandle = ctx[CpuRegister.Rdi];
var voiceIndex = unchecked((uint)ctx[CpuRegister.Rsi]);
var outHandleAddress = ctx[CpuRegister.Rdx];
lock (StateGate)
{
if (!Racks.ContainsKey(rackHandle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidRackHandle);
}
var existing = Voices.FirstOrDefault(
pair => pair.Value.RackHandle == rackHandle && pair.Value.VoiceIndex == voiceIndex);
if (existing.Key != 0)
{
return ctx.TryWriteUInt64(outHandleAddress, existing.Key)
? SetReturn(ctx, 0)
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
if (outHandleAddress == 0)
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidOutAddress);
}
if (!TryCreateHandle(ctx, type: 4, rackHandle, out var handle) ||
!ctx.TryWriteUInt64(outHandleAddress, handle))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
lock (StateGate)
{
Voices[handle] = new VoiceState(rackHandle, voiceIndex);
}
return SetReturn(ctx, 0);
}
[SysAbiExport(
Nid = "uu94irFOGpA",
ExportName = "sceNgs2VoiceControl",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2VoiceControl(CpuContext ctx)
{
lock (StateGate)
{
return SetReturn(
ctx,
Voices.ContainsKey(ctx[CpuRegister.Rdi]) ? 0 : OrbisNgs2ErrorInvalidVoiceHandle);
}
}
[SysAbiExport(
Nid = "AbYvTOZ8Pts",
ExportName = "sceNgs2VoiceRunCommands",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2VoiceRunCommands(CpuContext ctx) => Ngs2VoiceControl(ctx);
[SysAbiExport(
Nid = "i0VnXM-C9fc",
ExportName = "sceNgs2SystemRender",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceNgs2")]
public static int Ngs2SystemRender(CpuContext ctx)
{
var systemHandle = ctx[CpuRegister.Rdi];
var bufferInfoAddress = ctx[CpuRegister.Rsi];
var bufferInfoCount = unchecked((uint)ctx[CpuRegister.Rdx]);
lock (StateGate)
{
if (!Systems.ContainsKey(systemHandle))
{
return SetReturn(ctx, OrbisNgs2ErrorInvalidSystemHandle);
}
}
if (bufferInfoCount != 0 && bufferInfoAddress == 0)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
for (uint i = 0; i < bufferInfoCount; i++)
{
var entryAddress = bufferInfoAddress + (i * RenderBufferInfoSize);
if (!ctx.TryReadUInt64(entryAddress, out var bufferAddress) ||
!ctx.TryReadUInt64(entryAddress + 8, out var bufferSize))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (bufferAddress != 0 && bufferSize != 0)
{
if (bufferSize > MaximumRenderBufferSize || !TryClearGuestBuffer(ctx, bufferAddress, bufferSize))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
}
var count = Interlocked.Increment(ref _renderCount);
if (ShouldTrace() && (count <= 4 || count % 10_000 == 0))
{
Console.Error.WriteLine(
$"[LOADER][TRACE] ngs2.render#{count} system=0x{systemHandle:X16} buffers={bufferInfoCount}");
}
return SetReturn(ctx, 0);
}
private static bool TryCreateHandle(CpuContext ctx, uint type, ulong ownerHandle, out ulong handle)
{
handle = 0;
if (!KernelMemoryCompatExports.TryAllocateHleData(ctx, HandleStorageSize, 16, out handle))
{
return false;
}
Span<byte> data = stackalloc byte[(int)HandleStorageSize];
data.Clear();
BinaryPrimitives.WriteUInt64LittleEndian(data[0..8], handle);
BinaryPrimitives.WriteUInt64LittleEndian(data[8..16], ownerHandle);
BinaryPrimitives.WriteUInt32LittleEndian(data[16..20], 1);
BinaryPrimitives.WriteUInt32LittleEndian(data[24..28], type);
return ctx.Memory.TryWrite(handle, data);
}
private static bool TryClearGuestBuffer(CpuContext ctx, ulong address, ulong length)
{
Span<byte> zeroes = stackalloc byte[4096];
zeroes.Clear();
for (ulong offset = 0; offset < length;)
{
var chunkSize = (int)Math.Min((ulong)zeroes.Length, length - offset);
if (!ctx.Memory.TryWrite(address + offset, zeroes[..chunkSize]))
{
return false;
}
offset += unchecked((uint)chunkSize);
}
return true;
}
private static void RemoveRackLocked(ulong rackHandle)
{
Racks.Remove(rackHandle);
foreach (var voiceHandle in Voices
.Where(pair => pair.Value.RackHandle == rackHandle)
.Select(pair => pair.Key)
.ToArray())
{
Voices.Remove(voiceHandle);
}
}
private static bool ShouldTrace() =>
string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_NGS2"),
"1",
StringComparison.Ordinal);
private static int SetReturn(CpuContext ctx, int result)
{
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
}
+4 -79
View File
@@ -4,7 +4,6 @@
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace SharpEmu.Libs.Pad;
@@ -59,7 +58,6 @@ public static class PadExports
return SetReturn(ctx, OrbisPadErrorDeviceNotConnected);
}
Console.Error.WriteLine("[LOADER][INFO] Keyboard controls: Arrow keys = D-pad, WASD = left stick, IJKL = right stick, Z/Enter = Cross, X/Esc = Circle, C = Square, V = Triangle, Q = L1, E = R1, R = L2, F = R2, Tab/Backspace = Options");
return SetReturn(ctx, PrimaryPadHandle);
}
@@ -160,33 +158,14 @@ public static class PadExports
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
[SysAbiExport(
Nid = "W2G-yoyMF5U",
ExportName = "scePadSetVibrationMode",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libScePad")]
public static int PadSetVibrationMode(CpuContext ctx)
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_OK);
}
private static bool WriteNeutralPadData(CpuContext ctx, ulong dataAddress)
{
Span<byte> data = stackalloc byte[PadDataSize];
data.Clear();
var acceptsKeyboardInput = IsEmulatorWindowFocused();
var buttons = acceptsKeyboardInput ? ReadKeyboardButtons() : 0;
BinaryPrimitives.WriteUInt32LittleEndian(data[0x00..], buttons);
var leftX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x41), IsKeyDown(0x44)) : (byte)128;
var leftY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x57), IsKeyDown(0x53)) : (byte)128;
var rightX = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x4A), IsKeyDown(0x4C)) : (byte)128;
var rightY = acceptsKeyboardInput ? ReadAnalogStick(IsKeyDown(0x49), IsKeyDown(0x4B)) : (byte)128;
data[0x04] = leftX;
data[0x05] = leftY;
data[0x06] = rightX;
data[0x07] = rightY;
data[0x08] = acceptsKeyboardInput && IsKeyDown(0x52) ? (byte)255 : (byte)0;
data[0x09] = acceptsKeyboardInput && IsKeyDown(0x46) ? (byte)255 : (byte)0;
data[0x04] = 128;
data[0x05] = 128;
data[0x06] = 128;
data[0x07] = 128;
BinaryPrimitives.WriteSingleLittleEndian(data[0x18..], 1.0f);
data[0x4C] = 1;
var timestampTicks = Stopwatch.GetTimestamp();
@@ -206,58 +185,4 @@ public static class PadExports
ctx[CpuRegister.Rax] = unchecked((ulong)result);
return result;
}
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
[DllImport("user32.dll")]
private static extern nint GetForegroundWindow();
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint processId);
private static bool IsKeyDown(int vk) =>
(GetAsyncKeyState(vk) & 0x8000) != 0;
private static bool IsEmulatorWindowFocused()
{
var foregroundWindow = GetForegroundWindow();
if (foregroundWindow == 0)
{
return false;
}
GetWindowThreadProcessId(foregroundWindow, out var processId);
return processId == (uint)Environment.ProcessId;
}
private static uint ReadKeyboardButtons()
{
uint buttons = 0;
// D-pad
if (IsKeyDown(0x25)) buttons |= 0x0080; // Left
if (IsKeyDown(0x27)) buttons |= 0x0020; // Right
if (IsKeyDown(0x26)) buttons |= 0x0010; // Up
if (IsKeyDown(0x28)) buttons |= 0x0040; // Down
// Face buttons
if (IsKeyDown(0x5A) || IsKeyDown(0x0D)) buttons |= 0x4000; // Z / Enter = Cross
if (IsKeyDown(0x58) || IsKeyDown(0x1B)) buttons |= 0x2000; // X / Escape = Circle
if (IsKeyDown(0x43)) buttons |= 0x8000; // C = Square
if (IsKeyDown(0x56)) buttons |= 0x1000; // V = Triangle
// Shoulder buttons
if (IsKeyDown(0x51)) buttons |= 0x0400; // Q = L1
if (IsKeyDown(0x45)) buttons |= 0x0800; // E = R1
if (IsKeyDown(0x52)) buttons |= 0x0100; // R = L2 (digital)
if (IsKeyDown(0x46)) buttons |= 0x0200; // F = R2 (digital)
// Options (Start)
if (IsKeyDown(0x09) || IsKeyDown(0x08)) buttons |= 0x0008; // Tab / Backspace = Options
return buttons;
}
private static byte ReadAnalogStick(bool negative, bool positive)
{
if (negative && !positive) return 0;
if (positive && !negative) return 255;
return 128;
}
}
+1 -126
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Text;
@@ -11,8 +10,6 @@ namespace SharpEmu.Libs.SaveData;
public static class SaveDataExports
{
private const int OrbisSaveDataErrorParameter = unchecked((int)0x809F0000);
private const int OrbisSaveDataErrorExists = unchecked((int)0x809F0007);
private const int OrbisSaveDataErrorNotFound = unchecked((int)0x809F0008);
private const int OrbisSaveDataErrorInternal = unchecked((int)0x809F000B);
private const int SaveDataTitleIdSize = 10;
private const int SaveDataDirNameSize = 32;
@@ -26,9 +23,6 @@ public static class SaveDataExports
private const ulong ResultInfosOffset = 0x20;
private const uint SortKeyFreeBlocks = 5;
private const uint SortOrderDescent = 1;
private const uint MountModeCreate = 1u << 2;
private const uint MountModeCreate2 = 1u << 5;
private const int MountResultSize = 0x40;
private static readonly object _stateGate = new();
private static string? _titleId;
@@ -155,125 +149,6 @@ public static class SaveDataExports
}
}
[SysAbiExport(
Nid = "ZP4e7rlzOUk",
ExportName = "sceSaveDataMount3",
Target = Generation.Gen5,
LibraryName = "libSceSaveData")]
public static int SaveDataMount3(CpuContext ctx)
{
var mountAddress = ctx[CpuRegister.Rdi];
var resultAddress = ctx[CpuRegister.Rsi];
if (mountAddress == 0 || resultAddress == 0)
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
if (!TryReadInt32(ctx, mountAddress, out var userId) ||
!ctx.TryReadUInt64(mountAddress + 0x08, out var dirNameAddress) ||
!ctx.TryReadUInt64(mountAddress + 0x10, out var blocks) ||
!ctx.TryReadUInt64(mountAddress + 0x18, out var systemBlocks) ||
!TryReadUInt32(ctx, mountAddress + 0x20, out var mountMode) ||
!TryReadUInt32(ctx, mountAddress + 0x24, out var resource) ||
!TryReadUInt32(ctx, mountAddress + 0x28, out var mode) ||
dirNameAddress == 0 ||
!TryReadFixedAscii(ctx, dirNameAddress, SaveDataDirNameSize, out var dirName))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (userId < 0 || string.IsNullOrWhiteSpace(dirName))
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
try
{
var titleId = ResolveConfiguredTitleId();
var savePath = Path.Combine(
ResolveTitleSaveRoot(userId, titleId),
SanitizePathSegment(dirName));
var existed = Directory.Exists(savePath);
var create = (mountMode & MountModeCreate) != 0;
var createIfMissing = (mountMode & MountModeCreate2) != 0;
if (!existed && !create && !createIfMissing)
{
return SetReturn(ctx, OrbisSaveDataErrorNotFound);
}
if (existed && create)
{
return SetReturn(ctx, OrbisSaveDataErrorExists);
}
if (!existed)
{
Directory.CreateDirectory(savePath);
}
const string mountPoint = "/savedata0";
KernelMemoryCompatExports.RegisterGuestPathMount(mountPoint, savePath);
Span<byte> result = stackalloc byte[MountResultSize];
result.Clear();
WriteAscii(result[..16], mountPoint);
BinaryPrimitives.WriteUInt32LittleEndian(result[0x1C..], createIfMissing && !existed ? 1u : 0u);
if (!ctx.Memory.TryWrite(resultAddress, result))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceSaveData(
$"mount3 user={userId} title={titleId} dir={dirName} blocks={blocks} " +
$"system_blocks={systemBlocks} mount_mode=0x{mountMode:X} resource={resource} mode={mode} " +
$"mount_point={mountPoint} created={!existed} root='{savePath}'");
return SetReturn(ctx, 0);
}
catch (IOException)
{
return SetReturn(ctx, OrbisSaveDataErrorInternal);
}
catch (UnauthorizedAccessException)
{
return SetReturn(ctx, OrbisSaveDataErrorInternal);
}
catch (ArgumentException)
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
}
private static int _nextTransactionResource;
[SysAbiExport(
Nid = "gjRZNnw0JPE",
ExportName = "sceSaveDataCreateTransactionResource",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceSaveData")]
public static int SaveDataCreateTransactionResource(CpuContext ctx)
{
var userId = unchecked((int)ctx[CpuRegister.Rdi]);
var reserved = ctx[CpuRegister.Rsi];
var resourceAddress = ctx[CpuRegister.Rdx];
if (resourceAddress == 0)
{
return SetReturn(ctx, OrbisSaveDataErrorParameter);
}
var id = (uint)Interlocked.Increment(ref _nextTransactionResource);
if (!TryWriteUInt32(ctx, resourceAddress, id))
{
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceSaveData(
$"create_transaction_resource user={userId} reserved=0x{reserved:X} resource_addr=0x{resourceAddress:X} id={id}");
return SetReturn(ctx, 0);
}
private static bool TryReadSearchCond(CpuContext ctx, ulong address, out SearchCond cond)
{
cond = default;
@@ -430,7 +305,7 @@ public static class SaveDataExports
{
var configured = Environment.GetEnvironmentVariable("SHARPEMU_SAVEDATA_DIR");
var root = string.IsNullOrWhiteSpace(configured)
? Path.Combine(AppContext.BaseDirectory, "user", "savedata")
? Path.Combine(Environment.CurrentDirectory, "user", "savedata")
: configured;
return Path.GetFullPath(root);
}
-1
View File
@@ -10,7 +10,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<ItemGroup>
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="Silk.NET.Windowing" />
</ItemGroup>
+3 -65
View File
@@ -4,7 +4,6 @@
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
@@ -49,13 +48,6 @@ public static class VideoOutExports
private static int _frameDumpCount;
private static long _nextFrameDumpIndex;
private static string _windowTitle = "SharpEmu VideoOut";
private static readonly bool _logFrameRate = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_FPS"),
"1",
StringComparison.Ordinal);
private static long _frameRateWindowStart = Stopwatch.GetTimestamp();
private static long _submittedFrameCount;
private static long _presentedFrameCount;
public static void ConfigureApplicationInfo(string? title, string? titleId, string? version)
{
@@ -368,7 +360,7 @@ public static class VideoOutExports
var bufferIndex = unchecked((int)ctx[CpuRegister.Rsi]);
var flipMode = unchecked((int)ctx[CpuRegister.Rdx]);
var flipArg = unchecked((long)ctx[CpuRegister.Rcx]);
return SubmitFlip(ctx, handle, bufferIndex, flipMode, flipArg, submitGpuImage: true);
return SubmitFlip(ctx, handle, bufferIndex, flipMode, flipArg);
}
[SysAbiExport(
@@ -448,7 +440,7 @@ public static class VideoOutExports
}
public static int SubmitFlipFromAgc(CpuContext ctx, int handle, int bufferIndex, int flipMode, long flipArg) =>
SubmitFlip(ctx, handle, bufferIndex, flipMode, flipArg, submitGpuImage: false);
SubmitFlip(ctx, handle, bufferIndex, flipMode, flipArg);
internal static void SubmitHostRgbaFrame(ReadOnlySpan<byte> rgbaFrame, uint width, uint height)
{
@@ -756,13 +748,7 @@ public static class VideoOutExports
return groupIndex < 0 ? groupIndex : setIndex;
}
private static int SubmitFlip(
CpuContext ctx,
int handle,
int bufferIndex,
int flipMode,
long flipArg,
bool submitGpuImage)
private static int SubmitFlip(CpuContext ctx, int handle, int bufferIndex, int flipMode, long flipArg)
{
if (!TryGetPort(handle, out var port))
{
@@ -790,17 +776,6 @@ public static class VideoOutExports
flipEvents = new List<FlipEventRegistration>(port.FlipEvents);
}
if (submitGpuImage &&
bufferIndex >= 0 &&
TryGetDisplayBufferInfo(handle, bufferIndex, out var displayBuffer))
{
_ = VulkanVideoPresenter.TrySubmitGuestImage(
displayBuffer.Address,
displayBuffer.Width,
displayBuffer.Height,
displayBuffer.PitchInPixel);
}
if (string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_VIDEOOUT"),
"1",
@@ -820,46 +795,9 @@ public static class VideoOutExports
}
TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}");
ReportFrameRate(presented: false);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
internal static void ReportPresentedFrame() =>
ReportFrameRate(presented: true);
private static void ReportFrameRate(bool presented)
{
if (!_logFrameRate)
{
return;
}
if (presented)
{
Interlocked.Increment(ref _presentedFrameCount);
}
else
{
Interlocked.Increment(ref _submittedFrameCount);
}
var started = Volatile.Read(ref _frameRateWindowStart);
var now = Stopwatch.GetTimestamp();
var elapsedTicks = now - started;
if (elapsedTicks < Stopwatch.Frequency ||
Interlocked.CompareExchange(ref _frameRateWindowStart, now, started) != started)
{
return;
}
var elapsedSeconds = (double)elapsedTicks / Stopwatch.Frequency;
var submitted = Interlocked.Exchange(ref _submittedFrameCount, 0);
var presentedCount = Interlocked.Exchange(ref _presentedFrameCount, 0);
Console.Error.WriteLine(
$"[LOADER][PERF] videoout submitted_fps={submitted / elapsedSeconds:F1} " +
$"presented_fps={presentedCount / elapsedSeconds:F1}");
}
private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan<ulong> addresses, BufferAttribute attribute, int requestedGroupIndex = -1)
{
lock (_stateGate)
File diff suppressed because it is too large Load Diff
-10
View File
@@ -11,16 +11,6 @@
"Silk.NET.Core": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.EXT": {
"type": "Direct",
"requested": "[2.23.0, )",
"resolved": "2.23.0",
"contentHash": "+Oth189ksRiL6HvGCwIdnsYHawqrbO8y49u1H61z3wsfcHhQZeVDYe/wF5LD7fk3NcdgDvwFD3mLm1QWhdZySw==",
"dependencies": {
"Silk.NET.Core": "2.23.0",
"Silk.NET.Vulkan": "2.23.0"
}
},
"Silk.NET.Vulkan.Extensions.KHR": {
"type": "Direct",
"requested": "[2.23.0, )",