[GH-ISSUE #3784] [Feature] Read-only inventory of Unity IL2CPP global-metadata.dat (first slice of #3607) #1469

Closed
opened 2026-10-16 21:05:45 +00:00 by bsakaguchi · 7 comments
Owner

Originally created by @8rulerstar on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3784

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Evidence, artifacts, or reconstruction

Current limitation

rea cannot read Unity IL2CPP metadata. docs/managed-code-analysis.md states that the parser "does not ... decode IL2CPP metadata", and no provider reads global-metadata.dat. For an IL2CPP build, an analyst gets the unity convention marker and nothing about which assemblies, types and methods the build contains.

Use cases

  • For an IL2CPP build, list its assemblies, namespaces, types and methods: the same kind of names inspect_managed_members already shows for a .NET assembly.
  • Triage an unknown Unity app, such as suspected malware, without running it.

Desired outcome and success criteria

Given a global-metadata.dat, rea reports:

  • magic, raw metadata version and the section table, with bounds checks
  • the images (assemblies), each with its type range
  • per image: types with their namespaces (nested types under their enclosing type), and each type's methods
  • an explicit unsupported or corrupt result with a reason when the file does not validate

The output contains names and counts only, already present in the build: no native addresses, RVAs or method bodies. A full build is large (my test file has 112,232 methods), so the result would be a summary plus paged inspect_analysis_view rows, like the managed tools, with a measured output budget.

Expected interaction with targets

Read existing files or captured data

Target and scope details

Supported raw versions (Unity ranges are approximate, from Cpp2IL source comments and public version reports; the 6000.0 boundary is unverified, and v29/v31 are not yet checked against real files):

Version Unity
29 2021.2 to 2021.3.39, 2022.1 to 2022.3.32, 2023.x, early 6000.0
31 2021.3.40+, 2022.3.33+, later 6000.0 (likely 6000.1 and 6000.2)
39 6000.3 and 6000.4 releases

v29 and v31 use fixed record sizes; v39 adds per-section counts and variable-width indices. The 29.1 and 31.1 sub-variants that other tools track differ only in the native CodeRegistration, which this does not read. Every section gets bounds, overlap and record-size checks before allocation.

Out of scope:

  • Decrypting, de-obfuscating or repairing metadata. A wrong magic, an unknown version or a failed check is reported as unsupported or corrupt, with the reason.
  • Versions before 29, the 6000.3 alpha-only versions (35, 38) and 6000.5+ (104 and later), reported as unsupported.
  • Linking metadata to GameAssembly or libil2cpp.so: method addresses, RVAs, symbol injection. That would need its own scope decision.

Questions for maintainers:

  1. Is a read-only global-metadata.dat inventory in scope as a new provider?
  2. Is v29/v31/v39 with an operator-supplied verification lane enough for a first slice?

Verification available so far

I have a prototype or observed results

Verification details

  • A real v39 file from my own iOS build (Unity 6000.4.2f1, 12.9 MB): a read-only prototype reads 96 images, 14,739 types and 112,232 methods. The method ranges of all types cover every method exactly once.
  • Before opening a PR I will build the same project with Unity 2022.3.32f1 (v29) and 2022.3.33f1 or later (v31), and compare names and counts on all three files against Cpp2IL's output. If v29 or v31 cannot be checked by then, the PR ships v39 only and reports the others as unsupported.
  • Synthetic fixtures in test code for each version (under 1 KB), with a negative case for each check.
  • An opt-in lane, verify:il2cpp:metadata -- --metadata PATH, where the operator supplies a self-built file, as verify:pcap -- --pcap PATH does. Unity builds are not practical in CI. When the lane is skipped, no real-file coverage is claimed. Build artifacts and commercial game files are not committed.

Alternatives considered

Il2CppDumper and Cpp2IL dump this metadata, but they run outside rea, expect the native binary as well, and do not produce rea Evidence.

Possible direction

A new provider exposed as a dedicated inspect_il2cpp_metadata tool (CLI and MCP), shaped like inspect_go_binary (#1363), taking a global-metadata.dat path.

References

#3607 lists metadata parsing first, together with Mono CIL decompilation, RVA correlation, Ghidra injection, assets and scenes. This issue covers only metadata parsing, from v29 rather than v24.

Originally created by @8rulerstar on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3784 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal. - [x] I understand the contribution scope must be reviewed before integration; open questions are welcome here. - [x] This request does not include credentials, private samples, or sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Current limitation rea cannot read Unity IL2CPP metadata. `docs/managed-code-analysis.md` states that the parser "does not ... decode IL2CPP metadata", and no provider reads `global-metadata.dat`. For an IL2CPP build, an analyst gets the `unity` convention marker and nothing about which assemblies, types and methods the build contains. ### Use cases - For an IL2CPP build, list its assemblies, namespaces, types and methods: the same kind of names `inspect_managed_members` already shows for a .NET assembly. - Triage an unknown Unity app, such as suspected malware, without running it. ### Desired outcome and success criteria Given a `global-metadata.dat`, rea reports: - magic, raw metadata version and the section table, with bounds checks - the images (assemblies), each with its type range - per image: types with their namespaces (nested types under their enclosing type), and each type's methods - an explicit `unsupported` or `corrupt` result with a reason when the file does not validate The output contains names and counts only, already present in the build: no native addresses, RVAs or method bodies. A full build is large (my test file has 112,232 methods), so the result would be a summary plus paged `inspect_analysis_view` rows, like the managed tools, with a measured output budget. ### Expected interaction with targets Read existing files or captured data ### Target and scope details Supported raw versions (Unity ranges are approximate, from Cpp2IL source comments and public version reports; the 6000.0 boundary is unverified, and v29/v31 are not yet checked against real files): | Version | Unity | |---|---| | 29 | 2021.2 to 2021.3.39, 2022.1 to 2022.3.32, 2023.x, early 6000.0 | | 31 | 2021.3.40+, 2022.3.33+, later 6000.0 (likely 6000.1 and 6000.2) | | 39 | 6000.3 and 6000.4 releases | v29 and v31 use fixed record sizes; v39 adds per-section counts and variable-width indices. The 29.1 and 31.1 sub-variants that other tools track differ only in the native `CodeRegistration`, which this does not read. Every section gets bounds, overlap and record-size checks before allocation. Out of scope: - Decrypting, de-obfuscating or repairing metadata. A wrong magic, an unknown version or a failed check is reported as unsupported or corrupt, with the reason. - Versions before 29, the 6000.3 alpha-only versions (35, 38) and 6000.5+ (104 and later), reported as unsupported. - Linking metadata to `GameAssembly` or `libil2cpp.so`: method addresses, RVAs, symbol injection. That would need its own scope decision. Questions for maintainers: 1. Is a read-only `global-metadata.dat` inventory in scope as a new provider? 2. Is v29/v31/v39 with an operator-supplied verification lane enough for a first slice? ### Verification available so far I have a prototype or observed results ### Verification details - A real v39 file from my own iOS build (Unity 6000.4.2f1, 12.9 MB): a read-only prototype reads 96 images, 14,739 types and 112,232 methods. The method ranges of all types cover every method exactly once. - Before opening a PR I will build the same project with Unity 2022.3.32f1 (v29) and 2022.3.33f1 or later (v31), and compare names and counts on all three files against Cpp2IL's output. If v29 or v31 cannot be checked by then, the PR ships v39 only and reports the others as unsupported. - Synthetic fixtures in test code for each version (under 1 KB), with a negative case for each check. - An opt-in lane, `verify:il2cpp:metadata -- --metadata PATH`, where the operator supplies a self-built file, as `verify:pcap -- --pcap PATH` does. Unity builds are not practical in CI. When the lane is skipped, no real-file coverage is claimed. Build artifacts and commercial game files are not committed. ### Alternatives considered Il2CppDumper and Cpp2IL dump this metadata, but they run outside rea, expect the native binary as well, and do not produce rea Evidence. ### Possible direction A new provider exposed as a dedicated `inspect_il2cpp_metadata` tool (CLI and MCP), shaped like `inspect_go_binary` (#1363), taking a `global-metadata.dat` path. ### References #3607 lists metadata parsing first, together with Mono CIL decompilation, RVA correlation, Ghidra injection, assets and scenes. This issue covers only metadata parsing, from v29 rather than v24.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

A read-only inventory of global-metadata.dat is a very sensible first slice: names and counts only, no addresses or method bodies, keeps the scope safe and matches what inspect_managed_members already exposes for .NET. Splitting it from #3607 is right — metadata layout has changed across Unity versions, so landing an inventory with bounds checks first lets the parser prove correctness before anything deeper is attempted. On the verification question: v29/v31/v39 plus an operator-supplied lane sounds like a reasonable starting set, though worth keeping the version gate explicit so a future vXX silently no-ops instead of misparsing.

<!-- gh-comment-id:6184929531 --> @XinwangTao commented on GitHub (Oct 15, 2026): A read-only inventory of global-metadata.dat is a very sensible first slice: names and counts only, no addresses or method bodies, keeps the scope safe and matches what inspect_managed_members already exposes for .NET. Splitting it from #3607 is right — metadata layout has changed across Unity versions, so landing an inventory with bounds checks first lets the parser prove correctness before anything deeper is attempted. On the verification question: v29/v31/v39 plus an operator-supplied lane sounds like a reasonable starting set, though worth keeping the version gate explicit so a future vXX silently no-ops instead of misparsing.
Author
Owner

@8rulerstar commented on GitHub (Oct 15, 2026):

While waiting on scope here, I prototyped the next slices of #3607 locally on separate branches (not pushed), each checked against real files from my own Unity builds:

  • More metadata versions: 24 and 27 (Unity 2019.4 and 2020.3), same tool. Names match LibCpp2IL on all seven sample files (versions 24 to 39).
  • Member names and string literals: field, property, event and parameter names with their owners, plus managed string literals. Also zero differences from LibCpp2IL.
  • Native method addresses and type names: an optional binary_path (thin Mach-O for now) locates the code and metadata registrations without symbols and reports method addresses plus field, parameter and return type names. Checked by disassembling Slot.Add in four builds; ELF and chained fixups are not handled yet.
  • Unity asset inventories: a separate read-only tool for SerializedFile (versions 9 to 22) and UnityFS bundles (uncompressed and LZ4; LZMA is refused), with bounded decompression.
  • Bundle routing: inspect_artifact suggests the metadata tool when a bundle contains global-metadata.dat.

All of this stays out of #3807. If the first slice fits, which of these, if any, would you want proposed next? Happy to open a separate issue for each.

<!-- gh-comment-id:6184954710 --> @8rulerstar commented on GitHub (Oct 15, 2026): While waiting on scope here, I prototyped the next slices of #3607 locally on separate branches (not pushed), each checked against real files from my own Unity builds: - **More metadata versions**: 24 and 27 (Unity 2019.4 and 2020.3), same tool. Names match LibCpp2IL on all seven sample files (versions 24 to 39). - **Member names and string literals**: field, property, event and parameter names with their owners, plus managed string literals. Also zero differences from LibCpp2IL. - **Native method addresses and type names**: an optional `binary_path` (thin Mach-O for now) locates the code and metadata registrations without symbols and reports method addresses plus field, parameter and return type names. Checked by disassembling `Slot.Add` in four builds; ELF and chained fixups are not handled yet. - **Unity asset inventories**: a separate read-only tool for SerializedFile (versions 9 to 22) and UnityFS bundles (uncompressed and LZ4; LZMA is refused), with bounded decompression. - **Bundle routing**: `inspect_artifact` suggests the metadata tool when a bundle contains `global-metadata.dat`. All of this stays out of #3807. If the first slice fits, which of these, if any, would you want proposed next? Happy to open a separate issue for each.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Thanks for the breakdown. If the first slice lands, I'd prioritize the member names / string literals slice next: it directly extends what inspect_managed_members already surfaces for IL2CPP builds (owners + names, no addresses), and your zero-diff result against LibCpp2IL across versions makes it the lowest-risk follow-up. Native addresses and asset inventories are valuable too, but they pull in binary parsing dependencies (Mach-O/ELF, SerializedFile/UnityFS) that are better scoped separately. Also glad the metadata tool gets suggested by bundle routing via inspect_artifact — that makes it discoverable for real triage workflows.

<!-- gh-comment-id:6185218507 --> @XinwangTao commented on GitHub (Oct 15, 2026): Thanks for the breakdown. If the first slice lands, I'd prioritize the member names / string literals slice next: it directly extends what inspect_managed_members already surfaces for IL2CPP builds (owners + names, no addresses), and your zero-diff result against LibCpp2IL across versions makes it the lowest-risk follow-up. Native addresses and asset inventories are valuable too, but they pull in binary parsing dependencies (Mach-O/ELF, SerializedFile/UnityFS) that are better scoped separately. Also glad the metadata tool gets suggested by bundle routing via inspect_artifact — that makes it discoverable for real triage workflows.
Author
Owner

@8rulerstar commented on GitHub (Oct 15, 2026):

Thanks, agreed. If the first slice fits, I'll propose members and string literals next, and keep native addresses and asset inventories scoped separately. Unknown versions already fail as unsupported-version rather than being parsed.

<!-- gh-comment-id:6185259625 --> @8rulerstar commented on GitHub (Oct 15, 2026): Thanks, agreed. If the first slice fits, I'll propose members and string literals next, and keep native addresses and asset inventories scoped separately. Unknown versions already fail as `unsupported-version` rather than being parsed.
Author
Owner

@8rulerstar commented on GitHub (Oct 15, 2026):

For reference, the members and string literals slice is now on my fork: diff (+507 lines on top of #3807). It is exploratory and not a request for review or integration; I'll wait for a scope decision on #3807 first.

Names and owners match LibCpp2IL exactly on five files (metadata 29, 31, 39) and, with the versions 24 and 27 slice, on the two older files too. String literal values match as well; on the version 39 file LibCpp2IL reports one extra trailing empty literal, which is the format's sentinel record.

The other prototypes from my comment above stay on my fork and are only checked on my own unprotected builds; I can share them if they become useful.

<!-- gh-comment-id:6185486630 --> @8rulerstar commented on GitHub (Oct 15, 2026): For reference, the members and string literals slice is now on my fork: [diff](https://github.com/8rulerstar/rea/compare/feat/il2cpp-metadata...feat/il2cpp-members) (+507 lines on top of #3807). It is exploratory and not a request for review or integration; I'll wait for a scope decision on #3807 first. Names and owners match LibCpp2IL exactly on five files (metadata 29, 31, 39) and, with the versions 24 and 27 slice, on the two older files too. String literal values match as well; on the version 39 file LibCpp2IL reports one extra trailing empty literal, which is the format's sentinel record. The other prototypes from my comment above stay on my fork and are only checked on my own unprotected builds; I can share them if they become useful.
Author
Owner

@alexlanghart commented on GitHub (Oct 18, 2026):

A real-world data point for the scope questions here, from a commercial Unity game (Unity 2022.3, metadata v31, Windows x64, 60 MB GameAssembly.dll, 17 MB global-metadata.dat):

  • On question 2: v31 exists in shipped builds today. Il2CppDumper 6.7.46 reads this file as v31 and finds CodeRegistration/MetadataRegistration in ~9 s.
  • The address-linking step this issue leaves out is the one that changes REA's Ghidra output. I converted Il2CppDumper's script.json into an REA_GHIDRA_SEED_FILE (130,048 function seeds, 124,086 named; 65,080 labels for *_TypeInfo, Method$... and string literals). rea decompile --provider ghidra then reported 0 failed seeds (9,908 functions created, 120,140 already present), and output went from FUN_*/DAT_* to named callees and string literals. Cost: ~24 min cold analysis, 7.6 GB peak RSS, with a 90 min startup deadline and a 9G heap.

I opened #5112 for that follow-up (resolve method addresses from metadata + registration tables and emit seeds in the existing format), so the inventory here can stay names-only as scoped.

<!-- gh-comment-id:6254345808 --> @alexlanghart commented on GitHub (Oct 18, 2026): A real-world data point for the scope questions here, from a commercial Unity game (Unity 2022.3, metadata v31, Windows x64, 60 MB `GameAssembly.dll`, 17 MB `global-metadata.dat`): - On question 2: v31 exists in shipped builds today. Il2CppDumper 6.7.46 reads this file as v31 and finds `CodeRegistration`/`MetadataRegistration` in ~9 s. - The address-linking step this issue leaves out is the one that changes REA's Ghidra output. I converted Il2CppDumper's `script.json` into an `REA_GHIDRA_SEED_FILE` (130,048 function seeds, 124,086 named; 65,080 labels for `*_TypeInfo`, `Method$...` and string literals). `rea decompile --provider ghidra` then reported **0 failed** seeds (9,908 functions created, 120,140 already present), and output went from `FUN_*`/`DAT_*` to named callees and string literals. Cost: ~24 min cold analysis, 7.6 GB peak RSS, with a 90 min startup deadline and a 9G heap. I opened #5112 for that follow-up (resolve method addresses from metadata + registration tables and emit seeds in the existing format), so the inventory here can stay names-only as scoped.
Author
Owner

@XinwangTao commented on GitHub (Oct 20, 2026):

Thanks for the v31 data point from a shipped game and for taking the address-linking follow-up to #5112 — that confirms keeping this inventory names-only is the right scope, and the seed-format handoff keeps the Ghidra pipeline usable in the meantime. Glad to see #3784 land via #3807.

<!-- gh-comment-id:6280947479 --> @XinwangTao commented on GitHub (Oct 20, 2026): Thanks for the v31 data point from a shipped game and for taking the address-linking follow-up to #5112 — that confirms keeping this inventory names-only is the right scope, and the seed-format handoff keeps the Ghidra pipeline usable in the meantime. Glad to see #3784 land via #3807.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1469
No description provided.