Appearance
aowlspt_invoke2.h
Source: abi/aowlspt_invoke2.h — 521 lines, 29 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_invoke2.h -- DIRECT invocation of managed IL2CPP methods by static
RVA, with no reflection anywhere.
===========================================================================
WHY THIS EXISTS
===========================================================================
Post-1.0 this client's IL2CPP *reflection* surface is dead: the live P2-P5
probe proved `il2cpp_object_get_class`, `il2cpp_class_get_name`,
`il2cpp_value_box` and field iteration all FAULT, even on the Unity main
thread, and `il2cpp_runtime_invoke` crashed outright. Everything the host has
built since -- the version brand, botcap, the settings control walk -- is
therefore raw field reads and writes at fixed offsets.
Raw fields are enough to CHANGE the UI. They are not enough to CREATE it: a
new GameObject, a component attached to it, a Canvas child -- all of that is
managed code, and the only door left is to call that managed code.
The hypothesis this header implements: **IL2CPP AOT-compiles every managed
method into an ordinary native function, so it can be called directly at its
RVA as a native function pointer.** No MethodInfo lookup, no metadata query,
no reflection -- just a `call`, exactly as the game itself does.
===========================================================================
THE CALLING CONVENTION FOR THIS BUILD (1.1.0.1.46777), WITH EVIDENCE
===========================================================================
It is Win64 (Microsoft x64) with ONE addition: IL2CPP appends a hidden
trailing `const MethodInfo*` argument after the declared ones, in the next
free INTEGER register (and on the stack past the fourth). It is passed as a
literal NULL by the compiler for every ordinary (non-generic) method.
instance: RCX = this, RDX/R8/R9 = arg0..arg2, then MethodInfo*
static: RCX/RDX/R8/R9 = arg0..arg3, then MethodInfo*
floats: XMM0..XMM3 by POSITION, per Win64 (a float in slot 1 is XMM1)
returns: RAX (integer/reference) or XMM0 (float/double), per Win64
Evidence 1 -- an INSTANCE method with one reference argument. From
`TMPro.TMP_DefaultControls::CreateUIElementRoot` @ RVA 0x5190080, which is
literally "make a new UI GameObject":
51900c9: mov rcx, [rip+0x1c52f88] ; Il2CppClass* UnityEngine.GameObject
51900d0: call 0x1805d9e20 ; il2cpp::vm::Object::New(klass)
51900d5: xor r8d, r8d ; <-- R8 = MethodInfo* = NULL
51900d8: mov rdx, rdi ; RDX = arg0, the name String*
51900db: mov rcx, rax ; RCX = this, the fresh GameObject
51900de: mov rbx, rax
51900e1: call 0x1852a8f40 ; UnityEngine.GameObject::.ctor(string)
`.ctor(string)` declares ONE parameter, and the call site sets THREE integer
registers. The third is the hidden MethodInfo*, and it is zero.
Evidence 2 -- a STATIC method with two reference arguments, from the same
function (`TMP_DefaultControls::SetParentAndAlign(GameObject, GameObject)`):
519020e: xor r8d, r8d ; <-- R8 = MethodInfo* = NULL
5190211: mov rdx, rdi ; RDX = arg1, the parent
5190214: mov rcx, rbx ; RCX = arg0, the child
5190217: call 0x1851903a0 ; ...::SetParentAndAlign
Two declared args in RCX/RDX -- no `this` shift, because it is static -- and
the MethodInfo* in the next register, again NULL.
Evidence 3 -- the callee side confirms it is genuinely ignored. Every simple
accessor compiles to a body that reads RCX and never touches the MethodInfo
register at all, e.g. `UnityEngine.Component::get_gameObject` @ 0x11F57E0:
11f57e0: push rbx ; sub rsp,0x20
11f57e6: mov rax, [rip+0x5edecfb] ; cached icall pointer
11f57ed: mov rbx, rcx ; RCX = this (RDX never read)
... test rax,rax / jne -> resolve the icall by name on first use
11f580d: mov rcx, rbx ; add rsp,0x20 ; pop rbx ; jmp rax
and `UnityEngine.Time::get_frameCount` @ 0x52B3400 (static, zero args) never
reads RCX either. Note the lazy-resolve shape: an unresolved icall resolves
itself on first call, so calling these cold from a detour is safe.
Evidence 4 -- the ONE case where the MethodInfo* is load-bearing: a shared
generic instantiation. `UnityEngine.GameObject::AddComponent<T>` @ 0x2A9AE90:
2a9aea4: cmp qword [rdx+0x38], 0 ; <-- RDX *is* the MethodInfo*
2a9aea9: mov rdi, rdx
2a9aeac: mov rbp, rcx ; RCX = this
... call 0x180563290 ; initialise its RGCTX if unset
2a9aed8: mov rbx, [rdi+0x38] ; the runtime generic context
An instance method with ZERO declared parameters, and RDX is the MethodInfo*
-- which it dereferences. So a generic method cannot be called with NULL; it
needs the real `MethodInfo*` for that exact instantiation. The game keeps
those in .data cache slots (see AOWL_MI2_DATA_ADDCOMP_RECTTRANSFORM below).
===========================================================================
WHAT THIS HEADER PROVIDES
===========================================================================
* A build-pinned target table: name + RVA + 16 prologue bytes, verified the
same way `aowlspt_bridge.h` verifies a detour target -- VirtualQuery for
committed executable memory, then memcmp of the prologue. On any other
build the lookup returns NULL and every step simply does not run.
* Typed call thunks, one per shape used by the ladder. They exist in C
because casting a pointer to a function type is not expressible in nimony,
and because the hidden MethodInfo* must be a real argument of the callee
type rather than something bolted on afterwards.
* The two IL2CPP *allocation* exports (`il2cpp_object_new`,
`il2cpp_string_new`) plus the two *type-object* exports the AddComponent
(Type) route would need. Allocation is proven to work on this build (the
version brand allocates a String on the Unity thread); the type-object
pair is UNPROVEN and is probed, not assumed.
* A guarded reader for the game's own .data metadata cache slots, which is
how a real generic `MethodInfo*` is obtained without reflection.
NOTHING here calls anything on its own. It is a table and a set of thunks;
`host/Aowlspt.Host.Il2Cpp/invoke2.nim` drives it, from inside a proven
Unity-thread detour, one step at a time, under the VEH/SEH guard.
All RVAs are for `GameAssembly.dll` imagebase 0x180000000, build
1.1.0.1.46777, and were resolved offline by `tools/il2cpp_resolve.py` +
the 185k-entry RVA map, then confirmed by disassembly of real call sites.Constants
AOWLSPT_INVOKE2_HAOWL_MI2_ADDCOMPONENT_GENAOWL_MI2_ADDCOMPONENT_TYPEAOWL_MI2_DATA_ADDCOMP_RECTTRANSFORMAOWL_MI2_DATA_GAMEOBJECT_CLASSAOWL_MI2_GET_FRAMECOUNTAOWL_MI2_GET_GAMEOBJECTAOWL_MI2_GET_INSTANCE_IDAOWL_MI2_GET_NAMEAOWL_MI2_GET_SCREEN_HEIGHTAOWL_MI2_GET_SCREEN_WIDTHAOWL_MI2_GO_CTOR_STRINGAOWL_MI2_GO_GET_TRANSFORMAOWL_MI2_GO_SETACTIVEAOWL_MI2_INSTANTIATEAOWL_MI2_PROBE_NAMEAOWL_MI2_PROBE_TEXTAOWL_MI2_SET_AS_FIRST_SIBLINGAOWL_MI2_SET_NAMEAOWL_MI2_SET_PARENT_ALIGNAOWL_MI2_TARGET_COUNT
Types
struct AowlMi2Target
Functions
| Signature | Line |
|---|---|
void aowl_mi2_fn(int32_t i) | 275 |
char aowl_mi2_name(int32_t i) | 298 |
uint32_t aowl_mi2_rva(int32_t i) | 302 |
int32_t aowl_mi2_target_count(void) | 306 |
int32_t aowl_mi2_base_ok(void) | 307 |
int32_t aowl_mi2_ok_count(void) | 308 |
int32_t aowl_mi2_bad_count(void) | 309 |
void aowl_mi2_call_p_p(void* fn, void* self) | 327 |
int32_t aowl_mi2_call_i_p(void* fn, void* self) | 333 |
int32_t aowl_mi2_call_i_v(void* fn) | 339 |
void aowl_mi2_call_v_pp(void* fn, void* self, void* a0) | 345 |
void aowl_mi2_call_v_pb(void* fn, void* self, int32_t a0) | 351 |
void aowl_mi2_call_p_pp(void* fn, void* self, void* a0) | 357 |
void aowl_mi2_call_p_s1(void* fn, void* a0) | 363 |
void aowl_mi2_call_v_s2(void* fn, void* a0, void* a1) | 369 |
void aowl_mi2_call_generic0(void* fn, void* self, void* mi) | 378 |
void aowl_mi2_exports_init(void) | 409 |
int32_t aowl_mi2_have_object_new(void) | 424 |
int32_t aowl_mi2_have_type_route(void) | 427 |
void aowl_mi2_object_new(void* klass) | 431 |
void aowl_mi2_string_new(const char* s) | 436 |
void aowl_mi2_type_object_of(void* klass) | 443 |
char aowl_mi2_probe_name(void) | 461 |
char aowl_mi2_probe_text(void) | 462 |
void aowl_mi2_probe_name_str(void) | 463 |
void aowl_mi2_probe_text_str(void) | 466 |
void aowl_mi2_data_ptr(uint32_t rva) | 500 |
void aowl_mi2_go_class_slot(void) | 513 |
void aowl_mi2_addcomp_rect_mi(void) | 516 |

