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

This commit is contained in:
ParantezTech
2026-03-14 20:38:25 +03:00
parent 028494a83b
commit 1f71c970d9
21 changed files with 2291 additions and 216 deletions
+73 -15
View File
@@ -12,6 +12,12 @@ namespace SharpEmu.Core.Cpu;
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
{
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<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default)
{
Console.Error.WriteLine("[DISPATCHER] === DispatchModuleInitializer START ===");
Console.Error.WriteLine($"[DISPATCHER] moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
try
{
return DispatchEntryCore(
entryPoint,
generation,
importStubs,
runtimeSymbols,
moduleName,
executionOptions,
EntryFrameKind.ModuleInitializer);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
throw;
}
}
private OrbisGen2Result DispatchEntryCore(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? 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<ulong, string>()
: new Dictionary<ulong, string>(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);
+8
View File
@@ -34,4 +34,12 @@ public interface ICpuDispatcher
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default);
OrbisGen2Result DispatchModuleInitializer(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default);
}
@@ -4,6 +4,7 @@
using System;
using System.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<ulong, int>(targetList.Count);
for (var i = 0; i < targetList.Count; i++)
{
hitCounts[targetList[i]] = 0;
}
ulong address = scanBase;
while (address < scanEnd)
{
if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0)
{
break;
}
ulong regionBase = mbi.BaseAddress;
ulong regionEnd = regionBase + mbi.RegionSize;
if (regionEnd <= address)
{
break;
}
if (mbi.State == MEM_COMMIT &&
IsReadableProtection(mbi.Protect) &&
IsExecutableProtection(mbi.Protect))
{
ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget);
}
var allTargetsSatisfied = true;
for (var i = 0; i < targetList.Count; i++)
{
if (hitCounts[targetList[i]] < maxHitsPerTarget)
{
allTargetsSatisfied = false;
break;
}
}
if (allTargetsSatisfied)
{
break;
}
address = regionEnd;
}
for (var i = 0; i < targetList.Count; i++)
{
var target = targetList[i];
if (!hitCounts.TryGetValue(target, out var count) || count == 0)
{
Console.Error.WriteLine($"[LOADER][INFO] Ref scan 0x{target:X16}: none");
}
}
}
private void DumpGuestPointerWindowDiagnostics()
{
var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOWS"));
if (targetList.Count == 0)
{
return;
}
var windowSize = 0x80;
var rawWindowSize = Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOW_SIZE");
if (!string.IsNullOrWhiteSpace(rawWindowSize))
{
var normalized = rawWindowSize.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? rawWindowSize[2..]
: rawWindowSize;
if (int.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsedWindowSize) &&
parsedWindowSize > 0)
{
windowSize = parsedWindowSize;
}
}
foreach (var target in targetList)
{
DumpPointerWindow($"ptrwin-0x{target:X16}", target, windowSize);
}
}
private void ScanExecutableRegionForTargetReferences(
ulong regionBase,
ulong regionEnd,
IReadOnlyList<ulong> targets,
IDictionary<ulong, int> hitCounts,
int maxHitsPerTarget)
{
if (_cpuContext == null || regionEnd <= regionBase)
{
return;
}
ulong rip = regionBase;
while (rip < regionEnd)
{
if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) ||
!IcedDecoder.TryDecode(rip, bytes, out var instruction) ||
instruction.Length <= 0)
{
rip++;
continue;
}
if (instruction.MemoryAddress is { } memoryAddress)
{
for (var i = 0; i < targets.Count; i++)
{
var target = targets[i];
if (memoryAddress != target ||
!hitCounts.TryGetValue(target, out var count) ||
count >= maxHitsPerTarget)
{
continue;
}
hitCounts[target] = count + 1;
Console.Error.WriteLine(
$"[LOADER][INFO] Ref scan hit target=0x{target:X16} rip=0x{instruction.Rip:X16} text={instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}");
}
}
rip += (ulong)instruction.Length;
}
}
private static List<ulong> ParseDiagnosticAddresses(string? rawValue)
{
var result = new List<ulong>();
if (string.IsNullOrWhiteSpace(rawValue))
{
return result;
}
foreach (var token in rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? token[2..]
: token;
if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address))
{
continue;
}
if (!result.Contains(address))
{
result.Add(address);
}
}
return result;
}
private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size)
{
if (baseAddress < 0x10000 || size <= 0)
@@ -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;
@@ -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)
+12
View File
@@ -17,6 +17,9 @@ public sealed class SelfImage
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
IReadOnlyList<ImportedSymbolRelocation>? importedRelocations = null,
IReadOnlyList<ulong>? preInitializerFunctions = null,
IReadOnlyList<ulong>? initializerFunctions = null,
ulong initFunctionEntryPoint = 0,
ulong imageBase = 0,
ulong procParamAddress = 0)
{
@@ -30,6 +33,9 @@ public sealed class SelfImage
ImportStubs = importStubs ?? new Dictionary<ulong, string>();
RuntimeSymbols = runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal);
ImportedRelocations = importedRelocations ?? Array.Empty<ImportedSymbolRelocation>();
PreInitializerFunctions = preInitializerFunctions ?? Array.Empty<ulong>();
InitializerFunctions = initializerFunctions ?? Array.Empty<ulong>();
InitFunctionEntryPoint = initFunctionEntryPoint;
_imageBase = imageBase;
ProcParamAddress = procParamAddress;
}
@@ -48,6 +54,12 @@ public sealed class SelfImage
public IReadOnlyList<ImportedSymbolRelocation> ImportedRelocations { get; }
public IReadOnlyList<ulong> PreInitializerFunctions { get; }
public IReadOnlyList<ulong> InitializerFunctions { get; }
public ulong InitFunctionEntryPoint { get; }
public ulong EntryPoint => ElfHeader.EntryPoint + _imageBase;
public ulong ProcParamAddress { get; }
+197 -4
View File
@@ -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<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>();
private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols =
new Dictionary<string, ulong>(StringComparer.Ordinal);
private static readonly IReadOnlyList<ulong> EmptyInitializerFunctions = Array.Empty<ulong>();
private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>();
private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>();
private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>();
@@ -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<byte> imageData,
LoadContext loadContext,
IReadOnlyList<ProgramHeader> programHeaders,
IVirtualMemory virtualMemory,
ulong imageBase,
out ulong initFunctionEntryPoint,
out IReadOnlyList<ulong> preInitializerFunctions,
out IReadOnlyList<ulong> initializerFunctions)
{
initFunctionEntryPoint = 0;
preInitializerFunctions = EmptyInitializerFunctions;
initializerFunctions = EmptyInitializerFunctions;
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex) ||
dynamicHeader.FileSize == 0)
{
return;
}
if (!TryLoadDynamicTableBytes(
imageData,
loadContext,
virtualMemory,
imageBase,
dynamicHeader,
dynamicHeaderIndex,
out var dynamicTable))
{
return;
}
var dynamicInfo = ParseDynamicInfo(dynamicTable);
var preInitializers = new List<ulong>(4);
var initializers = new List<ulong>(8);
initFunctionEntryPoint = ResolveMappedAddressOrFallback(virtualMemory, dynamicInfo.InitOffset, imageBase);
if (initFunctionEntryPoint < 0x10000)
{
initFunctionEntryPoint = 0;
}
AppendInitializerArrayEntries(
preInitializers,
imageData,
virtualMemory,
imageBase,
dynamicInfo.PreInitArrayOffset,
dynamicInfo.PreInitArraySize);
AppendResolvedInitializer(initializers, dynamicInfo.InitOffset, virtualMemory, imageBase);
AppendInitializerArrayEntries(
initializers,
imageData,
virtualMemory,
imageBase,
dynamicInfo.InitArrayOffset,
dynamicInfo.InitArraySize);
if (preInitializers.Count != 0)
{
preInitializerFunctions = preInitializers;
}
if (initializers.Count != 0)
{
initializerFunctions = initializers;
}
if (preInitializers.Count != 0 || initializers.Count != 0)
{
Console.Error.WriteLine(
$"[LOADER] Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
}
}
private static void AppendInitializerArrayEntries(
ICollection<ulong> destination,
ReadOnlySpan<byte> imageData,
IVirtualMemory virtualMemory,
ulong imageBase,
ulong arrayOffset,
ulong arraySize)
{
if (arrayOffset == 0 || arraySize < sizeof(ulong))
{
return;
}
if (!TryLoadTableBytes(imageData, virtualMemory, imageBase, arrayOffset, arraySize, out var arrayBytes))
{
return;
}
var entryCount = arrayBytes.Length / sizeof(ulong);
for (var i = 0; i < entryCount; i++)
{
var entryOffset = i * sizeof(ulong);
var entryAddress = BinaryPrimitives.ReadUInt64LittleEndian(arrayBytes.AsSpan(entryOffset, sizeof(ulong)));
AppendResolvedInitializer(destination, entryAddress, virtualMemory, imageBase);
}
}
private static void AppendResolvedInitializer(
ICollection<ulong> destination,
ulong functionAddress,
IVirtualMemory virtualMemory,
ulong imageBase)
{
var resolvedAddress = ResolveMappedAddressOrFallback(virtualMemory, functionAddress, imageBase);
if (resolvedAddress < 0x10000)
{
return;
}
foreach (var existing in destination)
{
if (existing == resolvedAddress)
{
return;
}
}
destination.Add(resolvedAddress);
}
private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations(
IReadOnlyList<RelocationDescriptor> 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 &&
@@ -11,6 +11,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IDisposable
{
private readonly object _gate = new();
private readonly List<MemoryRegion> _regions = new();
private readonly Dictionary<ulong, ProgramHeaderFlags> _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;
+203 -7
View File
@@ -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<string> 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<ulong, string>(image.ImportStubs);
var activeRuntimeSymbols = new Dictionary<string, ulong>(image.RuntimeSymbols, StringComparer.Ordinal);
var loadedModuleImages = LoadAdjacentSceModules(ebootPath, activeImportStubs, activeRuntimeSymbols);
RebindImportedDataSymbols(image, loadedModuleImages, activeRuntimeSymbols);
var processImageName = Path.GetFileName(ebootPath);
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<SelfImage> LoadAdjacentSceModules(
private OrbisGen2Result? RunAllInitializers(
SelfImage mainImage,
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
var moduleStartResult = RunPreloadedModuleInitializers(
loadedModuleImages,
generation,
activeImportStubs,
activeRuntimeSymbols);
if (moduleStartResult is not null)
{
return moduleStartResult;
}
// On current PS5 dumps DT_INIT commonly resolves to imageBase+0x10, which is inside
// the mapped ELF header rather than a callable guest routine. Startup must remain
// guest-driven until the PS5 init/module ABI is identified precisely.
return null;
}
private OrbisGen2Result? RunPreloadedModuleInitializers(
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols)
{
for (var i = 0; i < loadedModuleImages.Count; i++)
{
var loadedModule = loadedModuleImages[i];
var initEntryPoint = loadedModule.Image.InitFunctionEntryPoint;
if (initEntryPoint < 0x10000)
{
continue;
}
var moduleName = Path.GetFileName(loadedModule.Path);
if (string.IsNullOrWhiteSpace(moduleName))
{
moduleName = $"module#{i}";
}
Console.Error.WriteLine(
$"[RUNTIME] Starting module {moduleName}: dt_init=0x{initEntryPoint:X16}");
var result = _cpuDispatcher.DispatchModuleInitializer(
initEntryPoint,
generation,
activeImportStubs,
activeRuntimeSymbols,
moduleName,
_cpuExecutionOptions);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
Console.Error.WriteLine(
$"[RUNTIME] Module start failed: {moduleName} -> {result}");
return result;
}
}
return null;
}
private OrbisGen2Result? RunImageInitializers(
string label,
SelfImage image,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
if (image.PreInitializerFunctions.Count == 0 && image.InitializerFunctions.Count == 0)
{
return null;
}
Console.Error.WriteLine(
$"[RUNTIME] Running initializers for {label}: preinit={image.PreInitializerFunctions.Count}, init={image.InitializerFunctions.Count}");
var result = RunInitializerList(
$"{label}:preinit",
image.PreInitializerFunctions,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
if (result is not null)
{
return result;
}
return RunInitializerList(
$"{label}:init",
image.InitializerFunctions,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName);
}
private OrbisGen2Result? RunInitializerList(
string label,
IReadOnlyList<ulong> initializerFunctions,
Generation generation,
IReadOnlyDictionary<ulong, string> activeImportStubs,
IReadOnlyDictionary<string, ulong> activeRuntimeSymbols,
string processImageName)
{
for (var i = 0; i < initializerFunctions.Count; i++)
{
var initializerAddress = initializerFunctions[i];
if (initializerAddress < 0x10000)
{
continue;
}
Console.Error.WriteLine(
$"[RUNTIME] Initializer {label}[{i}] -> 0x{initializerAddress:X16}");
var result = _cpuDispatcher.DispatchEntry(
initializerAddress,
generation,
activeImportStubs,
activeRuntimeSymbols,
processImageName,
_cpuExecutionOptions);
if (result != OrbisGen2Result.ORBIS_GEN2_OK)
{
return result;
}
}
return null;
}
private List<LoadedModuleImage> LoadAdjacentSceModules(
string ebootPath,
IDictionary<ulong, string> importStubs,
IDictionary<string, ulong> runtimeSymbols)
{
var loadedImages = new List<SelfImage>();
var loadedImages = new List<LoadedModuleImage>();
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<SelfImage> loadedModuleImages,
IReadOnlyList<LoadedModuleImage> loadedModuleImages,
IReadOnlyDictionary<string, ulong> 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<string, ulong> runtimeSymbols)
{
foreach (var nid in HleDataSymbols.EnumerateKnownNids())
{
if (runtimeSymbols.ContainsKey(nid) ||
!HleDataSymbols.TryGetAddress(nid, out var symbolAddress) ||
!IsUsableRuntimeSymbolAddress(symbolAddress))
{
continue;
}
runtimeSymbols[nid] = symbolAddress;
}
}
private static int MergeImportStubs(
IDictionary<ulong, string> destination,
IReadOnlyDictionary<ulong, string> source,
+130
View File
@@ -0,0 +1,130 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using System.Text;
namespace SharpEmu.HLE;
public static class HleDataSymbols
{
private const string StackChkGuardNid = "f7uOxY9mM1U";
private const string ProgNameNid = "djxxOmW6-aw";
private const string LibcNeedFlagNid = "P330P3dFF68";
private const string LibcInternalNeedFlagNid = "ZT4ODD2Ts9o";
private const int ProgNameMaxBytes = 511;
private const ulong StackChkGuardValue = 0xC0DEC0DECAFEBABEUL;
private static readonly object _gate = new();
private static readonly nint _stackChkGuardAddress = Allocate(sizeof(ulong) * 2);
private static readonly nint _progNameBufferAddress = Allocate(ProgNameMaxBytes + 1);
private static readonly nint _progNamePointerAddress = Allocate(nint.Size);
private static readonly nint _libcNeedFlagAddress = Allocate(sizeof(uint));
private static readonly nint _libcInternalNeedFlagAddress = Allocate(sizeof(uint));
static HleDataSymbols()
{
if (_stackChkGuardAddress != 0)
{
Marshal.WriteInt64(_stackChkGuardAddress, unchecked((long)StackChkGuardValue));
Marshal.WriteInt64(
IntPtr.Add(_stackChkGuardAddress, sizeof(ulong)),
unchecked((long)StackChkGuardValue));
}
if (_libcNeedFlagAddress != 0)
{
Marshal.WriteInt32(_libcNeedFlagAddress, 1);
}
if (_libcInternalNeedFlagAddress != 0)
{
Marshal.WriteInt32(_libcInternalNeedFlagAddress, 1);
}
ConfigureProcessImageName("eboot.bin");
}
public static IEnumerable<string> EnumerateKnownNids()
{
yield return StackChkGuardNid;
yield return ProgNameNid;
yield return LibcNeedFlagNid;
yield return LibcInternalNeedFlagNid;
}
public static void ConfigureProcessImageName(string? processImageName)
{
var effectiveName = string.IsNullOrWhiteSpace(processImageName)
? "eboot.bin"
: processImageName;
var encodedName = Encoding.UTF8.GetBytes(effectiveName);
var byteCount = Math.Min(encodedName.Length, ProgNameMaxBytes);
lock (_gate)
{
if (_progNameBufferAddress == 0 || _progNamePointerAddress == 0)
{
return;
}
for (var i = 0; i <= ProgNameMaxBytes; i++)
{
Marshal.WriteByte(_progNameBufferAddress, i, 0);
}
Marshal.Copy(encodedName, 0, _progNameBufferAddress, byteCount);
WritePointer(_progNamePointerAddress, _progNameBufferAddress);
}
}
public static bool TryGetAddress(string nid, out ulong address)
{
var pointer = nid switch
{
StackChkGuardNid => _stackChkGuardAddress,
ProgNameNid => _progNamePointerAddress,
LibcNeedFlagNid => _libcNeedFlagAddress,
LibcInternalNeedFlagNid => _libcInternalNeedFlagAddress,
_ => 0,
};
if (pointer == 0)
{
address = 0;
return false;
}
address = unchecked((ulong)pointer);
return true;
}
private static nint Allocate(int size)
{
try
{
var memory = Marshal.AllocHGlobal(size);
for (var i = 0; i < size; i++)
{
Marshal.WriteByte(memory, i, 0);
}
return memory;
}
catch
{
return 0;
}
}
private static void WritePointer(nint target, nint value)
{
if (nint.Size == sizeof(int))
{
Marshal.WriteInt32(target, value.ToInt32());
return;
}
Marshal.WriteInt64(target, value.ToInt64());
}
}
+2
View File
@@ -17,5 +17,7 @@ public interface IModuleManager
bool TryGetExportByName(string exportName, out ExportedFunction export);
bool TryDispatch(string nid, CpuContext context, out OrbisGen2Result result);
OrbisGen2Result Dispatch(string nid, CpuContext context);
}
+12 -3
View File
@@ -93,6 +93,12 @@ public sealed class ModuleManager : IModuleManager
}
public OrbisGen2Result Dispatch(string nid, CpuContext context)
{
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<Type, object> instances)
+5
View File
@@ -45,6 +45,11 @@ public enum OrbisGen2Result : int
/// </summary>
ORBIS_GEN2_ERROR_BUSY = unchecked((int)0x80020010),
/// <summary>
/// Indicates that the operation should be retried later.
/// </summary>
ORBIS_GEN2_ERROR_TRY_AGAIN = unchecked((int)0x80020023),
/// <summary>
/// Indicates that behavior is recognized but not implemented yet.
/// </summary>
+30 -29
View File
@@ -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}" : "<unreadable>")}");
$"[LOADER][TRACE] {op}: guard=0x{guardPtr:X16} init={initialized} in_progress={inProgress} word={(readable ? $"0x{word:X16}" : "<unreadable>")}");
}
private static void LogGuardResult(string op, ulong guardPtr, int result, bool initialized, bool inProgress, int ownerThreadId)
+20
View File
@@ -11,6 +11,9 @@ public static class KernelExports
private static int _nextFileDescriptor = 2;
private static readonly object _cxaGate = new();
private static readonly List<CxaDestructorEntry> _cxaDestructors = new();
private static readonly object _coredumpGate = new();
private static ulong _coredumpHandler;
private static ulong _coredumpHandlerContext;
private readonly record struct CxaDestructorEntry(
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",
@@ -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<CpuContext, int> operation,
ulong rdi = 0,
ulong rsi = 0,
ulong rdx = 0,
ulong rcx = 0,
ulong r8 = 0,
ulong r9 = 0)
{
var savedRdi = ctx[CpuRegister.Rdi];
var savedRsi = ctx[CpuRegister.Rsi];
var savedRdx = ctx[CpuRegister.Rdx];
var savedRcx = ctx[CpuRegister.Rcx];
var savedR8 = ctx[CpuRegister.R8];
var savedR9 = ctx[CpuRegister.R9];
ctx[CpuRegister.Rdi] = rdi;
ctx[CpuRegister.Rsi] = rsi;
ctx[CpuRegister.Rdx] = rdx;
ctx[CpuRegister.Rcx] = rcx;
ctx[CpuRegister.R8] = r8;
ctx[CpuRegister.R9] = r9;
try
{
return operation(ctx);
}
finally
{
ctx[CpuRegister.Rdi] = savedRdi;
ctx[CpuRegister.Rsi] = savedRsi;
ctx[CpuRegister.Rdx] = savedRdx;
ctx[CpuRegister.Rcx] = savedRcx;
ctx[CpuRegister.R8] = savedR8;
ctx[CpuRegister.R9] = savedR9;
}
}
private static bool TryReadBatchMapEntry(CpuContext ctx, ulong entryAddress, out BatchMapEntry entry)
{
entry = default;
if (!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryStartOffset, out var start) ||
!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryOffsetOffset, out var offset) ||
!ctx.TryReadUInt64(entryAddress + OrbisKernelBatchMapEntryLengthOffset, out var length))
{
return false;
}
Span<byte> protection = stackalloc byte[1];
Span<byte> memoryType = stackalloc byte[1];
if (!TryReadCompat(ctx, entryAddress + OrbisKernelBatchMapEntryProtectionOffset, protection) ||
!TryReadCompat(ctx, entryAddress + OrbisKernelBatchMapEntryTypeOffset, memoryType) ||
!TryReadUInt32Compat(ctx, entryAddress + OrbisKernelBatchMapEntryOperationOffset, out var operation))
{
return false;
}
entry = new BatchMapEntry(start, offset, length, protection[0], memoryType[0], unchecked((int)operation));
return true;
}
private static bool TryApplyMappedRegionProtectionLocked(
ulong address,
ulong length,
int protection,
int? memoryType = null)
{
if (!_mappedRegions.TryGetValue(address, out var region) || region.Length != length)
{
return false;
}
_mappedRegions[address] = region with { Protection = protection };
if (memoryType.HasValue &&
region.DirectStart != 0 &&
_directAllocations.TryGetValue(region.DirectStart, out var allocation))
{
_directAllocations[region.DirectStart] = allocation with { MemoryType = memoryType.Value };
}
return true;
}
private static void TraceDirectMemoryCall(
CpuContext ctx,
string operation,
ulong length,
ulong alignment,
int memoryType,
ulong outAddress,
ulong selectedAddress = 0,
OrbisGen2Result? result = null)
{
if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal))
{
return;
}
var returnRip = 0UL;
var stackPointer = ctx[CpuRegister.Rsp];
if (stackPointer != 0)
{
_ = ctx.TryReadUInt64(stackPointer, out returnRip);
}
Console.Error.WriteLine(
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
}
private static bool TryAllocateDirectMemoryLocked(
ulong searchStart,
ulong searchEnd,
ulong length,
ulong alignment,
int memoryType,
out ulong selectedAddress)
{
selectedAddress = 0;
if (length == 0 || searchStart >= searchEnd)
{
return false;
}
var effectiveAlignment = alignment == 0 ? 0x1000UL : alignment;
if (!TryFindAllocatableDirectMemoryRangeLocked(searchStart, searchEnd, length, effectiveAlignment, out var freePosition) ||
!TryAddU64(freePosition, length, out var endAddress))
{
return false;
}
_directAllocations[freePosition] = new DirectAllocation(freePosition, length, memoryType);
_nextPhysicalAddress = endAddress;
selectedAddress = freePosition;
return true;
}
private static bool TryFindAllocatableDirectMemoryRangeLocked(
ulong searchStart,
ulong searchEnd,
ulong length,
ulong alignment,
out ulong selectedAddress)
{
selectedAddress = 0;
if (length == 0 || searchStart >= searchEnd)
{
return false;
}
var effectiveEnd = Math.Min(searchEnd, DirectMemorySizeBytes);
var candidate = AlignUp(searchStart, alignment);
if (candidate >= effectiveEnd)
{
return false;
}
var allocations = new List<DirectAllocation>(_directAllocations.Values);
allocations.Sort(static (left, right) => left.Start.CompareTo(right.Start));
foreach (var allocation in allocations)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var allocationEnd))
{
return false;
}
if (allocationEnd <= candidate)
{
continue;
}
var gapEnd = Math.Min(allocation.Start, effectiveEnd);
if (candidate < gapEnd &&
TryAddU64(candidate, length, out var candidateEnd) &&
candidateEnd <= gapEnd)
{
selectedAddress = candidate;
return true;
}
if (allocation.Start >= effectiveEnd)
{
break;
}
candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment);
if (candidate >= effectiveEnd)
{
return false;
}
}
if (!TryAddU64(candidate, length, out var endAddress) || endAddress > effectiveEnd)
{
return false;
}
selectedAddress = candidate;
return true;
}
private static bool TryFindAvailableDirectMemorySpanLocked(
ulong searchStart,
ulong searchEnd,
ulong alignment,
out ulong spanStart,
out ulong spanLength)
{
spanStart = 0;
spanLength = 0;
if (searchStart >= searchEnd)
{
return false;
}
var effectiveEnd = Math.Min(searchEnd, DirectMemorySizeBytes);
var candidate = AlignUp(searchStart, alignment);
if (candidate >= effectiveEnd)
{
return false;
}
var allocations = new List<DirectAllocation>(_directAllocations.Values);
allocations.Sort(static (left, right) => left.Start.CompareTo(right.Start));
foreach (var allocation in allocations)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var allocationEnd))
{
return false;
}
if (allocationEnd <= candidate)
{
continue;
}
var gapEnd = Math.Min(allocation.Start, effectiveEnd);
if (candidate < gapEnd)
{
spanStart = candidate;
spanLength = gapEnd - candidate;
return true;
}
if (allocation.Start >= effectiveEnd)
{
break;
}
candidate = AlignUp(Math.Max(candidate, allocationEnd), alignment);
if (candidate >= effectiveEnd)
{
return false;
}
}
spanStart = candidate;
spanLength = effectiveEnd - candidate;
return spanLength != 0;
}
private static ulong GetDirectMemoryHighWaterMarkLocked()
{
ulong highWaterMark = 0;
foreach (var allocation in _directAllocations.Values)
{
if (!TryAddU64(allocation.Start, allocation.Length, out var endAddress))
{
return DirectMemorySizeBytes;
}
if (endAddress > highWaterMark)
{
highWaterMark = endAddress;
}
}
return Math.Min(highWaterMark, DirectMemorySizeBytes);
}
private static bool TryReadHostMemory(ulong address, Span<byte> destination)
{
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;
}
}
@@ -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<ulong, PthreadMutexState> _mutexStates = new();
@@ -22,6 +23,7 @@ public static class KernelPthreadCompatExports
private static readonly HashSet<ulong> _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)
@@ -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<ulong, ThreadState> _threadStates = new();
@@ -25,6 +26,7 @@ public static class KernelPthreadExtendedCompatExports
private static readonly Dictionary<ulong, ReaderWriterLockSlim> _rwlockStates = new();
private static readonly Dictionary<int, TlsKeyState> _tlsKeys = new();
private static int _nextTlsKey = 1;
private static long _nextSyntheticRwlockHandleId = 1;
[ThreadStatic]
private static Dictionary<int, ulong>? _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))
@@ -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;
+78
View File
@@ -0,0 +1,78 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.InteropServices;
using SharpEmu.HLE;
namespace SharpEmu.Libs.LibcInternal;
public static class LibcInternalExports
{
private const ulong HeapTraceInfoSize = 32;
private const int HeapTraceTableEntryCount = 64;
private const int HeapTraceMaskOffset = 0;
private const int HeapTraceTableOffset = HeapTraceMaskOffset + sizeof(ulong);
private const int HeapTraceStorageSize = HeapTraceTableOffset + (HeapTraceTableEntryCount * sizeof(ulong));
private static readonly object _heapTraceGate = new();
private static nint _heapTraceStorage;
[SysAbiExport(
Nid = "NWtTN10cJzE",
ExportName = "LibcHeapGetTraceInfo",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "LibcInternalExt")]
public static int LibcHeapGetTraceInfo(CpuContext ctx)
{
var infoAddress = ctx[CpuRegister.Rdi];
if (infoAddress == 0 || !ctx.TryReadUInt64(infoAddress, out var size) || size != HeapTraceInfoSize)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var storage = EnsureHeapTraceStorage();
if (storage == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var maskAddress = unchecked((ulong)(storage + HeapTraceMaskOffset));
var tableAddress = unchecked((ulong)(storage + HeapTraceTableOffset));
if (!ctx.TryWriteUInt64(infoAddress + 16, maskAddress) ||
!ctx.TryWriteUInt64(infoAddress + 24, tableAddress))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static nint EnsureHeapTraceStorage()
{
lock (_heapTraceGate)
{
if (_heapTraceStorage != 0)
{
return _heapTraceStorage;
}
var storage = Marshal.AllocHGlobal(HeapTraceStorageSize);
if (storage == 0)
{
return 0;
}
unsafe
{
NativeMemory.Clear((void*)storage, (nuint)HeapTraceStorageSize);
}
_heapTraceStorage = storage;
return storage;
}
}
}
+119
View File
@@ -0,0 +1,119 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.Libs.Rtc;
public static class RtcExports
{
[SysAbiExport(
Nid = "ZPD1YOKI+Kw",
ExportName = "sceRtcGetCurrentClockLocalTime",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRtc")]
public static int RtcGetCurrentClockLocalTime(CpuContext ctx)
{
var timeAddress = ctx[CpuRegister.Rdi];
if (timeAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var now = DateTimeOffset.Now;
Span<byte> rtcDateTime = stackalloc byte[16];
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[0..2], checked((ushort)now.Year));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[2..4], checked((ushort)now.Month));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[4..6], checked((ushort)now.Day));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[6..8], checked((ushort)now.Hour));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[8..10], checked((ushort)now.Minute));
BinaryPrimitives.WriteUInt16LittleEndian(rtcDateTime[10..12], checked((ushort)now.Second));
BinaryPrimitives.WriteUInt32LittleEndian(
rtcDateTime[12..16],
checked((uint)((now.Ticks % TimeSpan.TicksPerSecond) / 10)));
if (!ctx.Memory.TryWrite(timeAddress, rtcDateTime))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "8w-H19ip48I",
ExportName = "sceRtcGetTick",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libSceRtc")]
public static int RtcGetTick(CpuContext ctx)
{
var dateTimeAddress = ctx[CpuRegister.Rdi];
var tickAddress = ctx[CpuRegister.Rsi];
if (dateTimeAddress == 0 || tickAddress == 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!TryReadRtcDateTime(ctx, dateTimeAddress, out var rtcDateTime))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ulong tickValue;
try
{
var baseDateTime = new DateTime(
rtcDateTime.Year,
rtcDateTime.Month,
rtcDateTime.Day,
rtcDateTime.Hour,
rtcDateTime.Minute,
rtcDateTime.Second,
DateTimeKind.Utc);
tickValue = checked((ulong)((baseDateTime.Ticks / 10) + rtcDateTime.Microsecond));
}
catch (ArgumentOutOfRangeException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryWriteUInt64(tickAddress, tickValue))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryReadRtcDateTime(CpuContext ctx, ulong address, out RtcDateTime rtcDateTime)
{
Span<byte> buffer = stackalloc byte[16];
if (!ctx.Memory.TryRead(address, buffer))
{
rtcDateTime = default;
return false;
}
rtcDateTime = new RtcDateTime(
BinaryPrimitives.ReadUInt16LittleEndian(buffer[0..2]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[2..4]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[4..6]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[6..8]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[8..10]),
BinaryPrimitives.ReadUInt16LittleEndian(buffer[10..12]),
BinaryPrimitives.ReadUInt32LittleEndian(buffer[12..16]));
return true;
}
private readonly record struct RtcDateTime(
ushort Year,
ushort Month,
ushort Day,
ushort Hour,
ushort Minute,
ushort Second,
uint Microsecond);
}