32 KiB
r2unity - IL2CPP Wire Versions, Layout Deltas, And Unused Metadata
This document is a technical reference for:
- Which
global-metadata.datwire versions exist, what changed in each, which fractional sub-versions affect the on-disk layout, and which Unity release each one maps to. - Which logical records inside
global-metadata.datand which native-binary structures are declared but not yet decoded by r2unity, with enough detail to add decoders against without re-reading Il2CppDumper.
The numbers and field deltas below are cross-checked against three authoritative reference implementations vendored or cited:
third_party/Il2CppDumper/— C# reference (Il2CppDumper/Il2Cpp/MetadataClass.csis the per-version struct layout source of truth). https://github.com/Perfare/Il2CppDumperthird_party/lib-global-metadata/— Java rewrite, independent cross-check of every record range. https://github.com/REAndroid/lib-global-metadata- djkaty/Il2CppInspector — public Unity-version matrix. https://github.com/djkaty/Il2CppInspector
1. IL2CPP metadata wire versions
1.1 Wire versions known on disk
The int32_t at file offset 4 of global-metadata.dat is known
16, 19, 20, 21, 22, 23, 24, 27, 29, 31, 38, 39
Earlier values between those releases were reserved or skipped.
Current Il2CppDumper v39 sources define the new section-based header
from v38 onward and have a v39-specific ParameterIndex encoding,
so the old [16,31] high-water mark is no longer current.
1.2 Fractional sub-versions
Within each wire version Il2CppDumper tracks fractional sub-versions
(24.1, 24.2, 24.3, 24.4, 24.5, 27.1, 27.2, 29.1;
newer Unity 6 metadata uses whole wire versions v38/v39 for the
observed on-disk break).
These are not written to disk. They are reverse-engineered
signatures inferred by probing:
- section sizes divided by row counts (e.g.
assembliesSize / imageCount→ 48 B or 52 B tail) - field counts and pointer layouts on the native side
(
CodeRegistration/MetadataRegistrationstruct size probes) - the presence of specific arrays in the binary (
genericAdjustorThunksexists from v24.5 onward)
For most wire versions the on-disk .dat is byte-identical across
the fractional family; the sub-version only selects how the native
binary's registration structures are laid out. A few exceptions do
leak into the .dat:
Il2CppRGCTXDefinition._datawidens fromint32→uint64at v27.2 (reference:Il2CppDumper/Il2Cpp/MetadataClass.cs,type_post29+_datafields). Row grows from 8 B to 12 B/16 B.Il2CppMethodDefinitiongainsreturnParameterTokenat v31. Row grows from 32 B to 36 B.- All
customAttributeIndexfields (onTypeDef,MethodDef,FieldDef,ParameterDef,EventDef,PropertyDef,AssemblyDef,ImageDef) are removed at v24.1 in favour of token-based lookup. Affects row sizes of every def type. Il2CppAssemblyNameDefinition.hashValueIndexis removed at v24.3+, shrinking assembly rows by 4 B.- Attribute tables (
attributesInfo+attributeTypes) are replaced by a BLOB layout (attributeDataRange+attributeData) at v29. Same header slots, entirely new semantics. Il2CppStringLiteraldrops itslengthfield at v32. Rows aredataIndexonly; length is inferred from the next row or the end of the string-literal data section.Il2CppTypeDefinition.elementTypeIndexis no longer serialized at v32.TypeIndex,TypeDefinitionIndex, andGenericContainerIndexuse compact reader hooks from v35, but the current v39 Il2CppDumper source initializes their widths to 4 bytes before v38 because legacy headers have no section counts.- The header switches from
{offset,size}pairs to 31{offset,size,count}section records at v38. - Starting at v38, the compact index widths are inferred from section counts and may be 1, 2, or 4 bytes.
ParameterIndexbecomes compact at v39.Il2CppAssemblyDefinitiongainsmoduleTokenat v38, moving the embeddedIl2CppAssemblyNameDefinitiontail from offset 16 to offset 20.
1.3 Unity release → wire-version mapping
Reconstructed from Il2CppDumper's probe logic and Il2CppInspector's published matrix. "Wire version" is what lands on disk; "Sub-version" is the fractional binary-side refinement.
| Unity release | Wire version | Sub-version | Notes |
|---|---|---|---|
| 5.3.0 – 5.3.5 | 21 | 21 | First public IL2CPP on iOS. |
| 5.3.6 – 5.4 | 22 | 22 | Drops delegate-wrapper tables; adds unresolvedVirtualCall*. |
| 5.5 | 23 | 23 | Adds interopData, WinRT tables. |
| 5.6 – 2017.x – 2018.2 | 24 | 24 | Adds exportedTypeDefinitions. |
| 2018.3 – 2018.4 | 24 | 24.1 | Token-based attribute ranges; drops customAttributeIndex from defs. |
| 2019.1 – 2019.3.6 | 24 | 24.2 | Il2CppCodeGenModule introduced; per-image method tables. |
| 2019.3.7 – 2019.4.14 | 24 | 24.3 | WinRT factory table. |
| 2019.4.15 – 2019.4.20 | 24 | 24.4 | genericAdjustorThunks added; hashValueIndex dropped. |
| 2019.4.21+ | 24 | 24.5 | Drops customAttributeGenerators from CodeRegistration. |
| 2020.1 | 24 | 24.4 | Same layout as 2019.4.15. |
| 2020.2 – 2020.3 | 27 | 27 | customAttributeCacheGenerator moves per-image into CodeGenModule. |
| 2021.1 | 27 | 27.1 | Re-adds genericAdjustorThunks. |
| 2021.2 – 2021.3 | 27 | 27.2 | RGCTX widens to 64 bits on disk; Il2CppType repacks valuetype bit. |
| 2022.1 | 29 | 29 | Inline custom-attribute BLOB; customAttributeGenerators removed. |
| 2022.2 – 2022.3 | 29 | 29.1 | unresolvedVirtualCallPointers splits into instance + static arrays. |
| 2023.1 – 2023.x | 31 | 31 | Il2CppMethodDefinition adds returnParameterToken. |
| Unsupported gap | 36 – 37 | 36 – 37 | No header layout declared by the v39 reference source. |
| 6000.x (Unity 6 era) | 38 | 38 | Section header triples; compact index widths inferred from counts. |
Il2CppDumper's bundled README claims "Unity 5.3 – 2022.2", i.e.
wire versions 21–29. Support for wire v31 (Unity 2023+) is present
in current master via the [Version(Min = 31)] annotation on
returnParameterToken in MetadataClass.cs; v39 sources add the
section-triple header and compact-index annotations used above.
1.4 r2unity's current acceptance band
From r2unity_parse_metadata() in src/lib/lib.c:
if (version < 24 || version > 39 || (version > 35 && version < 38)) return NULL;
if (version >= 38) header_size = sizeof (v38);
else if (version < 27) header_size = sizeof (v24);
else if (version < 29) header_size = sizeof (v27);
else header_size = sizeof (v29); /* 29..35 */
/* After normalizing header fields, reject v24.0 via image-stride probe. */
if (version == 24 && is_v24_0 (meta)) return NULL;
| Wire version | Status | Reality |
|---|---|---|
| 16, 19, 20 | Rejected | Pre-Unity-5.5 builds, effectively extinct. |
| 21, 22, 23 | Rejected | Unity 5.3 – 5.5 IPAs exist but are rare. |
| 24.0 | Rejected after sub-version probe | On-disk version is 24 for both v24.0 and v24.1+. The parser probes image-row stride (32 B vs 40 B) and rejects v24.0 with an explicit error; TypeDefinition/MethodDefinition/ImageDefinition rows would otherwise decode at wrong offsets. |
| 24.1 – 24.5 | Accepted via _v24 header struct |
Row decoders target the v24.1+ strides. |
| 27 | Accepted via _v27 header struct |
Header correct. Row decoders still assume the v24.1+ layout; v27.2 RGCTX widening not handled (only affects RGCTX which is unused). |
| 29 | Accepted via _v29 header struct |
Header correct. Method rows decoded as 32 B. Reverse-P/Invoke BLOB path is the only v29-specific code. |
| 30 | Accepted | Never shipped on disk; dead slot, safe. |
| 31 | Accepted | Method rows decoded as 36 B (dedicated v31 branch); other row types still assume v24.1+ layout. |
| 36 – 37 | Rejected | Header shape is unknown; v38 is the first confirmed section-triple layout. |
| 39 | Accepted via _v38 header struct |
Confirmed against the latest local reproducer. Adds compact ParameterIndex. |
Bottom line: the acceptance band covers every commercially relevant Unity build from 2017 onward, but within the band the old fixed 88 / 32 / 40 / 48–52 strides are correct only for a subset. The current parser now computes the decoded v38/v39 row sizes from section counts and compact-index widths; pre-v24 and native-side version gaps remain future work.
1.5 Architecture-independence of global-metadata.dat
The file contains zero pointers. Every scalar is 16- or 32-bit
little-endian; inline fixed arrays (public_key_token[8]) are
byte-addressed. The reference implementations (Il2CppDumper,
REAndroid Java port) both read the file with unconditional
ReadInt32() / ReadUInt16(); there is no architecture branch.
Therefore, for a given wire version, global-metadata.dat is
byte-identical across:
- iOS ARM64 / ARM64e
- Android arm64-v8a
- Android armv7a (32-bit)
- Android x86 / x86_64 (emulator)
- Windows x64 (
GameAssembly.dll) - macOS x64 / arm64 standalone (
GameAssembly.dylib) - Linux x64 (
GameAssembly.so) - UWP / HoloLens
- Nintendo Switch
- WebAssembly (wasm32)
r2unity's RD_LE32 / RD_LE16 helpers are correct for every
target. The only endianness concern is a big-endian host, which the
in-place r_read_le32 sweep already handles.
The native side is not architecture-independent:
Il2CppCodeRegistration, Il2CppMetadataRegistration,
Il2CppCodeGenModule, and Il2CppInteropData all contain
host-width ulong pointers. Format and relocation semantics differ
between ELF, Mach-O and PE:
- 8-byte pointers: iOS ARM64/ARM64e, Android arm64-v8a, Windows x64, macOS, Switch, Linux x64.
- 4-byte pointers: Android armv7a, Android x86, WebAssembly (wasm32).
r2unity's ELF/Mach-O paths handle 64-bit correctly; 32-bit Android ELF is parsed but not validated; WebAssembly is unsupported.
2. Tables declared but not decoded
src/lib/lib.h already lays out the full
Il2CppGlobalMetadataHeader with every offset/size pair, but
src/lib/lib.c consumes only six of them in general (string,
stringLiteral, stringLiteralData, methods, typeDefinitions,
images) plus assemblies / referencedAssemblies / v29
attributesInfo+attributeTypes for SBOM and reverse-P/Invoke.
Everything else is dead weight today.
Each subsection below gives the on-disk shape, the indexing invariant, and the reverse-engineering value of decoding it.
2.1 parameters — Il2CppParameterDefinition[]
uint32_t nameIndex;
uint32_t token; /* 0x08?????? */
int32_t customAttributeIndex; /* removed at v24.1 */
int32_t typeIndex; /* -> MetadataRegistration.types */
Size: 16 B (v≤24.0) / 12 B (v≥24.1). Sliced by
methodDef.parameterStart, length methodDef.parameterCount.
Value: every method currently dumps as Method(). With this table
we emit real C# signatures Foo(string name, int count, Vector3 pos). Combined with parameterDefaultValues (§2.3) we also
recover optional parameters. This is the highest-leverage table
r2unity does not read today.
2.2 fields — Il2CppFieldDefinition[]
uint32_t nameIndex;
int32_t typeIndex; /* -> MetadataRegistration.types */
int32_t customAttributeIndex; /* removed at v24.1 */
uint32_t token; /* 0x04??????, added at v19 */
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 (doc/ptrtables.md), which is what actually
lets us label ldr X, [x0, #0x18] as Player.health. Without
fields, struct-internal code is unreadable.
2.3 fieldDefaultValues / parameterDefaultValues + BLOB
typedef struct { int fieldIndex; int typeIndex; int dataIndex; } Il2CppFieldDefaultValue;
typedef struct { int parameterIndex; int typeIndex; int dataIndex; } Il2CppParameterDefaultValue;
Each entry indexes into
fieldAndParameterDefaultValueData (raw BLOB heap), decoded by
Il2CppExecutor.GetConstantValueFromBlob
(Il2CppDumper/Utils/Il2CppExecutor.cs:343). The decoder handles
ECMA-335 primitive type codes (ECMA-335 §II.23.1.16):
I1/U1/I2/U2/I4/U4/I8/U8 — little-endian scalars
R4/R8 — IEEE-754 floats
STRING — compressed-int length + UTF-8 bytes
CHAR/BOOLEAN — 2 B / 1 B
SZARRAY — compressed length + elements
IL2CPP_TYPE_INDEX — type reference
CLASS — null reference marker
v29 reuses this same compressed-int scheme for the custom-attribute BLOB (§2.9).
Value: every const string URL = "...", static readonly int MaxHp = 42, and default argument value. Major for finding hardcoded
endpoints, feature flags, encryption constants.
2.4 properties — Il2CppPropertyDefinition[]
uint32_t nameIndex;
int32_t get; /* MethodDefIndex, -1 if no getter */
int32_t set; /* MethodDefIndex, -1 if no setter */
uint32_t attrs; /* MethodSemanticsAttributes, ECMA-335 §II.23.1.12 */
int32_t customAttributeIndex; /* ≤24 */
uint32_t token; /* ≥19, 0x17?????? */
Value: rejoin get_X / set_X method pairs into a single C#
property. Auto-properties identifiable via <X>k__BackingField
patterns. [JsonProperty] / [SerializeField] most often decorate
properties.
2.5 events — Il2CppEventDefinition[]
uint32_t nameIndex;
int32_t typeIndex; /* -> MetadataRegistration.types */
int32_t add; /* MethodDefIndex for add_X */
int32_t remove; /* MethodDefIndex for remove_X */
int32_t raise; /* MethodDefIndex for raise_X, often -1 */
int32_t customAttributeIndex; /* ≤24 */
uint32_t token; /* ≥19, 0x14?????? */
Value: C# event declarations. Labels every add_ / remove_
delegate-subscription method. Unity UnityEvents, C# events on
MonoBehaviour, INotifyPropertyChanged patterns.
2.6 Generics: genericContainers + genericParameters +
genericParameterConstraints
typedef struct { /* Il2CppGenericContainer, 16 B */
int32_t ownerIndex; /* TypeDef or MethodDef index */
int32_t type_argc;
int32_t is_method; /* 0 class, 1 method */
int32_t genericParameterStart;
} Il2CppGenericContainer;
typedef struct { /* Il2CppGenericParameter, 16 B */
int32_t ownerIndex;
uint32_t nameIndex; /* "T", "TKey", … */
int16_t constraintsStart;
int16_t constraintsCount;
uint16_t num; /* argument position */
uint16_t flags; /* GenericParameterAttributes
(variance + constraint kinds),
ECMA-335 §II.23.1.7 */
} Il2CppGenericParameter;
/* genericParameterConstraints: raw int32_t[] of type indices */
Value: reconstruct Dictionary<TKey, TValue> where TKey : IComparable<TKey>. Gate for using methodSpecs / genericInsts
(doc/ptrtables.md) to emit real instantiation names like
List<int>::Add.
2.7 interfaces + interfaceOffsets + vtableMethods
-
interfaces— rawint32_t[]; per-type viatypeDef.interfacesStart/typeDef.interfaces_count. Each entry is a type index (intoMetadataRegistration.types). -
interfaceOffsets—Il2CppInterfaceOffsetPair[] = { int interfaceTypeIndex; int offset; }(8 B). Per-type viatypeDef.interfaceOffsetsStart/interface_offsets_count. Maps each implemented interface to the vtable slot where its methods begin. -
vtableMethods— rawuint32_t[]; each slot is anEncodedMethodIndex:usageType = (idx & 0xE0000000) >> 29; /* Il2CppMetadataUsage */ decoded = (version >= 27) ? ((idx & 0x1FFFFFFE) >> 1) : (idx & 0x1FFFFFFF);usageType == 3→ concrete method definition,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, IEnumerable<int> declarations. Find-all-implementers searches for a
given interface.
2.8 nestedTypes — TypeDefinitionIndex[]
Raw int32_t[]; per-type via typeDef.nestedTypesStart /
nested_type_count. Reconstructs Outer.Inner hierarchies —
important for accurate symbol naming in compiler-generated types
(<DisplayClass> etc.).
2.9 Attributes — two very different encodings
Pre-v29 (wire 21..27.2) — token-range table + flat type list + native-side generators:
attributesInfo—Il2CppCustomAttributeTypeRange[] = { uint token; int start; int count; }(12 B; 8 B pre-24.1 without the token).attributeTypes— flatint32_t[]of attribute type indices (intoMetadataRegistration.types).CodeRegistration.customAttributeGenerators— onevoid(void*)function pointer per range. Its bodynews up the attribute instance(s), baking constructor arguments as immediate operands. Reading the thunk recovers the arguments; this is what Il2CppDumper'sGetCustomAttributeValuesFromGeneratordoes.
v29+ — inline BLOB:
attributesInfo*header slots repurposed asattributeDataRange:{ uint token; uint startOffset; }array.attributeTypes*header slots repurposed asattributeData: raw BLOB heap.- BLOB encoding matches ECMA-335 §II.23.3
CustomAttribextended with IL2CPP's 7-bit compressed-int (same as ECMA-335 §II.23.2): leading compressed count of constructor references; each ref encoded as a method index plus inline-serialized argument values. - Decoded by
Il2CppDumper/Utils/CustomAttributeDataReader.cs. customAttributeGeneratorsno longer exists inCodeRegistration.
Value: [Obsolete], [Serializable], [SerializeField],
[Preserve], [RequireComponent(typeof(Rigidbody))],
[RuntimeInitializeOnLoadMethod],
[DllImport("x.dll",EntryPoint="y")]. [Preserve] in particular
flags code reachable only via reflection — a high-value RE signal.
Cross-reference: https://ecma-international.org/publications-and-standards/standards/ecma-335/
2.10 Pre-v27 metadata usage tables
metadataUsageLists—Il2CppMetadataUsageList[] = { uint start; uint count; }(8 B).metadataUsagePairs—Il2CppMetadataUsagePair[] = { uint destinationIndex; uint encodedSourceIndex; }(8 B).
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
(doc/ptrtables.md).
Value: on v19–v26 builds, the compiled code is full of ldr X, [g_MetadataUsages + offset] loads. These tables plus
metadataUsages together let a decoder label each slot as
[TypeInfo] System.String, [StringLiteral_42] "hello world", etc.
From v27+ the mechanism was replaced by per-site inline globals
lazily initialized by Il2CppCodeGenModule.moduleInitializer, and
these tables disappear from the .dat.
2.11 assemblies + referencedAssemblies
typedef struct { /* Il2CppAssemblyDefinition */
int32_t imageIndex;
uint32_t token; /* ≥24.1, 0x20?????? */
int32_t customAttributeIndex; /* ≤24 */
int32_t referencedAssemblyStart; /* ≥20 */
int32_t referencedAssemblyCount; /* ≥20 */
Il2CppAssemblyNameDefinition aname;
} Il2CppAssemblyDefinition;
typedef struct { /* Il2CppAssemblyNameDefinition */
uint32_t nameIndex, cultureIndex, publicKeyIndex;
uint32_t hashValueIndex; /* removed at v24.3+ */
uint32_t hash_alg; /* AssemblyHashAlgorithm enum */
int32_t hash_len;
uint32_t flags; /* AssemblyFlags, ECMA-335 §II.22.2 */
int32_t major, minor, build, revision;
uint8_t public_key_token[8]; /* low 8 bytes of SHA1(publicKey), byte-reversed */
} Il2CppAssemblyNameDefinition;
Value: distinguish Unity-shipped assemblies (mscorlib.dll,
UnityEngine.*, UnityEditor.*) from game assemblies. Recover
AssemblyVersion quadruples and strong-name public-key tokens.
Dependency graph between assemblies — needed for emitting a
compilable C# skeleton or for filtering dumps by assembly.
This is the one large "still-declared" table r2unity does
decode today, and it drives the -S SBOM output.
2.12 fieldRefs (v19+)
typedef struct { int typeIndex; int fieldIndex; } Il2CppFieldRef;
Canonical list of field references used at metadata-usage
destinations, typically for fields of generic-instantiation types
that cannot be addressed directly by (typeDef, fieldDef).
Dereferences the FieldInfo kind of metadata-usage slot (§2.10).
2.13 exportedTypeDefinitions (v24+)
Raw TypeDefinitionIndex[]. Per-image via
imageDef.exportedTypeStart / exportedTypeCount. These are .NET
type forwarders — matters for faithfully reproducing runtime
assemblies (System.Runtime.dll re-exports into mscorlib.dll).
Optional for most application-level dumping.
2.14 RGCTX tables (≥v24.1 on disk; moved to
codeGenModule from v24.2)
typedef struct { /* Il2CppRGCTXDefinition (v≤27.1) */
int32_t type; /* Il2CppRGCTXDataType enum */
Il2CppRGCTXDefinitionData data; /* typically int rgctxDataDummy */
} Il2CppRGCTXDefinition; /* 8 B */
/* v≥27.2: type_post29 widens to ulong; _data widens to ulong -> 16 B */
Il2CppRGCTXDataType = { Invalid, Type, Class, Method, Array, Constrained }. Per-type via typeDef.rgctxStartIndex /
rgctxCount (pre-v24.1); per-method similarly. From v24.2 onward
these tables move per-image into
Il2CppCodeGenModule.rgctxs + rgctxRanges.
Value: Runtime Generic Context — per-instantiation lookup table.
Resolves generic-virtual-dispatch sites and typeof(T) /
default(T) accesses inside generic methods. "The 3rd RGCTX slot
of List<T>::Sort" → concrete type/method resolution.
2.15 fieldMarshaledSizes
typedef struct { int fieldIndex; int typeIndex; int size; } Il2CppFieldMarshaledSize;
Marshalled size for [MarshalAs] / [StructLayout] interop
structs. Relevant only when the game talks heavily to native
plugins via custom marshalling; safe to ignore for plain RE.
2.16 unresolvedVirtualCallParameterTypes /
unresolvedVirtualCallParameterRanges (v22+)
/* Types: raw TypeIndex[] */
typedef struct { int start; int length; } Il2CppRange;
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 +
windowsRuntimeFactoryTable
UWP / HoloLens only. For iOS + Android + desktop builds these sizes are always zero; a defensive parser bounds-checks and skips.
3. Native binary pointer-table reference
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
4.1 Portability fixes (correctness)
-
Version-gated entry sizes. The decoded tables now compute the v38/v39 row sizes from section counts and compact-index widths. Keep extending this approach instead of reintroducing literal strides. Critical historical values:
Il2CppMethodDefinition:- v≤24.0: 52 B (includes
customAttributeIndex,methodIndex,invokerIndex,delegateWrapperIndex,rgctxStartIndex,rgctxCount). - v24.1 – v30: 32 B.
- v31+: 36 B (
returnParameterTokenadded). - v35..v37: compact reader hooks exist, but current reference initializes the widths to 4 bytes before v38.
- v38+:
declaringType,returnType, andgenericContainerIndexmay shrink to 1/2/4-byte compact indices. - v39+:
parameterStartmay also shrink to a compactParameterIndex.
- v≤24.0: 52 B (includes
Il2CppImageDefinition:- v≤18: 24 B.
- v19 – v23: 28 B (adds
token). - v24 – v24.0: 36 B (adds exported-type range).
- v24.1+: 40 B before compact-index shrinkage (adds
customAttributerange). - v38+:
typeStartandexportedTypeStartmay shrink to compactTypeDefinitionIndexfields.
Il2CppTypeDefinition:- pre-v24: extra legacy fields
(
delegateWrapperFromManagedToNativeIndex,marshalingFunctionsIndex,ccwFunctionIndex,guidIndex). - v24 – v24.0:
customAttributeIndex+byrefTypeIndex+rgctxStartIndex+rgctxCount. - v24.1+: 88 B before compact-index shrinkage.
- v32+:
elementTypeIndexis no longer serialized. - v38+: type-like indices may shrink to compact serialized widths.
- pre-v24: extra legacy fields
(
Always source the size from
third_party/Il2CppDumper/Il2Cpp/MetadataClass.csper wire version. -
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.
-
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.
-
Fail loudly on truly unknown versions. v36/v37 are currently rejected because their header shape is not proven. Keep that behavior until a reliable reference or fixture confirms the layout.
4.2 Feature additions
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
.datparser stays architecture-neutral. No branching needed.CodeRegistration/MetadataRegistrationwalkers must be written withptrsz = is64bit ? 8 : 4and{u32|u64}readers accordingly. This generalises cleanly from the existing Mach-O / ELF scanner scaffolding.
4.4 Format-specific scanner gaps
- ELF: packed Android relocations (
DT_ANDROID_RELA = 0x60000010,DT_ANDROID_RELR = 0x6fffe000) not handled — Play-Store builds compressed with packed relocs read as "no relocations".DT_VERSYM/DT_VERNEEDnot consulted (would help identify Unity version from the.sodirectly).ET_EXECvsET_DYNbranching (we assume PIC shared objects). - Mach-O: 32-bit, FAT multi-slice selection,
LC_DYLD_CHAINED_FIXUPS(0x34) interpretation,LC_ENCRYPTION_INFO_64detection (FairPlay-encrypted App Store IPAs silently return garbage), ARM64e pointer-authentication awareness.IMAGE_DIRECTORY_ENTRY_BASERELOC(not needed today, link-timeImageBaseis concrete).
4.5 Sources and caveats
- 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.5f1ships IL2CPP sources that asserts_GlobalMetadataHeader->version == 39inlibil2cpp/vm/GlobalMetadata.cpp. ItsGlobalMetadataFileInternals.handMetadataDeserialization.*files confirm the v39 runtime shape: noelementTypeIndex,Il2CppStringLiteralisdataIndexonly,moduleTokenis present on assemblies, and compact index widths are read from metadata counts. - Public Unity release notes and documentation searched for
2023.3/6000.x do not document the private
global-metadata.datwire format. The useful public source for the v32..v39 gap is still Il2CppDumper v39's annotatedMetadataClass.cs, plus the local Unity runtime sources above. - Il2CppDumper's bundled snapshot is what drives these layout
tables; newer Unity releases may introduce another whole wire
version or sub-version that won't be caught until the vendored
copy is refreshed. A quarterly bump of
third_party/Il2CppDumper/is the lightest way to track new Unity releases. - Il2CppInspector's
MetadataClasses.cs/MetadataVersions.csis an independent second opinion; pulling it as a cross-check would let us confirm a few uncertain sub-version boundaries (notably the 2020.x → v24.4 vs v27 transition month).
5. Further reading
- ECMA-335, "Common Language Infrastructure" (6th edition) — the
primary source for token encoding (§II.22), metadata BLOB
signatures (§II.23.2, §II.23.3), element-type codes
(§II.23.1.16), attribute enums (
TypeAttributes,MethodAttributes,MethodSemanticsAttributes,GenericParameterAttributes,AssemblyFlags), instance layout andIl2CppObjectheader (§II.25.4), strong-name public-key derivation (§II.6.3). https://ecma-international.org/publications-and-standards/standards/ecma-335/ - ECMA-334, "C# Language Specification" — attribute semantics, property/event syntactic sugar. https://ecma-international.org/publications-and-standards/standards/ecma-334/
- Il2CppDumper — vendored reference implementation.
Il2Cpp/MetadataClass.csis the authoritative per-version layout source;Utils/Il2CppExecutor.csis the authoritative BLOB decoder. https://github.com/Perfare/Il2CppDumper - Il2CppDumper v39 metadata structs — current source used for the v38/v39 section header and compact-index rules. https://raw.githubusercontent.com/roytu/Il2CppDumper/v39/Il2CppDumper/Il2Cpp/MetadataClass.cs
- Il2CppInspector — per-version deltas and Unity-version matrix. https://github.com/djkaty/Il2CppInspector
- REAndroid/lib-global-metadata — independent Java port. https://github.com/REAndroid/lib-global-metadata
- Unity blog series on IL2CPP internals (method calls, generics, strings, P/Invoke): https://blog.unity.com/technology/il2cpp-internals-method-calls
- Android linker packed-relocation format: https://android.googlesource.com/platform/bionic/+/master/linker/linker_relocate.cpp
- Mach-O chained fixups (dyld source): https://github.com/apple-oss-distributions/dyld
- PE/COFF specification (Microsoft): https://learn.microsoft.com/windows/win32/debug/pe-format