diff --git a/doc/datvsbin.md b/doc/datvsbin.md index 7e60bb9..96e8e9b 100644 --- a/doc/datvsbin.md +++ b/doc/datvsbin.md @@ -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 diff --git a/doc/future.md b/doc/future.md index cdde0dc..33bee30 100644 --- a/doc/future.md +++ b/doc/future.md @@ -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 where TKey : IComparable`. Gate for using `methodSpecs` / `genericInsts` -(§3) to emit real instantiation names like `List::Add`. +(`doc/ptrtables.md`) to emit real instantiation names like +`List::Add`. ### 2.7 `interfaces` + `interfaceOffsets` + `vtableMethods` @@ -358,7 +359,7 @@ IComparable`. 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_` 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::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 `..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)`, …). 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.Bar` 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::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 diff --git a/doc/pinvoke.md b/doc/pinvoke.md index f46d290..1f438e1 100644 --- a/doc/pinvoke.md +++ b/doc/pinvoke.md @@ -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. -- `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. diff --git a/doc/ptrtables.md b/doc/ptrtables.md new file mode 100644 index 0000000..b97be0f --- /dev/null +++ b/doc/ptrtables.md @@ -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.Add` or `Foo.Bar` + +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. diff --git a/doc/r2unity.md b/doc/r2unity.md index 0f36a35..6d26bab 100644 --- a/doc/r2unity.md +++ b/doc/r2unity.md @@ -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`-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::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_` 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+ ..cctor */ - ulong staticConstructorTypeIndices; /* v27+ */ - ulong metadataRegistration; /* v27+ per-assembly */ - ulong codeRegistaration; /* sic — v27+ */ -} Il2CppCodeGenModule; -``` - -`moduleInitializer` is each image's `..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>[]&`. - -### 3.9 Generics on the native side - -```c -/* genericInsts[]: concrete type-argument tuples - ( (int), (string,int), (Player, Dictionary), … ) */ - -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::Add @ va" -} -``` - -Il2CppDumper's `GetMethodSpecName` reconstructs the full -`Foo.Bar` 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`) (§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 diff --git a/doc/sbom.md b/doc/sbom.md index cb44b83..2a5a1c6 100644 --- a/doc/sbom.md +++ b/doc/sbom.md @@ -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 diff --git a/doc/strings.md b/doc/strings.md index 44a7efa..789f016 100644 --- a/doc/strings.md +++ b/doc/strings.md @@ -294,5 +294,5 @@ text. - Il2CppDumper's string-literal emitter: `Il2CppDumper/Outputs/*`. -- `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. diff --git a/doc/todo.md b/doc/todo.md new file mode 100644 index 0000000..d116e87 --- /dev/null +++ b/doc/todo.md @@ -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.Add` or `Foo.Bar`. + +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.