From 1f71c970d9766d47ce75578fc7f42a0f2f584c23 Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Sat, 14 Mar 2026 20:38:25 +0300 Subject: [PATCH] A dozen changes; new HLEs, AV fixes, ELF loader fixes, new return codes, etc. --- src/SharpEmu.Core/Cpu/CpuDispatcher.cs | 88 ++- src/SharpEmu.Core/Cpu/ICpuDispatcher.cs | 8 + .../DirectExecutionBackend.Exceptions.cs | 237 ++++++++ .../Native/DirectExecutionBackend.Imports.cs | 115 ++-- .../Cpu/Native/DirectExecutionBackend.cs | 144 ++++- src/SharpEmu.Core/Loader/SelfImage.cs | 12 + src/SharpEmu.Core/Loader/SelfLoader.cs | 201 ++++++- .../Memory/PhysicalVirtualMemory.cs | 97 +++- src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs | 210 ++++++- src/SharpEmu.HLE/HleDataSymbols.cs | 130 +++++ src/SharpEmu.HLE/IModuleManager.cs | 2 + src/SharpEmu.HLE/ModuleManager.cs | 15 +- src/SharpEmu.HLE/OrbisGen2Result.cs | 5 + src/SharpEmu.Libs/CxxAbiExports.cs | 59 +- src/SharpEmu.Libs/Kernel/KernelExports.cs | 20 + .../Kernel/KernelMemoryCompatExports.cs | 548 ++++++++++++++++-- .../Kernel/KernelPthreadCompatExports.cs | 215 +++++-- .../KernelPthreadExtendedCompatExports.cs | 199 ++++++- .../Kernel/KernelRuntimeCompatExports.cs | 5 +- src/SharpEmu.Libs/LibcInternalExports.cs | 78 +++ src/SharpEmu.Libs/Rtc/RtcExports.cs | 119 ++++ 21 files changed, 2291 insertions(+), 216 deletions(-) create mode 100644 src/SharpEmu.HLE/HleDataSymbols.cs create mode 100644 src/SharpEmu.Libs/LibcInternalExports.cs create mode 100644 src/SharpEmu.Libs/Rtc/RtcExports.cs diff --git a/src/SharpEmu.Core/Cpu/CpuDispatcher.cs b/src/SharpEmu.Core/Cpu/CpuDispatcher.cs index 1fdcfda..64d3d96 100644 --- a/src/SharpEmu.Core/Cpu/CpuDispatcher.cs +++ b/src/SharpEmu.Core/Cpu/CpuDispatcher.cs @@ -12,6 +12,12 @@ namespace SharpEmu.Core.Cpu; public sealed class CpuDispatcher : ICpuDispatcher, IDisposable { + private enum EntryFrameKind + { + ProcessEntry, + ModuleInitializer, + } + private const ulong StackBaseAddress = 0x7FFF_F000_0000UL; private const ulong StackSize = 0x0020_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? importStubs = null, + IReadOnlyDictionary? 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( ulong entryPoint, Generation generation, IReadOnlyDictionary? importStubs = null, IReadOnlyDictionary? runtimeSymbols = null, string processImageName = "eboot.bin", - CpuExecutionOptions executionOptions = default) + CpuExecutionOptions executionOptions = default, + EntryFrameKind frameKind = EntryFrameKind.ProcessEntry) { Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING..."); @@ -164,23 +201,33 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable var effectiveImportStubs = importStubs is null ? new Dictionary() : new Dictionary(importStubs); - var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion(); - if (programExitHandlerStubAddress == 0) + var entryParamsConfigured = false; + if (frameKind == EntryFrameKind.ProcessEntry) { - return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); - } - - if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress)) - { - return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); - } - - if (ShouldInjectBootstrapPayload(entryPoint)) - { - if (!TryInstallBootstrapPayload(context, effectiveImportStubs)) + var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion(); + if (programExitHandlerStubAddress == 0) { 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( @@ -188,7 +235,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable context, sentinelEnabled: true, sentinelValue: returnToHostStubAddress, - entryParamsConfigured: true); + entryParamsConfigured: entryParamsConfigured); if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly) { @@ -360,6 +407,17 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable 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) { return value & ~(alignment - 1); diff --git a/src/SharpEmu.Core/Cpu/ICpuDispatcher.cs b/src/SharpEmu.Core/Cpu/ICpuDispatcher.cs index 48d7826..08093a4 100644 --- a/src/SharpEmu.Core/Cpu/ICpuDispatcher.cs +++ b/src/SharpEmu.Core/Cpu/ICpuDispatcher.cs @@ -34,4 +34,12 @@ public interface ICpuDispatcher IReadOnlyDictionary? runtimeSymbols = null, string processImageName = "eboot.bin", CpuExecutionOptions executionOptions = default); + + OrbisGen2Result DispatchModuleInitializer( + ulong entryPoint, + Generation generation, + IReadOnlyDictionary? importStubs = null, + IReadOnlyDictionary? runtimeSymbols = null, + string moduleName = "module", + CpuExecutionOptions executionOptions = default); } diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs index fe24260..c7619ab 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs @@ -4,6 +4,7 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; +using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; 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"); } DumpRecentImportTrace(); + DumpGuestDisasmDiagnostics(rip, rbp); + DumpGuestReferenceDiagnostics(); + DumpGuestPointerWindowDiagnostics(); break; case 2147483651u: 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(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 targets, + IDictionary 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 ParseDiagnosticAddresses(string? rawValue) + { + var result = new List(); + 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) { if (baseAddress < 0x10000 || size <= 0) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 0dc2b0d..a84e1a6 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -237,6 +237,7 @@ public sealed partial class DirectExecutionBackend try { OrbisGen2Result orbisGen2Result; + bool dispatchResolved = true; if (string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) { orbisGen2Result = DispatchBootstrapBridge(); @@ -247,34 +248,31 @@ public sealed partial class DirectExecutionBackend } 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; - Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}"); - if (importStubEntry.Nid == "L-Q3LEjIbgA") + LastError = "Missing HLE export for NID: " + importStubEntry.Nid; + Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}"); + 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"); - })); - Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]"); - Delegate function; - bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function); - ExportedFunction export2; - bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2); - 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"); - } - break; - default: - Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})"); - break; - case OrbisGen2Result.ORBIS_GEN2_OK: - break; + int num10 = c; + return num10.ToString("X2"); + })); + Console.Error.WriteLine($"[LOADER][WARN] map_direct nid raw len={importStubEntry.Nid.Length} chars=[{value18}]"); + Delegate function; + bool value19 = _moduleManager.TryGetFunction(importStubEntry.Nid, out function); + ExportedFunction export2; + bool value20 = _moduleManager.TryGetExport(importStubEntry.Nid, out export2); + 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) + { + Console.Error.WriteLine($"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid})"); } _cpuContext[CpuRegister.Rbx] = value3; _cpuContext[CpuRegister.Rbp] = value4; @@ -334,7 +332,12 @@ public sealed partial class DirectExecutionBackend { 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 (_importLoopPatternHits > 0) @@ -347,21 +350,18 @@ public sealed partial class DirectExecutionBackend 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 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; + _importLoopNidHashes[_importLoopSignatureWriteIndex] = nidHash; + _importLoopReturnRips[_importLoopSignatureWriteIndex] = returnRip; _importLoopSignatureWriteIndex = (_importLoopSignatureWriteIndex + 1) % _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) @@ -418,6 +418,51 @@ public sealed partial class DirectExecutionBackend 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) { return string.Equals(nid, "j4ViWNHEgww", StringComparison.Ordinal) && !_logStrlenImports; diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index bb42882..d3ff65c 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -89,6 +89,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private const uint PAGE_EXECUTE_READ = 32u; + private const int TlsHandlerRegionSize = 4096; + private const ulong TlsModuleAllocStart = 140726751354880uL; private const ulong TlsModuleAllocStride = 65536uL; @@ -101,6 +103,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private bool _ownsTlsBaseAddress; + private int _tlsPatchStubOffset; + private nint _unresolvedReturnStub; 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[] _importLoopNidHashes = new ulong[192]; + + private readonly ulong[] _importLoopReturnRips = new ulong[192]; + private int _importLoopSignatureCount; 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..."); + 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 (!PreferLleForLibcExport(symbolByNid.ExportName)) @@ -587,9 +603,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { "_init_env" or "atexit" or - "__cxa_guard_acquire" or - "__cxa_guard_release" or - "__cxa_guard_abort" or "strlen" or "strnlen" or "strcmp" or @@ -841,10 +854,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private unsafe void CreateTlsHandler() { - _tlsHandlerAddress = (nint)TryAllocateNearEntry(256u); + _tlsHandlerAddress = (nint)TryAllocateNearEntry(TlsHandlerRegionSize); if (_tlsHandlerAddress == 0) { - _tlsHandlerAddress = (nint)VirtualAlloc(null, 256u, 12288u, 64u); + _tlsHandlerAddress = (nint)VirtualAlloc(null, TlsHandlerRegionSize, 12288u, 64u); } if (_tlsHandlerAddress == 0) { @@ -857,9 +870,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I *(long*)(tlsHandlerAddress + num) = _tlsBaseAddress; num += 8; tlsHandlerAddress[num++] = 195; + _tlsPatchStubOffset = (num + 15) & ~15; uint num2 = default(uint); - VirtualProtect((void*)_tlsHandlerAddress, 256u, 32u, &num2); - FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 256u); + VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2); + FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, TlsHandlerRegionSize); 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; int num3 = 0; int num4 = 0; + int num9 = 0; while (num < num2) { 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++; PatchTlsInstruction(address); } + else if (TryPatchTlsImmediateStoreInstruction(address, ptr + i)) + { + num9++; + } else if (TryPatchStackCanaryInstruction(address, ptr + i)) { num4++; @@ -975,7 +994,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } 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) @@ -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) { if (VirtualQuery((void*)baseAddress, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 && lpBuffer.State != 65536) diff --git a/src/SharpEmu.Core/Loader/SelfImage.cs b/src/SharpEmu.Core/Loader/SelfImage.cs index 293012d..5123e84 100644 --- a/src/SharpEmu.Core/Loader/SelfImage.cs +++ b/src/SharpEmu.Core/Loader/SelfImage.cs @@ -17,6 +17,9 @@ public sealed class SelfImage IReadOnlyDictionary? importStubs = null, IReadOnlyDictionary? runtimeSymbols = null, IReadOnlyList? importedRelocations = null, + IReadOnlyList? preInitializerFunctions = null, + IReadOnlyList? initializerFunctions = null, + ulong initFunctionEntryPoint = 0, ulong imageBase = 0, ulong procParamAddress = 0) { @@ -30,6 +33,9 @@ public sealed class SelfImage ImportStubs = importStubs ?? new Dictionary(); RuntimeSymbols = runtimeSymbols ?? new Dictionary(StringComparer.Ordinal); ImportedRelocations = importedRelocations ?? Array.Empty(); + PreInitializerFunctions = preInitializerFunctions ?? Array.Empty(); + InitializerFunctions = initializerFunctions ?? Array.Empty(); + InitFunctionEntryPoint = initFunctionEntryPoint; _imageBase = imageBase; ProcParamAddress = procParamAddress; } @@ -48,6 +54,12 @@ public sealed class SelfImage public IReadOnlyList ImportedRelocations { get; } + public IReadOnlyList PreInitializerFunctions { get; } + + public IReadOnlyList InitializerFunctions { get; } + + public ulong InitFunctionEntryPoint { get; } + public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase; public ulong ProcParamAddress { get; } diff --git a/src/SharpEmu.Core/Loader/SelfLoader.cs b/src/SharpEmu.Core/Loader/SelfLoader.cs index f26590d..822ee1c 100644 --- a/src/SharpEmu.Core/Loader/SelfLoader.cs +++ b/src/SharpEmu.Core/Loader/SelfLoader.cs @@ -38,8 +38,13 @@ public sealed class SelfLoader : ISelfLoader private const long DtSymTab = 0x06; private const long DtRela = 0x07; private const long DtRelaSize = 0x08; + private const long DtInit = 0x0C; private const long DtStrSize = 0x0A; 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 DtScePltRelSize = 0x6100002D; private const long DtSceRela = 0x6100002F; @@ -61,8 +66,8 @@ public sealed class SelfLoader : ISelfLoader private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL; private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL; private const ulong ModulePlacementStep = 0x00200000UL; - private const ulong FocusRelocGuestStart = 0x00000008030FC300UL; - private const ulong FocusRelocGuestEnd = 0x00000008030FC3F0UL; + private const ulong FocusRelocGuestStart = 0x0000000807BA25B0UL; + private const ulong FocusRelocGuestEnd = 0x0000000807BA2608UL; private const byte SymbolBindLocal = 0; private const byte SymbolBindGlobal = 1; private const byte SymbolBindWeak = 2; @@ -74,6 +79,7 @@ public sealed class SelfLoader : ISelfLoader private static readonly IReadOnlyDictionary EmptyImportStubs = new Dictionary(); private static readonly IReadOnlyDictionary EmptyRuntimeSymbols = new Dictionary(StringComparer.Ordinal); + private static readonly IReadOnlyList EmptyInitializerFunctions = Array.Empty(); private static readonly int SelfHeaderSize = Unsafe.SizeOf(); private static readonly int SelfSegmentSize = Unsafe.SizeOf(); private static readonly int ProgramHeaderSize = Unsafe.SizeOf(); @@ -218,6 +224,15 @@ public sealed class SelfLoader : ISelfLoader var finalizedRuntimeSymbols = runtimeSymbols.Count == 0 ? EmptyRuntimeSymbols : runtimeSymbols; + CollectInitializerFunctions( + imageData, + loadContext, + programHeaders, + virtualMemory, + imageBase, + out var initFunctionEntryPoint, + out var preInitializerFunctions, + out var initializerFunctions); var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase); Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}"); @@ -251,6 +266,9 @@ public sealed class SelfLoader : ISelfLoader finalizedImportStubs, finalizedRuntimeSymbols, importedRelocations, + preInitializerFunctions, + initializerFunctions, + initFunctionEntryPoint, imageBase, procParamAddress); } @@ -929,6 +947,131 @@ public sealed class SelfLoader : ISelfLoader } } + private static void CollectInitializerFunctions( + ReadOnlySpan imageData, + LoadContext loadContext, + IReadOnlyList programHeaders, + IVirtualMemory virtualMemory, + ulong imageBase, + out ulong initFunctionEntryPoint, + out IReadOnlyList preInitializerFunctions, + out IReadOnlyList 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(4); + var initializers = new List(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 destination, + ReadOnlySpan 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 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 BuildImportedRelocations( IReadOnlyList descriptors) { @@ -1371,11 +1514,16 @@ public sealed class SelfLoader : ISelfLoader ulong strTabSize = 0; ulong symTabOffset = 0; ulong symTabSize = 0; + ulong initOffset = 0; ulong relaOffset = 0; ulong relaSize = 0; ulong jmpRelOffset = 0; ulong jmpRelSize = 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) { @@ -1422,6 +1570,13 @@ public sealed class SelfLoader : ISelfLoader relaSize = value; } + break; + case DtInit: + if (initOffset == 0) + { + initOffset = value; + } + break; case DtJmpRel: if (jmpRelOffset == 0) @@ -1429,6 +1584,34 @@ public sealed class SelfLoader : ISelfLoader 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; case DtPltRelSize: if (jmpRelSize == 0) @@ -1476,11 +1659,16 @@ public sealed class SelfLoader : ISelfLoader strTabSize, symTabOffset, symTabSize, + initOffset, relaOffset, relaSize, jmpRelOffset, jmpRelSize, - pltGotOffset); + pltGotOffset, + initArrayOffset, + initArraySize, + preInitArrayOffset, + preInitArraySize); } private static bool IsSupportedRelocationType(uint relocationType) @@ -2087,11 +2275,16 @@ public sealed class SelfLoader : ISelfLoader ulong StrTabSize, ulong SymTabOffset, ulong SymTabSize, + ulong InitOffset, ulong RelaOffset, ulong RelaSize, ulong JmpRelOffset, ulong JmpRelSize, - ulong PltGotOffset) + ulong PltGotOffset, + ulong InitArrayOffset, + ulong InitArraySize, + ulong PreInitArrayOffset, + ulong PreInitArraySize) { public bool HasImportMetadata => StrTabOffset != 0 && diff --git a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs index 5f034b2..ee18cae 100644 --- a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs @@ -11,6 +11,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable { private readonly object _gate = new(); private readonly List _regions = new(); + private readonly Dictionary _pageProtections = new(); private bool _disposed; private const ulong PageSize = 0x1000; 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 = 0x10; private const uint PAGE_EXECUTE_WRITECOPY = 0x80; + private const uint PAGE_NOACCESS = 0x01; private const uint PAGE_READWRITE = 0x04; 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..."); result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection); - + if (result == null) { if (!executable) @@ -219,6 +221,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE); } _regions.Clear(); + _pageProtections.Clear(); } } @@ -264,20 +267,35 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable 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})"); } } + 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) { uint protection; - - if ((flags & ProgramHeaderFlags.Execute) != 0) + + if (flags == ProgramHeaderFlags.None) { - protection = (flags & ProgramHeaderFlags.Write) != 0 - ? PAGE_EXECUTE_READWRITE + protection = PAGE_NOACCESS; + } + else if ((flags & ProgramHeaderFlags.Execute) != 0) + { + protection = (flags & ProgramHeaderFlags.Write) != 0 + ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; } else if ((flags & ProgramHeaderFlags.Write) != 0) @@ -309,10 +327,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable { var r = _regions[i]; snapshot[i] = new VirtualMemoryRegion( - r.VirtualAddress, - r.Size, - 0, - r.Size, + r.VirtualAddress, + r.Size, + 0, + r.Size, r.IsExecutable ? ProgramHeaderFlags.Execute | ProgramHeaderFlags.Read : ProgramHeaderFlags.Read); } return snapshot; @@ -328,10 +346,28 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var 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; } } @@ -394,7 +430,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable { foreach (var region in _regions) { - if (virtualAddress >= region.VirtualAddress && + if (virtualAddress >= region.VirtualAddress && virtualAddress < region.VirtualAddress + region.Size) { 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; } + 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) { var mask = alignment - 1; diff --git a/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs b/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs index 6b4ca13..329c9b1 100644 --- a/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs +++ b/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs @@ -19,6 +19,8 @@ namespace SharpEmu.Core.Runtime; public sealed class SharpEmuRuntime : ISharpEmuRuntime { + private readonly record struct LoadedModuleImage(string Path, SelfImage Image); + private static readonly HashSet PreloadSkipModules = new(StringComparer.OrdinalIgnoreCase) { "libkernel.prx", @@ -138,14 +140,33 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime var generation = image.ElfHeader.AbiVersion == 2 ? Generation.Gen5 : Generation.Gen4; var activeImportStubs = new Dictionary(image.ImportStubs); var activeRuntimeSymbols = new Dictionary(image.RuntimeSymbols, StringComparer.Ordinal); - var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols); - RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols); var processImageName = Path.GetFileName(ebootPath); if (string.IsNullOrWhiteSpace(processImageName)) { 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] About to call DispatchEntry with entryPoint=0x{image.EntryPoint:X16}"); @@ -329,12 +350,150 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime return result; } - private List LoadAdjacentSceModules( + private OrbisGen2Result? RunAllInitializers( + SelfImage mainImage, + IReadOnlyList loadedModuleImages, + Generation generation, + IReadOnlyDictionary activeImportStubs, + IReadOnlyDictionary 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 loadedModuleImages, + Generation generation, + IReadOnlyDictionary activeImportStubs, + IReadOnlyDictionary 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 activeImportStubs, + IReadOnlyDictionary 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 initializerFunctions, + Generation generation, + IReadOnlyDictionary activeImportStubs, + IReadOnlyDictionary 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 LoadAdjacentSceModules( string ebootPath, IDictionary importStubs, IDictionary runtimeSymbols) { - var loadedImages = new List(); + var loadedImages = new List(); var ebootDirectory = Path.GetDirectoryName(ebootPath); if (string.IsNullOrWhiteSpace(ebootDirectory)) { @@ -418,7 +577,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime mergedImportCount += MergeImportStubs(importStubs, moduleImage.ImportStubs, modulePath); mergedSymbolCount += MergeRuntimeSymbols(runtimeSymbols, moduleImage.RuntimeSymbols); RegisterLoadedModule(modulePath, moduleImage, isMain: false, isSystemModule: false); - loadedImages.Add(moduleImage); + loadedImages.Add(new LoadedModuleImage(modulePath, moduleImage)); loadedModules++; Console.Error.WriteLine( @@ -438,7 +597,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime private void RebindImportedDataSymbols( SelfImage mainImage, - IReadOnlyList loadedModuleImages, + IReadOnlyList loadedModuleImages, IReadOnlyDictionary runtimeSymbols) { var rebound = 0; @@ -447,7 +606,7 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime rebound += RebindImportedDataSymbols(mainImage, runtimeSymbols, ref unresolved); 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) @@ -468,6 +627,10 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime } 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++) { var relocation = image.ImportedRelocations[i]; @@ -479,6 +642,12 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime if (!runtimeSymbols.TryGetValue(relocation.Nid, out var 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++; continue; } @@ -486,16 +655,43 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime var reboundValue = AddSigned(symbolAddress, relocation.Addend); 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++; continue; } + if (logRebind) + { + Console.Error.WriteLine( + $"[RUNTIME] Imported data rebound: nid={relocation.Nid} target=0x{relocation.TargetAddress:X16} value=0x{reboundValue:X16}"); + } + rebound++; } return rebound; } + private static void MergeKnownHleDataSymbols(IDictionary 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( IDictionary destination, IReadOnlyDictionary source, diff --git a/src/SharpEmu.HLE/HleDataSymbols.cs b/src/SharpEmu.HLE/HleDataSymbols.cs new file mode 100644 index 0000000..aa0bad0 --- /dev/null +++ b/src/SharpEmu.HLE/HleDataSymbols.cs @@ -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 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()); + } +} diff --git a/src/SharpEmu.HLE/IModuleManager.cs b/src/SharpEmu.HLE/IModuleManager.cs index 7ecd21f..32e2827 100644 --- a/src/SharpEmu.HLE/IModuleManager.cs +++ b/src/SharpEmu.HLE/IModuleManager.cs @@ -17,5 +17,7 @@ public interface IModuleManager bool TryGetExportByName(string exportName, out ExportedFunction export); + bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result); + OrbisGen2Result Dispatch(string nid, CpuContext context); } diff --git a/src/SharpEmu.HLE/ModuleManager.cs b/src/SharpEmu.HLE/ModuleManager.cs index 8335306..a899634 100644 --- a/src/SharpEmu.HLE/ModuleManager.cs +++ b/src/SharpEmu.HLE/ModuleManager.cs @@ -93,6 +93,12 @@ public sealed class ModuleManager : IModuleManager } 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); 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)) { 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) { 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(); @@ -117,7 +125,8 @@ public sealed class ModuleManager : IModuleManager context[CpuRegister.Rax] = unchecked((ulong)ret); } - return (OrbisGen2Result)ret; + result = (OrbisGen2Result)ret; + return true; } private static Delegate CreateHandler(Type ownerType, MethodInfo method, IDictionary instances) diff --git a/src/SharpEmu.HLE/OrbisGen2Result.cs b/src/SharpEmu.HLE/OrbisGen2Result.cs index 00f6f52..56a22b6 100644 --- a/src/SharpEmu.HLE/OrbisGen2Result.cs +++ b/src/SharpEmu.HLE/OrbisGen2Result.cs @@ -45,6 +45,11 @@ public enum OrbisGen2Result : int /// ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010), + /// + /// Indicates that the operation should be retried later. + /// + ORBIS_GEN2_ERROR_TRY_AGAIN = unchecked((int)0x80020023), + /// /// Indicates that behavior is recognized but not implemented yet. /// diff --git a/src/SharpEmu.Libs/CxxAbiExports.cs b/src/SharpEmu.Libs/CxxAbiExports.cs index 94940f7..b696427 100644 --- a/src/SharpEmu.Libs/CxxAbiExports.cs +++ b/src/SharpEmu.Libs/CxxAbiExports.cs @@ -10,6 +10,10 @@ namespace SharpEmu.Libs.CxxAbi; 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 { public int OwnerThreadId { get; set; } @@ -36,13 +40,13 @@ public static class CxaGuardExports var spinner = new SpinWait(); while (true) { - if (!TryReadGuardInitialized(ctx, guardPtr, out var initialized)) + if (!TryReadGuardState(ctx, guardPtr, out _, out var initialized, out var inProgress)) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } - LogGuardState(ctx, "guard_acquire", guardPtr, initialized); + LogGuardState(ctx, "guard_acquire", guardPtr, initialized, inProgress); if (initialized) { @@ -58,6 +62,13 @@ public static class CxaGuardExports }; 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; LogGuardResult("guard_acquire", guardPtr, result: 1, initialized, inProgress: true, ownerThreadId: currentThreadId); 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; return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; } _inProgress.TryRemove(guardPtr, out _); - LogGuardState(ctx, "guard_release", guardPtr, initialized: true); + LogGuardState(ctx, "guard_release", guardPtr, initialized: true, inProgress: false); ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; @@ -152,60 +163,50 @@ public static class CxaGuardExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - _ = TryWriteGuardInitialized(ctx, guardPtr, initialized: false); + _ = TryWriteGuardState(ctx, guardPtr, 0); _inProgress.TryRemove(guardPtr, out _); - LogGuardState(ctx, "guard_abort", guardPtr, initialized: false); + LogGuardState(ctx, "guard_abort", guardPtr, initialized: false, inProgress: false); ctx[CpuRegister.Rax] = 0; 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; - - var aligned = guardPtr & ~7UL; - var shift = (int)((guardPtr & 7UL) * 8); - - if (!ctx.TryReadUInt64(aligned, out var word)) + inProgress = false; + if (!ctx.TryReadUInt64(guardPtr, out word)) { return false; } - var b0 = (byte)((word >> shift) & 0xFF); - initialized = (b0 & 0x01) != 0; + initialized = (word & GuardCompleteValue) != 0; + inProgress = (word & 0x0000_0000_0000_FF00) != 0; 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; - var shift = (int)((guardPtr & 7UL) * 8); - var mask = 0xFFUL << shift; - - if (!ctx.TryReadUInt64(aligned, out var word)) + if (!ctx.TryReadUInt64(guardPtr, out var word)) { return false; } - var b0 = (byte)((word >> shift) & 0xFF); - b0 = initialized ? (byte)(b0 | 0x01) : (byte)(b0 & ~0x01); - - var newWord = (word & ~mask) | ((ulong)b0 << shift); - return ctx.TryWriteUInt64(aligned, newWord); + var newWord = (word & ~GuardStateMask) | (stateValue & GuardStateMask); + return ctx.TryWriteUInt64(guardPtr, 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)) { return; } - var aligned = guardPtr & ~7UL; - var readable = ctx.TryReadUInt64(aligned, out var word); + var readable = ctx.TryReadUInt64(guardPtr, out var word); Console.Error.WriteLine( - $"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} aligned=0x{aligned:X16} init={initialized} word={(readable ? $"0x{word:X16}" : "")}"); + $"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} init={initialized} in_progress={inProgress} word={(readable ? $"0x{word:X16}" : "")}"); } private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId) diff --git a/src/SharpEmu.Libs/Kernel/KernelExports.cs b/src/SharpEmu.Libs/Kernel/KernelExports.cs index d3f91f3..3d910ff 100644 --- a/src/SharpEmu.Libs/Kernel/KernelExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelExports.cs @@ -11,6 +11,9 @@ public static class KernelExports private static int _nextFileDescriptor = 2; private static readonly object _cxaGate = new(); private static readonly List _cxaDestructors = new(); + private static readonly object _coredumpGate = new(); + private static ulong _coredumpHandler; + private static ulong _coredumpHandlerContext; private readonly record struct CxaDestructorEntry( ulong Function, @@ -28,6 +31,23 @@ public static class KernelExports 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( Nid = "uMei1W9uyNo", ExportName = "exit", diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index bdf6e34..3696910 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -21,6 +21,19 @@ public static class KernelMemoryCompatExports private const int O_CREAT = 0x0200; private const int O_TRUNC = 0x0400; 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 SeekCur = 1; 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 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 BatchMapEntry(ulong Start, ulong Offset, ulong Length, byte Protection, byte Type, int Operation); [SysAbiExport( Nid = "8zTFvBIAIN8", @@ -993,17 +1007,11 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - var candidate = AlignUp(Math.Max(searchStart, _nextPhysicalAddress), alignment); - if (candidate >= searchEnd) + if (!TryFindAvailableDirectMemorySpanLocked(searchStart, searchEnd, alignment, out var candidate, out var rangeAvailable)) { - candidate = AlignUp(searchStart, alignment); - if (candidate >= searchEnd) - { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; - } + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } - var rangeAvailable = searchEnd - candidate; if (!ctx.TryWriteUInt64(outAddress, candidate) || !ctx.TryWriteUInt64(outSize, rangeAvailable)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; @@ -1070,7 +1078,17 @@ public static class KernelMemoryCompatExports var outAddress = ctx[CpuRegister.R9]; 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; + } var limit = DirectMemorySizeBytes; ulong searchStart; @@ -1104,41 +1122,48 @@ public static class KernelMemoryCompatExports } var align = alignment == 0 ? 0x1000UL : alignment; - var alignedStart = AlignUp(searchStart, align); - ulong selectedAddress; lock (_memoryGate) { - selectedAddress = AlignUp(Math.Max(alignedStart, _nextPhysicalAddress), align); - - if (!TryAdd(selectedAddress, length, out var endAddr) || - endAddr > searchEnd || - endAddr > limit) + if (!TryAllocateDirectMemoryLocked(searchStart, searchEnd, length, align, memoryType, out selectedAddress)) { - selectedAddress = alignedStart; - - if (!TryAdd(selectedAddress, length, out endAddr) || - endAddr > searchEnd || - endAddr > limit) - { - return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; - } + TraceDirectMemoryCall( + ctx, + "allocate_direct", + length, + align, + memoryType, + 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)) + { + TraceDirectMemoryCall( + ctx, + "allocate_direct", + length, + align, + memoryType, + outAddress, + selectedAddress, + 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; - - static bool TryAdd(ulong a, ulong b, out ulong sum) - { - sum = a + b; - return sum >= a; - } } [SysAbiExport( @@ -1154,26 +1179,59 @@ public static class KernelMemoryCompatExports var outAddress = ctx[CpuRegister.Rcx]; 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; } - var aligned = AlignUp(_nextPhysicalAddress, alignment == 0 ? 0x1000UL : alignment); + var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment; + ulong aligned; 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)) { + TraceDirectMemoryCall( + ctx, + "allocate_main_direct", + length, + effectiveAlignment, + memoryType, + outAddress, + aligned, + 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; } @@ -1199,6 +1257,7 @@ public static class KernelMemoryCompatExports } _directAllocations.Remove(start); + _nextPhysicalAddress = GetDirectMemoryHighWaterMarkLocked(); } return (int)OrbisGen2Result.ORBIS_GEN2_OK; @@ -1362,6 +1421,26 @@ public static class KernelMemoryCompatExports 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( Nid = "cQke9UuBQOk", ExportName = "sceKernelMunmap", @@ -1494,12 +1573,37 @@ public static class KernelMemoryCompatExports 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; } + } - _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; @@ -2460,6 +2564,364 @@ public static class KernelMemoryCompatExports 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 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 protection = stackalloc byte[1]; + Span 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() ?? ""}"); + } + + 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(_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(_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 destination) { if (destination.IsEmpty || !IsHostRangeAccessible(address, (ulong)destination.Length, writeAccess: false)) @@ -2808,4 +3270,10 @@ public static class KernelMemoryCompatExports var mask = alignment - 1; return (value + mask) & ~mask; } + + private static bool TryAddU64(ulong left, ulong right, out ulong sum) + { + sum = left + right; + return sum >= left; + } } diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs index 8132dd5..98fd63c 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadCompatExports.cs @@ -14,6 +14,7 @@ public static class KernelPthreadCompatExports private const int MutexTypeNormal = 4; private const ulong SyntheticMutexHandleBase = 0x00006000_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 Dictionary _mutexStates = new(); @@ -22,6 +23,7 @@ public static class KernelPthreadCompatExports private static readonly HashSet _condAttrStates = new(); private static long _nextSyntheticMutexHandleId = 1; private static long _nextSyntheticMutexAttrHandleId = 1; + private static long _nextSyntheticCondHandleId = 1; private sealed class PthreadMutexState { @@ -228,14 +230,14 @@ public static class KernelPthreadCompatExports ExportName = "scePthreadCondInit", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")] - public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx[CpuRegister.Rdi]); + public static int PthreadCondInit(CpuContext ctx) => PthreadCondInitCore(ctx, ctx[CpuRegister.Rdi]); [SysAbiExport( Nid = "g+PZd2hiacg", ExportName = "scePthreadCondDestroy", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")] - public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx[CpuRegister.Rdi]); + public static int PthreadCondDestroy(CpuContext ctx) => PthreadCondDestroyCore(ctx, ctx[CpuRegister.Rdi]); [SysAbiExport( Nid = "WKAXJ4XBPQ4", @@ -370,6 +372,7 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } + _ = ctx.TryWriteUInt64(mutexAddress, 0); state.Semaphore.Dispose(); return (int)OrbisGen2Result.ORBIS_GEN2_OK; } @@ -381,20 +384,10 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); - PthreadMutexState state; - lock (_stateGate) + if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state)) { - if (!_mutexStates.TryGetValue(resolvedAddress, out state!)) - { - state = new PthreadMutexState(); - _mutexStates[resolvedAddress] = state; - } - - if (resolvedAddress != mutexAddress) - { - _mutexStates[mutexAddress] = 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; } var currentThreadId = KernelPthreadState.GetCurrentThreadHandle(); @@ -449,14 +442,7 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - var resolvedAddress = ResolveMutexHandle(ctx, mutexAddress); - PthreadMutexState? state; - lock (_stateGate) - { - _mutexStates.TryGetValue(resolvedAddress, out state); - } - - if (state is null) + if (!TryResolveMutexState(ctx, mutexAddress, createIfZero: true, out var resolvedAddress, out var state)) { TracePthreadMutex(ctx, "unlock", mutexAddress, resolvedAddress, null, KernelPthreadState.GetCurrentThreadHandle(), (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; } + 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) { 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) { var id = unchecked((ulong)Interlocked.Increment(ref nextId)); return baseAddress + (id << 4); } - private static int PthreadCondInitCore(ulong condAddress) + private static int PthreadCondInitCore(CpuContext ctx, ulong condAddress) { if (condAddress == 0) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } + var syntheticHandle = AllocateSyntheticHandle(SyntheticCondHandleBase, ref _nextSyntheticCondHandleId); 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; } - private static int PthreadCondDestroyCore(ulong condAddress) + private static int PthreadCondDestroyCore(CpuContext ctx, ulong condAddress) { if (condAddress == 0) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } + var resolvedAddress = ResolveCondHandle(ctx, condAddress); lock (_stateGate) { - _condStates.Remove(condAddress); + _condStates.Remove(resolvedAddress); + if (resolvedAddress != condAddress) + { + _condStates.Remove(condAddress); + } } + _ = ctx.TryWriteUInt64(condAddress, 0); return (int)OrbisGen2Result.ORBIS_GEN2_OK; } @@ -708,14 +851,9 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - PthreadCondState state; - lock (_stateGate) + if (!TryResolveCondState(ctx, condAddress, createIfZero: true, out _, out var state)) { - if (!_condStates.TryGetValue(condAddress, out state!)) - { - state = new PthreadCondState(); - _condStates[condAddress] = state; - } + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } var waitResult = (int)OrbisGen2Result.ORBIS_GEN2_OK; @@ -770,14 +908,9 @@ public static class KernelPthreadCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - PthreadCondState state; - lock (_stateGate) + if (!TryResolveCondState(null, condAddress, createIfZero: false, out _, out var state)) { - if (!_condStates.TryGetValue(condAddress, out state!)) - { - state = new PthreadCondState(); - _condStates[condAddress] = state; - } + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } lock (state.SyncRoot) diff --git a/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs index 908a6bf..56f4c52 100644 --- a/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelPthreadExtendedCompatExports.cs @@ -18,6 +18,7 @@ public static class KernelPthreadExtendedCompatExports private const int DefaultInheritSched = 0; private const int DefaultSchedPolicy = 0; private const int DefaultSchedPriority = 0; + private const ulong SyntheticRwlockHandleBase = 0x00006003_0000_0000; private static readonly object _stateGate = new(); private static readonly Dictionary _threadStates = new(); @@ -25,6 +26,7 @@ public static class KernelPthreadExtendedCompatExports private static readonly Dictionary _rwlockStates = new(); private static readonly Dictionary _tlsKeys = new(); private static int _nextTlsKey = 1; + private static long _nextSyntheticRwlockHandleId = 1; [ThreadStatic] private static Dictionary? _threadLocalSpecific; @@ -591,20 +593,32 @@ public static class KernelPthreadExtendedCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } + var syntheticHandle = AllocateSyntheticHandle(SyntheticRwlockHandleBase, ref _nextSyntheticRwlockHandleId); lock (_stateGate) { - if (_rwlockStates.Remove(rwlockAddress, out var existing)) + var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress); + if (_rwlockStates.Remove(resolvedAddress, out var existing)) { 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; 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( Nid = "BB+kb08Tl9A", ExportName = "scePthreadRwlockDestroy", @@ -618,10 +632,15 @@ public static class KernelPthreadExtendedCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } + var resolvedAddress = ResolveRwlockHandle(ctx, rwlockAddress); ReaderWriterLockSlim? state; lock (_stateGate) { - _rwlockStates.Remove(rwlockAddress, out state); + _rwlockStates.Remove(resolvedAddress, out state); + if (resolvedAddress != rwlockAddress) + { + _rwlockStates.Remove(rwlockAddress); + } } if (state is null) @@ -629,24 +648,46 @@ public static class KernelPthreadExtendedCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } + _ = ctx.TryWriteUInt64(rwlockAddress, 0); state.Dispose(); ctx[CpuRegister.Rax] = 0; 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( Nid = "Ox9i0c7L5w0", ExportName = "scePthreadRwlockRdlock", Target = Generation.Gen4 | Generation.Gen5, 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( Nid = "mqdNorrB+gI", ExportName = "scePthreadRwlockWrlock", Target = Generation.Gen4 | Generation.Gen5, 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( Nid = "+L98PIbGttk", @@ -661,13 +702,7 @@ public static class KernelPthreadExtendedCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - ReaderWriterLockSlim? rwlock; - lock (_stateGate) - { - _rwlockStates.TryGetValue(rwlockAddress, out rwlock); - } - - if (rwlock is null) + if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } @@ -695,6 +730,13 @@ public static class KernelPthreadExtendedCompatExports 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( Nid = "mqULNdimTn0", ExportName = "pthread_key_create", @@ -730,6 +772,13 @@ public static class KernelPthreadExtendedCompatExports 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( Nid = "6BpEZuDT7YI", ExportName = "pthread_key_delete", @@ -751,6 +800,13 @@ public static class KernelPthreadExtendedCompatExports 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( Nid = "WrOLvHU0yQM", ExportName = "pthread_setspecific", @@ -774,6 +830,13 @@ public static class KernelPthreadExtendedCompatExports 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( Nid = "0-KXaS70xy4", ExportName = "pthread_getspecific", @@ -798,21 +861,23 @@ public static class KernelPthreadExtendedCompatExports 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) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - ReaderWriterLockSlim rwlock; - lock (_stateGate) + if (!TryResolveRwlockState(ctx, rwlockAddress, createIfZero: true, out _, out var rwlock)) { - if (!_rwlockStates.TryGetValue(rwlockAddress, out rwlock!)) - { - rwlock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - _rwlockStates[rwlockAddress] = rwlock; - } + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } try @@ -834,6 +899,100 @@ public static class KernelPthreadExtendedCompatExports 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) { if (_threadStates.TryGetValue(thread, out var state)) diff --git a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs index 99a0462..f49f5c1 100644 --- a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs @@ -39,7 +39,10 @@ public static class KernelRuntimeCompatExports private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader(); private static readonly ulong _kernelTscFrequency = ResolveKernelTscFrequency(); 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 _processProcParamAddress; private static ulong _nextReservedVirtualBase = 0x6000_0000_0UL; diff --git a/src/SharpEmu.Libs/LibcInternalExports.cs b/src/SharpEmu.Libs/LibcInternalExports.cs new file mode 100644 index 0000000..9449f7d --- /dev/null +++ b/src/SharpEmu.Libs/LibcInternalExports.cs @@ -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; + } + } +} diff --git a/src/SharpEmu.Libs/Rtc/RtcExports.cs b/src/SharpEmu.Libs/Rtc/RtcExports.cs new file mode 100644 index 0000000..f02017f --- /dev/null +++ b/src/SharpEmu.Libs/Rtc/RtcExports.cs @@ -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 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 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); +}