initial commit

This commit is contained in:
ParantezTech
2026-03-11 15:48:28 +03:00
commit 4d73f469bc
92 changed files with 166746 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Linq;
namespace SharpEmu.HLE;
public sealed class Aerolib : ISymbolCatalog
{
private static readonly Lazy<Aerolib> _instance = new(() => new Aerolib());
private static readonly Aerolib EmptyCatalog = new Aerolib(empty: true);
private Dictionary<string, SysAbiSymbol> _byNid;
private Dictionary<string, SysAbiSymbol> _byExportName;
public static Aerolib Instance => _instance.Value;
private Aerolib(
Dictionary<string, SysAbiSymbol> byNid,
Dictionary<string, SysAbiSymbol> byExportName)
{
_byNid = byNid;
_byExportName = byExportName;
}
private Aerolib()
{
_byNid = new Dictionary<string, SysAbiSymbol>(StringComparer.Ordinal);
_byExportName = new Dictionary<string, SysAbiSymbol>(StringComparer.Ordinal);
LoadFromEmbeddedBinary();
}
private Aerolib(bool empty)
{
_byNid = new Dictionary<string, SysAbiSymbol>(StringComparer.Ordinal);
_byExportName = new Dictionary<string, SysAbiSymbol>(StringComparer.Ordinal);
}
public static ISymbolCatalog Empty => EmptyCatalog;
public string GetName(string nid)
{
if (string.IsNullOrEmpty(nid))
return nid ?? string.Empty;
if (_byNid.TryGetValue(nid, out var symbol))
return symbol.ExportName;
return nid;
}
public bool TryGetName(string nid, out string name)
{
if (string.IsNullOrEmpty(nid))
{
name = string.Empty;
return false;
}
if (_byNid.TryGetValue(nid, out var symbol))
{
name = symbol.ExportName;
return true;
}
name = string.Empty;
return false;
}
public bool ContainsNid(string nid)
{
if (string.IsNullOrEmpty(nid))
return false;
return _byNid.ContainsKey(nid);
}
public Dictionary<string, string> GetAllNidNames()
{
var result = new Dictionary<string, string>(_byNid.Count, StringComparer.Ordinal);
foreach (var kvp in _byNid)
{
result[kvp.Key] = kvp.Value.ExportName;
}
return result;
}
public int Count => _byNid.Count;
public bool TryGetByNid(string nid, out SysAbiSymbol symbol)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
return _byNid.TryGetValue(nid, out symbol);
}
public bool TryGetByExportName(string exportName, out SysAbiSymbol symbol)
{
ArgumentException.ThrowIfNullOrWhiteSpace(exportName);
return _byExportName.TryGetValue(exportName, out symbol);
}
private void LoadFromEmbeddedBinary()
{
try
{
var assembly = typeof(Aerolib).Assembly;
var resourceName = assembly.GetManifestResourceNames()
.FirstOrDefault(n => n.EndsWith("aerolib.bin", StringComparison.OrdinalIgnoreCase));
if (resourceName == null)
{
Console.Error.WriteLine("[AEROLIB] Embedded resource 'aerolib.bin' not found");
return;
}
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
Console.Error.WriteLine("[AEROLIB] Failed to open embedded resource stream");
return;
}
var data = new byte[stream.Length];
stream.ReadExactly(data);
int offset = 0;
uint count = BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(offset, 4));
offset += 4;
_byNid = new Dictionary<string, SysAbiSymbol>((int)count, StringComparer.Ordinal);
_byExportName = new Dictionary<string, SysAbiSymbol>((int)count, StringComparer.Ordinal);
for (uint i = 0; i < count; i++)
{
byte nidLen = data[offset++];
string nid = System.Text.Encoding.UTF8.GetString(data, offset, nidLen);
offset += nidLen;
ushort nameLen = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(offset, 2));
offset += 2;
string name = System.Text.Encoding.UTF8.GetString(data, offset, nameLen);
offset += nameLen;
var symbol = new SysAbiSymbol(nid, name, name, Generation.Gen5);
_byNid[nid] = symbol;
_byExportName[name] = symbol;
}
Console.Error.WriteLine($"[AEROLIB] Loaded {_byNid.Count} NID entries from binary resource");
}
catch (Exception ex)
{
Console.Error.WriteLine($"[AEROLIB] Failed to load embedded aerolib.bin: {ex.Message}");
}
}
}
Binary file not shown.
+172
View File
@@ -0,0 +1,172 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
namespace SharpEmu.HLE;
public sealed class CpuContext
{
private readonly ulong[] _registers = new ulong[16];
private readonly ulong[] _xmmRegisters = new ulong[32];
private readonly ulong[] _ymmUpperRegisters = new ulong[32];
private bool _raxWritten;
public CpuContext(ICpuMemory memory, Generation generation)
{
Memory = memory ?? throw new ArgumentNullException(nameof(memory));
TargetGeneration = generation;
}
public ICpuMemory Memory { get; }
public Generation TargetGeneration { get; }
public ulong Rip { get; set; }
public ulong Rflags { get; set; }
public ulong FsBase { get; set; }
public ulong GsBase { get; set; }
public ulong this[CpuRegister register]
{
get => _registers[(int)register];
set
{
_registers[(int)register] = value;
if (register == CpuRegister.Rax)
{
_raxWritten = true;
}
}
}
public void ClearRaxWriteFlag()
{
_raxWritten = false;
}
public bool WasRaxWritten => _raxWritten;
public void GetXmmRegister(int registerIndex, out ulong low, out ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
low = _xmmRegisters[offset];
high = _xmmRegisters[offset + 1];
}
public void SetXmmRegister(int registerIndex, ulong low, ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
_xmmRegisters[offset] = low;
_xmmRegisters[offset + 1] = high;
}
public void GetYmmUpper(int registerIndex, out ulong low, out ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
low = _ymmUpperRegisters[offset];
high = _ymmUpperRegisters[offset + 1];
}
public void SetYmmUpper(int registerIndex, ulong low, ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
_ymmUpperRegisters[offset] = low;
_ymmUpperRegisters[offset + 1] = high;
}
public void ClearYmmUpper(int registerIndex)
{
SetYmmUpper(registerIndex, 0, 0);
}
public void ClearAllYmmUpper()
{
Array.Clear(_ymmUpperRegisters);
}
public void GetYmmRegister(
int registerIndex,
out ulong lowLow,
out ulong lowHigh,
out ulong highLow,
out ulong highHigh)
{
GetXmmRegister(registerIndex, out lowLow, out lowHigh);
GetYmmUpper(registerIndex, out highLow, out highHigh);
}
public void SetYmmRegister(
int registerIndex,
ulong lowLow,
ulong lowHigh,
ulong highLow,
ulong highHigh)
{
SetXmmRegister(registerIndex, lowLow, lowHigh);
SetYmmUpper(registerIndex, highLow, highHigh);
}
public bool TryReadUInt64(ulong address, out ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
if (!Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
return true;
}
public bool TryWriteUInt64(ulong address, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
return Memory.TryWrite(address, buffer);
}
public bool PushUInt64(ulong value)
{
var rsp = this[CpuRegister.Rsp];
rsp -= sizeof(ulong);
this[CpuRegister.Rsp] = rsp;
return TryWriteUInt64(rsp, value);
}
public bool PopUInt64(out ulong value)
{
var rsp = this[CpuRegister.Rsp];
if (!TryReadUInt64(rsp, out value))
{
return false;
}
this[CpuRegister.Rsp] = rsp + sizeof(ulong);
return true;
}
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public enum CpuRegister : int
{
Rax = 0,
Rcx = 1,
Rdx = 2,
Rbx = 3,
Rsp = 4,
Rbp = 5,
Rsi = 6,
Rdi = 7,
R8 = 8,
R9 = 9,
R10 = 10,
R11 = 11,
R12 = 12,
R13 = 13,
R14 = 14,
R15 = 15,
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public sealed class ExportedFunction
{
public ExportedFunction(string libraryName, string nid, string name, Generation target, SysAbiFunction function)
{
ArgumentException.ThrowIfNullOrWhiteSpace(libraryName);
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
ArgumentException.ThrowIfNullOrWhiteSpace(name);
ArgumentNullException.ThrowIfNull(function);
LibraryName = libraryName;
Nid = nid;
Name = name;
Target = target;
Function = function;
}
public string LibraryName { get; }
public string Nid { get; }
public string Name { get; }
public Generation Target { get; }
public SysAbiFunction Function { get; }
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
[Flags]
public enum Generation
{
None = 0,
Gen4 = 1,
Gen5 = 2,
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public interface ICpuMemory
{
bool TryRead(ulong virtualAddress, Span<byte> destination);
bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source);
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Reflection;
namespace SharpEmu.HLE;
public interface IModuleManager
{
int RegisterFromAssembly(Assembly assembly, Generation generation, ISymbolCatalog? symbolCatalog = null);
void Freeze();
bool TryGetFunction(string nid, out Delegate function);
bool TryGetExport(string nid, out ExportedFunction export);
bool TryGetExportByName(string exportName, out ExportedFunction export);
OrbisGen2Result Dispatch(string nid, CpuContext context);
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public interface ISymbolCatalog
{
bool TryGetByNid(string nid, out SysAbiSymbol symbol);
bool TryGetByExportName(string exportName, out SysAbiSymbol symbol);
}
+226
View File
@@ -0,0 +1,226 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using System.Reflection;
namespace SharpEmu.HLE;
public sealed class ModuleManager : IModuleManager
{
private readonly ConcurrentDictionary<string, Delegate> _dispatchTable = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ExportedFunction> _exportTable = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, ExportedFunction> _exportNameTable = new(StringComparer.Ordinal);
private readonly object _registrationGate = new();
private bool _isFrozen;
public int RegisterFromAssembly(Assembly assembly, Generation generation, ISymbolCatalog? symbolCatalog = null)
{
ArgumentNullException.ThrowIfNull(assembly);
lock (_registrationGate)
{
if (_isFrozen)
{
throw new InvalidOperationException("Module registration is frozen.");
}
var registeredCount = 0;
var instances = new Dictionary<Type, object>();
foreach (var type in assembly.GetTypes())
{
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
var exportAttribute = method.GetCustomAttribute<SysAbiExportAttribute>(inherit: false);
if (exportAttribute is null)
{
continue;
}
var exportInfo = ResolveExportInfo(exportAttribute, method, generation, symbolCatalog);
if (exportInfo is null)
{
continue;
}
var handler = CreateHandler(type, method, instances);
if (!_dispatchTable.TryAdd(exportInfo.Value.Nid, handler))
{
continue;
}
_exportTable[exportInfo.Value.Nid] = new ExportedFunction(
exportInfo.Value.LibraryName,
exportInfo.Value.Nid,
exportInfo.Value.ExportName,
exportInfo.Value.Target,
(SysAbiFunction)handler);
_exportNameTable.TryAdd(exportInfo.Value.ExportName, _exportTable[exportInfo.Value.Nid]);
registeredCount++;
}
}
return registeredCount;
}
}
public void Freeze()
{
lock (_registrationGate)
{
_isFrozen = true;
}
}
public bool TryGetFunction(string nid, out Delegate function)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
return _dispatchTable.TryGetValue(nid, out function!);
}
public bool TryGetExport(string nid, out ExportedFunction export)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
return _exportTable.TryGetValue(nid, out export!);
}
public bool TryGetExportByName(string exportName, out ExportedFunction export)
{
ArgumentException.ThrowIfNullOrWhiteSpace(exportName);
return _exportNameTable.TryGetValue(exportName, out export!);
}
public OrbisGen2Result Dispatch(string nid, CpuContext context)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
ArgumentNullException.ThrowIfNull(context);
if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export))
{
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if ((export.Target & context.TargetGeneration) == 0)
{
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
context.ClearRaxWriteFlag();
int ret = ((SysAbiFunction)function).Invoke(context);
if (!context.WasRaxWritten)
{
context[CpuRegister.Rax] = unchecked((ulong)ret);
}
return (OrbisGen2Result)ret;
}
private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances)
{
ValidateSignature(method);
object? target = null;
if (!method.IsStatic)
{
if (!instances.TryGetValue(ownerType, out target))
{
target = Activator.CreateInstance(ownerType)
?? throw new InvalidOperationException($"Cannot instantiate module type: {ownerType.FullName}");
instances.Add(ownerType, target);
}
}
var parameterCount = method.GetParameters().Length;
if (parameterCount == 0)
{
var noArg = method.IsStatic
? (Func<int>)method.CreateDelegate(typeof(Func<int>))
: (Func<int>)method.CreateDelegate(typeof(Func<int>), target!);
SysAbiFunction adapter = _ => noArg();
return adapter;
}
return method.IsStatic
? method.CreateDelegate(typeof(SysAbiFunction))
: method.CreateDelegate(typeof(SysAbiFunction), target!);
}
private static void ValidateSignature(MethodInfo method)
{
if (method.ReturnType != typeof(int))
{
throw new InvalidOperationException(
$"Method {method.DeclaringType?.FullName}.{method.Name} must return int.");
}
var parameters = method.GetParameters();
if (parameters.Length == 0)
{
return;
}
if (parameters.Length == 1 && parameters[0].ParameterType == typeof(CpuContext))
{
return;
}
throw new InvalidOperationException(
$"Method {method.DeclaringType?.FullName}.{method.Name} must accept no arguments or one {nameof(CpuContext)} argument.");
}
private static ExportInfo? ResolveExportInfo(
SysAbiExportAttribute exportAttribute,
MethodInfo method,
Generation generation,
ISymbolCatalog? symbolCatalog)
{
var target = exportAttribute.Target == Generation.None
? generation
: exportAttribute.Target;
if ((target & generation) == 0)
{
return null;
}
var nid = exportAttribute.Nid;
var exportName = exportAttribute.ExportName;
if (string.IsNullOrWhiteSpace(nid) && !string.IsNullOrWhiteSpace(exportName) && symbolCatalog?.TryGetByExportName(exportName, out var byName) == true)
{
nid = byName.Nid;
}
if (!string.IsNullOrWhiteSpace(nid) && symbolCatalog?.TryGetByNid(nid, out var byNid) == true)
{
exportName = string.IsNullOrWhiteSpace(exportName) ? byNid.ExportName : exportName;
target = exportAttribute.Target == Generation.None ? byNid.Target : target;
}
if (string.IsNullOrWhiteSpace(nid))
{
throw new InvalidOperationException(
$"Method {method.DeclaringType?.FullName}.{method.Name} must define a NID or match one in symbols catalog.");
}
if (string.IsNullOrWhiteSpace(exportName))
{
exportName = method.Name;
}
if ((target & generation) == 0)
{
return null;
}
var libraryName = string.IsNullOrWhiteSpace(exportAttribute.LibraryName) ? "libKernel" : exportAttribute.LibraryName;
return new ExportInfo(nid, exportName, libraryName, target);
}
private readonly record struct ExportInfo(string Nid, string ExportName, string LibraryName, Generation Target);
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
/// <summary>
/// Represents synthetic kernel-style result codes used by the Gen5 runtime.
/// Prefixed with ORBIS_GEN2 to distinguish from PS4-oriented ORBIS_* codes
/// used by other emulators such as shadPS4.
/// </summary>
public enum OrbisGen2Result : int
{
/// <summary>
/// Indicates successful completion.
/// </summary>
ORBIS_GEN2_OK = 0,
/// <summary>
/// Indicates that the requested export was not found.
/// </summary>
ORBIS_GEN2_ERROR_NOT_FOUND = unchecked((int)0x80020002),
/// <summary>
/// Indicates that one or more arguments were invalid.
/// </summary>
ORBIS_GEN2_ERROR_INVALID_ARGUMENT = unchecked((int)0x80020003),
/// <summary>
/// Indicates that an item already exists.
/// </summary>
ORBIS_GEN2_ERROR_ALREADY_EXISTS = unchecked((int)0x80020004),
/// <summary>
/// Indicates that behavior is recognized but not implemented yet.
/// </summary>
ORBIS_GEN2_ERROR_NOT_IMPLEMENTED = unchecked((int)0x8002FFFF),
/// <summary>
/// Indicates that memory access failed.
/// </summary>
ORBIS_GEN2_ERROR_MEMORY_FAULT = unchecked((int)0x80020101),
/// <summary>
/// Indicates that CPU execution trapped on an unsupported instruction.
/// </summary>
ORBIS_GEN2_ERROR_CPU_TRAP = unchecked((int)0x80020102),
}
+14
View File
@@ -0,0 +1,14 @@
<!--
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Aerolib\aerolib.bin" />
</ItemGroup>
</Project>
+16
View File
@@ -0,0 +1,16 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class SysAbiExportAttribute : Attribute
{
public string LibraryName { get; set; } = "libKernel";
public string Nid { get; set; } = string.Empty;
public string ExportName { get; set; } = string.Empty;
public Generation Target { get; set; } = Generation.None;
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public delegate int SysAbiFunction(CpuContext context);
+26
View File
@@ -0,0 +1,26 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.HLE;
public readonly struct SysAbiSymbol
{
public SysAbiSymbol(string nid, string aliasName, string exportName, Generation target)
{
ArgumentException.ThrowIfNullOrWhiteSpace(nid);
ArgumentException.ThrowIfNullOrWhiteSpace(exportName);
Nid = nid;
AliasName = aliasName;
ExportName = exportName;
Target = target;
}
public string Nid { get; }
public string AliasName { get; }
public string ExportName { get; }
public Generation Target { get; }
}
+6
View File
@@ -0,0 +1,6 @@
{
"version": 2,
"dependencies": {
"net10.0": {}
}
}