Documentation cleanup

This commit is contained in:
pancake committed 2026-06-08 12:32:03 +02:00
1 parent 4a52bc9db3
commit 50a7fcc804
8 files changed
+1069 -619

No files matched your search

+2 -1
View File
@@ -64,7 +64,8 @@ Important native-side data:
- runtime helper code, wrappers, trampolines, and runtime strings
These tables provide the addresses and runtime layouts that
`global-metadata.dat` intentionally does not carry.
`global-metadata.dat` intentionally does not carry. `doc/ptrtables.md`
is the detailed native pointer-table reference.
## Mapping model
+26 -221
View File
@@ -242,7 +242,7 @@ Size: **12 B** (v≤24.0) / **8 B** (v≥24.1). Sliced by
`typeDef.fieldStart` / `typeDef.field_count`.
Value: names and types for every field. Indirectly unlocks
`MetadataRegistration.fieldOffsets` (§3.12), which is what actually
`MetadataRegistration.fieldOffsets` (`doc/ptrtables.md`), which is what actually
lets us label `ldr X, [x0, #0x18]` as `Player.health`. Without
fields, struct-internal code is unreadable.
@@ -335,7 +335,8 @@ typedef struct { /* Il2CppGenericParameter, 16 B */
Value: reconstruct `Dictionary<TKey, TValue> where TKey :
IComparable<TKey>`. Gate for using `methodSpecs` / `genericInsts`
(§3) to emit real instantiation names like `List<int>::Add`.
(`doc/ptrtables.md`) to emit real instantiation names like
`List<int>::Add`.
### 2.7 `interfaces` + `interfaceOffsets` + `vtableMethods`
@@ -358,7 +359,7 @@ IComparable<TKey>`. Gate for using `methodSpecs` / `genericInsts`
```
`usageType == 3` → concrete method definition,
`usageType == 6` → `methodSpecs` (§3.11) index.
`usageType == 6` → `methodSpecs` (`doc/ptrtables.md`) index.
Value: fully resolved virtual dispatch. Every `callvirt` / indexed
vtable load gets a real method name. `class Foo : IDisposable,
@@ -421,7 +422,8 @@ Cross-reference:
`encodedSourceIndex`'s top three bits encode `Il2CppMetadataUsage ∈
{ Invalid, TypeInfo, Il2CppType, MethodDef, FieldInfo,
StringLiteral, MethodRef }`. `destinationIndex` is the slot in the
native `MetadataRegistration.metadataUsages[]` pointer table (§3.14).
native `MetadataRegistration.metadataUsages[]` pointer table
(`doc/ptrtables.md`).
Value: on v19–v26 builds, the compiled code is full of `ldr X,
[g_MetadataUsages + offset]` loads. These tables plus
@@ -522,9 +524,8 @@ plugins via custom marshalling; safe to ignore for plain RE.
typedef struct { int start; int length; } Il2CppRange;
```
Correlates with
`CodeRegistration.unresolvedVirtualCallPointers` (§3.5 of
`r2unity.md`). Lets us name each unresolved-virtual-call stub with
Correlates with native unresolved-call pointer tables documented in
`doc/ptrtables.md`. Lets us name each unresolved-virtual-call stub with
its parameter signature.
### 2.17 `windowsRuntimeTypeNames` + `windowsRuntimeStrings` +
@@ -533,183 +534,13 @@ its parameter signature.
UWP / HoloLens only. For iOS + Android + desktop builds these sizes
are always zero; a defensive parser bounds-checks and skips.
## 3. Native binary — pointer arrays we don't follow
## 3. Native binary pointer-table reference
`src/lib/bin/native.c` currently recovers method pointers from
`Il2CppCodeRegistration` when the registration anchor is available,
and falls back to a generic section scan backed by r_bin or the simple
ELF/Mach-O/PE parsers for stripped binaries.
The registration structures expose many more pointer arrays, each
with its own metadata table partner. Each entry below is a
`{ ulong count; ulong ptr; }` pair inside the registration (see §3 of
`doc/r2unity.md` for the full struct).
### 3.1 `methodPointers` (≥v24.1 global; v≥24.2 per-module)
What r2unity extracts today. From v24.2 onwards this field is on
`Il2CppCodeGenModule`, **not** `CodeRegistration`, and there is one
module per image. The structural path walks `codeGenModules[]` and
maps modules back to `.dat` image rows; the fallback section scan may
still find only one image's table because it does not know the
registration structure.
### 3.2 `invokerPointers`
Reflection-dispatch invokers — one per unique method-signature
shape (return type + param count + blittable-ness). Methods
reference them via `methodDef.invokerIndex` (≤v24.1) or
`Il2CppCodeGenModule.invokerIndices[]` (v≥24.2).
Value: naming invokers doesn't rename methods directly, but it
clusters methods by signature and makes reflection-based hook
points discoverable.
### 3.3 `customAttributeGenerators` (pre-v29)
Per-range `void(void*)` — each one news up the attribute objects
for one range. Given `attributesInfo` (§2.9), a tiny
arch-specific interpreter walking the generator body recovers every
attribute argument (strings, `typeof` references, enums). Required
for pre-v29 reverse-P/Invoke DLL-name recovery.
### 3.4 `reversePInvokeWrappers` (v22+)
Array of native-to-managed thunks for `[MonoPInvokeCallback]`
methods. Every callback the game registers with a C library routes
through these. Naming them `ReversePInvoke_<method>` lights up
native plugin callback surfaces (ads SDKs, analytics, audio,
networking).
### 3.5 `genericMethodPointers` + `genericAdjustorThunks`
Array of function pointers for every generic-method instantiation
present in the binary. Bridged to metadata via
`MetadataRegistration.genericMethodTable` and `methodSpecs`:
```c
foreach (genericMethodTable[k]) {
spec = methodSpecs[k.genericMethodIndex];
addr = genericMethodPointers[k.indices.methodIndex];
/* emit "List<int>::Add @ 0x401234" */
}
```
`genericAdjustorThunks` (v24.5, v27.1+) are struct-vs-class `T`
fixup thunks: `mov x0, [x0, #8]; b real_body` (skip the
`Il2CppObject` header for value-type `T`). Labelling both separates
the thunk from the real body.
Value: enormous on generics-heavy games. Without this, every
instantiation is a distinct unnamed `sub_xxx`.
### 3.6 `unresolvedVirtualCallPointers` (v22..v29); split at v29.1
into `unresolvedInstanceCallPointers` +
`unresolvedStaticCallPointers`
Stubs the runtime patches when a virtual / interface call target
becomes known. 1:1 with the metadata ranges in §2.16.
### 3.7 `interopData` (v23+)
Array of `Il2CppInteropData`, each containing a bundle of
P/Invoke / COM-interop function pointers for one type. See §3.6 of
`doc/r2unity.md` for the struct. Lights up every `[DllImport]`
target wrapper and every COM bridge (CCW/RCW).
### 3.8 `codeGenModules` (v24.2+)
One `Il2CppCodeGenModule` per image. Struct definition in §3.7 of
`doc/r2unity.md`.
Highlights that are particularly useful on obfuscated games:
- `moduleInitializer` — each image's `<Module>..cctor`, the per-
image static-init entry point.
- `staticConstructorTypeIndices` — types whose `.cctor` runs at
load. Prime targets when keys/strings are materialised at
startup.
- `adjustorThunks` + `adjustorThunkCount` — the generic-method
struct-vs-class fixup thunks.
### 3.9 `MetadataRegistration.types` — the `Il2CppType` pool
Every `typeIndex` anywhere in metadata indexes this array. Without
it, we can't resolve any method return type, parameter type, field
type, or interface name that goes beyond a plain `TypeDef`.
`Il2CppExecutor.GetTypeName`
(`third_party/Il2CppDumper/Utils/Il2CppExecutor.cs:61`) walks it
recursively to produce qualified names, decoding the ECMA-335
`ELEMENT_TYPE_*` byte (§II.23.1.16) to branch on:
- primitive (`I1`, `U4`, `R8`, …)
- `VALUETYPE`, `CLASS` (→ `typeDef.name`)
- `SZARRAY`, `ARRAY` (→ rank + element recursion)
- `PTR`, `BYREF` (→ pointee recursion)
- `VAR`, `MVAR` (→ generic-parameter name)
- `GENERICINST` (→ open type + type-argument tuple from
`genericInsts`)
- `FNPTR`, `MODOPT`, `MODREQ`
### 3.10 `MetadataRegistration.genericInsts` + `genericClasses`
Pool of concrete type-argument tuples (`(int)`, `(string, int)`,
`(Player, Dictionary<int, float>)`, …). Every generic-instantiation
reference in the binary indexes into this array.
### 3.11 `MetadataRegistration.methodSpecs` + `genericMethodTable`
See §3.9 of `doc/r2unity.md` for the structs and consumption
pattern. `GetMethodSpecName` reconstructs the full
`Foo<string>.Bar<int>` form.
### 3.12 `MetadataRegistration.fieldOffsets`
- Pre-v22: flat `int32_t[]` indexed by global field index.
- v22+: pointer-of-pointers — one `int32_t *` per type definition,
pointing at that type's field-offset array.
`GetFieldOffsetFromIndex`
(`third_party/Il2CppDumper/Il2Cpp/Il2Cpp.cs:274`) has the full
logic, including the `-8` / `-16` adjustment for non-static
value-type fields (to account for the `Il2CppObject` header:
`MonitorData *` + `Il2CppClass *`; see ECMA-335 §II.25.4 and Unity's
`Il2CppClass.cs`).
Value: the byte-level layout of every class / struct. Once read, we
can label every `ldr/str [this + N]` with a field name and type.
Second only to method pointers in RE value.
### 3.13 `MetadataRegistration.typeDefinitionsSizes`
```c
typedef struct {
uint32_t instance_size; /* sizeof(object) w/ Il2CppObject header */
uint32_t native_size; /* P/Invoke marshal size, 0 if N/A */
uint32_t static_fields_size;
uint32_t thread_static_fields_size;
} Il2CppTypeDefinitionSizes; /* 16 B */
```
Total size of every class / struct. Distinguishes 16-byte vs 24-byte
Vector types. Gives us the element stride for array indexing
(`ldr X, [Xn + Xindex * instance_size]`). `static_fields_size`
lets us label every static-field access once we find the per-class
statics pointer.
### 3.14 `MetadataRegistration.metadataUsages` (v19..v26)
The native side of §2.10. Huge `void *[]`. Each slot is an
`Il2CppClass *`, `Il2CppType *`, `MethodInfo *`, `FieldInfo *`,
managed `string *`, or generic `MethodInfo *` — decoded by
combining with `metadataUsageLists` + `metadataUsagePairs`.
Value: on pre-v27 binaries, labelling this array turns every
indirect metadata load in the disassembly into a readable reference.
From v27+ the mechanism was replaced by per-site inline globals and
this array is gone; labels must instead be derived per-site via
`codeGenModule.staticConstructorTypeIndices` + `moduleInitializer`
and code scanning.
Native registration structures and pointer-table shapes are documented in
`doc/ptrtables.md`. This file keeps only version and row-layout details;
put `Il2CppCodeRegistration`, `Il2CppMetadataRegistration`,
`Il2CppCodeGenModule`, field-offset, generic-method, interop, invoker,
and method-address recovery details in `doc/ptrtables.md`.
## 4. Implications for r2unity
@@ -757,50 +588,23 @@ and code scanning.
`third_party/Il2CppDumper/Il2Cpp/MetadataClass.cs` per wire
version.
2. **Per-version header struct.** v38+ is now represented as the
31-section header. Remaining gaps: distinguish v24.0 from v24.1+
headers so assembly / image entries are decoded correctly on
unresolved-virtual-call pointer arrays.
2. **Per-version header structs.** v38+ is represented as the
31-section header. Keep v24.0/v24.1+ discrimination explicit so
assembly, image, type, and method rows do not silently decode at
the wrong stride.
distinct wire version we claim to support (21, 22, 23, 24, 27,
29, 31, 38, 39). Pull candidates from the Il2CppDumper
issue-tracker corpus if new ones are needed.
3. **Version fixtures.** Keep at least one fixture per distinct wire
version we claim to support (24.1, 27, 29, 31, 38, 39), plus edge
cases for rejected v24.0 and v36/v37.
4. **Fail loudly on truly unknown versions.** v36/v37 are currently
rejected because their header shape is not proven. Keep that
confirm the layout.
behavior until a reliable reference or fixture confirms the layout.
### 4.2 Feature additions (coverage)
### 4.2 Feature additions
Ordered by RE value vs. implementation effort:
1. `parameters` table — small, unlocks real method signatures.
2. `fields` table — small, prerequisite for field offsets.
3. `CodeRegistration` / `MetadataRegistration` structural anchor
detection — unlocks everything in §3.
4. `MetadataRegistration.types` + `fieldOffsets` +
`typeDefinitionsSizes` — class layouts + field labels.
5. `codeGenModules` walk for v24.2+ — currently dropping most
methods on modern Unity.
6. Attributes bundle:
- pre-v29: `attributesInfo` + `attributeTypes` +
`customAttributeGenerators` thunk interpreter
- v29+: `attributeDataRange` + `attributeData` BLOB decoder
7. Generics bundle: `genericContainers` + `genericParameters` +
`methodSpecs` + `genericInsts` + `genericMethodPointers` +
`genericMethodTable`. Recovers `List<int>::Add`-style native
functions.
8. `properties` + `events` — recompose `get_X`/`set_X`/`add_X` /
`remove_X` into clean declarations.
9. `metadataUsageLists/Pairs` + `MetadataRegistration.metadataUsages`
— pre-v27 only, labels every indirect metadata load.
10. `interfaces` + `interfaceOffsets` + `vtableMethods` — full
resolved virtual dispatch tables.
11. `nestedTypes` — correct `Outer.Inner` naming.
12. Richer SBOM: native dependencies (`DT_NEEDED`, `LC_LOAD_DYLIB`,
PE import table), file hashes, ELF build IDs, Mach-O UUIDs, PE
debug GUIDs, SPDX output.
13. Manual `-a`/`-c` pointer read with pointer-width detection.
Implementation tasks live in `doc/todo.md`. Keep this document focused
on version/layout facts and portability constraints.
### 4.3 Architecture-neutral vs architecture-branching code
@@ -830,6 +634,7 @@ Ordered by RE value vs. implementation effort:
- v39 is confirmed by the local Unity 6 reproducer. v38 support is
based on the current Il2CppDumper v39 source layout; keep it
guarded by fixtures as samples become available.
- The local Unity install at
`/Applications/Unity/Hub/Editor/6000.4.5f1` ships IL2CPP sources
that assert `s_GlobalMetadataHeader->version == 39` in
+15 -34
View File
@@ -58,36 +58,18 @@ thunk. r2unity does not do this today.
### 0.3 Native-side registration structures (rich, unused by r2unity)
The IL2CPP runtime exposes interop targets through several fields
on `Il2CppCodeRegistration` and through one per-type struct:
The native table shapes are canonical in `doc/ptrtables.md`. For
interop, the important native records are:
```c
/* Il2CppCodeRegistration (relevant slice) */
ulong reversePInvokeWrapperCount;
ulong reversePInvokeWrappers; /* v22+ */
ulong customAttributeCount;
ulong customAttributeGenerators; /* removed at v29 */
ulong interopDataCount;
ulong interopData; /* v23+ */
- `CodeRegistration.reversePInvokeWrappers`
- pre-v29 `CodeRegistration.customAttributeGenerators`
- v23+ `CodeRegistration.interopData`
- `Il2CppInteropData` entries for per-type marshal/delegate/COM
wrappers
/* Il2CppInteropData — one per type involved in P/Invoke or COM */
typedef struct {
ulong delegatePInvokeWrapperFunction;
ulong pinvokeMarshalToNativeFunction;
ulong pinvokeMarshalFromNativeFunction;
ulong marshalToNativeFunction;
ulong marshalFromNativeFunction;
ulong marshalCleanupFunction;
ulong ccwFunction; /* COM CCW */
ulong guid; /* COM IID ptr */
ulong type; /* -> Il2CppType * */
} Il2CppInteropData;
```
This is where the **real native VAs** live: the actual marshalled
thunk that converts managed arguments to native, calls the
`[DllImport]` target, and unmarshals return values. r2unity does
not read any of this today.
This is where the real native VAs live: the actual marshalled thunk that
converts managed arguments to native, calls the `[DllImport]` target, and
unmarshals return values. r2unity does not read any of this today.
## 1. Managed → native: `[DllImport]` and r2unity's `-P`
@@ -197,7 +179,7 @@ and does not answer:
`Utils/CustomAttributeDataReader.cs` is the reference decoder.
- Locate `CodeRegistration` structurally (§3 of `doc/r2unity.md`),
- Locate `CodeRegistration` structurally (`doc/ptrtables.md`),
walk `customAttributeGenerators[range_index]`, step through the
native thunk body, recover immediate operands that correspond to
`string` ctor arguments. Architecture-specific interpreter work.
@@ -338,7 +320,7 @@ the **native trampoline stubs** that call them. Steps to close the
gap:
1. Structurally locate `Il2CppCodeRegistration` in the binary
(§3 of `doc/r2unity.md`).
(`doc/ptrtables.md`).
2. Read `reversePInvokeWrapperCount` and `reversePInvokeWrappers`.
3. For each Mach-O/ELF/PE image: walk `codeGenModules[]`, read
each module's `reversePInvokeWrapperIndices[]`, and for every
@@ -446,7 +428,6 @@ Once wrapper VAs land, `-r` will switch to real
- Il2CppDumper's `Utils/CustomAttributeDataReader.cs` — v29+ BLOB
decoder, authoritative reference.
<https://github.com/Perfare/Il2CppDumper>
- `doc/r2unity.md` §3 — full `Il2CppCodeRegistration` /
`Il2CppMetadataRegistration` field list.
- `doc/future.md` §2.9 and §3 — attribute table layouts per wire
version and the native-side interop pointer arrays.
- `doc/ptrtables.md` - full `Il2CppCodeRegistration` /
`Il2CppMetadataRegistration` pointer-table reference.
- `doc/future.md` §2.9 - attribute table layouts per wire version.
+788
View File
@@ -0,0 +1,788 @@
# IL2CPP Native Pointer Tables
Unity IL2CPP builds split information across two sides:
- `global-metadata.dat`: logical metadata tables, names, tokens, type
definitions, method definitions, field definitions, generic metadata,
and dense indices.
- The native IL2CPP binary: compiled code, registration structures,
function-pointer tables, layout tables, and runtime helper data.
The metadata file does not contain real code addresses. Native addresses
come from tables reachable from `Il2CppCodeRegistration` and
`Il2CppMetadataRegistration`, then are joined back to metadata by row
indices and per-image/per-type ranges.
## What r2unity Resolves Today
The current native recovery code resolves method bodies only:
- It may find or accept `Il2CppCodeRegistration`.
- It may find or accept `Il2CppMetadataRegistration`, but only reports
and caches that VA today.
- It copies recovered method body addresses into
`R2UnityNativeResult.method_ptrs[]`.
- It reports the source as `symbol`, `override`, or `heuristic`.
The current code does not yet parse the rest of
`Il2CppMetadataRegistration`:
- field offsets
- type sizes
- `Il2CppType` pool
- metadata usages
- generic method tables
- invoker pointers
- reverse-P/Invoke wrappers
- `Il2CppInteropData`
Those tables are documented below because they are the natural next
native-recovery targets.
## Minimal Binary Knowledge Required
The heuristic carver does not need a full semantic binary loader, but it
does need a memory view:
- pointer size: 4 or 8 bytes
- readable/data ranges to scan
- executable text range to validate code pointers
- base VA, for rebasing raw RVAs or low addresses
- a `ptr_at(va)` callback that maps a virtual address to file or memory
bytes
For ELF/Mach-O/PE files, this information usually comes from segments or
sections. With r2 loaded, r2unity asks r_bin for sections, symbols,
relocations, pointer size, and VA mapping. The simple fallback readers
read only enough format data to build the same generic view.
Relocations matter. Many IL2CPP tables are in relocatable data sections,
so raw file bytes can contain unpatched addends instead of final VAs. The
simple ELF path applies common relative relocations before scanning.
## Address Normalization
Native table entries may be absolute VAs or raw values that need the
module base added. r2unity treats a raw code pointer as valid when:
```text
raw is inside executable text
or
base_vaddr + raw is inside executable text
```
Data pointers are accepted when they can be dereferenced through the
current memory view.
This is intentionally conservative: a table candidate must point to
readable data, and sampled entries must point to executable code.
## How Method Indices Come From Metadata
For the supported modern metadata layouts, the method index is the row
ordinal in the decoded `Il2CppMethodDefinition[]` table:
```text
methods[0] -> method index 0
methods[1] -> method index 1
...
methods[n] -> method index n
```
The metadata header gives the method table offset and size. r2unity
decodes each row with a version-gated row width. The array index becomes
the native method-pointer index.
Types give structure over that flat method row array:
```text
Il2CppTypeDefinition.methodStart
Il2CppTypeDefinition.method_count
```
For a type:
```text
for k in 0..method_count-1:
method_index = methodStart + k
method = methods[method_index]
```
Images give a larger grouping over type rows:
```text
Il2CppImageDefinition.typeStart
Il2CppImageDefinition.typeCount
```
For modern per-image codegen modules, r2unity matches a native module
name to a metadata image name, walks that image's type range, and scatters
module-local method pointers into the global `method_ptrs[method_index]`
array.
Older metadata rows carried explicit native-side indices such as
`methodIndex`, `invokerIndex`, `delegateWrapperIndex`, and RGCTX ranges.
Those fields disappeared in the v24.1 row shrink. Modern recovery relies
more heavily on row order plus native codegen-module index arrays.
## Top-Level Native Anchors
The two important native anchors are:
```c
typedef struct {
uintptr_t methodPointersCount; /* pre-v24.2 only */
uintptr_t methodPointers; /* pre-v24.2 only */
uintptr_t reversePInvokeWrapperCount;
uintptr_t reversePInvokeWrappers;
uintptr_t genericMethodPointersCount;
uintptr_t genericMethodPointers;
uintptr_t genericAdjustorThunks; /* version-gated */
uintptr_t invokerPointersCount;
uintptr_t invokerPointers;
uintptr_t customAttributeCount;
uintptr_t customAttributeGenerators; /* removed in v29 */
uintptr_t unresolvedVirtualCallCount;
uintptr_t unresolvedVirtualCallPointers;
uintptr_t interopDataCount;
uintptr_t interopData; /* v23+ */
uintptr_t windowsRuntimeFactoryCount;
uintptr_t windowsRuntimeFactoryTable;
uintptr_t codeGenModulesCount; /* v24.2+ */
uintptr_t codeGenModules; /* v24.2+ */
} Il2CppCodeRegistration;
typedef struct {
intptr_t genericClassesCount;
uintptr_t genericClasses;
intptr_t genericInstsCount;
uintptr_t genericInsts;
intptr_t genericMethodTableCount;
uintptr_t genericMethodTable;
intptr_t typesCount;
uintptr_t types;
intptr_t methodSpecsCount;
uintptr_t methodSpecs;
intptr_t fieldOffsetsCount;
uintptr_t fieldOffsets;
intptr_t typeDefinitionsSizesCount;
uintptr_t typeDefinitionsSizes;
uintptr_t metadataUsagesCount;
uintptr_t metadataUsages;
} Il2CppMetadataRegistration;
```
The real struct shape is version-dependent. Parsers should not hardcode
one final layout across Unity versions. Treat these as the conceptual
field families.
## Table Shape Taxonomy
Not every native table is an array of pointers.
Common shapes:
- `void * []`: flat array of function/data pointers.
- `Struct * []`: array of pointers to structs.
- `Struct []`: array of inline structs.
- `int32_t []`: index array into another native or metadata table.
- `int32_t **`: pointer-of-pointers, usually one subarray per type.
- Count/pointer pairs inside a registration struct:
`{ count, pointer_to_table }`.
The heuristic carver looks only for the count/pointer shape and validates
that the pointed-to table looks like function pointers.
## CodeRegistration Tables
### `methodPointers`
Shape:
```text
void *methodPointers[]
```
Meaning:
- native address of compiled managed method bodies
- slot `i` maps to metadata method row `i` on older/global layouts
- zero entries are normal for abstract methods, interface declarations,
open generics, some extern/P-Invoke methods, and runtime intrinsics
Version behavior:
- pre-v24.2: global table is directly in `Il2CppCodeRegistration`
- v24.2+: global table disappears; method pointers move into each
`Il2CppCodeGenModule`
r2unity behavior:
- resolved today
- if `CodeRegistration` is known, it first tries the codegen-module path
and then the older global method-pointer path
- if no anchor is known, the heuristic scanner may find a single
`{count, ptr}` table, but on v24.2+ that may be only one image's
method table
### `codeGenModules`
Shape:
```text
Il2CppCodeGenModule *codeGenModules[]
```
Meaning:
- one module per metadata image/assembly
- introduced in v24.2
- each module owns per-image method pointer tables and several
per-image index tables
Core fields:
```c
typedef struct {
uintptr_t moduleName; /* char * */
intptr_t methodPointerCount;
uintptr_t methodPointers; /* void * [] */
uintptr_t adjustorThunks; /* void * [], version-gated */
uintptr_t adjustorThunkCount;
uintptr_t invokerIndices; /* int32_t [] per method */
uintptr_t reversePInvokeWrapperCount;
uintptr_t reversePInvokeWrapperIndices; /* int32_t [] */
intptr_t rgctxRangesCount;
uintptr_t rgctxRanges; /* range structs */
intptr_t rgctxsCount;
uintptr_t rgctxs; /* RGCTX structs */
uintptr_t debuggerMetadata;
uintptr_t customAttributeCacheGenerator; /* v27-era */
uintptr_t moduleInitializer; /* v27+ */
uintptr_t staticConstructorTypeIndices; /* v27+ int32_t [] */
uintptr_t metadataRegistration; /* v27+ */
uintptr_t codeRegistration; /* v27+ */
} Il2CppCodeGenModule;
```
Mapping model:
```text
moduleName -> metadata image name
image.typeStart/typeCount -> types belonging to this module
type.methodStart/method_count -> method rows
module.methodPointers[local_index] -> method_ptrs[method_index]
```
### `invokerPointers`
Shape:
```text
void *invokerPointers[]
```
Meaning:
- reflection-dispatch invokers
- not one per managed method
- usually one per method-signature/runtime-call shape
Association:
- old layouts: method row carried an `invokerIndex`
- v24.2+: `Il2CppCodeGenModule.invokerIndices[]` has one index per
module-local method
r2unity behavior:
- not parsed today
### `reversePInvokeWrappers`
Shape:
```text
void *reversePInvokeWrappers[]
```
Meaning:
- native-to-managed trampoline stubs for callbacks
- relevant to `[MonoPInvokeCallback]`, unmanaged callbacks, native SDK
hooks, audio callbacks, analytics callbacks, and similar surfaces
Association:
- old layouts had explicit wrapper index fields in method metadata rows
- v24.2+: `Il2CppCodeGenModule.reversePInvokeWrapperIndices[]` has one
index per method; non-negative entries index the global wrapper table
r2unity behavior:
- metadata-only reverse-P/Invoke enumeration exists
- wrapper VAs are not attached yet
### `genericMethodPointers`
Shape:
```text
void *genericMethodPointers[]
```
Meaning:
- native function bodies for concrete generic method instantiations
- separate from ordinary `methodPointers[]`
Association:
```text
MetadataRegistration.genericMethodTable[k]
-> methodSpecs[entry.methodIndex]
-> genericMethodPointers[entry.indices.methodIndex]
```
Value:
- recovers names for concrete instantiations such as
`List<int>.Add` or `Foo<string>.Bar<float>`
r2unity behavior:
- not parsed today
### `genericAdjustorThunks`
Shape:
```text
void *genericAdjustorThunks[]
```
Meaning:
- small thunks used for generic method calls that need value-type vs
reference-type adjustment
- often skip the `Il2CppObject` header before tail-calling the real body
Version behavior:
- appears in late v24.x / Unity 2019.4-era layouts
- appears again in v27.1+
r2unity behavior:
- not parsed today
### `customAttributeGenerators`
Shape:
```text
void (*customAttributeGenerators[])(void *)
```
Meaning:
- pre-v29 native thunks that construct custom attribute objects
- each thunk encodes constructor arguments in machine code
Version behavior:
- used before v29
- v29 replaces this with inline custom-attribute BLOB data in
`global-metadata.dat`
- v27-era layouts may move cache generators per module
r2unity behavior:
- not parsed/interpreted today
### `unresolvedVirtualCallPointers`
Shape:
```text
void *unresolvedVirtualCallPointers[]
```
Meaning:
- dispatch stubs used when virtual/interface call targets are resolved
dynamically
- metadata has parameter/range tables that can name these stubs
Version behavior:
- v22..v29: one unresolved virtual call pointer table
- v29.1+: split into instance and static unresolved call pointer arrays
r2unity behavior:
- not parsed today
### `interopData`
Shape:
```text
Il2CppInteropData interopData[]
```
This is an array of structs, not a flat pointer array.
Conceptual element:
```c
typedef struct {
uintptr_t delegatePInvokeWrapperFunction;
uintptr_t pinvokeMarshalToNativeFunction;
uintptr_t pinvokeMarshalFromNativeFunction;
uintptr_t marshalToNativeFunction;
uintptr_t marshalFromNativeFunction;
uintptr_t marshalCleanupFunction;
uintptr_t ccwFunction;
uintptr_t guid;
uintptr_t type; /* Il2CppType * */
} Il2CppInteropData;
```
Meaning:
- P/Invoke and COM interop functions for one type
- useful for attaching native wrapper VAs to `[DllImport]` methods and
COM bridge code
Version behavior:
- v23+
r2unity behavior:
- not parsed today
### `windowsRuntimeFactoryTable`
Shape:
- UWP/WinRT-specific table
Meaning:
- Windows Runtime factory data
- usually absent or empty on iOS, Android, Linux, macOS, and normal
desktop builds
r2unity behavior:
- not parsed today
## MetadataRegistration Tables
### `types`
Shape:
```text
Il2CppType pool/table
```
Conceptual element:
```c
typedef struct {
uintptr_t data; /* type def index, nested type ptr, array ptr,
generic class ptr, generic parameter index, etc. */
uint32_t bits; /* attrs, ELEMENT_TYPE, byref, pinned, version bits */
} Il2CppType;
```
Meaning:
- every metadata `typeIndex` resolves through this table
- method return types, parameter types, field types, interface entries,
generic constraints, and many other metadata records point here
Value:
- required for full type names beyond simple type definitions:
arrays, pointers, byrefs, generic instances, generic parameters,
modifiers, function pointers
r2unity behavior:
- not parsed today
- current names are limited by what can be reconstructed directly from
decoded metadata rows
### `genericInsts` and `genericClasses`
Shape:
- generic instantiation tables and pointer/struct pools
Meaning:
- concrete type argument tuples such as `(int)` or `(string, int)`
- instantiated generic classes
Association:
- consumed by `Il2CppType` decoding and generic method name recovery
r2unity behavior:
- not parsed today
### `methodSpecs`
Shape:
```c
typedef struct {
int32_t methodDefinitionIndex;
int32_t classIndexIndex;
int32_t methodIndexIndex;
} Il2CppMethodSpec;
```
Meaning:
- describes a concrete generic method instantiation
- points back to the open generic method definition and the class/method
generic argument tuples
r2unity behavior:
- not parsed today
### `genericMethodTable`
Shape:
```c
typedef struct {
int32_t methodIndex; /* -> methodSpecs */
struct {
int32_t methodIndex; /* -> genericMethodPointers */
int32_t invokerIndex; /* -> invokerPointers */
int32_t adjustorThunk; /* version-gated */
} indices;
} Il2CppGenericMethodFunctionsDefinitions;
```
Meaning:
- bridge from a generic method spec to the concrete native function
pointer table
Association:
```text
genericMethodTable[k].methodIndex
-> methodSpecs[]
-> open method + generic args
genericMethodTable[k].indices.methodIndex
-> CodeRegistration.genericMethodPointers[]
-> function VA
```
r2unity behavior:
- not parsed today
### `fieldOffsets`
Shape:
```text
pre-v22: int32_t fieldOffsets[]
v22+: int32_t *fieldOffsets[]
```
Meaning:
- final runtime field offsets
- this is native-side layout data, not present in `global-metadata.dat`
Association:
- pre-v22: indexed by global field index
- v22+: one offset array per type definition
Notes:
- value-type instance fields need object-header adjustment in some
contexts
- once decoded, this table lets analysis label `[this + offset]` memory
accesses with field names and types
r2unity behavior:
- not parsed today
### `typeDefinitionsSizes`
Shape:
```c
typedef struct {
uint32_t instance_size;
uint32_t native_size;
uint32_t static_fields_size;
uint32_t thread_static_fields_size;
} Il2CppTypeDefinitionSizes;
```
Meaning:
- runtime class/struct sizes
- array element stride
- marshalled native size
- static and thread-static storage sizes
r2unity behavior:
- not parsed today
### `metadataUsages`
Shape:
```text
void *metadataUsages[]
```
Meaning:
- native side of old metadata usage lists/pairs
- each slot is a runtime metadata object pointer, such as:
`Il2CppClass *`, `Il2CppType *`, `MethodInfo *`, `FieldInfo *`,
managed string object, or generic `MethodInfo *`
Version behavior:
- v19..v26
- v27+ replaces this model with per-site inline globals and
module-initializer setup patterns
r2unity behavior:
- not parsed today
## Heuristic Carver
The fallback section scanner searches readable/data sections for:
```text
u32 count
padding if 64-bit
ptr table
```
For each candidate pair:
1. `count` must be plausible.
2. `table` must dereference as data.
3. The first up to 128 entries are sampled.
4. Enough entries must be non-zero.
5. Enough entries must normalize to executable text VAs.
6. Accepted entries are copied into `method_ptrs[]`.
This finds flat method-pointer-like tables. It does not understand:
- `Il2CppCodeRegistration` layout
- `Il2CppMetadataRegistration` layout
- `Il2CppCodeGenModule` arrays unless an anchor path reaches them
- generic method table joins
- reverse-P/Invoke wrapper index joins
- field-offset pointer-of-pointer layouts
Because modern Unity stores method pointers per image, a single
heuristically found `{count, ptr}` table can be incomplete.
## Structural CodeRegistration Method Recovery
When `Il2CppCodeRegistration` is known, r2unity uses a stronger path.
First it searches near the registration VA for a count/pointer pair where
the count equals metadata image count:
```text
count == image_count
ptr == codeGenModules[]
```
Then for every module:
```text
moduleName
methodPointerCount
methodPointers
```
The module name is matched to a metadata image name. After that:
```text
image.typeStart/typeCount
-> type rows
type.methodStart/method_count
-> method rows
module.methodPointers[local]
-> method_ptrs[global_method_index]
```
If the module path fails, r2unity tries the older global
`methodPointersCount + methodPointers` pair near `CodeRegistration` and
copies it positionally.
## Version-sensitive native layouts
Native pointer-table layouts are version-gated. This document mentions
version constraints only where they affect a specific table shape, such as
pre-v24.2 global method pointers vs. v24.2+ per-image codegen-module
method tables. The canonical Unity-release, metadata wire-version,
sub-version, and row-layout matrix lives in `doc/future.md`.
## Common Pitfalls
- `global-metadata.dat` does not contain method RVAs.
- Method table slot order is not recovered from names; it is row order
and native index arrays.
- One flat method-pointer table is only the old layout. Modern Unity
needs the `codeGenModules[]` walk for full method coverage.
- Null method pointer entries are normal.
- Generic method instantiations are not ordinary method rows. They need
`methodSpecs`, `genericMethodTable`, and `genericMethodPointers`.
- Invokers are not method bodies and are not one per method.
- Reverse-P/Invoke wrapper VAs require wrapper index joins.
- Field offsets are layout data from `MetadataRegistration`, not from
field definitions alone.
- Metadata registration is a companion native anchor, not an automatic
VA association for every metadata row.
- Pointer width and relocation state must be correct before scanning.
## Practical Recovery Order
For broad native-symbol recovery, the most useful order is:
1. Parse `global-metadata.dat` tables and counts.
2. Establish pointer size, base VA, readable ranges, and text range.
3. Resolve or structurally find `Il2CppCodeRegistration`.
4. Resolve or structurally find `Il2CppMetadataRegistration`.
5. Walk `codeGenModules[]` for v24.2+ method pointers.
6. Fall back to global `methodPointers[]` for older layouts.
7. Parse `MetadataRegistration.types`, `fieldOffsets`, and
`typeDefinitionsSizes`.
8. Join `methodSpecs`, `genericMethodTable`, and
`genericMethodPointers`.
9. Join reverse-P/Invoke wrapper indices to
`reversePInvokeWrappers[]`.
10. Decode invoker indices, unresolved call stubs, metadata usages, and
interop data.
This order keeps method naming useful early while preserving the path to
full class layout, generic-instantiation, interop, and dispatch recovery.
+50 -341
View File
@@ -150,9 +150,9 @@ v29+ — the BLOB pool fields at `attributesInfo*` / `attributeTypes*`
the same logical regions are consumed through `R2U_SEC_*` section
ids instead of individual `*Offset` and `*Size` fields.
Every other header entry is declared but unused. §2.11 and
`doc/future.md` enumerate what's still on the floor and why each
would be worth decoding.
Every other header entry is declared but unused. §2.11 describes the
custom-attribute tables that r2unity partially consumes; `doc/todo.md`
tracks the remaining decoders.
### 1.3 Two distinct string mechanisms
@@ -193,8 +193,8 @@ index types in 1, 2, or 4 bytes depending on the target table count.
`Il2CppMethodDefinition.declaringType` is an index into
`typeDefinitions`; `Il2CppTypeDefinition.methodStart` is an index
into `methods`; `Il2CppFieldDefinition.typeIndex` is an index into
the native-side `Il2CppMetadataRegistration.types` pool (§3). No
cross-reference uses a file offset.
the native-side `Il2CppMetadataRegistration.types` pool documented in
`doc/ptrtables.md`. No cross-reference uses a file offset.
Sentinel "none" is all-bits-one at the serialized width:
`0xFF`, `0xFFFF`, or `0xFFFFFFFF`. r2unity expands all of them to
@@ -331,8 +331,8 @@ v24.0 and below carried an additional set of inline indices:
`customAttributeIndex` (before tokens took over), `methodIndex`,
`invokerIndex`, `delegateWrapperIndex`, `rgctxStartIndex`,
`rgctxCount`. Those were removed at v24.1 and replaced by token-based
lookup + per-module RGCTX ranges (§3.8). r2unity does not decode
that layout today.
lookup + per-module RGCTX ranges in `Il2CppCodeGenModule`
(`doc/ptrtables.md`). r2unity does not decode that layout today.
`flags` is an ECMA-335 `MethodAttributes` bitmask, with
`PINVOKE_IMPL = 0x2000` being the one bit r2unity's `-P` uses. The
@@ -394,7 +394,8 @@ uint32_t token; /* 0x04?????? — added at v19 */
```
Without this table, r2unity cannot label any `this`-relative memory
access. Joined with `MetadataRegistration.fieldOffsets` (§3.6), every
access. Joined with `MetadataRegistration.fieldOffsets`
(`doc/ptrtables.md`), every
`ldr X, [x0, #0x18]` in disassembly would become
`ldr X, [this, #Player.health]`. Second only to the method-pointer
table in RE value.
@@ -518,7 +519,7 @@ encodes variance (`in`/`out`) and special constraints
(`class`/`struct`/`new()`) per ECMA-335 §II.23.1.7.
Gate for `where TKey : IComparable<TKey>`-style reconstruction and
for using `methodSpecs` / `genericInsts` (§3.9) to produce real
for using `methodSpecs` / `genericInsts` (`doc/ptrtables.md`) to produce real
instantiation names like `List<int>::Add`.
Not decoded by r2unity today.
@@ -551,8 +552,8 @@ decoded = (version >= 27)
`{ Invalid=0, TypeInfo=1, Il2CppType=2, MethodDef=3, FieldInfo=4,
StringLiteral=5, MethodRef=6 }`.
`usageType == 3` → concrete method-definition index;
`usageType == 6` → index into `methodSpecs` (§3.9).
`usageType == 3` -> concrete method-definition index;
`usageType == 6` -> index into `methodSpecs` (`doc/ptrtables.md`).
Exposing this unlocks full virtual dispatch resolution, real
interface implementation chains, and `find-all-implementers`
@@ -613,7 +614,7 @@ typedef struct { uint32_t destinationIndex; uint32_t encodedSourceIndex; } Il2Cp
`encodedSourceIndex` uses the same `Il2CppMetadataUsage`-tagged
encoding as vtable slots (§2.10). `destinationIndex` is the slot in
the native `MetadataRegistration.metadataUsages[]` pointer table
(§3.10).
(`doc/ptrtables.md`).
On pre-v27 binaries, compiled code loads every metadata pointer via
`ldr X, [g_MetadataUsages + const]`. Labelling the array turns every
@@ -652,7 +653,8 @@ ignore otherwise.
typedef struct { int start; int length; } Il2CppRange;
```
Pairs with `CodeRegistration.unresolvedVirtualCallPointers` (§3.5)
Pairs with `CodeRegistration.unresolvedVirtualCallPointers`
(`doc/ptrtables.md`)
to name each unresolved-virtual-call stub with its parameter
signature.
@@ -688,315 +690,51 @@ a fractional sub-version leaks into the on-disk layout.
Constrained }`. Per-type via `typeDef.rgctxStartIndex/rgctxCount`
(pre-v24.1), per-method similarly; from v24.2 onward RGCTX tables
migrate to `Il2CppCodeGenModule.rgctxs` + `rgctxRanges` on the
native side (§3.7).
native side (`doc/ptrtables.md`).
**RGCTX** = "Runtime Generic Context" — the per-instantiation
lookup table that lets a single compiled generic method body
resolve `typeof(T)`, `default(T)`, and generic-virtual dispatch
sites without recompiling per instantiation.
## 3. The native side: CodeRegistration / MetadataRegistration
## 3. Native side and pointer-table recovery
The other half of the IL2CPP runtime knowledge lives in the binary,
typically in `.rodata` / `.data.rel.ro`. Two top-level anchors
exist; every other array hangs off one of them.
The other half of the IL2CPP runtime knowledge lives in the native
binary, typically in `.rodata`, `.data`, or `.data.rel.ro`. The two
top-level native anchors are `Il2CppCodeRegistration` and
`Il2CppMetadataRegistration`; codegen modules, method pointers,
generic-method pointers, reverse-P/Invoke wrappers, invokers, field
offsets, type sizes, and metadata-usage tables hang from those anchors.
```c
typedef struct { /* Il2CppCodeRegistration (shape varies by version) */
ulong methodPointersCount; /* pre-v24.2 only */
ulong methodPointers; /* pre-v24.2 only */
ulong reversePInvokeWrapperCount;
ulong reversePInvokeWrappers;
ulong genericMethodPointersCount;
ulong genericMethodPointers;
ulong genericAdjustorThunks; /* v24.5 / v27.1+ */
ulong invokerPointersCount;
ulong invokerPointers;
ulong customAttributeCount;
ulong customAttributeGenerators; /* removed at v29 */
ulong unresolvedVirtualCallCount;
ulong unresolvedVirtualCallPointers; /* split at v29.1 */
ulong interopDataCount;
ulong interopData; /* v23+ */
ulong windowsRuntimeFactoryCount;
ulong windowsRuntimeFactoryTable;
ulong codeGenModulesCount; /* v24.2+ */
ulong codeGenModules; /* v24.2+ */
} Il2CppCodeRegistration;
`doc/ptrtables.md` is the canonical reference for native table shapes,
pointer widths, address normalization, method table carving, and
registration recovery. Keep native pointer-table details there instead
of duplicating them here.
typedef struct { /* Il2CppMetadataRegistration */
long genericClassesCount; ulong genericClasses;
long genericInstsCount; ulong genericInsts;
long genericMethodTableCount; ulong genericMethodTable;
long typesCount; ulong types;
long methodSpecsCount; ulong methodSpecs;
long fieldOffsetsCount; ulong fieldOffsets;
long typeDefinitionsSizesCount; ulong typeDefinitionsSizes;
ulong metadataUsagesCount;
ulong metadataUsages;
} Il2CppMetadataRegistration;
```
r2unity currently uses the native side only for method body VAs:
`ulong` means host pointer width — 8 B on arm64, x86-64, Windows
x64, macOS; 4 B on armeabi-v7a, x86, wasm32. Parsers must branch on
`EI_CLASS` for ELF, `MH_MAGIC_64` vs `MH_MAGIC` for Mach-O,
`IMAGE_FILE_MACHINE_*` for PE. r2unity today only walks arm64 /
x86-64 targets robustly; 32-bit paths exist but are less exercised.
- Resolve registration anchors from overrides, r2 flags, or symbols when
available.
- If `Il2CppCodeRegistration` is usable, walk `codeGenModules[]` for
v24.2+ per-image method tables, or fall back to the old global
`methodPointers[]` pair.
- If no anchor is usable, scan readable data sections for plausible
`{count, pointer}` tables whose entries land in executable code.
Finding the anchors without symbols typically relies on structural
matching: `methodPointersCount` or
`codeGenModulesCount * typesCount * methodsCount` must equal known
integers derivable from the `.dat`. Il2CppDumper's
`ExecuteBinarySearch` does this.
The `Il2CppMetadataRegistration` anchor is tracked as a companion VA, but
the native metadata-registration tables are not decoded yet. The current
backlog for that work lives in `doc/todo.md`.
### 3.1 `methodPointers` (pre-v24.2 global; v24.2+ per-module)
## 4. Version handling
Method `i`'s entry at slot `i`. Zeros for abstract, generic-open,
external/P-Invoke, and intrinsic runtime methods.
`doc/future.md` is the canonical wire-version and Unity-version matrix.
It owns the row-size deltas, acceptance-band notes, compact-index rules,
and sub-version caveats.
From v24.2 onward the array moves **inside each `Il2CppCodeGenModule`**
(one module per image). A global `methodPointers` no longer exists.
A scanner that locks onto a single `{count, ptr}` on a v24.2+ binary
only extracts one image's methods. This is the single most common
"why is my dumper missing half the methods" bug on modern Unity.
r2unity's current heuristic finds one `{count, ptr}` and is
therefore vulnerable to this on Unity ≥ 2019.3. Walking
`codeGenModules[]` and enumerating each module is mandatory for
full coverage.
### 3.2 `invokerPointers`
Reflection-dispatch invokers. One per unique
`(returnType, paramCount, blittable-ness)` signature shape, not per
method. Methods reference them via `methodDef.invokerIndex`
(pre-v24.2) or `Il2CppCodeGenModule.invokerIndices[]` (v24.2+).
Naming these clusters methods by signature; very useful for
discovering reflection hook points (`MethodInfo.Invoke` calls route
through here).
### 3.3 `customAttributeGenerators` (pre-v29)
Per-range `void(void*)` function pointer, each one news up the
attribute object(s) for one token range. The generator body is
typically a few dozen instructions: load constructor argument
constants, call the attribute's constructor, store the result. A
tiny arch-specific interpreter can recover every attribute
argument. Gone at v29 (§2.11).
### 3.4 `reversePInvokeWrappers` (v22+)
Array of native-to-managed trampolines for every
`[MonoPInvokeCallback]`-annotated method. Every callback the game
registers with a native library (ad SDK callback, audio driver
callback, analytics pingback) goes through one of these. Naming
them `ReversePInvoke_<method>` lights up the native plugin callback
surface immediately.
### 3.5 `unresolvedVirtualCallPointers` (v22..v29; split at v29.1
into `unresolvedInstanceCallPointers` + `unresolvedStaticCallPointers`)
Stubs the runtime patches when a virtual or interface call target
becomes known at run time. 1:1 with the metadata ranges in §2.15.
### 3.6 `interopData` (v23+)
Array of `Il2CppInteropData` — each element bundles P/Invoke and
COM-interop function pointers **for one type**:
```c
typedef struct {
ulong delegatePInvokeWrapperFunction;
ulong pinvokeMarshalToNativeFunction;
ulong pinvokeMarshalFromNativeFunction;
ulong marshalToNativeFunction;
ulong marshalFromNativeFunction;
ulong marshalCleanupFunction;
ulong ccwFunction; /* COM only */
ulong guid; /* COM IID pointer */
ulong type; /* -> Il2CppType * */
} Il2CppInteropData;
```
Walking this array gives the native VA of every `[DllImport]` target
wrapper and every COM CCW/RCW bridge. This is where real P/Invoke
target recovery lives — r2unity does not read it today.
### 3.7 `codeGenModules` (v24.2+)
One `Il2CppCodeGenModule` per image. Per Il2CppDumper's
`Il2CppClass.cs`:
```c
typedef struct {
ulong moduleName; /* C string */
long methodPointerCount;
ulong methodPointers; /* PER-IMAGE method table */
ulong adjustorThunks; /* v24.5 / v27.1+ */
ulong adjustorThunkCount;
ulong invokerIndices; /* int32[] per method */
ulong reversePInvokeWrapperCount;
ulong reversePInvokeWrapperIndices;
long rgctxRangesCount;
ulong rgctxRanges; /* Il2CppTokenRangePair[] */
long rgctxsCount;
ulong rgctxs; /* Il2CppRGCTXDefinition[] */
ulong debuggerMetadata;
ulong customAttributeCacheGenerator; /* v27 only */
ulong moduleInitializer; /* v27+ <Module>..cctor */
ulong staticConstructorTypeIndices; /* v27+ */
ulong metadataRegistration; /* v27+ per-assembly */
ulong codeRegistaration; /* sic — v27+ */
} Il2CppCodeGenModule;
```
`moduleInitializer` is each image's `<Module>..cctor` — the per-
image static-init entry point.
`staticConstructorTypeIndices` lists types whose `.cctor` runs at
load time. Both are prime targets in obfuscated games where encryption
keys or obfuscated strings are materialised at startup.
**`adjustorThunks`** (v24.5, v27.1+) are the struct-vs-class `T`
fixup thunks for generic methods: `mov x0, [x0, #8]; b real_body`
(skip the `Il2CppObject` header when `T` is a value type). Labelling
them separately from the real body reduces disassembly confusion.
### 3.8 `MetadataRegistration.types` — the `Il2CppType` pool
```c
typedef struct { /* Il2CppType, 16 B */
ulong datapoint; /* union: klassIndex / type* /
array* / genericParamIdx /
generic_class* */
uint bits; /* packed fields:
attrs:16 | type:8 | num_mods:{5|6} | byref:1 | pinned:1
[ | valuetype:1 at v27.2+ ] */
} Il2CppType;
```
Every `typeIndex` used anywhere in the metadata — method
return types, parameter types, field types, interface lists —
resolves into this pool. Without it, the textual form of any type
beyond a simple `TypeDef` (so: arrays, pointers, by-ref, generic
instantiations, generic parameters, `modreq`/`modopt`) cannot be
reconstructed.
`type` field at bits 16..23 is the ECMA-335 `ELEMENT_TYPE_*`
(ECMA-335 §II.23.1.16). Walking it recursively produces qualified
names like `List<Dictionary<int, string>>[]&`.
### 3.9 Generics on the native side
```c
/* genericInsts[]: concrete type-argument tuples
( (int), (string,int), (Player, Dictionary<int,float>), … ) */
typedef struct { /* Il2CppMethodSpec, 12 B */
int methodDefinitionIndex; /* the open generic method */
int classIndexIndex; /* -> genericInsts (-1 if none) */
int methodIndexIndex; /* -> genericInsts (-1 if none) */
} Il2CppMethodSpec;
typedef struct { /* Il2CppGenericMethodFunctionsDefinitions */
int methodIndex; /* -> methodSpecs */
struct {
int methodIndex; /* -> genericMethodPointers */
int invokerIndex;
int adjustorThunk; /* v24.5+ */
} indices;
} Il2CppGenericMethodFunctionsDefinitions;
```
Consumed together with `CodeRegistration.genericMethodPointers`:
```c
foreach (entry in genericMethodTable) {
spec = methodSpecs[entry.methodIndex];
va = genericMethodPointers[entry.indices.methodIndex];
// emit "List<int>::Add @ va"
}
```
Il2CppDumper's `GetMethodSpecName` reconstructs the full
`Foo<string>.Bar<int>` textual form.
### 3.10 Field offsets, type sizes, metadata usages
- `MetadataRegistration.fieldOffsets`:
- pre-v22: flat `int32_t[]` indexed by global field index
- v22+: pointer-of-pointers — one `int32_t *` per type definition,
pointing at that type's field-offset array
- `typeDefinitionsSizes`:
```c
typedef struct {
uint32_t instance_size; /* includes Il2CppObject hdr */
uint32_t native_size; /* P/Invoke marshalled */
uint32_t static_fields_size;
uint32_t thread_static_fields_size;
} Il2CppTypeDefinitionSizes; /* 16 B */
```
`instance_size` is the array element stride:
`ldr X, [Xn + Xindex*instance_size]` in disassembly.
- `metadataUsages`: native side of §2.12, void-pointer table.
On pre-v27 binaries, every metadata-aware load in the code
dereferences a slot here.
ECMA-335 §II.25.4 documents instance layout (the `MonitorData *` +
`Il2CppClass *` header prefix that makes non-static value-type
fields appear at offset `-8`/`-16` from the `Il2CppObject` pointer).
`GetFieldOffsetFromIndex` in Il2CppDumper's `Il2Cpp.cs:274` has the
exact math.
## 4. Wire-version history and per-version layout deltas
This is a condensed changelog; `doc/future.md` has the full
Unity-release → wire-version → sub-version mapping.
- **v16..v20** (Unity 5.0..5.2): pre-public-IL2CPP-shipped era.
Rejected.
- **v21** (Unity 5.3.0..5.3.5): first on-iOS IL2CPP. Rejected.
- **v22** (Unity 5.3.6..5.4): drops delegate-wrapper tables; adds
`unresolvedVirtualCall*` tables. Rejected.
- **v23** (Unity 5.5): adds `interopData` and WinRT tables.
Rejected.
- **v24** (Unity 5.6..2018.x): adds `exportedTypeDefinitions`. v24.1
(2018.3) switches all custom-attribute resolution to token-based
lookup and drops `customAttributeIndex` from every def. v24.2
(2019.1) introduces `Il2CppCodeGenModule` and per-image method
arrays. v24.5 (2019.4.15) adds `genericAdjustorThunks`.
- **v27** (Unity 2020.2..2021.3): `customAttributeGenerators` move
per-image into `codeGenModule.customAttributeCacheGenerator`.
v27.1 re-adds `genericAdjustorThunks`. v27.2 widens the RGCTX
`type`/`data` fields from 32 to 64 bits on disk and repacks
`Il2CppType.valuetype` as a dedicated bit. `MethodInfo` struct
changes on the native side.
- **v29** (Unity 2022.1..2022.3): replaces the custom-attribute
generator thunks with inline BLOB encoding (§2.11). v29.1 splits
`unresolvedVirtualCallPointers` into separate instance/static
arrays.
- **v30**: reserved, never shipped.
- **v31** (Unity 2023.1..2023.x): adds `returnParameterToken` to
`Il2CppMethodDefinition`, aligning with ECMA-335's return-value
param-row model.
- **v32..v35** (gap range in current references): keep the legacy
offset/size header. v32 removes `Il2CppStringLiteral.length` and
`Il2CppTypeDefinition.elementTypeIndex`; v35 introduces compact-
index reader hooks but keeps 4-byte widths in the current
Il2CppDumper source.
- **v36..v37**: no header layout is declared by the v39 reference;
- **v38** (Unity 6 era): switches the header from offset/size pairs
to 31 `{offset,size,count}` section records.
and compacts additional serialized indices, including
`ParameterIndex`.
r2unity's current shipped-version acceptance band is **v24.1..v31**
plus **v38..v39**. It also accepts unobserved v32..v35 with the
legacy offset/size header, the v32 row deltas above, and 4-byte
header shape. Since the on-disk `version` field is `24` for both
v24.0 and v24.1+, the parser probes the image-row stride (32 B on
v24.0, 40 B on v24.1+) and rejects v24.0 explicitly to avoid silent
mis-decoding of TypeDefinition/MethodDefinition/ImageDefinition rows.
r2unity currently accepts shipped wire versions in the v24.1..v31 and
v38..v39 families, rejects v24.0 after probing the row layout, rejects
v36/v37 because their header shape is not proven, and keeps v32..v35
conservative because public references are incomplete.
## 5. How the parser is wired in r2unity
@@ -1081,40 +819,11 @@ Already extracted by r2unity (library + CLI):
| reverse-P/Invoke on v29+ | `-R` enumeration via BLOB |
| method-pointer VA | CodeRegistration parse + r_bin/simple-parser section-scan fallback |
Data present on disk / in the binary but **not yet consumed**:
Data present on disk or in the binary but not yet consumed is summarized in
`doc/todo.md`. Keep the task list there; this document only records
current parser behavior and the source-file map.
- `parameters` + `parameterDefaultValues` + BLOB heap → real method
signatures with names, types, and default arguments (§2.3–2.4).
- `fields` + `fieldDefaultValues` + BLOB heap → field names, types,
and compile-time constants; prerequisite for labelling
`[this+N]` accesses (§2.5).
- `properties` + `events` → recomposed C# `property` / `event`
declarations (§2.6).
- `genericContainers` + `genericParameters` +
`genericParameterConstraints` → generic-parameter names and
constraints (`where T : IComparable<T>`) (§2.9).
- `interfaces` + `interfaceOffsets` + `vtableMethods` → fully
resolved virtual dispatch and interface-implementation graph
(§2.10).
- `nestedTypes` → correct `Outer.Inner` naming (§2).
- `attributesInfo`/`attributeTypes` (pre-v29) or
`attributeDataRange`/`attributeData` (v29+) → every `[Attribute]`
on every declaration; `[Preserve]` reflection markers;
`[DllImport]` DLL+entry args (§2.11).
- `metadataUsageLists/Pairs` (pre-v27) → labels every indirect
metadata load in compiled code (§2.12).
- `fieldMarshaledSizes`, `unresolvedVirtualCall*`, WinRT tables,
`exportedTypeDefinitions`, RGCTX tables (§2.14–2.18).
- Native-side registration data beyond method pointers →
`invokerPointers`, `customAttributeGenerators`,
`reversePInvokeWrappers`, `genericMethodPointers`,
`interopData`, `types`, `fieldOffsets`, `typeDefinitionsSizes`,
`metadataUsages` (§3).
- Richer native support: packed Android relocations and other loader
details not yet handled by r_bin for a given target, Mach-O FAT
multi-slice selection, chained-fixups, PE import table.
## 8. validation corpus
## 8. Validation corpus
- `json-one-line`, `json-unity` — `-j` shape/content
+4 -20
View File
@@ -67,7 +67,7 @@ None of this is implemented in r2unity today.
Inferred:
- Unity version range from the metadata wire version (mapping in
§3).
`doc/future.md`).
- IL2CPP runtime version from symbol signatures or strings in
the native binary (e.g. the `il2cpp_init` / `il2cpp_runtime_version`
exports or a compiled-in string constant).
@@ -191,25 +191,9 @@ date, because the fully-flattened metadata does not carry a Unity
version string (the Unity-version string exists as a constant
inside the native binary, typically `"20xx.y.z.*"` or `"6000.y.z"`).
r2unity uses this mapping:
| Wire version | Unity release range |
|--------------|---------------------|
| 21 | 5.3.0 – 5.3.5 |
| 22 | 5.3.6 – 5.4 |
| 23 | 5.5 |
| 24 | 5.6 – 2020.1 |
| 27 | 2020.2 – 2021.3 |
| 29 | 2022.1 – 2022.3 |
| 31 | 2023.1 – 2023.x |
| 38 | Unity 6 era |
| 39 | Unity 6 current |
The parser accepts shipped wire versions 24.1..31 and 38..39, rejects
24.0 and 36..37, and keeps a compatibility path for unobserved
32..35. `doc/future.md` §1.3 has the full version-to-sub-version
matrix with additional rows for specific Unity 2018/2019/2020/2021/
2022 minor releases and the v38/v39 Unity 6 metadata break.
r2unity uses the wire-version mapping documented in `doc/future.md`.
That file owns the Unity-release ranges, sub-version notes, accepted
version set, rejected versions, and v38/v39 Unity 6 metadata break.
## 3. What the shipped `-S` does not do
+2 -2
View File
@@ -294,5 +294,5 @@ text.
<https://blog.unity.com/technology/il2cpp-internals-a-tour-of-generated-code>
- Il2CppDumper's string-literal emitter: `Il2CppDumper/Outputs/*`.
<https://github.com/Perfare/Il2CppDumper>
- `doc/r2unity.md` §1.3 and §3.4 — metadata pool vs literal table
addressing model and the r2unity decode path.
- `doc/r2unity.md` §1.3 - metadata pool vs literal table addressing
model. This document owns the `-z` decode path details.
+182
View File
@@ -0,0 +1,182 @@
# r2unity TODO
This file tracks implementation work that is still open after the
current metadata parser, method-pointer recovery, and pointer-table notes
in `doc/ptrtables.md`.
## Current Baseline
Implemented or partially implemented today:
- Parse supported `global-metadata.dat` headers and core sections.
- Decode type definitions, method definitions, field definitions, images,
assemblies, string literals, and several index tables.
- Emit method/class names from metadata.
- Recover method body VAs through native method-pointer tables when
possible.
- Use symbols or user overrides for `Il2CppCodeRegistration` and
`Il2CppMetadataRegistration` when available.
- Walk `Il2CppCodeGenModule` method tables when a usable
`Il2CppCodeRegistration` anchor is available.
- Fall back to a generic `{count, pointer}` section scan for stripped
binaries.
Still missing:
- Structural native anchor discovery.
- Full `Il2CppMetadataRegistration` parsing.
- Field layout recovery.
- Generic method instantiation recovery.
- Invoker, reverse-P/Invoke wrapper, unresolved-call, and interop-data
native joins.
## Priority Backlog
1. Add structural registration discovery.
Find `Il2CppCodeRegistration` and `Il2CppMetadataRegistration` in
stripped binaries by validating native table shapes against counts
derived from `global-metadata.dat`. This should replace dependence on
symbols, manual overrides, and the weak generic section scan whenever
possible.
2. Harden modern method recovery.
Modern Unity stores methods per image through `codeGenModules[]`.
Make the module walker robust across v24.2+ layouts, validate module
names/counts, handle null entries cleanly, and add more version
samples. A single flat method-pointer table is only correct for older
layouts.
3. Implement manual pointer-table reads.
Wire up `-a` / class emission manual reads so a user can provide a
method-pointer table VA directly. Detect pointer width, validate the
table against text ranges, and report confidence/source in CLI and
plugin output.
4. Parse `Il2CppMetadataRegistration`.
Decode the native-side companion tables:
- `types`
- `genericInsts`
- `genericClasses`
- `methodSpecs`
- `genericMethodTable`
- `fieldOffsets`
- `typeDefinitionsSizes`
- `metadataUsages` for pre-v27 binaries
5. Recover class and struct field layouts.
Join metadata field rows with native `fieldOffsets` and
`typeDefinitionsSizes`. Emit class/struct layouts, field offsets, type
names, static/thread-static sizes, and enough data to label
`[this + offset]` accesses in r2.
6. Recover generic method instantiations.
Join:
```text
methodSpecs
genericMethodTable
genericMethodPointers
genericAdjustorThunks
genericInsts
```
Use this to name concrete generic bodies such as
`List<int>.Add` or `Foo<string>.Bar<float>`.
7. Attach reverse-P/Invoke wrapper VAs.
Decode module `reversePInvokeWrapperIndices[]`, join those indices to
`CodeRegistration.reversePInvokeWrappers[]`, and attach wrapper VAs
to P/Invoke/reverse-P/Invoke output.
8. Decode invoker and unresolved-call tables.
Parse `invokerPointers`, module `invokerIndices[]`, unresolved
virtual call tables, and v29.1+ instance/static unresolved call
tables. Use metadata range/signature information to name dispatch
stubs.
9. Parse `Il2CppInteropData`.
Recover P/Invoke marshal wrappers, delegate wrappers, COM bridges,
GUID/type associations, and native wrapper VAs from `interopData[]`.
10. Decode custom attributes fully.
- v29+: decode inline custom-attribute BLOBs, especially
`DllImportAttribute` DLL and entry-point names.
- pre-v29: recover constructor arguments by interpreting
`customAttributeGenerators` thunks.
11. Finish higher-level metadata tables.
Add or improve decoding for:
- parameters and full method signatures
- default values for fields and parameters
- properties and events
- nested types
- interfaces and interface offsets
- vtable method tables
- RGCTX ranges and entries
- generic containers, generic parameters, and constraints
12. Expand version and architecture coverage.
Add fixtures and tests for:
- v24.0, v24.1, v24.2, v24.5
- v27, v27.1, v27.2
- v29 and v29.1
- v31
- v38/v39 compact-index metadata
- 32-bit ARM/x86
- relocation-heavy ELF/Mach-O/PE samples
- Mach-O FAT and arm64e/pointer-authentication edge cases
Native walkers must be pointer-width aware and version-gated instead
of using one hardcoded registration layout.
## SBOM Work
- Add SHA-256 hashes for the executable and metadata file.
- Add native dependency extraction:
- ELF `DT_NEEDED`, `DT_SONAME`, build ID, Android notes.
- Mach-O dylib load commands, UUID, build version, code-signature
summary.
- PE imports, CodeView/PDB GUID, timestamp, Rich header.
- Add a CycloneDX `serialNumber`.
- Bring plugin SBOM properties up to parity with CLI SBOM output.
## Plugin and UX Work
- Add plugin commands for field/type browsing once native layout recovery
exposes those records.
- Make plugin symbol application report skipped methods and pointer-source
confidence.
- Keep CLI and plugin JSON schemas aligned.
- Surface partial recovery clearly: global old-style table, per-module
modern table, heuristic single-table hit, manual table, or no native
table.
## Reference Docs
- `doc/ptrtables.md` - native pointer-table inventory and recovery model.
- `doc/r2unity.md` - internal technical reference.
- `doc/future.md` - version/layout matrix and row-layout deltas.
- `doc/datvsbin.md` - metadata-vs-binary split.
- `doc/strings.md` - managed string-literal mode.
- `doc/sbom.md` - managed SBOM behavior and missing native SBOM work.
- `doc/pinvoke.md` - interop enumeration behavior and missing recovery
work.
- `INFO.md` - high-level explanation of what metadata can and cannot
provide alone.
- `INFO2.md` - implementation-oriented metadata parsing guide.
- `RVA.md` - mapping metadata indices to native addresses.