feat: implement cosf, time, ctype tables, tracked heap access, IL2CPP lookup ABI paths (#542)

* fix: add Messenger CRT and AGC compatibility shims

* fix: keep Messenger IL2CPP bootstrap on HLE shims

* Fix Messenger compatibility ABI handling

* Make IL2CPP ABI regression tests portable
This commit is contained in:
Kurt Himebauch
2026-07-23 08:35:59 -04:00
committed by GitHub
parent 7b950166d7
commit 2764aaab3f
8 changed files with 570 additions and 20 deletions
+166
View File
@@ -21,6 +21,11 @@ public static class CxaGuardExports
}
private static readonly ConcurrentDictionary<ulong, GuardState> _inProgress = new();
private static readonly ConcurrentDictionary<ulong, object> _onceGates = new();
private const int OnceUninitialized = 0;
private const int OnceInProgress = 1;
private const int OnceComplete = 2;
[SysAbiExport(
Nid = "3GPpjQdAMTw",
@@ -171,6 +176,167 @@ public static class CxaGuardExports
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "DiGVep5yB5w",
ExportName = "_ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1_",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int ExecuteOnce(CpuContext ctx)
{
var onceAddress = ctx[CpuRegister.Rdi];
var callbackAddress = ctx[CpuRegister.Rsi];
var parameter = ctx[CpuRegister.Rdx];
if (onceAddress == 0 || callbackAddress == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (!ctx.TryReadInt32(onceAddress, out var onceValue))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (onceValue == OnceComplete)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var gate = _onceGates.GetOrAdd(onceAddress, static _ => new object());
lock (gate)
{
if (!ctx.TryReadInt32(onceAddress, out onceValue))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
while (onceValue == OnceInProgress)
{
Monitor.Wait(gate, TimeSpan.FromMilliseconds(1));
if (!ctx.TryReadInt32(onceAddress, out onceValue))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
if (onceValue == OnceComplete)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
if (!ctx.TryWriteInt32(onceAddress, OnceInProgress))
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
var scheduler = GuestThreadExecution.Scheduler;
var callbackSucceeded = false;
string? callbackError = null;
var allocator = ctx.Memory as IGuestMemoryAllocator;
var hasScratchContext = false;
ulong scratchContextAddress = 0;
try
{
if (allocator is not null && allocator.TryAllocateGuestMemory(0x10, 0x10, out scratchContextAddress))
{
hasScratchContext = true;
_ = ctx.TryWriteUInt64(scratchContextAddress, 0);
_ = ctx.TryWriteUInt64(scratchContextAddress + 8, 0);
}
if (scheduler is null)
{
callbackError = "guest scheduler unavailable";
}
else if (scheduler.TryCallGuestFunction(
ctx,
callbackAddress,
onceAddress,
parameter,
scratchContextAddress,
0,
0,
"std::_Execute_once",
out var returnValue,
out callbackError))
{
callbackSucceeded = returnValue != 0;
}
}
finally
{
if (hasScratchContext && allocator is not null)
{
_ = allocator.TryFreeGuestMemory(scratchContextAddress);
}
}
lock (gate)
{
if (!callbackSucceeded)
{
_ = ctx.TryWriteInt32(onceAddress, OnceUninitialized);
Monitor.PulseAll(gate);
if (!string.IsNullOrWhiteSpace(callbackError))
{
Console.Error.WriteLine(
$"[LOADER][WARN] std::_Execute_once callback failed: {callbackError}");
}
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
}
if (!ctx.TryWriteInt32(onceAddress, OnceComplete))
{
_ = ctx.TryWriteInt32(onceAddress, OnceUninitialized);
Monitor.PulseAll(gate);
ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
Monitor.PulseAll(gate);
// A completed once flag no longer needs a host-side gate. Waiters
// already holding this gate will observe OnceComplete after the
// pulse; future callers take the fast path before looking it up.
_ = _onceGates.TryRemove(onceAddress, out _);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "PsrRUg671K0",
ExportName = "__cxa_increment_exception_refcount",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaIncrementExceptionRefcount(CpuContext ctx)
{
_ = ctx[CpuRegister.Rdi];
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(
Nid = "MQFPAqQPt1s",
ExportName = "__cxa_decrement_exception_refcount",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libc")]
public static int CxaDecrementExceptionRefcount(CpuContext ctx)
{
_ = ctx[CpuRegister.Rdi];
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static bool TryReadGuardState(CpuContext ctx, ulong guardPtr, out ulong word, out bool initialized, out bool inProgress)
{
word = 0;