Compare commits

..

11 Commits

Author SHA1 Message Date
Foued Attar 38d94d8e54 [vulkan] enable required PhysicalDeviceFeatures for translated shaders (#29)
- Enable VertexPipelineStoresAndAtomics/FragmentStoresAndAtomics:
  fixes vkCreateGraphicsPipelines() rejecting guestBuffers storage
  descriptor as NonWritable in vertex/fragment stages.
- Enable ShaderInt64: fixes vkCreateShaderModule() rejecting SPIR-V
  using 64-bit integer capability.
- Query GetPhysicalDeviceFeatures first and only enable what the GPU
  actually reports as supported, with a warning fallback otherwise.

Also adds optional Vulkan Validation Layers (SHARPEMU_VK_VALIDATION=1)
to surface these VUID errors during development instead of silent
VK_ERROR_DEVICE_LOST.
2026-07-06 16:56:03 +03:00
ParantezTech 61a1abdb13 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-07-06 09:53:04 +03:00
ParantezTech 88452218cb update readme 2026-07-06 09:52:46 +03:00
Berk 427a511e48 [unity-fixes] support for Media/Modules/*.prx files (#27) 2026-07-05 22:10:37 +03:00
Berk e103795768 [agc] new agc exports for agc improvements (sceAgcDcbDrawIndex, sceAgcDriverGetResourceRegistrationMaxNameLength, sceAgcDriverGetDefaultOwner, sceAgcDriverRegisterResource, sceAgcDriverUnknown_KRzWekV120(?)) (#26) 2026-07-05 22:10:15 +03:00
Berk 24c6e44800 [ngs2 implements] Implement NGS2 exports (#25) 2026-07-05 22:09:50 +03:00
Berk 3a30d6419f [pad improvements] Added support for scePadSetVibrationMode (#24) 2026-07-05 22:09:36 +03:00
Berk 2d8a795f23 [sceAudio] added simple audio output implementation, needs to be improved (#23) 2026-07-05 22:09:25 +03:00
Berk adb2acdb39 [sceMsgDialog] implemented sceMsgDialogInitialize (#22) 2026-07-05 22:09:14 +03:00
Berk 5e5bead02c Avplayer implements (#21) 2026-07-05 22:09:04 +03:00
Berk e98f02966a [savedata-improvements-2] added sceSaveDataCreateTransactionResource (#20) 2026-07-05 22:08:53 +03:00
15 changed files with 834 additions and 6 deletions
+2 -2
View File
@@ -2,7 +2,6 @@
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
@@ -10,7 +9,8 @@ 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.
+11
View File
@@ -81,6 +81,7 @@
"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, )"
}
@@ -103,6 +104,16 @@
"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,10 +338,15 @@ public sealed partial class DirectExecutionBackend
{
orbisGen2Result = DispatchBootstrapBridge();
}
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal))
else if (string.Equals(importStubEntry.Nid, RuntimeStubNids.KernelDynlibDlsym, StringComparison.Ordinal) ||
string.Equals(importStubEntry.Nid, "LwG8g3niqwA", 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)
{
@@ -1180,8 +1185,11 @@ public sealed partial class DirectExecutionBackend
cpuContext[CpuRegister.Rax] = 18446744073709551615uL;
return OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress))
if (!TryResolveRuntimeSymbolAddress(symbolName, out var resolvedAddress) &&
!TryResolveRuntimeSymbolAlias(symbolName, out 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;
}
@@ -1194,6 +1202,62 @@ 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;
+4 -2
View File
@@ -543,6 +543,7 @@ 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)
@@ -554,7 +555,9 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
}
var allModulePaths = moduleDirectories
.SelectMany(Directory.EnumerateFiles)
.SelectMany(directory => Directory
.EnumerateFiles(directory)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase))
.Where(path =>
{
var extension = Path.GetExtension(path);
@@ -562,7 +565,6 @@ 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,6 +72,7 @@
"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, )"
}
@@ -88,6 +89,16 @@
"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, )",
+127
View File
@@ -1204,6 +1204,51 @@ public static class AgcExports
return ReturnPointer(ctx, commandAddress);
}
[SysAbiExport(
Nid = "q88lQ+GP5Yk",
ExportName = "sceAgcDcbDrawIndex",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DcbDrawIndex(CpuContext ctx)
{
var commandBufferAddress = ctx[CpuRegister.Rdi];
var indexCount = (uint)ctx[CpuRegister.Rsi];
var indexAddress = ctx[CpuRegister.Rdx];
var modifier = (uint)ctx[CpuRegister.Rcx];
if (commandBufferAddress == 0 || modifier != 0x4000_0000)
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var baseCommand) ||
!TryWriteUInt32(ctx, baseCommand, Pm4(3, ItIndexBase, 0)) ||
!TryWriteUInt32(ctx, baseCommand + 4, (uint)indexAddress) ||
!TryWriteUInt32(ctx, baseCommand + 8, (uint)(indexAddress >> 32)) ||
!TryWriteUInt32(ctx, baseCommand + 12, Pm4(2, ItIndexBufferSize, 0)) ||
!TryWriteUInt32(ctx, baseCommand + 16, indexCount))
{
return ReturnPointer(ctx, 0);
}
if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 5, out var drawCommand) ||
!TryWriteUInt32(ctx, drawCommand, Pm4(5, ItDrawIndex2, 0)) ||
!TryWriteUInt32(ctx, drawCommand + 4, indexCount) ||
!TryWriteUInt32(ctx, drawCommand + 8, 0) ||
!TryWriteUInt32(ctx, drawCommand + 12, 0) ||
!TryWriteUInt32(ctx, drawCommand + 16, 0))
{
return ReturnPointer(ctx, 0);
}
TraceAgc(
$"agc.dcb_draw_index buf=0x{commandBufferAddress:X16} " +
$"base=0x{baseCommand:X16} draw=0x{drawCommand:X16} " +
$"count={indexCount} index=0x{indexAddress:X16}");
return ReturnPointer(ctx, drawCommand);
}
[SysAbiExport(
Nid = "Yw0jKSqop+E",
ExportName = "sceAgcDcbDrawIndexAuto",
@@ -2006,6 +2051,88 @@ public static class AgcExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "uJziRsODk1c",
ExportName = "sceAgcDriverGetResourceRegistrationMaxNameLength",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverGetResourceRegistrationMaxNameLength(CpuContext ctx)
{
var outAddress = ctx[CpuRegister.Rdi];
if (outAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryWriteUInt32(ctx, outAddress, 256))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.driver_get_resource_registration_max_name_length out=0x{outAddress:X16} value=256");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
private const uint DefaultAgcOwner = 1;
[SysAbiExport(
Nid = "F0ZXt5q0ZTA",
ExportName = "sceAgcDriverGetDefaultOwner",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverGetDefaultOwner(CpuContext ctx)
{
var ownerAddress = ctx[CpuRegister.Rdi];
if (ownerAddress == 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
if (!TryWriteUInt32(ctx, ownerAddress, DefaultAgcOwner))
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
TraceAgc($"agc.driver_get_default_owner out=0x{ownerAddress:X16} owner={DefaultAgcOwner}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "W5z4eZrjEas",
ExportName = "sceAgcDriverRegisterResource",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverRegisterResource(CpuContext ctx)
{
var resourceAddress = ctx[CpuRegister.Rdi];
var owner = (uint)ctx[CpuRegister.Rsi];
var nameAddress = ctx[CpuRegister.Rdx];
var type = (uint)ctx[CpuRegister.R8];
var flags = (uint)ctx[CpuRegister.R9];
TraceAgc(
$"agc.driver_register_resource resource=0x{resourceAddress:X16} owner={owner} " +
$"name=0x{nameAddress:X16} type={type} flags={flags}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "-KRzWekV120",
ExportName = "sceAgcDriverUnknown_KRzWekV120",
Target = Generation.Gen5,
LibraryName = "libSceAgc")]
public static int DriverUnknownKRzWekV120(CpuContext ctx)
{
TraceAgc(
$"agc.driver_unknown_krz rdi=0x{ctx[CpuRegister.Rdi]:X16} " +
$"rsi=0x{ctx[CpuRegister.Rsi]:X16} rdx=0x{ctx[CpuRegister.Rdx]:X16} " +
$"rcx=0x{ctx[CpuRegister.Rcx]:X16} r8=0x{ctx[CpuRegister.R8]:X16} r9=0x{ctx[CpuRegister.R9]:X16}");
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
}
[SysAbiExport(
Nid = "h9z6+0hEydk",
ExportName = "sceAgcSuspendPoint",
@@ -0,0 +1,92 @@
// 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;
}
}
@@ -0,0 +1,27 @@
// 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;
}
}
+322
View File
@@ -0,0 +1,322 @@
// 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;
}
}
+10
View File
@@ -160,6 +160,16 @@ 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];
@@ -244,6 +244,36 @@ public static class SaveDataExports
}
}
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;
+1
View File
@@ -10,6 +10,7 @@ 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>
@@ -7,6 +7,7 @@ using Silk.NET.Maths;
using SharpEmu.Libs.Agc;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using Silk.NET.Vulkan.Extensions.EXT;
using Silk.NET.Windowing;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -846,6 +847,8 @@ internal static unsafe class VulkanVideoPresenter
private delegate* unmanaged<CommandBuffer, void> _cmdEndDebugUtilsLabel;
private Instance _instance;
private SurfaceKHR _surface;
private DebugUtilsMessengerEXT _debugMessenger;
private ExtDebugUtils? _debugUtils;
private PhysicalDevice _physicalDevice;
private Device _device;
private Queue _queue;
@@ -1241,6 +1244,9 @@ internal static unsafe class VulkanVideoPresenter
private void CreateInstance()
{
var applicationName = (byte*)SilkMarshal.StringToPtr("SharpEmu");
var enableValidation = Environment.GetEnvironmentVariable("SHARPEMU_VK_VALIDATION") == "1";
byte* validationLayerName = null;
try
{
var applicationInfo = new ApplicationInfo
@@ -1268,12 +1274,29 @@ internal static unsafe class VulkanVideoPresenter
enabledExtensions[enabledExtensionCount++] = debugUtilsExtension;
}
if (enableValidation && IsInstanceLayerAvailable("VK_LAYER_KHRONOS_validation"))
{
validationLayerName = (byte*)SilkMarshal.StringToPtr("VK_LAYER_KHRONOS_validation");
}
else if (enableValidation)
{
Console.Error.WriteLine("[LOADER][WARN] SHARPEMU_VK_VALIDATION=1 but VK_LAYER_KHRONOS_validation not found (Vulkan SDK installed?).");
}
var layers = stackalloc byte*[1];
if (validationLayerName is not null)
{
layers[0] = validationLayerName;
}
var createInfo = new InstanceCreateInfo
{
SType = StructureType.InstanceCreateInfo,
PApplicationInfo = &applicationInfo,
EnabledExtensionCount = (uint)enabledExtensionCount,
PpEnabledExtensionNames = enabledExtensions,
EnabledLayerCount = validationLayerName is not null ? 1u : 0u,
PpEnabledLayerNames = validationLayerName is not null ? layers : null,
};
try
@@ -1283,6 +1306,13 @@ internal static unsafe class VulkanVideoPresenter
{
throw new InvalidOperationException("VK_KHR_surface is unavailable.");
}
if (validationLayerName is not null && _vk.TryGetInstanceExtension(_instance, out ExtDebugUtils debugUtils))
{
_debugUtils = debugUtils;
RegisterDebugMessenger(debugUtils);
Console.Error.WriteLine("[LOADER][INFO] Vulkan Validation Layers active (SHARPEMU_VK_VALIDATION=1).");
}
}
finally
{
@@ -1295,9 +1325,74 @@ internal static unsafe class VulkanVideoPresenter
finally
{
SilkMarshal.Free((nint)applicationName);
if (validationLayerName is not null)
{
SilkMarshal.Free((nint)validationLayerName);
}
}
}
private bool IsInstanceLayerAvailable(string layerName)
{
uint layerCount = 0;
if (_vk.EnumerateInstanceLayerProperties(&layerCount, null) != Result.Success || layerCount == 0)
{
return false;
}
var properties = new LayerProperties[layerCount];
fixed (LayerProperties* propertyPointer = properties)
{
if (_vk.EnumerateInstanceLayerProperties(&layerCount, propertyPointer) != Result.Success)
{
return false;
}
var expected = Encoding.UTF8.GetBytes(layerName);
for (var index = 0; index < layerCount; index++)
{
if (Utf8NullTerminatedEquals(propertyPointer[index].LayerName, expected))
{
return true;
}
}
}
return false;
}
private void RegisterDebugMessenger(ExtDebugUtils debugUtils)
{
var messengerInfo = new DebugUtilsMessengerCreateInfoEXT
{
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt
| DebugUtilsMessageSeverityFlagsEXT.WarningBitExt,
MessageType = DebugUtilsMessageTypeFlagsEXT.ValidationBitExt
| DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt
| DebugUtilsMessageTypeFlagsEXT.GeneralBitExt,
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(DebugCallback),
};
Check(debugUtils.CreateDebugUtilsMessenger(_instance, &messengerInfo, null, out _debugMessenger),
"vkCreateDebugUtilsMessengerEXT");
}
private static unsafe uint DebugCallback(
DebugUtilsMessageSeverityFlagsEXT severity,
DebugUtilsMessageTypeFlagsEXT type,
DebugUtilsMessengerCallbackDataEXT* callbackData,
void* userData)
{
var message = SilkMarshal.PtrToString((nint)callbackData->PMessage);
var prefix = severity switch
{
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt => "[VULKAN][ERROR]",
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt => "[VULKAN][WARN]",
_ => "[VULKAN][INFO]",
};
Console.Error.WriteLine($"{prefix} {message}");
return Vk.False;
}
private void CreateSurface()
{
var instanceHandle = new VkHandle(_instance.Handle);
@@ -1358,7 +1453,28 @@ internal static unsafe class VulkanVideoPresenter
QueueCount = 1,
PQueuePriorities = &priority,
};
_vk.GetPhysicalDeviceFeatures(_physicalDevice, out var supportedFeatures);
var enabledFeatures = new PhysicalDeviceFeatures
{
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
ShaderInt64 = supportedFeatures.ShaderInt64,
};
if (!supportedFeatures.ShaderInt64)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support shaderInt64 " +
"translated shaders using 64-bit integers will fail.");
}
if (!supportedFeatures.VertexPipelineStoresAndAtomics || !supportedFeatures.FragmentStoresAndAtomics)
{
Console.Error.WriteLine(
"[LOADER][WARN] GPU does not support vertexPipelineStoresAndAtomics/fragmentStoresAndAtomics " +
"translated shaders using storage buffers in vertex/fragment stages may fail.");
}
var swapchainExtension = (byte*)SilkMarshal.StringToPtr("VK_KHR_swapchain");
try
{
@@ -1369,6 +1485,7 @@ internal static unsafe class VulkanVideoPresenter
PQueueCreateInfos = &queueInfo,
EnabledExtensionCount = 1,
PpEnabledExtensionNames = &swapchainExtension,
PEnabledFeatures = &enabledFeatures,
};
Check(_vk.CreateDevice(_physicalDevice, &createInfo, null, out _device), "vkCreateDevice");
@@ -5956,6 +6073,10 @@ internal static unsafe class VulkanVideoPresenter
return;
}
if (_debugUtils is not null && _debugMessenger.Handle != 0)
{
_debugUtils.DestroyDebugUtilsMessenger(_instance, _debugMessenger, null);
}
_vulkanReady = false;
_vk.DeviceWaitIdle(_device);
CollectCompletedGuestSubmissions(waitForOldest: false);
+10
View File
@@ -11,6 +11,16 @@
"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, )",