mirror of
https://github.com/radareorg/r2unity.git
synced 2026-11-07 06:36:00 -10:00
Documentation cleanup
This commit is contained in:
1 parent
4a52bc9db3
commit
50a7fcc804
8 files changed
+1069
-619
No files matched your search
+2
-1
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
Reference in new issue
Block a user