mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7783173ecc | |||
| 9ab1e2f21a | |||
| 340eb9e0ab | |||
| 03e3d25e85 |
+1
-1
@@ -14,7 +14,7 @@ indent_size = 4
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
tab_width = 4
|
tab_width = 4
|
||||||
|
|
||||||
[*.{md,json,yml,props,csproj}]
|
[*.{md,json,yml,xml,props,csproj}]
|
||||||
|
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
tab_width = 2
|
tab_width = 2
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ arm64/
|
|||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
|
.packages
|
||||||
packages/
|
packages/
|
||||||
*.nupkg
|
*.nupkg
|
||||||
.nuget/
|
.nuget/
|
||||||
|
|||||||
+3
-1
@@ -3,12 +3,14 @@ version = 1
|
|||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = [
|
path = [
|
||||||
"REUSE.toml",
|
"REUSE.toml",
|
||||||
|
"nuget.config",
|
||||||
"global.json",
|
"global.json",
|
||||||
"**/packages.lock.json",
|
"**/packages.lock.json",
|
||||||
"scripts/ps5_names.txt",
|
"scripts/ps5_names.txt",
|
||||||
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
|
"src/SharpEmu.HLE/Aerolib/aerolib.bin",
|
||||||
"_logs/**",
|
"_logs/**",
|
||||||
".github/images/**"
|
".github/images/**",
|
||||||
|
"assets/images/**"
|
||||||
]
|
]
|
||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
|
SPDX-FileCopyrightText = "SharpEmu Emulator Project"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<?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>
|
||||||
@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;linux-x64;osx-arm64</RuntimeIdentifiers>
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<!-- <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> -->
|
||||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
@@ -30,6 +30,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64' Or '$(RuntimeIdentifier)' == ''">
|
||||||
|
<ApplicationIcon>..\..\assets\images\SharpEmu.ico</ApplicationIcon>
|
||||||
|
<Win32Icon>..\..\assets\images\SharpEmu.ico</Win32Icon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\..\LICENSE.txt">
|
<Content Include="..\..\LICENSE.txt">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user