A dozen changes; new HLEs, AV fixes, ELF loader fixes, new return codes, etc.

This commit is contained in:
ParantezTech
2026-03-14 20:38:25 +03:00
parent 028494a83b
commit 1f71c970d9
21 changed files with 2291 additions and 216 deletions
+73 -15
View File
@@ -12,6 +12,12 @@ namespace SharpEmu.Core.Cpu;
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
{ {
private enum EntryFrameKind
{
ProcessEntry,
ModuleInitializer,
}
private const ulong StackBaseAddress = 0x7FFF_F000_0000UL; private const ulong StackBaseAddress = 0x7FFF_F000_0000UL;
private const ulong StackSize = 0x0020_0000UL; private const ulong StackSize = 0x0020_0000UL;
private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL; private const ulong TlsBaseAddress = 0x7FFE_0000_0000UL;
@@ -88,13 +94,44 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
} }
} }
public OrbisGen2Result DispatchModuleInitializer(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default)
{
Console.Error.WriteLine("[DISPATCHER] === DispatchModuleInitializer START ===");
Console.Error.WriteLine($"[DISPATCHER] moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
try
{
return DispatchEntryCore(
entryPoint,
generation,
importStubs,
runtimeSymbols,
moduleName,
executionOptions,
EntryFrameKind.ModuleInitializer);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
throw;
}
}
private OrbisGen2Result DispatchEntryCore( private OrbisGen2Result DispatchEntryCore(
ulong entryPoint, ulong entryPoint,
Generation generation, Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null, IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin", string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default) CpuExecutionOptions executionOptions = default,
EntryFrameKind frameKind = EntryFrameKind.ProcessEntry)
{ {
Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING..."); Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING...");
@@ -164,23 +201,33 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
var effectiveImportStubs = importStubs is null var effectiveImportStubs = importStubs is null
? new Dictionary<ulong, string>() ? new Dictionary<ulong, string>()
: new Dictionary<ulong, string>(importStubs); : new Dictionary<ulong, string>(importStubs);
var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion(); var entryParamsConfigured = false;
if (programExitHandlerStubAddress == 0) if (frameKind == EntryFrameKind.ProcessEntry)
{ {
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion();
} if (programExitHandlerStubAddress == 0)
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (ShouldInjectBootstrapPayload(entryPoint))
{
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
{ {
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
} }
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
entryParamsConfigured = true;
if (ShouldInjectBootstrapPayload(entryPoint))
{
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
}
else if (!InitializeModuleInitializerFrame(context))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
} }
var entryFrameDiagnostic = BuildEntryFrameDiagnostic( var entryFrameDiagnostic = BuildEntryFrameDiagnostic(
@@ -188,7 +235,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
context, context,
sentinelEnabled: true, sentinelEnabled: true,
sentinelValue: returnToHostStubAddress, sentinelValue: returnToHostStubAddress,
entryParamsConfigured: true); entryParamsConfigured: entryParamsConfigured);
if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly) if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly)
{ {
@@ -360,6 +407,17 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
return true; return true;
} }
private static bool InitializeModuleInitializerFrame(CpuContext context)
{
context[CpuRegister.Rdi] = 0;
context[CpuRegister.Rsi] = 0;
context[CpuRegister.Rdx] = 0;
context[CpuRegister.Rcx] = 0;
context[CpuRegister.R8] = 0;
context[CpuRegister.R9] = 0;
return true;
}
private static ulong AlignDown(ulong value, ulong alignment) private static ulong AlignDown(ulong value, ulong alignment)
{ {
return value & ~(alignment - 1); return value & ~(alignment - 1);
+8
View File
@@ -34,4 +34,12 @@ public interface ICpuDispatcher
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin", string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default); CpuExecutionOptions executionOptions = default);
OrbisGen2Result DispatchModuleInitializer(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default);
} }
@@ -4,6 +4,7 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SharpEmu.Core.Cpu.Disasm; using SharpEmu.Core.Cpu.Disasm;
@@ -254,6 +255,9 @@ public sealed partial class DirectExecutionBackend
Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP"); Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP");
} }
DumpRecentImportTrace(); DumpRecentImportTrace();
DumpGuestDisasmDiagnostics(rip, rbp);
DumpGuestReferenceDiagnostics();
DumpGuestPointerWindowDiagnostics();
break; break;
case 2147483651u: case 2147483651u:
Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)"); Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)");
@@ -322,6 +326,239 @@ public sealed partial class DirectExecutionBackend
} }
} }
private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM"), "1", StringComparison.Ordinal))
{
return;
}
if (rip >= 0x20)
{
DumpGuestInstructionStream("fault-prelude", rip - 0x20, 24);
}
try
{
ulong frame = rbp;
for (int i = 0; i < 3; i++)
{
if (frame < 140733193388032L || frame > 140737488355327L)
{
break;
}
ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8));
if (ret >= 0x40)
{
DumpGuestInstructionStream($"frame#{i}-ret-prelude", ret - 0x40, 24);
}
ulong next = (ulong)Marshal.ReadInt64((nint)frame);
if (next <= frame)
{
break;
}
frame = next;
}
}
catch
{
Console.Error.WriteLine("[LOADER][WARNING] Could not dump disasm diagnostics.");
}
var extraAddresses = Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM_ADDRS");
if (string.IsNullOrWhiteSpace(extraAddresses))
{
return;
}
foreach (var token in extraAddresses.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? token[2..]
: token;
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address) || address < 0x20)
{
continue;
}
DumpGuestInstructionStream($"extra-0x{address:X16}", address, 48);
}
}
private unsafe void DumpGuestReferenceDiagnostics()
{
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_REFSCAN_ADDRS"));
if (targetList.Count == 0 || _cpuContext == null)
{
return;
}
const ulong scanBase = 0x0000000800000000UL;
const ulong scanEnd = 0x0000000810000000UL;
const int maxHitsPerTarget = 24;
Console.Error.WriteLine(
$"[LOADER][INFO] Ref scan targets: {string.Join(", ", targetList.ConvertAll(static addr => $"0x{addr:X16}"))}");
var hitCounts = new Dictionary<ulong, int>(targetList.Count);
for (var i = 0; i < targetList.Count; i++)
{
hitCounts[targetList[i]] = 0;
}
ulong address = scanBase;
while (address < scanEnd)
{
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
{
break;
}
ulong regionBase = mbi.BaseAddress;
ulong regionEnd = regionBase + mbi.RegionSize;
if (regionEnd <= address)
{
break;
}
if (mbi.State == MEM_COMMIT &&
IsReadableProtection(mbi.Protect) &&
IsExecutableProtection(mbi.Protect))
{
ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget);
}
var allTargetsSatisfied = true;
for (var i = 0; i < targetList.Count; i++)
{
if (hitCounts[targetList[i]] < maxHitsPerTarget)
{
allTargetsSatisfied = false;
break;
}
}
if (allTargetsSatisfied)
{
break;
}
address = regionEnd;
}
for (var i = 0; i < targetList.Count; i++)
{
var target = targetList[i];
if (!hitCounts.TryGetValue(target, out var count) || count == 0)
{
Console.Error.WriteLine($"[LOADER][INFO] Ref scan 0x{target:X16}: none");
}
}
}
private void DumpGuestPointerWindowDiagnostics()
{
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOWS"));
if (targetList.Count == 0)
{
return;
}
var windowSize = 0x80;
var rawWindowSize = Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOW_SIZE");
if (!string.IsNullOrWhiteSpace(rawWindowSize))
{
var normalized = rawWindowSize.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? rawWindowSize[2..]
: rawWindowSize;
if (int.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsedWindowSize) &&
parsedWindowSize > 0)
{
windowSize = parsedWindowSize;
}
}
foreach (var target in targetList)
{
DumpPointerWindow($"ptrwin-0x{target:X16}", target, windowSize);
}
}
private void ScanExecutableRegionForTargetReferences(
ulong regionBase,
ulong regionEnd,
IReadOnlyList<ulong> targets,
IDictionary<ulong, int> hitCounts,
int maxHitsPerTarget)
{
if (_cpuContext == null || regionEnd <= regionBase)
{
return;
}
ulong rip = regionBase;
while (rip < regionEnd)
{
if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
!IcedDecoder.TryDecode(rip, bytes, out var instruction) ||
instruction.Length <= 0)
{
rip++;
continue;
}
if (instruction.MemoryAddress is { } memoryAddress)
{
for (var i = 0; i < targets.Count; i++)
{
var target = targets[i];
if (memoryAddress != target ||
!hitCounts.TryGetValue(target, out var count) ||
count >= maxHitsPerTarget)
{
continue;
}
hitCounts[target] = count + 1;
Console.Error.WriteLine(
$"[LOADER][INFO] Ref scan hit target=0x{target:X16} rip=0x{instruction.Rip:X16} text={instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
}
}
rip += (ulong)instruction.Length;
}
}
private static List<ulong> ParseDiagnosticAddresses(string? rawValue)
{
var result = new List<ulong>();
if (string.IsNullOrWhiteSpace(rawValue))
{
return result;
}
foreach (var token in rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? token[2..]
: token;
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
{
continue;
}
if (!result.Contains(address))
{
result.Add(address);
}
}
return result;
}
private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size) private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size)
{ {
if (baseAddress < 0x10000 || size <= 0) if (baseAddress < 0x10000 || size <= 0)
@@ -237,6 +237,7 @@ public sealed partial class DirectExecutionBackend
try try
{ {
OrbisGen2Result orbisGen2Result; OrbisGen2Result orbisGen2Result;
bool dispatchResolved = true;
if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal))
{ {
orbisGen2Result = DispatchBootstrapBridge(); orbisGen2Result = DispatchBootstrapBridge();
@@ -247,34 +248,31 @@ public sealed partial class DirectExecutionBackend
} }
else else
{ {
orbisGen2Result = _moduleManager.Dispatch(importStubEntry.Nid, _cpuContext); dispatchResolved = _moduleManager.TryDispatch(importStubEntry.Nid, _cpuContext, out orbisGen2Result);
} }
switch (orbisGen2Result) if (!dispatchResolved)
{ {
case OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND: LastError = "Missing HLE export for NID: " + importStubEntry.Nid;
LastError = "Missing HLE export for NID: " + importStubEntry.Nid; Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}");
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}"); if (importStubEntry.Nid == "L-Q3LEjIbgA")
if (importStubEntry.Nid == "L-Q3LEjIbgA") {
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c)
{ {
string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c) int num10 = c;
{ return num10.ToString("X2");
int num10 = c; }));
return num10.ToString("X2"); Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]");
})); Delegate function;
Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]"); bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function);
Delegate function; ExportedFunction export2;
bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function); bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2);
ExportedFunction export2; Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}");
bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2); Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing");
Console.Error.WriteLine($"[LOADER][WARN] map_direct lookup with import nid: function={value19}, export={value20}"); }
Console.Error.WriteLine(_moduleManager.TryGetExport("L-Q3LEjIbgA", out ExportedFunction export3) ? $"[LOADER][WARN] Canonical map_direct exists as {export3.LibraryName}:{export3.Name}, target={export3.Target}, ctx_target={_cpuContext.TargetGeneration}" : "[LOADER][WARN] Canonical map_direct export lookup also missing"); }
} else if (orbisGen2Result != OrbisGen2Result.ORBIS_GEN2_OK)
break; {
default: Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})");
break;
case OrbisGen2Result.ORBIS_GEN2_OK:
break;
} }
_cpuContext[CpuRegister.Rbx] = value3; _cpuContext[CpuRegister.Rbx] = value3;
_cpuContext[CpuRegister.Rbp] = value4; _cpuContext[CpuRegister.Rbp] = value4;
@@ -334,7 +332,12 @@ public sealed partial class DirectExecutionBackend
{ {
return false; return false;
} }
RecordImportLoopSignature(BuildImportLoopSignature(nid, returnRip, arg0, arg1)); if (!_importNidHashCache.TryGetValue(nid, out var value))
{
value = StableHash64(nid);
_importNidHashCache[nid] = value;
}
RecordImportLoopSignature(value, returnRip, BuildImportLoopSignature(value, returnRip, arg0, arg1));
if (!HasRepeatingImportLoopPattern()) if (!HasRepeatingImportLoopPattern())
{ {
if (_importLoopPatternHits > 0) if (_importLoopPatternHits > 0)
@@ -347,21 +350,18 @@ public sealed partial class DirectExecutionBackend
return _importLoopPatternHits >= 6; return _importLoopPatternHits >= 6;
} }
private ulong BuildImportLoopSignature(string nid, ulong returnRip, ulong arg0, ulong arg1) private ulong BuildImportLoopSignature(ulong nidHash, ulong returnRip, ulong arg0, ulong arg1)
{ {
if (!_importNidHashCache.TryGetValue(nid, out var value))
{
value = StableHash64(nid);
_importNidHashCache[nid] = value;
}
ulong num = returnRip >> 2; ulong num = returnRip >> 2;
ulong num2 = ((arg0 >> 4) * 11400714819323198485uL) ^ ((arg1 >> 4) * 14029467366897019727uL); ulong num2 = ((arg0 >> 4) * 11400714819323198485uL) ^ ((arg1 >> 4) * 14029467366897019727uL);
return num ^ value * 11400714819323198485uL ^ num2; return num ^ nidHash * 11400714819323198485uL ^ num2;
} }
private void RecordImportLoopSignature(ulong signature) private void RecordImportLoopSignature(ulong nidHash, ulong returnRip, ulong signature)
{ {
_importLoopSignatures[_importLoopSignatureWriteIndex] = signature; _importLoopSignatures[_importLoopSignatureWriteIndex] = signature;
_importLoopNidHashes[_importLoopSignatureWriteIndex] = nidHash;
_importLoopReturnRips[_importLoopSignatureWriteIndex] = returnRip;
_importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _importLoopSignatures.Length; _importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _importLoopSignatures.Length;
if (_importLoopSignatureCount < _importLoopSignatures.Length) if (_importLoopSignatureCount < _importLoopSignatures.Length)
{ {
@@ -405,7 +405,7 @@ public sealed partial class DirectExecutionBackend
} }
} }
} }
return true; return IsSevereImportLoopPattern(num);
} }
private ulong GetImportLoopSignatureFromTail(int offset) private ulong GetImportLoopSignatureFromTail(int offset)
@@ -418,6 +418,51 @@ public sealed partial class DirectExecutionBackend
return _importLoopSignatures[num % _importLoopSignatures.Length]; return _importLoopSignatures[num % _importLoopSignatures.Length];
} }
private bool IsSevereImportLoopPattern(int sampleCount)
{
int num = CountDistinctImportLoopValuesFromTail(_importLoopNidHashes, sampleCount, 3);
if (num > 2)
{
return false;
}
int num2 = CountDistinctImportLoopValuesFromTail(_importLoopReturnRips, sampleCount, 3);
return num2 <= 2;
}
private int CountDistinctImportLoopValuesFromTail(ulong[] source, int sampleCount, int stopAfter)
{
int num = Math.Min(sampleCount, _importLoopSignatureCount);
int num2 = 0;
for (int i = 0; i < num; i++)
{
ulong importLoopValueFromTail = GetImportLoopValueFromTail(source, i);
bool flag = false;
for (int j = 0; j < i; j++)
{
if (GetImportLoopValueFromTail(source, j) == importLoopValueFromTail)
{
flag = true;
break;
}
}
if (!flag && ++num2 >= stopAfter)
{
return num2;
}
}
return num2;
}
private ulong GetImportLoopValueFromTail(ulong[] source, int offset)
{
int num = _importLoopSignatureWriteIndex - 1 - offset;
while (num < 0)
{
num += source.Length;
}
return source[num % source.Length];
}
private bool ShouldSuppressStrlenTrace(string nid) private bool ShouldSuppressStrlenTrace(string nid)
{ {
return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports; return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports;
@@ -89,6 +89,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private const uint PAGE_EXECUTE_READ = 32u; private const uint PAGE_EXECUTE_READ = 32u;
private const int TlsHandlerRegionSize = 4096;
private const ulong TlsModuleAllocStart = 140726751354880uL; private const ulong TlsModuleAllocStart = 140726751354880uL;
private const ulong TlsModuleAllocStride = 65536uL; private const ulong TlsModuleAllocStride = 65536uL;
@@ -101,6 +103,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private bool _ownsTlsBaseAddress; private bool _ownsTlsBaseAddress;
private int _tlsPatchStubOffset;
private nint _unresolvedReturnStub; private nint _unresolvedReturnStub;
private nint _rawExceptionHandler; private nint _rawExceptionHandler;
@@ -177,6 +181,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private readonly ulong[] _importLoopSignatures = new ulong[192]; private readonly ulong[] _importLoopSignatures = new ulong[192];
private readonly ulong[] _importLoopNidHashes = new ulong[192];
private readonly ulong[] _importLoopReturnRips = new ulong[192];
private int _importLoopSignatureCount; private int _importLoopSignatureCount;
private int _importLoopSignatureWriteIndex; private int _importLoopSignatureWriteIndex;
@@ -509,6 +517,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} not in HLE table, checking runtime symbols..."); Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} not in HLE table, checking runtime symbols...");
if (TryResolveRuntimeSymbolAddress(nid, out var directValue) && IsDirectImportTargetUsable(directValue))
{
targetAddress = directValue;
resolvedSymbol = nid;
Console.Error.WriteLine($"[LOADER][DEBUG] TryResolveDirectImportTarget: {nid} -> runtime symbol 0x{targetAddress:X16}");
return true;
}
if (Aerolib.Instance.TryGetByNid(nid, out var symbolByNid)) if (Aerolib.Instance.TryGetByNid(nid, out var symbolByNid))
{ {
if (!PreferLleForLibcExport(symbolByNid.ExportName)) if (!PreferLleForLibcExport(symbolByNid.ExportName))
@@ -587,9 +603,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
{ {
"_init_env" or "_init_env" or
"atexit" or "atexit" or
"__cxa_guard_acquire" or
"__cxa_guard_release" or
"__cxa_guard_abort" or
"strlen" or "strlen" or
"strnlen" or "strnlen" or
"strcmp" or "strcmp" or
@@ -841,10 +854,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
private unsafe void CreateTlsHandler() private unsafe void CreateTlsHandler()
{ {
_tlsHandlerAddress = (nint)TryAllocateNearEntry(256u); _tlsHandlerAddress = (nint)TryAllocateNearEntry(TlsHandlerRegionSize);
if (_tlsHandlerAddress == 0) if (_tlsHandlerAddress == 0)
{ {
_tlsHandlerAddress = (nint)VirtualAlloc(null, 256u, 12288u, 64u); _tlsHandlerAddress = (nint)VirtualAlloc(null, TlsHandlerRegionSize, 12288u, 64u);
} }
if (_tlsHandlerAddress == 0) if (_tlsHandlerAddress == 0)
{ {
@@ -857,9 +870,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
*(long*)(tlsHandlerAddress + num) = _tlsBaseAddress; *(long*)(tlsHandlerAddress + num) = _tlsBaseAddress;
num += 8; num += 8;
tlsHandlerAddress[num++] = 195; tlsHandlerAddress[num++] = 195;
_tlsPatchStubOffset = (num + 15) & ~15;
uint num2 = default(uint); uint num2 = default(uint);
VirtualProtect((void*)_tlsHandlerAddress, 256u, 32u, &num2); VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2);
FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 256u); FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, TlsHandlerRegionSize);
Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}"); Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}");
} }
@@ -939,6 +953,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
ulong num2 = num + MaxScanBytes; ulong num2 = num + MaxScanBytes;
int num3 = 0; int num3 = 0;
int num4 = 0; int num4 = 0;
int num9 = 0;
while (num < num2) while (num < num2)
{ {
if (VirtualQuery((void*)num, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || lpBuffer.RegionSize == 0) if (VirtualQuery((void*)num, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || lpBuffer.RegionSize == 0)
@@ -967,6 +982,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
num3++; num3++;
PatchTlsInstruction(address); PatchTlsInstruction(address);
} }
else if (TryPatchTlsImmediateStoreInstruction(address, ptr + i))
{
num9++;
}
else if (TryPatchStackCanaryInstruction(address, ptr + i)) else if (TryPatchStackCanaryInstruction(address, ptr + i))
{ {
num4++; num4++;
@@ -975,7 +994,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
num = num6 > num ? num6 : num + 4096uL; num = num6 > num ? num6 : num + 4096uL;
} }
Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS patterns, {num4} stack-canary accesses"); Console.Error.WriteLine($"[LOADER][INFO] Patched {num3} TLS loads, {num9} TLS stores, {num4} stack-canary accesses");
} }
private unsafe bool IsPatternMatch(byte* ptr, byte[] pattern) private unsafe bool IsPatternMatch(byte* ptr, byte[] pattern)
@@ -1089,6 +1108,115 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
} }
} }
private unsafe bool TryPatchTlsImmediateStoreInstruction(nint address, byte* source)
{
if (source[0] != 100 || source[1] != 199 || source[2] != 4 || source[3] != 37)
{
return false;
}
int tlsOffset = *(int*)(source + 4);
int immediateValue = *(int*)(source + 8);
nint num = CreateTlsImmediateStoreHelper(tlsOffset, immediateValue);
if (num == 0)
{
return false;
}
return PatchCallSite(address, 12, num);
}
private unsafe nint CreateTlsImmediateStoreHelper(int tlsOffset, int immediateValue)
{
nint num = AllocateTlsPatchStub(32);
if (num == 0)
{
return 0;
}
byte* ptr = (byte*)num;
int num2 = 0;
ptr[num2++] = 80;
ptr[num2++] = 232;
long num3 = _tlsHandlerAddress - (num + num2 + 4);
if (num3 < int.MinValue || num3 > int.MaxValue)
{
Console.Error.WriteLine($"[LOADER][WARNING] TLS store helper out of rel32 range at 0x{num:X16}");
return 0;
}
*(int*)(ptr + num2) = (int)num3;
num2 += 4;
ptr[num2++] = 199;
ptr[num2++] = 128;
*(int*)(ptr + num2) = tlsOffset;
num2 += 4;
*(int*)(ptr + num2) = immediateValue;
num2 += 4;
ptr[num2++] = 88;
ptr[num2++] = 195;
while (num2 < 32)
{
ptr[num2++] = 144;
}
uint flNewProtect = default(uint);
VirtualProtect((void*)num, 32u, 32u, &flNewProtect);
FlushInstructionCache(GetCurrentProcess(), (void*)num, 32u);
return num;
}
private unsafe nint AllocateTlsPatchStub(int size)
{
if (_tlsHandlerAddress == 0 || size <= 0)
{
return 0;
}
int num = (size + 15) & -16;
if (_tlsPatchStubOffset + num > TlsHandlerRegionSize)
{
Console.Error.WriteLine("[LOADER][WARNING] TLS patch stub region exhausted.");
return 0;
}
nint result = _tlsHandlerAddress + _tlsPatchStubOffset;
_tlsPatchStubOffset += num;
uint flNewProtect = default(uint);
if (!VirtualProtect((void*)result, (nuint)num, 64u, &flNewProtect))
{
return 0;
}
return result;
}
private unsafe bool PatchCallSite(nint address, int instructionLength, nint target)
{
if (instructionLength < 5)
{
return false;
}
uint flNewProtect = default(uint);
if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect))
{
return false;
}
try
{
long num = target - (address + 5);
if (num < int.MinValue || num > int.MaxValue)
{
Console.Error.WriteLine($"[LOADER][WARNING] TLS patch out of rel32 range at 0x{address:X16}");
return false;
}
*(byte*)address = 232;
*(int*)(address + 1) = (int)num;
for (int i = 5; i < instructionLength; i++)
{
*(byte*)(address + i) = 144;
}
}
finally
{
VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect);
FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength);
}
return true;
}
private unsafe void TryPreReservePrtAperture(ulong baseAddress, ulong size) private unsafe void TryPreReservePrtAperture(ulong baseAddress, ulong size)
{ {
if (VirtualQuery((void*)baseAddress, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 && lpBuffer.State != 65536) if (VirtualQuery((void*)baseAddress, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 && lpBuffer.State != 65536)
+12
View File
@@ -17,6 +17,9 @@ public sealed class SelfImage
IReadOnlyDictionary<ulong, string>? importStubs = null, IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null, IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
IReadOnlyList<ImportedSymbolRelocation>? importedRelocations = null, IReadOnlyList<ImportedSymbolRelocation>? importedRelocations = null,
IReadOnlyList<ulong>? preInitializerFunctions = null,
IReadOnlyList<ulong>? initializerFunctions = null,
ulong initFunctionEntryPoint = 0,
ulong imageBase = 0, ulong imageBase = 0,
ulong procParamAddress = 0) ulong procParamAddress = 0)
{ {
@@ -30,6 +33,9 @@ public sealed class SelfImage
ImportStubs = importStubs ?? new Dictionary<ulong, string>(); ImportStubs = importStubs ?? new Dictionary<ulong, string>();
RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal); RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal);
ImportedRelocations = importedRelocations ?? Array.Empty<ImportedSymbolRelocation>(); ImportedRelocations = importedRelocations ?? Array.Empty<ImportedSymbolRelocation>();
PreInitializerFunctions = preInitializerFunctions ?? Array.Empty<ulong>();
InitializerFunctions = initializerFunctions ?? Array.Empty<ulong>();
InitFunctionEntryPoint = initFunctionEntryPoint;
_imageBase = imageBase; _imageBase = imageBase;
ProcParamAddress = procParamAddress; ProcParamAddress = procParamAddress;
} }
@@ -48,6 +54,12 @@ public sealed class SelfImage
public IReadOnlyList<ImportedSymbolRelocation> ImportedRelocations { get; } public IReadOnlyList<ImportedSymbolRelocation> ImportedRelocations { get; }
public IReadOnlyList<ulong> PreInitializerFunctions { get; }
public IReadOnlyList<ulong> InitializerFunctions { get; }
public ulong InitFunctionEntryPoint { get; }
public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase; public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase;
public ulong ProcParamAddress { get; } public ulong ProcParamAddress { get; }
+197 -4
View File
@@ -38,8 +38,13 @@ public sealed class SelfLoader : ISelfLoader
private const long DtSymTab = 0x06; private const long DtSymTab = 0x06;
private const long DtRela = 0x07; private const long DtRela = 0x07;
private const long DtRelaSize = 0x08; private const long DtRelaSize = 0x08;
private const long DtInit = 0x0C;
private const long DtStrSize = 0x0A; private const long DtStrSize = 0x0A;
private const long DtJmpRel = 0x17; private const long DtJmpRel = 0x17;
private const long DtInitArray = 0x19;
private const long DtInitArraySize = 0x1B;
private const long DtPreInitArray = 0x20;
private const long DtPreInitArraySize = 0x21;
private const long DtSceJmpRel = 0x61000029; private const long DtSceJmpRel = 0x61000029;
private const long DtScePltRelSize = 0x6100002D; private const long DtScePltRelSize = 0x6100002D;
private const long DtSceRela = 0x6100002F; private const long DtSceRela = 0x6100002F;
@@ -61,8 +66,8 @@ public sealed class SelfLoader : ISelfLoader
private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL; private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL;
private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL; private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL;
private const ulong ModulePlacementStep = 0x00200000UL; private const ulong ModulePlacementStep = 0x00200000UL;
private const ulong FocusRelocGuestStart = 0x00000008030FC300UL; private const ulong FocusRelocGuestStart = 0x0000000807BA25B0UL;
private const ulong FocusRelocGuestEnd = 0x00000008030FC3F0UL; private const ulong FocusRelocGuestEnd = 0x0000000807BA2608UL;
private const byte SymbolBindLocal = 0; private const byte SymbolBindLocal = 0;
private const byte SymbolBindGlobal = 1; private const byte SymbolBindGlobal = 1;
private const byte SymbolBindWeak = 2; private const byte SymbolBindWeak = 2;
@@ -74,6 +79,7 @@ public sealed class SelfLoader : ISelfLoader
private static readonly IReadOnlyDictionary<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>(); private static readonly IReadOnlyDictionary<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>();
private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols = private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols =
new Dictionary<string, ulong>(StringComparer.Ordinal); new Dictionary<string, ulong>(StringComparer.Ordinal);
private static readonly IReadOnlyList<ulong> EmptyInitializerFunctions = Array.Empty<ulong>();
private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>(); private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>();
private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>(); private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>();
private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>(); private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>();
@@ -218,6 +224,15 @@ public sealed class SelfLoader : ISelfLoader
var finalizedRuntimeSymbols = runtimeSymbols.Count == 0 var finalizedRuntimeSymbols = runtimeSymbols.Count == 0
? EmptyRuntimeSymbols ? EmptyRuntimeSymbols
: runtimeSymbols; : runtimeSymbols;
CollectInitializerFunctions(
imageData,
loadContext,
programHeaders,
virtualMemory,
imageBase,
out var initFunctionEntryPoint,
out var preInitializerFunctions,
out var initializerFunctions);
var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase); var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase);
Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}"); Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}");
@@ -251,6 +266,9 @@ public sealed class SelfLoader : ISelfLoader
finalizedImportStubs, finalizedImportStubs,
finalizedRuntimeSymbols, finalizedRuntimeSymbols,
importedRelocations, importedRelocations,
preInitializerFunctions,
initializerFunctions,
initFunctionEntryPoint,
imageBase, imageBase,
procParamAddress); procParamAddress);
} }
@@ -929,6 +947,131 @@ public sealed class SelfLoader : ISelfLoader
} }
} }
private static void CollectInitializerFunctions(
ReadOnlySpan<byte> imageData,
LoadContext loadContext,
IReadOnlyList<ProgramHeader> programHeaders,
IVirtualMemory virtualMemory,
ulong imageBase,
out ulong initFunctionEntryPoint,
out IReadOnlyList<ulong> preInitializerFunctions,
out IReadOnlyList<ulong> initializerFunctions)
{
initFunctionEntryPoint = 0;
preInitializerFunctions = EmptyInitializerFunctions;
initializerFunctions = EmptyInitializerFunctions;
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex) ||
dynamicHeader.FileSize == 0)
{
return;
}
if (!TryLoadDynamicTableBytes(
imageData,
loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return;
}
var dynamicInfo = ParseDynamicInfo(dynamicTable);
var preInitializers = new List<ulong>(4);
var initializers = new List<ulong>(8);
initFunctionEntryPoint = ResolveMappedAddressOrFallback(virtualMemory, dynamicInfo.InitOffset, imageBase);
if (initFunctionEntryPoint < 0x10000)
{
initFunctionEntryPoint = 0;
}
AppendInitializerArrayEntries(
preInitializers,
imageData,
virtualMemory,
imageBase,
dynamicInfo.PreInitArrayOffset,
dynamicInfo.PreInitArraySize);
AppendResolvedInitializer(initializers, dynamicInfo.InitOffset, virtualMemory, imageBase);
AppendInitializerArrayEntries(
initializers,
imageData,
virtualMemory,
imageBase,
dynamicInfo.InitArrayOffset,
dynamicInfo.InitArraySize);
if (preInitializers.Count != 0)
{
preInitializerFunctions = preInitializers;
}
if (initializers.Count != 0)
{
initializerFunctions = initializers;
}
if (preInitializers.Count != 0 || initializers.Count != 0)
{
Console.Error.WriteLine(
$"[LOADER] Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
}
}
private static void AppendInitializerArrayEntries(
ICollection<ulong> destination,
ReadOnlySpan<byte> imageData,
IVirtualMemory virtualMemory,
ulong imageBase,
ulong arrayOffset,
ulong arraySize)
{
if (arrayOffset == 0 || arraySize < sizeof(ulong))
{
return;
}
if (!TryLoadTableBytes(imageData, virtualMemory, imageBase, arrayOffset, arraySize, out var arrayBytes))
{
return;
}
var entryCount = arrayBytes.Length / sizeof(ulong);
for (var i = 0; i < entryCount; i++)
{
var entryOffset = i * sizeof(ulong);
var entryAddress = BinaryPrimitives.ReadUInt64LittleEndian(arrayBytes.AsSpan(entryOffset, sizeof(ulong)));
AppendResolvedInitializer(destination, entryAddress, virtualMemory, imageBase);
}
}
private static void AppendResolvedInitializer(
ICollection<ulong> destination,
ulong functionAddress,
IVirtualMemory virtualMemory,
ulong imageBase)
{
var resolvedAddress = ResolveMappedAddressOrFallback(virtualMemory, functionAddress, imageBase);
if (resolvedAddress < 0x10000)
{
return;
}
foreach (var existing in destination)
{
if (existing == resolvedAddress)
{
return;
}
}
destination.Add(resolvedAddress);
}
private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations( private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations(
IReadOnlyList<RelocationDescriptor> descriptors) IReadOnlyList<RelocationDescriptor> descriptors)
{ {
@@ -1371,11 +1514,16 @@ public sealed class SelfLoader : ISelfLoader
ulong strTabSize = 0; ulong strTabSize = 0;
ulong symTabOffset = 0; ulong symTabOffset = 0;
ulong symTabSize = 0; ulong symTabSize = 0;
ulong initOffset = 0;
ulong relaOffset = 0; ulong relaOffset = 0;
ulong relaSize = 0; ulong relaSize = 0;
ulong jmpRelOffset = 0; ulong jmpRelOffset = 0;
ulong jmpRelSize = 0; ulong jmpRelSize = 0;
ulong pltGotOffset = 0; ulong pltGotOffset = 0;
ulong initArrayOffset = 0;
ulong initArraySize = 0;
ulong preInitArrayOffset = 0;
ulong preInitArraySize = 0;
for (var offset = 0; offset + DynamicEntrySize <= dynamicTable.Length; offset += DynamicEntrySize) for (var offset = 0; offset + DynamicEntrySize <= dynamicTable.Length; offset += DynamicEntrySize)
{ {
@@ -1422,6 +1570,13 @@ public sealed class SelfLoader : ISelfLoader
relaSize = value; relaSize = value;
} }
break;
case DtInit:
if (initOffset == 0)
{
initOffset = value;
}
break; break;
case DtJmpRel: case DtJmpRel:
if (jmpRelOffset == 0) if (jmpRelOffset == 0)
@@ -1429,6 +1584,34 @@ public sealed class SelfLoader : ISelfLoader
jmpRelOffset = value; jmpRelOffset = value;
} }
break;
case DtInitArray:
if (initArrayOffset == 0)
{
initArrayOffset = value;
}
break;
case DtInitArraySize:
if (initArraySize == 0)
{
initArraySize = value;
}
break;
case DtPreInitArray:
if (preInitArrayOffset == 0)
{
preInitArrayOffset = value;
}
break;
case DtPreInitArraySize:
if (preInitArraySize == 0)
{
preInitArraySize = value;
}
break; break;
case DtPltRelSize: case DtPltRelSize:
if (jmpRelSize == 0) if (jmpRelSize == 0)
@@ -1476,11 +1659,16 @@ public sealed class SelfLoader : ISelfLoader
strTabSize, strTabSize,
symTabOffset, symTabOffset,
symTabSize, symTabSize,
initOffset,
relaOffset, relaOffset,
relaSize, relaSize,
jmpRelOffset, jmpRelOffset,
jmpRelSize, jmpRelSize,
pltGotOffset); pltGotOffset,
initArrayOffset,
initArraySize,
preInitArrayOffset,
preInitArraySize);
} }
private static bool IsSupportedRelocationType(uint relocationType) private static bool IsSupportedRelocationType(uint relocationType)
@@ -2087,11 +2275,16 @@ public sealed class SelfLoader : ISelfLoader
ulong StrTabSize, ulong StrTabSize,
ulong SymTabOffset, ulong SymTabOffset,
ulong SymTabSize, ulong SymTabSize,
ulong InitOffset,
ulong RelaOffset, ulong RelaOffset,
ulong RelaSize, ulong RelaSize,
ulong JmpRelOffset, ulong JmpRelOffset,
ulong JmpRelSize, ulong JmpRelSize,
ulong PltGotOffset) ulong PltGotOffset,
ulong InitArrayOffset,
ulong InitArraySize,
ulong PreInitArrayOffset,
ulong PreInitArraySize)
{ {
public bool HasImportMetadata => public bool HasImportMetadata =>
StrTabOffset != 0 && StrTabOffset != 0 &&
@@ -11,6 +11,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
{ {
private readonly object _gate = new(); private readonly object _gate = new();
private readonly List<MemoryRegion> _regions = new(); private readonly List<MemoryRegion> _regions = new();
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
private bool _disposed; private bool _disposed;
private const ulong PageSize = 0x1000; private const ulong PageSize = 0x1000;
private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB private const ulong LargeDataReserveThreshold = 0x4000_0000UL; // 1 GiB
@@ -24,6 +25,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
private const uint PAGE_EXECUTE_READWRITE = 0x40; private const uint PAGE_EXECUTE_READWRITE = 0x40;
private const uint PAGE_EXECUTE = 0x10; private const uint PAGE_EXECUTE = 0x10;
private const uint PAGE_EXECUTE_WRITECOPY = 0x80; private const uint PAGE_EXECUTE_WRITECOPY = 0x80;
private const uint PAGE_NOACCESS = 0x01;
private const uint PAGE_READWRITE = 0x04; private const uint PAGE_READWRITE = 0x04;
private const uint PAGE_READONLY = 0x02; private const uint PAGE_READONLY = 0x02;
@@ -124,7 +126,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
Console.Error.WriteLine($"[VMEM] 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); result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection);
if (result == null) if (result == null)
{ {
if (!executable) if (!executable)
@@ -219,6 +221,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE); VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE);
} }
_regions.Clear(); _regions.Clear();
_pageProtections.Clear();
} }
} }
@@ -264,20 +267,35 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
NativeMemory.Clear((void*)(virtualAddress + (ulong)fileData.Length), (nuint)zeroFillSize); NativeMemory.Clear((void*)(virtualAddress + (ulong)fileData.Length), (nuint)zeroFillSize);
} }
SetProtection(mapStart, mapSize, protection); ApplySegmentProtection(mapStart, mapEnd, protection);
Console.Error.WriteLine($"[VMEM] 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})");
} }
} }
private void ApplySegmentProtection(ulong mapStart, ulong mapEnd, ProgramHeaderFlags flags)
{
for (var pageAddress = mapStart; pageAddress < mapEnd; pageAddress += PageSize)
{
_pageProtections.TryGetValue(pageAddress, out var existingFlags);
var mergedFlags = existingFlags | flags;
_pageProtections[pageAddress] = mergedFlags;
SetProtection(pageAddress, PageSize, mergedFlags);
}
}
private void SetProtection(ulong address, ulong size, ProgramHeaderFlags flags) private void SetProtection(ulong address, ulong size, ProgramHeaderFlags flags)
{ {
uint protection; uint protection;
if ((flags & ProgramHeaderFlags.Execute) != 0) if (flags == ProgramHeaderFlags.None)
{ {
protection = (flags & ProgramHeaderFlags.Write) != 0 protection = PAGE_NOACCESS;
? PAGE_EXECUTE_READWRITE }
else if ((flags & ProgramHeaderFlags.Execute) != 0)
{
protection = (flags & ProgramHeaderFlags.Write) != 0
? PAGE_EXECUTE_READWRITE
: PAGE_EXECUTE_READ; : PAGE_EXECUTE_READ;
} }
else if ((flags & ProgramHeaderFlags.Write) != 0) else if ((flags & ProgramHeaderFlags.Write) != 0)
@@ -309,10 +327,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
{ {
var r = _regions[i]; var r = _regions[i];
snapshot[i] = new VirtualMemoryRegion( snapshot[i] = new VirtualMemoryRegion(
r.VirtualAddress, r.VirtualAddress,
r.Size, r.Size,
0, 0,
r.Size, r.Size,
r.IsExecutable ? ProgramHeaderFlags.Execute | ProgramHeaderFlags.Read : ProgramHeaderFlags.Read); r.IsExecutable ? ProgramHeaderFlags.Execute | ProgramHeaderFlags.Read : ProgramHeaderFlags.Read);
} }
return snapshot; return snapshot;
@@ -328,10 +346,28 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset)) if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset))
{ {
var srcPtr = (void*)(region.VirtualAddress + offset); var srcPtr = (void*)(region.VirtualAddress + offset);
fixed (byte* destPtr = destination) if (destination.IsEmpty)
{ {
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length); return true;
} }
if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages))
{
return false;
}
try
{
fixed (byte* destPtr = destination)
{
Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length);
}
}
finally
{
RestorePageProtections(touchedPages);
}
return true; return true;
} }
} }
@@ -394,7 +430,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
{ {
foreach (var region in _regions) foreach (var region in _regions)
{ {
if (virtualAddress >= region.VirtualAddress && if (virtualAddress >= region.VirtualAddress &&
virtualAddress < region.VirtualAddress + region.Size) virtualAddress < region.VirtualAddress + region.Size)
{ {
return (void*)virtualAddress; return (void*)virtualAddress;
@@ -458,6 +494,41 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY; return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY;
} }
private bool TryTemporarilyProtectForRead(
ulong address,
ulong size,
MemoryRegion region,
out List<(ulong Address, uint Protection)> touchedPages)
{
touchedPages = new List<(ulong Address, uint Protection)>();
var startPage = AlignDown(address, PageSize);
var endPage = AlignUp(address + size, PageSize);
var temporaryProtection = region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize)
{
if (!VirtualProtect((void*)pageAddress, (nuint)PageSize, temporaryProtection, out var oldProtection))
{
RestorePageProtections(touchedPages);
touchedPages.Clear();
return false;
}
touchedPages.Add((pageAddress, oldProtection));
}
return true;
}
private static void RestorePageProtections(List<(ulong Address, uint Protection)> touchedPages)
{
foreach (var (pageAddress, protection) in touchedPages)
{
VirtualProtect((void*)pageAddress, (nuint)PageSize, protection, out _);
}
}
private static ulong AlignDown(ulong value, ulong alignment) private static ulong AlignDown(ulong value, ulong alignment)
{ {
var mask = alignment - 1; var mask = alignment - 1;
+203 -7
View File
@@ -19,6 +19,8 @@ namespace SharpEmu.Core.Runtime;
public sealed class SharpEmuRuntime : ISharpEmuRuntime public sealed class SharpEmuRuntime : ISharpEmuRuntime
{ {
private readonly record struct LoadedModuleImage(string Path, SelfImage Image);
private static readonly HashSet<string> PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase)
{ {
"libkernel.prx", "libkernel.prx",
@@ -138,14 +140,33 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4; var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4;
var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs); var activeImportStubs = new Dictionary<ulong, string>(image.ImportStubs);
var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal); var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal);
var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols);
var processImageName = Path.GetFileName(ebootPath); var processImageName = Path.GetFileName(ebootPath);
if (string.IsNullOrWhiteSpace(processImageName)) if (string.IsNullOrWhiteSpace(processImageName))
{ {
processImageName = "eboot.bin"; processImageName = "eboot.bin";
} }
HleDataSymbols.ConfigureProcessImageName(processImageName);
MergeKnownHleDataSymbols(activeRuntimeSymbols);
var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols);
var initializerResult = RunAllInitializers(
image,
loadedModuleImages,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
if (initializerResult is { } failedInitializerResult)
{
Console.Error.WriteLine($"[RUNTIME] Initializer dispatch failed: {failedInitializerResult}");
LastExecutionTrace = _cpuDispatcher.LastImportResolutionTrace;
LastMilestoneLog = _cpuDispatcher.LastMilestoneLog;
LastSessionSummary = BuildSessionSummary(_cpuDispatcher.LastSessionSummary);
LastBasicBlockTrace = _cpuDispatcher.LastBasicBlockTrace;
return failedInitializerResult;
}
Console.Error.WriteLine($"[RUNTIME] Dispatching, gen: {generation}"); Console.Error.WriteLine($"[RUNTIME] Dispatching, gen: {generation}");
Console.Error.WriteLine($"[RUNTIME] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}"); Console.Error.WriteLine($"[RUNTIME] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}");
@@ -329,12 +350,150 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
return result; return result;
} }
private List<SelfImage> LoadAdjacentSceModules( private OrbisGen2Result? RunAllInitializers(
SelfImage mainImage,
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
var moduleStartResult = RunPreloadedModuleInitializers(
loadedModuleImages,
generation,
activeImportStubs,
activeRuntimeSymbols);
if (moduleStartResult is not null)
{
return moduleStartResult;
}
// On current PS5 dumps DT_INIT commonly resolves to imageBase+0x10, which is inside
// the mapped ELF header rather than a callable guest routine. Startup must remain
// guest-driven until the PS5 init/module ABI is identified precisely.
return null;
}
private OrbisGen2Result? RunPreloadedModuleInitializers(
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols)
{
for (var i = 0; i < loadedModuleImages.Count; i++)
{
var loadedModule = loadedModuleImages[i];
var initEntryPoint = loadedModule.Image.InitFunctionEntryPoint;
if (initEntryPoint < 0x10000)
{
continue;
}
var moduleName = Path.GetFileName(loadedModule.Path);
if (string.IsNullOrWhiteSpace(moduleName))
{
moduleName = $"module#{i}";
}
Console.Error.WriteLine(
$"[RUNTIME] Starting module {moduleName}: dt_init=0x{initEntryPoint:X16}");
var result = _cpuDispatcher.DispatchModuleInitializer(
initEntryPoint,
generation,
activeImportStubs,
activeRuntimeSymbols,
moduleName,
_cpuExecutionOptions);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
Console.Error.WriteLine(
$"[RUNTIME] Module start failed: {moduleName} -> {result}");
return result;
}
}
return null;
}
private OrbisGen2Result? RunImageInitializers(
string label,
SelfImage image,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
if (image.PreInitializerFunctions.Count == 0 && image.InitializerFunctions.Count == 0)
{
return null;
}
Console.Error.WriteLine(
$"[RUNTIME] Running initializers for {label}: preinit={image.PreInitializerFunctions.Count}, init={image.InitializerFunctions.Count}");
var result = RunInitializerList(
$"{label}:preinit",
image.PreInitializerFunctions,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
if (result is not null)
{
return result;
}
return RunInitializerList(
$"{label}:init",
image.InitializerFunctions,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
}
private OrbisGen2Result? RunInitializerList(
string label,
IReadOnlyList<ulong> initializerFunctions,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
for (var i = 0; i < initializerFunctions.Count; i++)
{
var initializerAddress = initializerFunctions[i];
if (initializerAddress < 0x10000)
{
continue;
}
Console.Error.WriteLine(
$"[RUNTIME] Initializer {label}[{i}] -> 0x{initializerAddress:X16}");
var result = _cpuDispatcher.DispatchEntry(
initializerAddress,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName,
_cpuExecutionOptions);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
return result;
}
}
return null;
}
private List<LoadedModuleImage> LoadAdjacentSceModules(
string ebootPath, string ebootPath,
IDictionary<ulong, string> importStubs, IDictionary<ulong, string> importStubs,
IDictionary<string, ulong> runtimeSymbols) IDictionary<string, ulong> runtimeSymbols)
{ {
var loadedImages = new List<SelfImage>(); var loadedImages = new List<LoadedModuleImage>();
var ebootDirectory = Path.GetDirectoryName(ebootPath); var ebootDirectory = Path.GetDirectoryName(ebootPath);
if (string.IsNullOrWhiteSpace(ebootDirectory)) if (string.IsNullOrWhiteSpace(ebootDirectory))
{ {
@@ -418,7 +577,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath); mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath);
mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols); mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols);
RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false); RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false);
loadedImages.Add(moduleImage); loadedImages.Add(new LoadedModuleImage(modulePath, moduleImage));
loadedModules++; loadedModules++;
Console.Error.WriteLine( Console.Error.WriteLine(
@@ -438,7 +597,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
private void RebindImportedDataSymbols( private void RebindImportedDataSymbols(
SelfImage mainImage, SelfImage mainImage,
IReadOnlyList<SelfImage> loadedModuleImages, IReadOnlyList<LoadedModuleImage> loadedModuleImages,
IReadOnlyDictionary<string, ulong> runtimeSymbols) IReadOnlyDictionary<string, ulong> runtimeSymbols)
{ {
var rebound = 0; var rebound = 0;
@@ -447,7 +606,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
rebound += RebindImportedDataSymbols(mainImage, runtimeSymbols, ref unresolved); rebound += RebindImportedDataSymbols(mainImage, runtimeSymbols, ref unresolved);
for (var i = 0; i < loadedModuleImages.Count; i++) for (var i = 0; i < loadedModuleImages.Count; i++)
{ {
rebound += RebindImportedDataSymbols(loadedModuleImages[i], runtimeSymbols, ref unresolved); rebound += RebindImportedDataSymbols(loadedModuleImages[i].Image, runtimeSymbols, ref unresolved);
} }
if (rebound != 0 || unresolved != 0) if (rebound != 0 || unresolved != 0)
@@ -468,6 +627,10 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
} }
var rebound = 0; var rebound = 0;
var logRebind = string.Equals(
Environment.GetEnvironmentVariable("SHARPEMU_LOG_DATA_REBIND"),
"1",
StringComparison.Ordinal);
for (var i = 0; i < image.ImportedRelocations.Count; i++) for (var i = 0; i < image.ImportedRelocations.Count; i++)
{ {
var relocation = image.ImportedRelocations[i]; var relocation = image.ImportedRelocations[i];
@@ -479,6 +642,12 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
if (!runtimeSymbols.TryGetValue(relocation.Nid, out var symbolAddress) || if (!runtimeSymbols.TryGetValue(relocation.Nid, out var symbolAddress) ||
!IsUsableRuntimeSymbolAddress(symbolAddress)) !IsUsableRuntimeSymbolAddress(symbolAddress))
{ {
if (logRebind)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data unresolved: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} addend=0x{unchecked((ulong)relocation.Addend):X16}");
}
unresolved++; unresolved++;
continue; continue;
} }
@@ -486,16 +655,43 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime
var reboundValue = AddSigned(symbolAddress, relocation.Addend); var reboundValue = AddSigned(symbolAddress, relocation.Addend);
if (!TryWriteUInt64(_virtualMemory, relocation.TargetAddress, reboundValue)) if (!TryWriteUInt64(_virtualMemory, relocation.TargetAddress, reboundValue))
{ {
if (logRebind)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data write-failed: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
}
unresolved++; unresolved++;
continue; continue;
} }
if (logRebind)
{
Console.Error.WriteLine(
$"[RUNTIME] Imported data rebound: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}");
}
rebound++; rebound++;
} }
return rebound; return rebound;
} }
private static void MergeKnownHleDataSymbols(IDictionary<string, ulong> runtimeSymbols)
{
foreach (var nid in HleDataSymbols.EnumerateKnownNids())
{
if (runtimeSymbols.ContainsKey(nid) ||
!HleDataSymbols.TryGetAddress(nid, out var symbolAddress) ||
!IsUsableRuntimeSymbolAddress(symbolAddress))
{
continue;
}
runtimeSymbols[nid] = symbolAddress;
}
}
private static int MergeImportStubs( private static int MergeImportStubs(
IDictionary<ulong, string> destination, IDictionary<ulong, string> destination,
IReadOnlyDictionary<ulong, string> source, IReadOnlyDictionary<ulong, string> source,
+130
View File
@@ -0,0 +1,130 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using System.Text;
namespace SharpEmu.HLE;
public static class HleDataSymbols
{
private const string StackChkGuardNid = "f7uOxY9mM1U";
private const string ProgNameNid = "djxxOmW6-aw";
private const string LibcNeedFlagNid = "P330P3dFF68";
private const string LibcInternalNeedFlagNid = "ZT4ODD2Ts9o";
private const int ProgNameMaxBytes = 511;
private const ulong StackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
private static readonly object _gate = new();
private static readonly nint _stackChkGuardAddress = Allocate(sizeof(ulong) * 2);
private static readonly nint _progNameBufferAddress = Allocate(ProgNameMaxBytes + 1);
private static readonly nint _progNamePointerAddress = Allocate(nint.Size);
private static readonly nint _libcNeedFlagAddress = Allocate(sizeof(uint));
private static readonly nint _libcInternalNeedFlagAddress = Allocate(sizeof(uint));
static HleDataSymbols()
{
if (_stackChkGuardAddress != 0)
{
Marshal.WriteInt64(_stackChkGuardAddress, unchecked((long)StackChkGuardValue));
Marshal.WriteInt64(
IntPtr.Add(_stackChkGuardAddress, sizeof(ulong)),
unchecked((long)StackChkGuardValue));
}
if (_libcNeedFlagAddress != 0)
{
Marshal.WriteInt32(_libcNeedFlagAddress, 1);
}
if (_libcInternalNeedFlagAddress != 0)
{
Marshal.WriteInt32(_libcInternalNeedFlagAddress, 1);
}
ConfigureProcessImageName("eboot.bin");
}
public static IEnumerable<string> EnumerateKnownNids()
{
yield return StackChkGuardNid;
yield return ProgNameNid;
yield return LibcNeedFlagNid;
yield return LibcInternalNeedFlagNid;
}
public static void ConfigureProcessImageName(string? processImageName)
{
var effectiveName = string.IsNullOrWhiteSpace(processImageName)
? "eboot.bin"
: processImageName;
var encodedName = Encoding.UTF8.GetBytes(effectiveName);
var byteCount = Math.Min(encodedName.Length, ProgNameMaxBytes);
lock (_gate)
{
if (_progNameBufferAddress == 0 || _progNamePointerAddress == 0)
{
return;
}
for (var i = 0; i <= ProgNameMaxBytes; i++)
{
Marshal.WriteByte(_progNameBufferAddress, i, 0);
}
Marshal.Copy(encodedName, 0, _progNameBufferAddress, byteCount);
WritePointer(_progNamePointerAddress, _progNameBufferAddress);
}
}
public static bool TryGetAddress(string nid, out ulong address)
{
var pointer = nid switch
{
StackChkGuardNid => _stackChkGuardAddress,
ProgNameNid => _progNamePointerAddress,
LibcNeedFlagNid => _libcNeedFlagAddress,
LibcInternalNeedFlagNid => _libcInternalNeedFlagAddress,
_ => 0,
};
if (pointer == 0)
{
address = 0;
return false;
}
address = unchecked((ulong)pointer);
return true;
}
private static nint Allocate(int size)
{
try
{
var memory = Marshal.AllocHGlobal(size);
for (var i = 0; i < size; i++)
{
Marshal.WriteByte(memory, i, 0);
}
return memory;
}
catch
{
return 0;
}
}
private static void WritePointer(nint target, nint value)
{
if (nint.Size == sizeof(int))
{
Marshal.WriteInt32(target, value.ToInt32());
return;
}
Marshal.WriteInt64(target, value.ToInt64());
}
}
+2
View File
@@ -17,5 +17,7 @@ public interface IModuleManager
bool TryGetExportByName(string exportName, out ExportedFunction export); bool TryGetExportByName(string exportName, out ExportedFunction export);
bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result);
OrbisGen2Result Dispatch(string nid, CpuContext context); OrbisGen2Result Dispatch(string nid, CpuContext context);
} }
+12 -3
View File
@@ -93,6 +93,12 @@ public sealed class ModuleManager : IModuleManager
} }
public OrbisGen2Result Dispatch(string nid, CpuContext context) public OrbisGen2Result Dispatch(string nid, CpuContext context)
{
TryDispatch(nid, context, out var result);
return result;
}
public bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result)
{ {
ArgumentException.ThrowIfNullOrWhiteSpace(nid); ArgumentException.ThrowIfNullOrWhiteSpace(nid);
ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(context);
@@ -100,13 +106,15 @@ public sealed class ModuleManager : IModuleManager
if (!_dispatchTable.TryGetValue(nid, out var function) || !_exportTable.TryGetValue(nid, out var export)) 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); context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
return false;
} }
if ((export.Target & context.TargetGeneration) == 0) if ((export.Target & context.TargetGeneration) == 0)
{ {
context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); context[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
return false;
} }
context.ClearRaxWriteFlag(); context.ClearRaxWriteFlag();
@@ -117,7 +125,8 @@ public sealed class ModuleManager : IModuleManager
context[CpuRegister.Rax] = unchecked((ulong)ret); context[CpuRegister.Rax] = unchecked((ulong)ret);
} }
return (OrbisGen2Result)ret; result = (OrbisGen2Result)ret;
return true;
} }
private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances) private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary<Type, object> instances)
+5
View File
@@ -45,6 +45,11 @@ public enum OrbisGen2Result : int
/// </summary> /// </summary>
ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010), ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010),
/// <summary>
/// Indicates that the operation should be retried later.
/// </summary>
ORBIS_GEN2_ERROR_TRY_AGAIN = unchecked((int)0x80020023),
/// <summary> /// <summary>
/// Indicates that behavior is recognized but not implemented yet. /// Indicates that behavior is recognized but not implemented yet.
/// </summary> /// </summary>
+30 -29
View File
@@ -10,6 +10,10 @@ namespace SharpEmu.Libs.CxxAbi;
public static class CxaGuardExports public static class CxaGuardExports
{ {
private const ulong GuardCompleteValue = 0x0000_0000_0000_0001;
private const ulong GuardPendingValue = 0x0000_0000_0000_0100;
private const ulong GuardStateMask = 0x0000_0000_0000_FFFF;
private sealed class GuardState private sealed class GuardState
{ {
public int OwnerThreadId { get; set; } public int OwnerThreadId { get; set; }
@@ -36,13 +40,13 @@ public static class CxaGuardExports
var spinner = new SpinWait(); var spinner = new SpinWait();
while (true) while (true)
{ {
if (!TryReadGuardInitialized(ctx, guardPtr, out var initialized)) if (!TryReadGuardState(ctx, guardPtr, out _, out var initialized, out var inProgress))
{ {
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
LogGuardState(ctx, "guard_acquire", guardPtr, initialized); LogGuardState(ctx, "guard_acquire", guardPtr, initialized, inProgress);
if (initialized) if (initialized)
{ {
@@ -58,6 +62,13 @@ public static class CxaGuardExports
}; };
if (_inProgress.TryAdd(guardPtr, newState)) if (_inProgress.TryAdd(guardPtr, newState))
{ {
if (!TryWriteGuardState(ctx, guardPtr, GuardPendingValue))
{
_inProgress.TryRemove(guardPtr, out _);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 1; ctx[CpuRegister.Rax] = 1;
LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId); LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -117,14 +128,14 @@ public static class CxaGuardExports
} }
} }
if (!TryWriteGuardInitialized(ctx, guardPtr, initialized: true)) if (!TryWriteGuardState(ctx, guardPtr, GuardCompleteValue))
{ {
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
_inProgress.TryRemove(guardPtr, out _); _inProgress.TryRemove(guardPtr, out _);
LogGuardState(ctx, "guard_release", guardPtr, initialized: true); LogGuardState(ctx, "guard_release", guardPtr, initialized: true, inProgress: false);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -152,60 +163,50 @@ public static class CxaGuardExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
_ = TryWriteGuardInitialized(ctx, guardPtr, initialized: false); _ = TryWriteGuardState(ctx, guardPtr, 0);
_inProgress.TryRemove(guardPtr, out _); _inProgress.TryRemove(guardPtr, out _);
LogGuardState(ctx, "guard_abort", guardPtr, initialized: false); LogGuardState(ctx, "guard_abort", guardPtr, initialized: false, inProgress: false);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static bool TryReadGuardInitialized(CpuContext ctx, ulong guardPtr, out bool initialized) private static bool TryReadGuardState(CpuContext ctx, ulong guardPtr, out ulong word, out bool initialized, out bool inProgress)
{ {
word = 0;
initialized = false; initialized = false;
inProgress = false;
var aligned = guardPtr & ~7UL; if (!ctx.TryReadUInt64(guardPtr, out word))
var shift = (int)((guardPtr & 7UL) * 8);
if (!ctx.TryReadUInt64(aligned, out var word))
{ {
return false; return false;
} }
var b0 = (byte)((word >> shift) & 0xFF); initialized = (word & GuardCompleteValue) != 0;
initialized = (b0 & 0x01) != 0; inProgress = (word & 0x0000_0000_0000_FF00) != 0;
return true; return true;
} }
private static bool TryWriteGuardInitialized(CpuContext ctx, ulong guardPtr, bool initialized) private static bool TryWriteGuardState(CpuContext ctx, ulong guardPtr, ulong stateValue)
{ {
var aligned = guardPtr & ~7UL; if (!ctx.TryReadUInt64(guardPtr, out var word))
var shift = (int)((guardPtr & 7UL) * 8);
var mask = 0xFFUL << shift;
if (!ctx.TryReadUInt64(aligned, out var word))
{ {
return false; return false;
} }
var b0 = (byte)((word >> shift) & 0xFF); var newWord = (word & ~GuardStateMask) | (stateValue & GuardStateMask);
b0 = initialized ? (byte)(b0 | 0x01) : (byte)(b0 & ~0x01); return ctx.TryWriteUInt64(guardPtr, newWord);
var newWord = (word & ~mask) | ((ulong)b0 << shift);
return ctx.TryWriteUInt64(aligned, newWord);
} }
private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized) private static void LogGuardState(CpuContext ctx, string op, ulong guardPtr, bool initialized, bool inProgress)
{ {
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal)) if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUARDS"), "1", StringComparison.Ordinal))
{ {
return; return;
} }
var aligned = guardPtr & ~7UL; var readable = ctx.TryReadUInt64(guardPtr, out var word);
var readable = ctx.TryReadUInt64(aligned, out var word);
Console.Error.WriteLine( Console.Error.WriteLine(
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} aligned=0x{aligned:X16} init={initialized} word={(readable ? $"0x{word:X16}" : "<unreadable>")}"); $"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} init={initialized} in_progress={inProgress} word={(readable ? $"0x{word:X16}" : "<unreadable>")}");
} }
private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId) private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId)
+20
View File
@@ -11,6 +11,9 @@ public static class KernelExports
private static int _nextFileDescriptor = 2; private static int _nextFileDescriptor = 2;
private static readonly object _cxaGate = new(); private static readonly object _cxaGate = new();
private static readonly List<CxaDestructorEntry> _cxaDestructors = new(); private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
private static readonly object _coredumpGate = new();
private static ulong _coredumpHandler;
private static ulong _coredumpHandlerContext;
private readonly record struct CxaDestructorEntry( private readonly record struct CxaDestructorEntry(
ulong Function, ulong Function,
@@ -28,6 +31,23 @@ public static class KernelExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "8zLSfEfW5AU",
ExportName = "sceCoredumpRegisterCoredumpHandler",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceCoredump")]
public static int CoredumpRegisterHandler(CpuContext ctx)
{
lock (_coredumpGate)
{
_coredumpHandler = ctx[CpuRegister.Rdi];
_coredumpHandlerContext = ctx[CpuRegister.Rsi];
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport( [SysAbiExport(
Nid = "uMei1W9uyNo", Nid = "uMei1W9uyNo",
ExportName = "exit", ExportName = "exit",
@@ -21,6 +21,19 @@ public static class KernelMemoryCompatExports
private const int O_CREAT = 0x0200; private const int O_CREAT = 0x0200;
private const int O_TRUNC = 0x0400; private const int O_TRUNC = 0x0400;
private const int O_DIRECTORY = 0x00020000; private const int O_DIRECTORY = 0x00020000;
private const int OrbisKernelMapFixed = 0x0010;
private const int OrbisKernelMapOpMapDirect = 0;
private const int OrbisKernelMapOpUnmap = 1;
private const int OrbisKernelMapOpProtect = 2;
private const int OrbisKernelMapOpMapFlexible = 3;
private const int OrbisKernelMapOpTypeProtect = 4;
private const int OrbisKernelBatchMapEntrySize = 32;
private const int OrbisKernelBatchMapEntryStartOffset = 0;
private const int OrbisKernelBatchMapEntryOffsetOffset = 8;
private const int OrbisKernelBatchMapEntryLengthOffset = 16;
private const int OrbisKernelBatchMapEntryProtectionOffset = 24;
private const int OrbisKernelBatchMapEntryTypeOffset = 25;
private const int OrbisKernelBatchMapEntryOperationOffset = 28;
private const int SeekSet = 0; private const int SeekSet = 0;
private const int SeekCur = 1; private const int SeekCur = 1;
private const int SeekEnd = 2; private const int SeekEnd = 2;
@@ -87,6 +100,7 @@ public static class KernelMemoryCompatExports
private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType); private readonly record struct DirectAllocation(ulong Start, ulong Length, int MemoryType);
private readonly record struct LibcHeapAllocation(nint BaseAddress, nuint Size, nuint Alignment); private readonly record struct LibcHeapAllocation(nint BaseAddress, nuint Size, nuint Alignment);
private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart); private readonly record struct MappedRegion(ulong Address, ulong Length, int Protection, bool IsFlexible, ulong DirectStart);
private readonly record struct BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation);
[SysAbiExport( [SysAbiExport(
Nid = "8zTFvBIAIN8", Nid = "8zTFvBIAIN8",
@@ -993,17 +1007,11 @@ public static class KernelMemoryCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var candidate = AlignUp(Math.Max(searchStart, _nextPhysicalAddress), alignment); if (!TryFindAvailableDirectMemorySpanLocked(searchStart, searchEnd, alignment, out var candidate, out var rangeAvailable))
if (candidate >= searchEnd)
{ {
candidate = AlignUp(searchStart, alignment); return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
if (candidate >= searchEnd)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
} }
var rangeAvailable = searchEnd - candidate;
if (!ctx.TryWriteUInt64(outAddress, candidate) || !ctx.TryWriteUInt64(outSize, rangeAvailable)) if (!ctx.TryWriteUInt64(outAddress, candidate) || !ctx.TryWriteUInt64(outSize, rangeAvailable))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
@@ -1070,7 +1078,17 @@ public static class KernelMemoryCompatExports
var outAddress = ctx[CpuRegister.R9]; var outAddress = ctx[CpuRegister.R9];
if (length == 0 || outAddress == 0) if (length == 0 || outAddress == 0)
{
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
alignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var limit = DirectMemorySizeBytes; var limit = DirectMemorySizeBytes;
ulong searchStart; ulong searchStart;
@@ -1104,41 +1122,48 @@ public static class KernelMemoryCompatExports
} }
var align = alignment == 0 ? 0x1000UL : alignment; var align = alignment == 0 ? 0x1000UL : alignment;
var alignedStart = AlignUp(searchStart, align);
ulong selectedAddress; ulong selectedAddress;
lock (_memoryGate) lock (_memoryGate)
{ {
selectedAddress = AlignUp(Math.Max(alignedStart, _nextPhysicalAddress), align); if (!TryAllocateDirectMemoryLocked(searchStart, searchEnd, length, align, memoryType, out selectedAddress))
if (!TryAdd(selectedAddress, length, out var endAddr) ||
endAddr > searchEnd ||
endAddr > limit)
{ {
selectedAddress = alignedStart; TraceDirectMemoryCall(
ctx,
if (!TryAdd(selectedAddress, length, out endAddr) || "allocate_direct",
endAddr > searchEnd || length,
endAddr > limit) align,
{ memoryType,
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; outAddress,
} result: OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
} }
_directAllocations[selectedAddress] = new DirectAllocation(selectedAddress, length, memoryType);
_nextPhysicalAddress = selectedAddress + length;
} }
if (!ctx.TryWriteUInt64(outAddress, selectedAddress)) if (!ctx.TryWriteUInt64(outAddress, selectedAddress))
{
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
align,
memoryType,
outAddress,
selectedAddress,
OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
TraceDirectMemoryCall(
ctx,
"allocate_direct",
length,
align,
memoryType,
outAddress,
selectedAddress,
OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
static bool TryAdd(ulong a, ulong b, out ulong sum)
{
sum = a + b;
return sum >= a;
}
} }
[SysAbiExport( [SysAbiExport(
@@ -1154,26 +1179,59 @@ public static class KernelMemoryCompatExports
var outAddress = ctx[CpuRegister.Rcx]; var outAddress = ctx[CpuRegister.Rcx];
if (outAddress == 0 || length == 0) if (outAddress == 0 || length == 0)
{ {
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
alignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var aligned = AlignUp(_nextPhysicalAddress, alignment == 0 ? 0x1000UL : alignment); var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
ulong aligned;
lock (_memoryGate) lock (_memoryGate)
{ {
if (aligned + length > DirectMemorySizeBytes) if (!TryAllocateDirectMemoryLocked(0, DirectMemorySizeBytes, length, effectiveAlignment, memoryType, out aligned))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
result: OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
} }
_directAllocations[aligned] = new DirectAllocation(aligned, length, memoryType);
_nextPhysicalAddress = aligned + length;
} }
if (!ctx.TryWriteUInt64(outAddress, aligned)) if (!ctx.TryWriteUInt64(outAddress, aligned))
{ {
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
aligned,
OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
} }
TraceDirectMemoryCall(
ctx,
"allocate_main_direct",
length,
effectiveAlignment,
memoryType,
outAddress,
aligned,
OrbisGen2Result.ORBIS_GEN2_OK);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -1199,6 +1257,7 @@ public static class KernelMemoryCompatExports
} }
_directAllocations.Remove(start); _directAllocations.Remove(start);
_nextPhysicalAddress = GetDirectMemoryHighWaterMarkLocked();
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -1362,6 +1421,26 @@ public static class KernelMemoryCompatExports
return KernelMapNamedFlexibleMemory(ctx); return KernelMapNamedFlexibleMemory(ctx);
} }
[SysAbiExport(
Nid = "2SKEx6bSq-4",
ExportName = "sceKernelBatchMap",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelBatchMap(CpuContext ctx)
{
return KernelBatchMapCore(ctx, OrbisKernelMapFixed);
}
[SysAbiExport(
Nid = "kBJzF8x4SyE",
ExportName = "sceKernelBatchMap2",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelBatchMap2(CpuContext ctx)
{
return KernelBatchMapCore(ctx, unchecked((int)ctx[CpuRegister.Rcx]));
}
[SysAbiExport( [SysAbiExport(
Nid = "cQke9UuBQOk", Nid = "cQke9UuBQOk",
ExportName = "sceKernelMunmap", ExportName = "sceKernelMunmap",
@@ -1494,12 +1573,37 @@ public static class KernelMemoryCompatExports
lock (_memoryGate) lock (_memoryGate)
{ {
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length) if (!TryApplyMappedRegionProtectionLocked(address, length, protection))
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
}
_mappedRegions[address] = region with { Protection = protection }; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "9bfdLIyuwCY",
ExportName = "sceKernelMtypeprotect",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int KernelMtypeprotect(CpuContext ctx)
{
var address = ctx[CpuRegister.Rdi];
var length = ctx[CpuRegister.Rsi];
var memoryType = unchecked((int)ctx[CpuRegister.Rdx]);
var protection = unchecked((int)ctx[CpuRegister.Rcx]);
if (address == 0 || length == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
lock (_memoryGate)
{
if (!TryApplyMappedRegionProtectionLocked(address, length, protection, memoryType))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
} }
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -2460,6 +2564,364 @@ public static class KernelMemoryCompatExports
return TryWriteCompat(ctx, address, bytes); return TryWriteCompat(ctx, address, bytes);
} }
private static int KernelBatchMapCore(CpuContext ctx, int flags)
{
var entriesAddress = ctx[CpuRegister.Rdi];
var entryCount = unchecked((int)ctx[CpuRegister.Rsi]);
var processedOutAddress = ctx[CpuRegister.Rdx];
var processedCount = 0;
var result = (int)OrbisGen2Result.ORBIS_GEN2_OK;
for (var index = 0; index < entryCount; index++)
{
var entryAddress = entriesAddress + (ulong)(index * OrbisKernelBatchMapEntrySize);
if (!TryReadBatchMapEntry(ctx, entryAddress, out var entry) ||
entry.Length == 0 ||
entry.Operation < OrbisKernelMapOpMapDirect ||
entry.Operation > OrbisKernelMapOpTypeProtect)
{
result = (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
break;
}
result = entry.Operation switch
{
OrbisKernelMapOpMapDirect => InvokeKernelMemoryOperation(
ctx,
KernelMapDirectMemory,
entryAddress + OrbisKernelBatchMapEntryStartOffset,
entry.Length,
entry.Protection,
unchecked((ulong)(uint)flags),
entry.Offset,
0),
OrbisKernelMapOpUnmap => InvokeKernelMemoryOperation(
ctx,
KernelMunmap,
entry.Start,
entry.Length),
OrbisKernelMapOpProtect => InvokeKernelMemoryOperation(
ctx,
KernelMprotect,
entry.Start,
entry.Length,
entry.Protection),
OrbisKernelMapOpMapFlexible => InvokeKernelMemoryOperation(
ctx,
KernelMapNamedFlexibleMemory,
entryAddress + OrbisKernelBatchMapEntryStartOffset,
entry.Length,
entry.Protection,
unchecked((ulong)(uint)flags)),
OrbisKernelMapOpTypeProtect => InvokeKernelMemoryOperation(
ctx,
KernelMtypeprotect,
entry.Start,
entry.Length,
entry.Type,
entry.Protection),
_ => (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
};
if (result != (int)OrbisGen2Result.ORBIS_GEN2_OK)
{
break;
}
processedCount++;
}
if (processedOutAddress != 0 && !TryWriteInt32(ctx, processedOutAddress, processedCount))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
return result;
}
private static int InvokeKernelMemoryOperation(
CpuContext ctx,
Func<CpuContext, int> operation,
ulong rdi = 0,
ulong rsi = 0,
ulong rdx = 0,
ulong rcx = 0,
ulong r8 = 0,
ulong r9 = 0)
{
var savedRdi = ctx[CpuRegister.Rdi];
var savedRsi = ctx[CpuRegister.Rsi];
var savedRdx = ctx[CpuRegister.Rdx];
var savedRcx = ctx[CpuRegister.Rcx];
var savedR8 = ctx[CpuRegister.R8];
var savedR9 = ctx[CpuRegister.R9];
ctx[CpuRegister.Rdi] = rdi;
ctx[CpuRegister.Rsi] = rsi;
ctx[CpuRegister.Rdx] = rdx;
ctx[CpuRegister.Rcx] = rcx;
ctx[CpuRegister.R8] = r8;
ctx[CpuRegister.R9] = r9;
try
{
return operation(ctx);
}
finally
{
ctx[CpuRegister.Rdi] = savedRdi;
ctx[CpuRegister.Rsi] = savedRsi;
ctx[CpuRegister.Rdx] = savedRdx;
ctx[CpuRegister.Rcx] = savedRcx;
ctx[CpuRegister.R8] = savedR8;
ctx[CpuRegister.R9] = savedR9;
}
}
private static bool TryReadBatchMapEntry(CpuContext ctx, ulong entryAddress, out BatchMapEntry entry)
{
entry = default;
if (!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryStartOffset, out var start) ||
!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryOffsetOffset, out var offset) ||
!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryLengthOffset, out var length))
{
return false;
}
Span<byte> protection = stackalloc byte[1];
Span<byte> memoryType = stackalloc byte[1];
if (!TryReadCompat(ctx, entryAddress + OrbisKernelBatchMapEntryProtectionOffset, protection) ||
!TryReadCompat(ctx, entryAddress + OrbisKernelBatchMapEntryTypeOffset, memoryType) ||
!TryReadUInt32Compat(ctx, entryAddress + OrbisKernelBatchMapEntryOperationOffset, out var operation))
{
return false;
}
entry = new BatchMapEntry(start, offset, length, protection[0], memoryType[0], unchecked((int)operation));
return true;
}
private static bool TryApplyMappedRegionProtectionLocked(
ulong address,
ulong length,
int protection,
int? memoryType = null)
{
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length)
{
return false;
}
_mappedRegions[address] = region with { Protection = protection };
if (memoryType.HasValue &&
region.DirectStart != 0 &&
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
{
_directAllocations[region.DirectStart] = allocation with { MemoryType = memoryType.Value };
}
return true;
}
private static void TraceDirectMemoryCall(
CpuContext ctx,
string operation,
ulong length,
ulong alignment,
int memoryType,
ulong outAddress,
ulong selectedAddress = 0,
OrbisGen2Result? result = null)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
var stackPointer = ctx[CpuRegister.Rsp];
if (stackPointer != 0)
{
_ = ctx.TryReadUInt64(stackPointer, out returnRip);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
}
private static bool TryAllocateDirectMemoryLocked(
ulong searchStart,
ulong searchEnd,
ulong length,
ulong alignment,
int memoryType,
out ulong selectedAddress)
{
selectedAddress = 0;
if (length == 0 || searchStart >= searchEnd)
{
return false;
}
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
if (!TryFindAllocatableDirectMemoryRangeLocked(searchStart, searchEnd, length, effectiveAlignment, out var freePosition) ||
!TryAddU64(freePosition, length, out var endAddress))
{
return false;
}
_directAllocations[freePosition] = new DirectAllocation(freePosition, length, memoryType);
_nextPhysicalAddress = endAddress;
selectedAddress = freePosition;
return true;
}
private static bool TryFindAllocatableDirectMemoryRangeLocked(
ulong searchStart,
ulong searchEnd,
ulong length,
ulong alignment,
out ulong selectedAddress)
{
selectedAddress = 0;
if (length == 0 || searchStart >= searchEnd)
{
return false;
}
var effectiveEnd = Math.Min(searchEnd, DirectMemorySizeBytes);
var candidate = AlignUp(searchStart, alignment);
if (candidate >= effectiveEnd)
{
return false;
}
var allocations = new List<DirectAllocation>(_directAllocations.Values);
allocations.Sort(static (left, right) => left.Start.CompareTo(right.Start));
foreach (var allocation in allocations)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var allocationEnd))
{
return false;
}
if (allocationEnd <= candidate)
{
continue;
}
var gapEnd = Math.Min(allocation.Start, effectiveEnd);
if (candidate < gapEnd &&
TryAddU64(candidate, length, out var candidateEnd) &&
candidateEnd <= gapEnd)
{
selectedAddress = candidate;
return true;
}
if (allocation.Start >= effectiveEnd)
{
break;
}
candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment);
if (candidate >= effectiveEnd)
{
return false;
}
}
if (!TryAddU64(candidate, length, out var endAddress) || endAddress > effectiveEnd)
{
return false;
}
selectedAddress = candidate;
return true;
}
private static bool TryFindAvailableDirectMemorySpanLocked(
ulong searchStart,
ulong searchEnd,
ulong alignment,
out ulong spanStart,
out ulong spanLength)
{
spanStart = 0;
spanLength = 0;
if (searchStart >= searchEnd)
{
return false;
}
var effectiveEnd = Math.Min(searchEnd, DirectMemorySizeBytes);
var candidate = AlignUp(searchStart, alignment);
if (candidate >= effectiveEnd)
{
return false;
}
var allocations = new List<DirectAllocation>(_directAllocations.Values);
allocations.Sort(static (left, right) => left.Start.CompareTo(right.Start));
foreach (var allocation in allocations)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var allocationEnd))
{
return false;
}
if (allocationEnd <= candidate)
{
continue;
}
var gapEnd = Math.Min(allocation.Start, effectiveEnd);
if (candidate < gapEnd)
{
spanStart = candidate;
spanLength = gapEnd - candidate;
return true;
}
if (allocation.Start >= effectiveEnd)
{
break;
}
candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment);
if (candidate >= effectiveEnd)
{
return false;
}
}
spanStart = candidate;
spanLength = effectiveEnd - candidate;
return spanLength != 0;
}
private static ulong GetDirectMemoryHighWaterMarkLocked()
{
ulong highWaterMark = 0;
foreach (var allocation in _directAllocations.Values)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var endAddress))
{
return DirectMemorySizeBytes;
}
if (endAddress > highWaterMark)
{
highWaterMark = endAddress;
}
}
return Math.Min(highWaterMark, DirectMemorySizeBytes);
}
private static bool TryReadHostMemory(ulong address, Span<byte> destination) private static bool TryReadHostMemory(ulong address, Span<byte> destination)
{ {
if (destination.IsEmpty || !IsHostRangeAccessible(address, (ulong)destination.Length, writeAccess: false)) if (destination.IsEmpty || !IsHostRangeAccessible(address, (ulong)destination.Length, writeAccess: false))
@@ -2808,4 +3270,10 @@ public static class KernelMemoryCompatExports
var mask = alignment - 1; var mask = alignment - 1;
return (value + mask) & ~mask; return (value + mask) & ~mask;
} }
private static bool TryAddU64(ulong left, ulong right, out ulong sum)
{
sum = left + right;
return sum >= left;
}
} }
@@ -14,6 +14,7 @@ public static class KernelPthreadCompatExports
private const int MutexTypeNormal = 4; private const int MutexTypeNormal = 4;
private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000; private const ulong SyntheticMutexHandleBase = 0x00006000_0000_0000;
private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000; private const ulong SyntheticMutexAttrHandleBase = 0x00006001_0000_0000;
private const ulong SyntheticCondHandleBase = 0x00006002_0000_0000;
private static readonly object _stateGate = new(); private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new(); private static readonly Dictionary<ulong, PthreadMutexState> _mutexStates = new();
@@ -22,6 +23,7 @@ public static class KernelPthreadCompatExports
private static readonly HashSet<ulong> _condAttrStates = new(); private static readonly HashSet<ulong> _condAttrStates = new();
private static long _nextSyntheticMutexHandleId = 1; private static long _nextSyntheticMutexHandleId = 1;
private static long _nextSyntheticMutexAttrHandleId = 1; private static long _nextSyntheticMutexAttrHandleId = 1;
private static long _nextSyntheticCondHandleId = 1;
private sealed class PthreadMutexState private sealed class PthreadMutexState
{ {
@@ -228,14 +230,14 @@ public static class KernelPthreadCompatExports
ExportName = "scePthreadCondInit", ExportName = "scePthreadCondInit",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx[CpuRegister.Rdi]); public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport( [SysAbiExport(
Nid = "g+PZd2hiacg", Nid = "g+PZd2hiacg",
ExportName = "scePthreadCondDestroy", ExportName = "scePthreadCondDestroy",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx[CpuRegister.Rdi]); public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx, ctx[CpuRegister.Rdi]);
[SysAbiExport( [SysAbiExport(
Nid = "WKAXJ4XBPQ4", Nid = "WKAXJ4XBPQ4",
@@ -370,6 +372,7 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
_ = ctx.TryWriteUInt64(mutexAddress, 0);
state.Semaphore.Dispose(); state.Semaphore.Dispose();
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -381,20 +384,10 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state))
PthreadMutexState state;
lock (_stateGate)
{ {
if (!_mutexStates.TryGetValue(resolvedAddress, out state!)) TracePthreadMutex(ctx, tryOnly ? "trylock" : "lock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
{ return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
state = new PthreadMutexState();
_mutexStates[resolvedAddress] = state;
}
if (resolvedAddress != mutexAddress)
{
_mutexStates[mutexAddress] = state;
}
} }
var currentThreadId = KernelPthreadState.GetCurrentThreadHandle(); var currentThreadId = KernelPthreadState.GetCurrentThreadHandle();
@@ -449,14 +442,7 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state))
PthreadMutexState? state;
lock (_stateGate)
{
_mutexStates.TryGetValue(resolvedAddress, out state);
}
if (state is null)
{ {
TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND); TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
@@ -620,6 +606,65 @@ public static class KernelPthreadCompatExports
return mutexAddress; return mutexAddress;
} }
private static bool TryResolveMutexState(CpuContext ctx, ulong mutexAddress, bool createIfZero, out ulong resolvedAddress, out PthreadMutexState? state)
{
resolvedAddress = 0;
state = null;
if (mutexAddress == 0)
{
return false;
}
lock (_stateGate)
{
if (_mutexStates.TryGetValue(mutexAddress, out state))
{
resolvedAddress = mutexAddress;
return true;
}
}
if (!ctx.TryReadUInt64(mutexAddress, out var pointedHandle))
{
return false;
}
if (pointedHandle != 0)
{
lock (_stateGate)
{
if (_mutexStates.TryGetValue(pointedHandle, out state))
{
_mutexStates[mutexAddress] = state;
resolvedAddress = pointedHandle;
return true;
}
}
resolvedAddress = pointedHandle;
return false;
}
if (!createIfZero)
{
resolvedAddress = mutexAddress;
return false;
}
var createdState = new PthreadMutexState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticMutexHandleBase, ref _nextSyntheticMutexHandleId);
lock (_stateGate)
{
_mutexStates[mutexAddress] = createdState;
_mutexStates[syntheticHandle] = createdState;
}
_ = ctx.TryWriteUInt64(mutexAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
state = createdState;
return true;
}
private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress) private static ulong ResolveMutexAttrHandle(CpuContext ctx, ulong attrAddress)
{ {
if (attrAddress == 0) if (attrAddress == 0)
@@ -665,39 +710,137 @@ public static class KernelPthreadCompatExports
} }
} }
private static ulong ResolveCondHandle(CpuContext ctx, ulong condAddress)
{
if (condAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_condStates.ContainsKey(condAddress))
{
return condAddress;
}
}
if (ctx.TryReadUInt64(condAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_condStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return condAddress;
}
private static bool TryResolveCondState(CpuContext? ctx, ulong condAddress, bool createIfZero, out ulong resolvedAddress, out PthreadCondState? state)
{
resolvedAddress = 0;
state = null;
if (condAddress == 0)
{
return false;
}
lock (_stateGate)
{
if (_condStates.TryGetValue(condAddress, out state))
{
resolvedAddress = condAddress;
return true;
}
}
if (ctx is null || !ctx.TryReadUInt64(condAddress, out var pointedHandle))
{
return false;
}
if (pointedHandle != 0)
{
lock (_stateGate)
{
if (_condStates.TryGetValue(pointedHandle, out state))
{
_condStates[condAddress] = state;
resolvedAddress = pointedHandle;
return true;
}
}
resolvedAddress = pointedHandle;
return false;
}
if (!createIfZero)
{
resolvedAddress = condAddress;
return false;
}
var createdState = new PthreadCondState();
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
lock (_stateGate)
{
_condStates[condAddress] = createdState;
_condStates[syntheticHandle] = createdState;
}
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
state = createdState;
return true;
}
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId) private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
{ {
var id = unchecked((ulong)Interlocked.Increment(ref nextId)); var id = unchecked((ulong)Interlocked.Increment(ref nextId));
return baseAddress + (id << 4); return baseAddress + (id << 4);
} }
private static int PthreadCondInitCore(ulong condAddress) private static int PthreadCondInitCore(CpuContext ctx, ulong condAddress)
{ {
if (condAddress == 0) if (condAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId);
lock (_stateGate) lock (_stateGate)
{ {
_condStates[condAddress] = new PthreadCondState(); var state = new PthreadCondState();
_condStates[condAddress] = state;
_condStates[syntheticHandle] = state;
} }
_ = ctx.TryWriteUInt64(condAddress, syntheticHandle);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static int PthreadCondDestroyCore(ulong condAddress) private static int PthreadCondDestroyCore(CpuContext ctx, ulong condAddress)
{ {
if (condAddress == 0) if (condAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveCondHandle(ctx, condAddress);
lock (_stateGate) lock (_stateGate)
{ {
_condStates.Remove(condAddress); _condStates.Remove(resolvedAddress);
if (resolvedAddress != condAddress)
{
_condStates.Remove(condAddress);
}
} }
_ = ctx.TryWriteUInt64(condAddress, 0);
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
@@ -708,14 +851,9 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
PthreadCondState state; if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state))
lock (_stateGate)
{ {
if (!_condStates.TryGetValue(condAddress, out state!)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{
state = new PthreadCondState();
_condStates[condAddress] = state;
}
} }
var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK; var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK;
@@ -770,14 +908,9 @@ public static class KernelPthreadCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
PthreadCondState state; if (!TryResolveCondState(null, condAddress, createIfZero: false, out _, out var state))
lock (_stateGate)
{ {
if (!_condStates.TryGetValue(condAddress, out state!)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{
state = new PthreadCondState();
_condStates[condAddress] = state;
}
} }
lock (state.SyncRoot) lock (state.SyncRoot)
@@ -18,6 +18,7 @@ public static class KernelPthreadExtendedCompatExports
private const int DefaultInheritSched = 0; private const int DefaultInheritSched = 0;
private const int DefaultSchedPolicy = 0; private const int DefaultSchedPolicy = 0;
private const int DefaultSchedPriority = 0; private const int DefaultSchedPriority = 0;
private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000;
private static readonly object _stateGate = new(); private static readonly object _stateGate = new();
private static readonly Dictionary<ulong, ThreadState> _threadStates = new(); private static readonly Dictionary<ulong, ThreadState> _threadStates = new();
@@ -25,6 +26,7 @@ public static class KernelPthreadExtendedCompatExports
private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new(); private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new();
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new(); private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
private static int _nextTlsKey = 1; private static int _nextTlsKey = 1;
private static long _nextSyntheticRwlockHandleId = 1;
[ThreadStatic] [ThreadStatic]
private static Dictionary<int, ulong>? _threadLocalSpecific; private static Dictionary<int, ulong>? _threadLocalSpecific;
@@ -591,20 +593,32 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
lock (_stateGate) lock (_stateGate)
{ {
if (_rwlockStates.Remove(rwlockAddress, out var existing)) var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
if (_rwlockStates.Remove(resolvedAddress, out var existing))
{ {
existing.Dispose(); existing.Dispose();
} }
_rwlockStates[rwlockAddress] = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); var rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_rwlockStates[rwlockAddress] = rwlock;
_rwlockStates[syntheticHandle] = rwlock;
} }
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "ytQULN-nhL4",
ExportName = "pthread_rwlock_init",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockInit(CpuContext ctx) => PthreadRwlockInit(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "BB+kb08Tl9A", Nid = "BB+kb08Tl9A",
ExportName = "scePthreadRwlockDestroy", ExportName = "scePthreadRwlockDestroy",
@@ -618,10 +632,15 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress);
ReaderWriterLockSlim? state; ReaderWriterLockSlim? state;
lock (_stateGate) lock (_stateGate)
{ {
_rwlockStates.Remove(rwlockAddress, out state); _rwlockStates.Remove(resolvedAddress, out state);
if (resolvedAddress != rwlockAddress)
{
_rwlockStates.Remove(rwlockAddress);
}
} }
if (state is null) if (state is null)
@@ -629,24 +648,46 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
_ = ctx.TryWriteUInt64(rwlockAddress, 0);
state.Dispose(); state.Dispose();
ctx[CpuRegister.Rax] = 0; ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "1471ajPzxh0",
ExportName = "pthread_rwlock_destroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockDestroy(CpuContext ctx) => PthreadRwlockDestroy(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "Ox9i0c7L5w0", Nid = "Ox9i0c7L5w0",
ExportName = "scePthreadRwlockRdlock", ExportName = "scePthreadRwlockRdlock",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: false); public static int PthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockLockCore(ctx, ctx[CpuRegister.Rdi], write: false);
[SysAbiExport(
Nid = "iGjsr1WAtI0",
ExportName = "pthread_rwlock_rdlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockRdlock(CpuContext ctx) => PthreadRwlockRdlock(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "mqdNorrB+gI", Nid = "mqdNorrB+gI",
ExportName = "scePthreadRwlockWrlock", ExportName = "scePthreadRwlockWrlock",
Target = Generation.Gen4 | Generation.Gen5, Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")] LibraryName = "libKernel")]
public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx[CpuRegister.Rdi], write: true); public static int PthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockLockCore(ctx, ctx[CpuRegister.Rdi], write: true);
[SysAbiExport(
Nid = "sIlRvQqsN2Y",
ExportName = "pthread_rwlock_wrlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockWrlock(CpuContext ctx) => PthreadRwlockWrlock(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "+L98PIbGttk", Nid = "+L98PIbGttk",
@@ -661,13 +702,7 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
ReaderWriterLockSlim? rwlock; if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock))
lock (_stateGate)
{
_rwlockStates.TryGetValue(rwlockAddress, out rwlock);
}
if (rwlock is null)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
} }
@@ -695,6 +730,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "EgmLo6EWgso",
ExportName = "pthread_rwlock_unlock",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PosixPthreadRwlockUnlock(CpuContext ctx) => PthreadRwlockUnlock(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "mqULNdimTn0", Nid = "mqULNdimTn0",
ExportName = "pthread_key_create", ExportName = "pthread_key_create",
@@ -730,6 +772,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "geDaqgH9lTg",
ExportName = "scePthreadKeyCreate",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadKeyCreate(CpuContext ctx) => PosixPthreadKeyCreate(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "6BpEZuDT7YI", Nid = "6BpEZuDT7YI",
ExportName = "pthread_key_delete", ExportName = "pthread_key_delete",
@@ -751,6 +800,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "PrdHuuDekhY",
ExportName = "scePthreadKeyDelete",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadKeyDelete(CpuContext ctx) => PosixPthreadKeyDelete(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "WrOLvHU0yQM", Nid = "WrOLvHU0yQM",
ExportName = "pthread_setspecific", ExportName = "pthread_setspecific",
@@ -774,6 +830,13 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
[SysAbiExport(
Nid = "+BzXYkqYeLE",
ExportName = "scePthreadSetspecific",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadSetspecific(CpuContext ctx) => PosixPthreadSetspecific(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "0-KXaS70xy4", Nid = "0-KXaS70xy4",
ExportName = "pthread_getspecific", ExportName = "pthread_getspecific",
@@ -798,21 +861,23 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static int PthreadRwlockLockCore(ulong rwlockAddress, bool write) [SysAbiExport(
Nid = "eoht7mQOCmo",
ExportName = "scePthreadGetspecific",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int OrbisPthreadGetspecific(CpuContext ctx) => PosixPthreadGetspecific(ctx);
private static int PthreadRwlockLockCore(CpuContext ctx, ulong rwlockAddress, bool write)
{ {
if (rwlockAddress == 0) if (rwlockAddress == 0)
{ {
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
} }
ReaderWriterLockSlim rwlock; if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock))
lock (_stateGate)
{ {
if (!_rwlockStates.TryGetValue(rwlockAddress, out rwlock!)) return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
{
rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
_rwlockStates[rwlockAddress] = rwlock;
}
} }
try try
@@ -834,6 +899,100 @@ public static class KernelPthreadExtendedCompatExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK; return (int)OrbisGen2Result.ORBIS_GEN2_OK;
} }
private static ulong ResolveRwlockHandle(CpuContext ctx, ulong rwlockAddress)
{
if (rwlockAddress == 0)
{
return 0;
}
lock (_stateGate)
{
if (_rwlockStates.ContainsKey(rwlockAddress))
{
return rwlockAddress;
}
}
if (ctx.TryReadUInt64(rwlockAddress, out var pointedHandle) && pointedHandle != 0)
{
lock (_stateGate)
{
if (_rwlockStates.ContainsKey(pointedHandle))
{
return pointedHandle;
}
}
}
return rwlockAddress;
}
private static bool TryResolveRwlockState(CpuContext ctx, ulong rwlockAddress, bool createIfZero, out ulong resolvedAddress, out ReaderWriterLockSlim? rwlock)
{
resolvedAddress = 0;
rwlock = null;
if (rwlockAddress == 0)
{
return false;
}
lock (_stateGate)
{
if (_rwlockStates.TryGetValue(rwlockAddress, out rwlock))
{
resolvedAddress = rwlockAddress;
return true;
}
}
if (!ctx.TryReadUInt64(rwlockAddress, out var pointedHandle))
{
return false;
}
if (pointedHandle != 0)
{
lock (_stateGate)
{
if (_rwlockStates.TryGetValue(pointedHandle, out rwlock))
{
_rwlockStates[rwlockAddress] = rwlock;
resolvedAddress = pointedHandle;
return true;
}
}
resolvedAddress = pointedHandle;
return false;
}
if (!createIfZero)
{
resolvedAddress = rwlockAddress;
return false;
}
var createdRwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId);
lock (_stateGate)
{
_rwlockStates[rwlockAddress] = createdRwlock;
_rwlockStates[syntheticHandle] = createdRwlock;
}
_ = ctx.TryWriteUInt64(rwlockAddress, syntheticHandle);
resolvedAddress = syntheticHandle;
rwlock = createdRwlock;
return true;
}
private static ulong AllocateSyntheticHandle(ulong baseAddress, ref long nextId)
{
var id = unchecked((ulong)Interlocked.Increment(ref nextId));
return baseAddress + (id << 4);
}
private static ThreadState GetOrCreateThreadStateLocked(ulong thread) private static ThreadState GetOrCreateThreadStateLocked(ulong thread)
{ {
if (_threadStates.TryGetValue(thread, out var state)) if (_threadStates.TryGetValue(thread, out var state))
@@ -39,7 +39,10 @@ public static class KernelRuntimeCompatExports
private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader(); private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader();
private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency(); private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency();
private static readonly ulong _stackChkGuardValue = 0xC0DEC0DECAFEBABEUL; private static readonly ulong _stackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
private static readonly nint _stackChkGuardObjectAddress = AllocateStackChkGuardObject(); private static readonly nint _stackChkGuardObjectAddress =
HleDataSymbols.TryGetAddress("f7uOxY9mM1U", out var stackChkGuardAddress)
? unchecked((nint)stackChkGuardAddress)
: AllocateStackChkGuardObject();
private static ulong _applicationHeapApiAddress; private static ulong _applicationHeapApiAddress;
private static ulong _processProcParamAddress; private static ulong _processProcParamAddress;
private static ulong _nextReservedVirtualBase = 0x6000_0000_0UL; private static ulong _nextReservedVirtualBase = 0x6000_0000_0UL;
+78
View File
@@ -0,0 +1,78 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using SharpEmu.HLE;
namespace SharpEmu.Libs.LibcInternal;
public static class LibcInternalExports
{
private const ulong HeapTraceInfoSize = 32;
private const int HeapTraceTableEntryCount = 64;
private const int HeapTraceMaskOffset = 0;
private const int HeapTraceTableOffset = HeapTraceMaskOffset + sizeof(ulong);
private const int HeapTraceStorageSize = HeapTraceTableOffset + (HeapTraceTableEntryCount * sizeof(ulong));
private static readonly object _heapTraceGate = new();
private static nint _heapTraceStorage;
[SysAbiExport(
Nid = "NWtTN10cJzE",
ExportName = "LibcHeapGetTraceInfo",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "LibcInternalExt")]
public static int LibcHeapGetTraceInfo(CpuContext ctx)
{
var infoAddress = ctx[CpuRegister.Rdi];
if (infoAddress == 0 || !ctx.TryReadUInt64(infoAddress, out var size) || size != HeapTraceInfoSize)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var storage = EnsureHeapTraceStorage();
if (storage == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var maskAddress = unchecked((ulong)(storage + HeapTraceMaskOffset));
var tableAddress = unchecked((ulong)(storage + HeapTraceTableOffset));
if (!ctx.TryWriteUInt64(infoAddress + 16, maskAddress) ||
!ctx.TryWriteUInt64(infoAddress + 24, tableAddress))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static nint EnsureHeapTraceStorage()
{
lock (_heapTraceGate)
{
if (_heapTraceStorage != 0)
{
return _heapTraceStorage;
}
var storage = Marshal.AllocHGlobal(HeapTraceStorageSize);
if (storage == 0)
{
return 0;
}
unsafe
{
NativeMemory.Clear((void*)storage, (nuint)HeapTraceStorageSize);
}
_heapTraceStorage = storage;
return storage;
}
}
}
+119
View File
@@ -0,0 +1,119 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.Rtc;
public static class RtcExports
{
[SysAbiExport(
Nid = "ZPD1YOKI+Kw",
ExportName = "sceRtcGetCurrentClockLocalTime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRtc")]
public static int RtcGetCurrentClockLocalTime(CpuContext ctx)
{
var timeAddress = ctx[CpuRegister.Rdi];
if (timeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var now = DateTimeOffset.Now;
Span<byte> rtcDateTime = stackalloc byte[16];
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)now.Year));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)now.Month));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)now.Day));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)now.Hour));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)now.Minute));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)now.Second));
BinaryPrimitives.WriteUInt32LittleEndian(
rtcDateTime[12..16],
checked((uint)((now.Ticks % TimeSpan.TicksPerSecond) / 10)));
if (!ctx.Memory.TryWrite(timeAddress, rtcDateTime))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "8w-H19ip48I",
ExportName = "sceRtcGetTick",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRtc")]
public static int RtcGetTick(CpuContext ctx)
{
var dateTimeAddress = ctx[CpuRegister.Rdi];
var tickAddress = ctx[CpuRegister.Rsi];
if (dateTimeAddress == 0 || tickAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadRtcDateTime(ctx, dateTimeAddress, out var rtcDateTime))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ulong tickValue;
try
{
var baseDateTime = new DateTime(
rtcDateTime.Year,
rtcDateTime.Month,
rtcDateTime.Day,
rtcDateTime.Hour,
rtcDateTime.Minute,
rtcDateTime.Second,
DateTimeKind.Utc);
tickValue = checked((ulong)((baseDateTime.Ticks / 10) + rtcDateTime.Microsecond));
}
catch (ArgumentOutOfRangeException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryWriteUInt64(tickAddress, tickValue))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryReadRtcDateTime(CpuContext ctx, ulong address, out RtcDateTime rtcDateTime)
{
Span<byte> buffer = stackalloc byte[16];
if (!ctx.Memory.TryRead(address, buffer))
{
rtcDateTime = default;
return false;
}
rtcDateTime = new RtcDateTime(
BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[6..8]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[8..10]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[10..12]),
BinaryPrimitives.ReadUInt32LittleEndian(buffer[12..16]));
return true;
}
private readonly record struct RtcDateTime(
ushort Year,
ushort Month,
ushort Day,
ushort Hour,
ushort Minute,
ushort Second,
uint Microsecond);
}