Conversation
Adds parsing for IL2CPP global-metadata.dat version 39, which changes the on-disk layout in three ways relative to v31. Header: every table descriptor gained a trailing element count, turning the (offset, size) pairs into (offset, size, count) triples. The set of tables and their order are otherwise unchanged, so the counts are interleaved via [Version(Min = 39)] rather than needing a bespoke header reader. Narrowed indexes: paired start/count indexes and most TypeIndex fields dropped from int32 to uint16, with 0xFFFF as the "no value" sentinel. Il2CppTypeDefinition 88 -> 76 bytes, Il2CppMethodDefinition 36 -> 30, Il2CppFieldDefinition 12 -> 10. These are read into [Version(Min = 39)] ushort fields and widened back into the existing int fields by Metadata.FixIndexesV39, so no downstream consumer needs to know about the change. Removed and merged fields: - Il2CppTypeDefinition.parentIndex and elementTypeIndex now share a slot. An enum's parent is always System.Enum and therefore redundant, so for enums the slot carries the underlying type instead. FixIndexesV39 splits them back apart. Without this, generated enums derive from their underlying type, TypeDefinition.IsEnum is false, and Mono.Cecil cannot serialise any enum-typed custom attribute argument - on the test game that alone broke 75 of 81 dummy assemblies. - Il2CppStringLiteral.length is gone. The table is now a sentinel-terminated array of data offsets, so each length is recovered from the next entry. - Il2CppCodeRegistration lost reversePInvokeWrapperCount and reversePInvokeWrappers. Il2CppMetadataRegistration is unchanged. - Il2CppAssemblyDefinition gained a 4-byte field after token. It mirrors the token RID in every assembly observed and is read only to keep the stride correct. Also enables .h struct generation for v39, whose runtime C structs are unchanged from v29, and makes DummyAssemblyExporter report and skip an assembly that fails to serialise rather than aborting the entire export. Verified against a Unity 6 title (metadata v39, 16586 type definitions, 80 assemblies): dump.cs, il2cpp.h, script.json, stringliteral.json and 81/81 dummy assemblies all generate cleanly. Method addresses, field offsets and constant values were cross-checked against an independent parser written from scratch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Tried against Big Walk and got this: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Support metadata v39 (Unity 6.x)
Adds support for IL2CPP
global-metadata.datversion 39, as shipped by Unity 6.x.Currently
Metadata.csrejects anything above v31.Versions 32–38 were never shipped publicly, so v39 is gated explicitly
(
version > 31 && version != 39) rather than by widening the range.What changed in v39
1. Header descriptors became triples
Every table descriptor gained a trailing element count, so the classic
(offset, byteSize)pairs are now(offset, byteSize, count)triples. The set oftables and their order are otherwise identical to v31 — there are exactly 31 of
each — so the counts are interleaved with
[Version(Min = 39)]and no separateheader reader is needed.
The counts are read but not relied upon: array lengths still come from
byteSize / SizeOf(T), which doubles as a check that the struct sizes below areright.
2. Indexes narrowed to
uint16Paired start/count indexes and most
TypeIndexfields dropped fromint32touint16, with0xFFFFas the "no value" sentinel:Il2CppTypeDefinitionIl2CppMethodDefinitionIl2CppFieldDefinitionIl2CppParameterDefinitionIl2CppEventDefinitionIl2CppImageDefinitionIl2CppGenericParameterIl2CppFieldRefThe standalone
TypeIndexarrays (interfaces,genericParameterConstraints)narrowed to 2 bytes per entry as well.
Il2CppPropertyDefinition,Il2CppGenericContainer,Il2CppCustomAttributeDataRangeand thenestedTypes/vtableMethodsarrays are unchanged.These are read into
[Version(Min = 39)]ushortfields and widened back into theexisting
intfields byMetadata.FixIndexesV39, so nothing downstream needs toknow the format changed. Widening maps
0xFFFFto-1, which caps usable indexesat 65534 — well above anything Unity currently emits (the test game peaked at
59841
Il2CppTypes).3. Fields removed or merged
Il2CppTypeDefinition.parentIndexandelementTypeIndexnow share one slot.An enum's parent is always
System.Enumand therefore redundant, so for enums theslot carries the underlying type instead; every other type stores its parent as
before.
FixIndexesV39splits them back apart, recovering the parent fromSystem.Enum's ownbyvalTypeIndex.This one is easy to get wrong and fails far from its cause. Read the slot as
parentIndexunconditionally and generated enums derive fromSystem.Int32,TypeDefinition.IsEnumgoes false, andMono.Cecil'sWriteCustomAttributeEnumValuethrows
ArgumentExceptionon every enum-typed custom attribute argument — whichtook out 75 of 81 dummy assemblies on the test game, while small assemblies with no
such attributes wrote fine.
The giveaway that it is not a parent index: two enums with different underlying
types hold different values there. On the test game a
byte-backed enum held29610 and an
int-backed enum held 34664. Parent indexes could not differ.Also:
Il2CppStringLiteral.lengthis gone. The table is now a sentinel-terminatedarray of data offsets (count is
literals + 1), so each length is recovered fromthe following entry.
Il2CppCodeRegistrationlostreversePInvokeWrapperCount/reversePInvokeWrappers.This is in the binary rather than metadata. Reading the v31 layout shifts every
subsequent field by two slots and
genericMethodPointersCountpicks up apointer value, producing an immediate
OverflowExceptioninIl2Cpp.Init.Il2CppMetadataRegistrationis unchanged.Il2CppAssemblyDefinitiongained a 4-byte field aftertoken. It mirrors thetoken RID in every assembly observed (1 for real assemblies, 0 for the synthetic
__Generatedmodule). Read only to keep the stride correct; namedunknownV39since its purpose is unconfirmed.
Other changes
StructGeneratornow emits.hfiles for v39. The runtime C structs areunchanged from v29, so it reuses
HeaderConstants.HeaderV29.DummyAssemblyExporterreports and skips an assembly that fails to serialiseinstead of letting one bad attribute abort the whole export. This is dormant
now that v39 writes cleanly, but the previous behaviour lost all remaining
assemblies. Happy to drop this if you would rather keep the PR to v39 only.
Verification
Tested against a Unity 6 title: metadata v39, 16586 type definitions, 114591
methods, 80 assemblies.
dump.csil2cpp.hscript.jsonstringliteral.jsonDummyDllEach layout claim was checked against a property that breaks if the decode is
wrong, rather than by eyeballing plausible output:
declaringTypeIndex's non-sentinel count is exactly thenestedTypestablelength (7685).
typeStart/typeCountchain contiguously and sum to exactly the typedefinition count (16586).
referencedAssemblyStart/Countchain to exactly thereferencedAssembliescount (475).mscorlib's public key token readsb77a5c561934e089at version 4.0.0.0.unresolvedVirtualCallCountin the binary matches the metadata'sunresolvedVirtualCallParameterRangescount (4259).codeGenModulesresolves to the same address found independently by scanningthe binary for the module table.
genericContainerIndexlane andno enum does, confirming which lane is which.
Method addresses, field offsets and constant values were also cross-checked
against a separate parser written from scratch, and against disassembly of
specific methods.
Notes for review
unknownV39field inIl2CppAssemblyDefinitionand the exactexportedTypeStart/exportedTypeCountencoding in
Il2CppImageDefinitionare inferred from one sample. Neither isread by the dumper; both matter only for struct stride, which is verified.
GetConstantValueFromBlob's existingVersion >= 29branches already apply. Nochange was needed, but it is worth knowing when hand-checking constants: the
byte
0x08in the default value blob decodes to4, not8.🤖 Generated with Claude Code