Appearance
aowlspt_components.h
Source: abi/aowlspt_components.h — 255 lines, 12 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_components.h -- ask an object WHAT COMPONENTS IT HAS.
WHY THIS EXISTS
---------------
`component EXPR Type` is a yes/no oracle that charges for wrong guesses.
A right guess returns the component; a wrong-but-safe guess returns NULL,
which is free; a wrong-and-unlucky guess FAULTS inside
`Component::GetComponent(String)` and spends one of the inspector's eight
faults for the session. Naming a component you cannot already name therefore
costs a session, and it has: an unnamed checkbox component on the raid setup
screen defeated four type-name guesses across five ancestors.
Enumerating is strictly better than guessing, and it is reachable without
reflection.
THE ROUTE, AND WHY EACH HOP IS ALLOWED
--------------------------------------
1. A `System.Type` without reflection. `System.Type::GetTypeFromHandle`
takes a `RuntimeTypeHandle`, a one-field struct whose value IS the
`Il2CppType*` -- so on Win64 it is a plain pointer in RCX. The static
`Il2CppType` for any named type is in `.data` and its address is
resolved OFFLINE, out of `Il2CppMetadataRegistration.types`, by
`tools/il2cpp_nameindex.py` under the key `Ns.Type::@type/0`. Nothing
here calls `il2cpp_class_from_il2cpp_type` or any other export.
2. `UnityEngine.GameObject::GetComponents(Type) -> Component[]`. A real
managed body at a byte-verified RVA. Returns an `Il2CppArray`, read with
the array offsets this repo already uses live for
`Scene::GetRootGameObjects` (`AOWL_NAV_ARR_LEN` / `AOWL_NAV_ARR_DATA`).
3. Naming each element. Reflection is dead, so the name is read RAW out of
the `Il2CppClass` -- see the offset block below -- and then VALIDATED
against the offline type table before it is printed.
THE OFFSETS, AND EXACTLY HOW EACH WAS OBTAINED
---------------------------------------------
`Il2CppClass` is a NATIVE struct. It is not in
`Il2CppMetadataRegistration.fieldOffsets`, so `il2cpp_resolve.py fields`
cannot answer for it and "never guess an offset" needs a different source.
The source used here is GameAssembly.dll's OWN EXPORT TABLE: several
`il2cpp_*` exports are one-instruction accessors, and their instruction
bytes state the offset outright. Read out of the shipped DLL:
il2cpp_object_get_class 48 8b 01 c3 mov rax,[rcx]
il2cpp_class_get_image 48 8b 01 c3 mov rax,[rcx]
il2cpp_class_get_namespace 48 8b 41 18 c3 mov rax,[rcx+0x18]
il2cpp_class_get_element_class 48 8b 41 40 c3 mov rax,[rcx+0x40]
il2cpp_string_length 8b 41 10 c3 mov eax,[rcx+0x10]
il2cpp_array_length 8b 41 18 c3 mov eax,[rcx+0x18]
The last two are the CONTROL for the method: `String._stringLength` at
+0x10 and the array length at +0x18 are both independently known in this
repo (`il2cpp_resolve.py`'s built-in self-check, and `AOWL_NAV_ARR_LEN`).
The export bytes reproduce both, so reading offsets out of folded accessors
is a method that has been checked, not merely proposed.
`AOWL_COMP_KLASS_NAME` IS THE ONE INFERENCE, AND IT IS FENCED.
`il2cpp_class_get_name` is NOT an accessor on this build -- it is a
TLS-heavy thread-attach wrapper (`48 89 5c 24 10 ... 65 48 8b 04 25 58 00
00 00`, reading the TEB), which is very likely why calling it faults. So
+0x10 for `name` is inferred from the canonical `Il2CppClass` head --
image(0x00), gc_desc(0x08), name(0x10), namespaze(0x18) -- with 0x00 and
0x18 MEASURED either side of it.
An inference must not be allowed to print a plausible wrong type name, so
it is not trusted, it is TESTED: whatever string comes back is looked up in
the offline type table (`Ns.Type::@name/0`, one key per typedef in this
build). A hit means the offset is right for that object and the name is
real. A miss prints UNVERIFIED and the raw bytes, and never a type name.
If +0x10 were wrong, every single line would say UNVERIFIED -- the failure
announces itself instead of lying.
SAFETY
------
Everything here is called from inside the inspector's per-command
`aowl_p_p_seh`, which is NOT re-entrant, so this file opens NO guard of its
own. Nesting one would DISARM the caller's, which is worse than having none.
It does not need one. Every read here is a fixed-size read at an address
VirtualQuery'd first, page by page -- a C string that straddles into an
uncommitted page is truncated, not faulted. The component array is capped at
AOWL_COMP_MAX; each name copy is capped at AOWL_COMP_NAME_MAX; there is no
unbounded loop. Nothing here writes anything, and nothing here calls into
game code -- the two managed calls are made by the caller, through the
inspector's own verified-call path, so they land inside its guard and its
breadcrumb.Constants
AOWLSPT_COMPONENTS_HAOWL_COMP_FULL_MAXAOWL_COMP_KLASS_IMAGEAOWL_COMP_KLASS_NAMEAOWL_COMP_KLASS_NSAOWL_COMP_MAXAOWL_COMP_NAME_MAXAOWL_COMP_OBJ_KLASS
Functions
| Signature | Line |
|---|---|
int32_t aowl_comp_off_klass(void) | 109 |
int32_t aowl_comp_off_name(void) | 110 |
int32_t aowl_comp_off_ns(void) | 111 |
int32_t aowl_comp_max(void) | 112 |
int32_t aowl_comp_page_ok(const void *p) | 121 |
int32_t aowl_comp_cstr(const void *p, char *out, int32_t cap) | 144 |
int32_t aowl_comp_klass_fullname(const void *klass, char *out, int32_t cap) | 172 |
uint32_t aowl_comp_type_rva(const char *full) | 205 |
int32_t aowl_comp_is_known_type(const char *full) | 217 |
void aowl_comp_data_at(uint64_t rva) | 234 |
int32_t aowl_comp_name_of(void *klass) | 248 |
char aowl_comp_name_buf(void) | 252 |

