Appearance
aowlspt_il2cppready.h
Source: abi/aowlspt_il2cppready.h — 200 lines, 10 file-scope functions.
What this header owns
Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.
text
aowlspt_il2cppready.h — knowing when IL2CPP is actually up.
The host is injected into a suspended process and has to wait for the game
to build its runtime before it can resolve a single type. The obvious way to
wait is to poll: is `GameAssembly.dll` loaded, and does `il2cpp_domain_get`
return a domain yet?
That is what `waitForIl2Cpp` did, and on the first real client it crashed
the game. From `Player.log`, 2026-08-19:
0x00007FFD822A5639 (GameAssembly) mono_class_has_parent
0x00007FFE13555DA5 (aowlspt-host-il2cpp) domainGet
0x00007FFE1354135A (aowlspt-host-il2cpp) waitForIl2Cpp
0x00007FFE1354E40C (aowlspt-host-il2cpp) aowlspt_nim_host_main
The module is mapped and its exports are resolvable a long time before
`il2cpp_init` has run. `il2cpp_domain_get` in that window does not return
NULL politely -- it reads a global that has not been written yet and faults
inside the runtime. The old comment knew the first half of this ("the module
appears well before the domain does") and drew the wrong conclusion from it:
that asking is safe as long as you are willing to be told no. Asking *is*
the unsafe act. There is no answer that can be polled for here, because the
question itself is what crashes.
## What is watched instead
`UnityPlayer.dll` does not import `il2cpp_init`; it looks it up with
`GetProcAddress` and calls it. So this replaces `GetProcAddress` in
`UnityPlayer.dll`'s import table -- and only there -- and watches what is
asked for. When UnityPlayer asks `GameAssembly.dll` for `il2cpp_init` -- or
for `il2cpp_init_utf16`, since both are exported and which one a build calls
is not knowable from here -- it is handed a wrapper instead of the real
function. The wrapper calls through, and what it records is a *successful*
return: not that the runtime was asked to initialise, and not merely that
the call came back, but that it came back saying it worked.
That distinction is not academic. On the first real client this watched for
any return at all, and `il2cpp_init` answered 0 -- a failure -- 100ms into
the process. The host believed it, asked for the domain, and faulted in
precisely the place the polling version had. The fix and the original bug
are the same shape one layer apart, which is worth saying plainly here: a
signal that fires whether or not the thing happened is not a signal.
Nothing is patched in `GameAssembly.dll`, no instruction is rewritten, and
no thread is parked. One pointer in one module's IAT is swapped in the
constructor, and one flag is set some seconds later on the game's own
thread.
## Why this and not the alternatives
*Polling with an exception handler around the probe* -- a vectored handler
that swallows the fault and retries -- would work often enough to look
correct. It also swallows real faults, in the one component whose crashes
are hardest to attribute, and it leaves the runtime having been entered
mid-initialisation, which is not a state anyone has promised is recoverable.
*Detouring `il2cpp_init` in GameAssembly* is deterministic but cannot be
armed in the constructor: `GameAssembly.dll` is loaded dynamically and is
not mapped yet at that point (it is not in the import table of
`EscapeFromTarkov.exe`, which imports only `UnityPlayer.dll` and
`KERNEL32.dll`, nor of `UnityPlayer.dll`). Arming it later means noticing
the load and installing a code patch before the very next call -- a race
with no upper bound on how badly it can lose. Watching the lookup has no
race at all: the pointer is in place before the game runs an instruction,
and UnityPlayer cannot call what it has not yet looked up.
## What happens when this does not fire
It fails *closed*, and that is the whole reason it reports separately from
the wait. If a future client initialises IL2CPP some other way, the flag
never sets, the host waits out its timeout and says so, and mods load with
the runtime reported unavailable -- which is the documented behaviour for a
host that has no runtime, and is what `hostharness` exercises. It does not
fall back to asking `il2cpp_domain_get` directly. That is the call that
crashed the game, and a fallback to it would reintroduce the crash on
exactly the machines where the primary mechanism had already failed.Constants
AOWLSPT_IL2CPPREADY_H
Functions
| Signature | Line |
|---|---|
int aowl_il2_init_thunk(const char* domainName) | 112 |
int aowl_il2_init_thunk_w(const wchar_t* domainName) | 139 |
FARPROC WINAPI aowl_il2_GetProcAddress(HMODULE mod, LPCSTR name) | 149 |
void aowl_il2_ready_arm(void) | 184 |
int32_t aowl_il2_is_ready(void) | 192 |
int32_t aowl_il2_guard_armed(void) | 193 |
int32_t aowl_il2_guard_module(void) | 194 |
int32_t aowl_il2_guard_seen(void) | 195 |
int32_t aowl_il2_guard_rc(void) | 196 |
int32_t aowl_il2_guard_calls(void) | 197 |

