[PR #5327] [MERGED] feat(il2cpp): link methods to native addresses and write Ghidra seeds #6883

Closed
opened 2026-11-01 21:43:48 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/5327
Author: @8rulerstar
Created: 10/20/2026
Status: ✅ Merged
Merged: 10/20/2026
Merged by: @gensokyo-dev

Base: main ← Head: feat/il2cpp-native-seeds


📝 Commits (6)

  • e139382 feat(il2cpp): link methods to native addresses and write Ghidra seeds
  • f1ffa1f fix(il2cpp): bound native linking and retain queryable evidence
  • 95a20b1 fix(il2cpp): accept pointer tables spanning loader ranges
  • f009591 fix(il2cpp): retain overlapping candidates and consolidate boundary facts
  • 9b6045b fix(javascript): retain exported initializer order in semantic calls
  • 66bcbfd test(cli): assert file URL rejection independently of schema wording

📊 Changes

43 files changed (+5059 additions, -367 deletions)

View changed files

📝 .agents/skills/reverse-engineer-anything/SKILL.md (+1 -2)
📝 docs/cli.md (+4 -2)
📝 docs/il2cpp-metadata.md (+175 -4)
📝 scripts/verify-il2cpp-metadata.mjs (+7 -0)
📝 src/application/JsonFiles.ts (+5 -2)
📝 src/application/il2cpp/Il2cppMetadataService.test.ts (+157 -38)
📝 src/application/il2cpp/Il2cppMetadataService.ts (+177 -24)
➕ src/application/il2cpp/Il2cppSeedWriterPort.ts (+32 -0)
📝 src/cli/il2cppCommands.ts (+69 -7)
📝 src/cliJsonPaths.test.ts (+0 -3)
📝 src/composition/il2cpp.ts (+5 -1)
📝 src/contracts/analysisViewToolContracts.ts (+1 -1)
📝 src/contracts/il2cpp/il2cppToolContracts.ts (+10 -1)
📝 src/domain/analysisView/il2cppMetadataView.test.ts (+288 -24)
📝 src/domain/analysisView/il2cppMetadataView.ts (+232 -19)
➕ src/domain/il2cpp/il2cppMetadata.fixture.ts (+42 -0)
📝 src/domain/il2cpp/il2cppMetadata.ts (+161 -1)
📝 src/domain/javascript/javascriptSemanticMutationInitializers.ts (+11 -12)
📝 src/ghidra/GhidraAnalysisSeeds.ts (+3 -3)
📝 src/go/GoBinaryContainer.ts (+6 -0)

...and 23 more files

📄 Description

Summary

Fixes #5112. Link IL2CPP metadata names to native method addresses/type facts and export Ghidra seeds, extending @8rulerstar's implementation and retaining their latest commit e13938233 with follow-ups.

Behavior and boundaries

binary_path selects a matching 64-bit PE/ELF/Mach-O binary. CLI/MCP share the same application/producer implementation. Retained views select batches of indices, addresses or states, fields and pages; every folded-body alternative and both artifact identities remain accessible. Module tables have their own page instead of inflating the default summary; full Evidence remains exportable.

Native readers reuse the container owner's mappings/executable spans and populate facts once. Cooperative scans, lazy generic names, no pointer-table copy, early row/text admission and bounded expansion control work and allocation. The shared application seed workflow reuses the completed report, enforces existing consumer limits, publishes atomically to a new file and retains completed Evidence/output receipts on failure; failed cleanup stays owned. Superseded formatter/writer paths and redundant tests were removed.

Observed metadata, derived static associations, and inferred executable-byte fallback remain distinct. Unknown relocations never become observed-zero/no-body. Unrequested native counts are null; unsupported/partial results retain source identity and reasons. Matching is structural, not cryptographic same-build proof.

Scope: existing local static file reading, caller-requested output and existing Ghidra import; no new execution/injection/provider authority, dependency or installer changes. Domain facts, native adapters, application workflows and CLI/MCP keep inward dependencies. Authored contracts/docs/skill updated; generated outputs stay ignored.

Executed validation

Production verification at 9b6045b26; final head 66bcbfdb0c5d78111364bb2861622039915eed09 only removes one stale acceptance assertion:

  • 381 tests across 17 files passed: IL2CPP producer/service/views/public CLI/actual SDK, shared Go mappings/containers, atomic filesystem, and affected JavaScript/CLI regression suites. SDK schemas validated under JSON Schema2020-12; retained queries work after both source files are removed.
  • npm run check:ci: all 8 tasks and architecture guards passed; docs:generate /docs:check passed, including 5 README tests. No threshold/exclusion changes.
  • Both real Windows x64 and Android arm64 artifacts from MIT-licensed Between Two Worlds 1.0.5, Unity 2023.2.13f1 /metadata 29: 43,198 and 42,098 code rows, zero differences against official Il2CppDumper6.7.46 qualified names and RVA+base. All8 metadata collections match the independent reference API row-for-row; all 4 input hashes unchanged.
  • Final seeds cover 34,485 Windows and 42,098 Android unique addresses and are byte-identical to the earlier qualified files. Real Ghidra 12.1.4 accepted all Windows seeds (5,110 new /29,375 existing /0 failed /0 unmapped), found IngameDebugConsole.DebugLogManager$$ShouldRemoveLogEntryToShow at 0x180c5ca60, and returned its 26-byte body,8 instructions and pseudocode after default auto-analysis. JVM/project removed on close; input unchanged. Ghidra runtime was pinned to 95a20b11a; later changes leave Ghidra and seed rows unchanged.
  • The documented metadata verifier passed actual Windows CLI/MCP complete/summary/page checks. Regression counterexamples proved PE split-array rejection (red→green), overlapping fallback-array loss (red→green), unknown/zero distinctions, bounded generic expansion, cancellation/cleanup, seed consumer limits and historical omission.

An additional acceptance consumer of the same stale URL wording was independently reproduced on built main and corrected. All17 related CLI/path tests and the duplication gate pass; this follow-up changes no production code.

Two reproduced main CI blockers are repaired in a separate follow-up commit: URL rejection tests depended on obsolete wording (3 failures), and exported declarations lost their semantic statement position (1 actual built-worker failure). Statement ownership now normalizes the export wrapper and removes the class-only workaround; URL tests preserve rejection/path assertions. A separately built main with only those repairs passes 74 relevant tests, including branch/activation uncertainty cases. Redundant fixture/setup/response parsing was consolidated with original oracles retained; the global duplication gate passes unchanged.

Artifact identities and complete-task cost evidence
Artifact Metadata SHA-256 Binary SHA-256
Windows 5691bdd9161d054db69cc842397c7a42c6bf4a544060f05d741e99a6a3a07d29 a3ae7f6ecd1f671297b7d98d12713ef024062d93cd7a4205298102656c93b67a
Android 3814d666a9f83388a71d7ad7ffb67b2cdaf7f2fef5c48c021ddf43373cdad64e 9fa9170439ccdca152bfb86ea5d1ee2fae62c476b226c4251fec9fc935c88d9e

Task: find DebugLogManager.get_IsLogWindowVisible, address 0x180c56ed0, both shared-body definitions (indices 39403/45972), their names/owners/return types, and export full Evidence. Legacy scans 512-row address pages; selected uses the exact address filter. Both final variants retain identical requested facts/Evidence access. Revision 95a alternated both orders; small fixtures also ran both orders (representative rows below). Revision 9b repeats the equivalent complete real task after review fixes.

Tokenx estimates include used/all input declarations, arguments and full replies, with all extra calls. Final whole-wire estimates, including catalog output schemas: legacy 4,063,553, selected 1,057,887. Actual model usage/rendering/tool selection are unmeasured.

Revision /task RPCs Used-declaration estimated tokens All-input-declaration estimated tokens Wall s Family CPU s Sampled family RSS GiB
fc14dbaab /baseline-real-legacy 103 2,998,739 3,223,018 21.997 26.543 2.391
95a20b11a /final-real-legacy-1 103 3,039,891 3,264,969 17.616 22.373 2.291
95a20b11a /final-real-selected-1 8 38,969 264,047 12.077 14.730 1.080
95a20b11a /final-real-selected-2 8 38,969 264,047 12.022 14.652 1.113
95a20b11a /final-real-legacy-2 103 3,039,891 3,264,969 17.287 22.224 2.326
95a20b11a /final-small-legacy-1 8 38,411 263,489 7.161 8.878 0.901
95a20b11a /final-small-selected-1 8 38,239 263,317 6.988 8.743 0.926
9b6045b26 /integration-real-legacy 103 3,039,888 3,264,966 22.707 24.743 2.424
9b6045b26 /integration-real-selected-only 8 38,972 264,050 12.143 14.895 1.158

Separate 24 warm retained-address probes: median 17.51ms, range 15.80–26.35ms, excluded from the primary rows above. Whole-task profiles include SDK/client/server, catalog/AJV, decoding, queries, export and teardown. Engine-only costs unmeasured; fresh runtime caches, OS caches/host load uncontrolled. Baseline fc14 includes an older main, so its timing change is not attributed solely to this feature. Small input is authored, not real-engine coverage.

Linux/Node 24.18.0; two-CPU affinity,3 GiB V8 heaps,8 GiB sampled process-family guard; no OOM/guard stops/retries. RSS sampled20 ms may double-count shared pages or miss brief peaks. Report budgets are not total-RSS bounds. Reference tools lived privately, outside REA dependencies.

Ghidra default cold analysis is expensive: 1395.88s wall /1705.24s familyCPU /7.218 GiB sampled familyRSS,2 CPUs/4 GiB JVM heap/8 GiB guard; no guard stop. No Ghidra speedup is claimed.

Coverage limits: generic instances/thunks/invokers,32-bit images, packed ELF relocations and Mach-O chained fixups remain unsupported. Original contributor-reported non-v29/platform runs remain credited in the discussion/docs and were not rerun here. Reference omission does not prove no-body/unknown semantics. Fallback bytes do not prove instruction boundaries/runtime use. No game executed or executable modified.

Author readiness

Human owner: @gensokyo-dev

Original contribution: @8rulerstar. The human owner authorized the work, delegated technical review/validation to Codex and directed these declarations and ready submission. This records that instruction; it does not assert an independent human review of follow-ups or review of later commits by the original contributor.

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/5327 **Author:** [@8rulerstar](https://github.com/8rulerstar) **Created:** 10/20/2026 **Status:** ✅ Merged **Merged:** 10/20/2026 **Merged by:** [@gensokyo-dev](https://github.com/gensokyo-dev) **Base:** `main` ← **Head:** `feat/il2cpp-native-seeds` --- ### 📝 Commits (6) - [`e139382`](https://github.com/morluto/rea/commit/e1393823351c0621ef744721ee87c8c76741b268) feat(il2cpp): link methods to native addresses and write Ghidra seeds - [`f1ffa1f`](https://github.com/morluto/rea/commit/f1ffa1f80506bbd7732cfa4c0e12273e36e3ce87) fix(il2cpp): bound native linking and retain queryable evidence - [`95a20b1`](https://github.com/morluto/rea/commit/95a20b11a90d4215a70ac7b6b28cf259fb1e7ef8) fix(il2cpp): accept pointer tables spanning loader ranges - [`f009591`](https://github.com/morluto/rea/commit/f009591467c4215de268d7f02a69d9635f19178e) fix(il2cpp): retain overlapping candidates and consolidate boundary facts - [`9b6045b`](https://github.com/morluto/rea/commit/9b6045b268144f993441c14227ac0763057a6af8) fix(javascript): retain exported initializer order in semantic calls - [`66bcbfd`](https://github.com/morluto/rea/commit/66bcbfdb0c5d78111364bb2861622039915eed09) test(cli): assert file URL rejection independently of schema wording ### 📊 Changes **43 files changed** (+5059 additions, -367 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/reverse-engineer-anything/SKILL.md` (+1 -2) 📝 `docs/cli.md` (+4 -2) 📝 `docs/il2cpp-metadata.md` (+175 -4) 📝 `scripts/verify-il2cpp-metadata.mjs` (+7 -0) 📝 `src/application/JsonFiles.ts` (+5 -2) 📝 `src/application/il2cpp/Il2cppMetadataService.test.ts` (+157 -38) 📝 `src/application/il2cpp/Il2cppMetadataService.ts` (+177 -24) ➕ `src/application/il2cpp/Il2cppSeedWriterPort.ts` (+32 -0) 📝 `src/cli/il2cppCommands.ts` (+69 -7) 📝 `src/cliJsonPaths.test.ts` (+0 -3) 📝 `src/composition/il2cpp.ts` (+5 -1) 📝 `src/contracts/analysisViewToolContracts.ts` (+1 -1) 📝 `src/contracts/il2cpp/il2cppToolContracts.ts` (+10 -1) 📝 `src/domain/analysisView/il2cppMetadataView.test.ts` (+288 -24) 📝 `src/domain/analysisView/il2cppMetadataView.ts` (+232 -19) ➕ `src/domain/il2cpp/il2cppMetadata.fixture.ts` (+42 -0) 📝 `src/domain/il2cpp/il2cppMetadata.ts` (+161 -1) 📝 `src/domain/javascript/javascriptSemanticMutationInitializers.ts` (+11 -12) 📝 `src/ghidra/GhidraAnalysisSeeds.ts` (+3 -3) 📝 `src/go/GoBinaryContainer.ts` (+6 -0) _...and 23 more files_ </details> ### 📄 Description ## Summary Fixes #5112. Link IL2CPP metadata names to native method addresses/type facts and export Ghidra seeds, extending @8rulerstar's implementation and retaining their latest commit `e13938233` with follow-ups. ## Behavior and boundaries `binary_path` selects a matching 64-bit PE/ELF/Mach-O binary. CLI/MCP share the same application/producer implementation. Retained views select batches of indices, addresses or states, fields and pages; every folded-body alternative and both artifact identities remain accessible. Module tables have their own page instead of inflating the default summary; full Evidence remains exportable. Native readers reuse the container owner's mappings/executable spans and populate facts once. Cooperative scans, lazy generic names, no pointer-table copy, early row/text admission and bounded expansion control work and allocation. The shared application seed workflow reuses the completed report, enforces existing consumer limits, publishes atomically to a new file and retains completed Evidence/output receipts on failure; failed cleanup stays owned. Superseded formatter/writer paths and redundant tests were removed. Observed metadata, derived static associations, and inferred executable-byte fallback remain distinct. Unknown relocations never become observed-zero/no-body. Unrequested native counts are null; unsupported/partial results retain source identity and reasons. Matching is structural, not cryptographic same-build proof. Scope: existing local static file reading, caller-requested output and existing Ghidra import; no new execution/injection/provider authority, dependency or installer changes. Domain facts, native adapters, application workflows and CLI/MCP keep inward dependencies. Authored contracts/docs/skill updated; generated outputs stay ignored. ## Executed validation Production verification at `9b6045b26`; final head `66bcbfdb0c5d78111364bb2861622039915eed09` only removes one stale acceptance assertion: - **381 tests across 17 files passed**: IL2CPP producer/service/views/public CLI/actual SDK, shared Go mappings/containers, atomic filesystem, and affected JavaScript/CLI regression suites. SDK schemas validated under JSON Schema2020-12; retained queries work after both source files are removed. - `npm run check:ci`: all 8 tasks and architecture guards passed; `docs:generate` /`docs:check` passed, including 5 README tests. No threshold/exclusion changes. - Both real Windows x64 and Android arm64 artifacts from MIT-licensed [Between Two Worlds 1.0.5](https://github.com/SkS-GameDev/Between-two-worlds/releases/tag/1.0.5), Unity 2023.2.13f1 /metadata 29: **43,198 and 42,098 code rows, zero differences** against official [Il2CppDumper6.7.46](https://github.com/Perfare/Il2CppDumper/releases/tag/v6.7.46) qualified names and RVA+base. All8 metadata collections match the independent reference API row-for-row; all 4 input hashes unchanged. - Final seeds cover 34,485 Windows and 42,098 Android unique addresses and are byte-identical to the earlier qualified files. Real Ghidra 12.1.4 accepted all Windows seeds (5,110 new /29,375 existing /0 failed /0 unmapped), found `IngameDebugConsole.DebugLogManager$$ShouldRemoveLogEntryToShow` at `0x180c5ca60`, and returned its 26-byte body,8 instructions and pseudocode after default auto-analysis. JVM/project removed on close; input unchanged. Ghidra runtime was pinned to `95a20b11a`; later changes leave Ghidra and seed rows unchanged. - The documented metadata verifier passed actual Windows CLI/MCP complete/summary/page checks. Regression counterexamples proved PE split-array rejection (red→green), overlapping fallback-array loss (red→green), unknown/zero distinctions, bounded generic expansion, cancellation/cleanup, seed consumer limits and historical omission. An additional acceptance consumer of the same stale URL wording was independently reproduced on built main and corrected. All17 related CLI/path tests and the duplication gate pass; this follow-up changes no production code. Two reproduced main CI blockers are repaired in a separate follow-up commit: URL rejection tests depended on obsolete wording (3 failures), and exported declarations lost their semantic statement position (1 actual built-worker failure). Statement ownership now normalizes the export wrapper and removes the class-only workaround; URL tests preserve rejection/path assertions. A separately built main with only those repairs passes 74 relevant tests, including branch/activation uncertainty cases. Redundant fixture/setup/response parsing was consolidated with original oracles retained; the global duplication gate passes unchanged. <details> <summary>Artifact identities and complete-task cost evidence</summary> |Artifact|Metadata SHA-256|Binary SHA-256| |---|---|---| |Windows|`5691bdd9161d054db69cc842397c7a42c6bf4a544060f05d741e99a6a3a07d29`|`a3ae7f6ecd1f671297b7d98d12713ef024062d93cd7a4205298102656c93b67a`| |Android|`3814d666a9f83388a71d7ad7ffb67b2cdaf7f2fef5c48c021ddf43373cdad64e`|`9fa9170439ccdca152bfb86ea5d1ee2fae62c476b226c4251fec9fc935c88d9e`| Task: find `DebugLogManager.get_IsLogWindowVisible`, address `0x180c56ed0`, both shared-body definitions (indices 39403/45972), their names/owners/return types, and export full Evidence. Legacy scans 512-row address pages; selected uses the exact address filter. Both final variants retain identical requested facts/Evidence access. Revision 95a alternated both orders; small fixtures also ran both orders (representative rows below). Revision 9b repeats the equivalent complete real task after review fixes. Tokenx estimates include used/all input declarations, arguments and full replies, with all extra calls. Final whole-wire estimates, including catalog output schemas: legacy 4,063,553, selected 1,057,887. Actual model usage/rendering/tool selection are unmeasured. |Revision /task|RPCs|Used-declaration estimated tokens|All-input-declaration estimated tokens|Wall s|Family CPU s|Sampled family RSS GiB| |---|---:|---:|---:|---:|---:|---:| |fc14dbaab /baseline-real-legacy|103|2,998,739|3,223,018|21.997|26.543|2.391| |95a20b11a /final-real-legacy-1|103|3,039,891|3,264,969|17.616|22.373|2.291| |95a20b11a /final-real-selected-1|8|38,969|264,047|12.077|14.730|1.080| |95a20b11a /final-real-selected-2|8|38,969|264,047|12.022|14.652|1.113| |95a20b11a /final-real-legacy-2|103|3,039,891|3,264,969|17.287|22.224|2.326| |95a20b11a /final-small-legacy-1|8|38,411|263,489|7.161|8.878|0.901| |95a20b11a /final-small-selected-1|8|38,239|263,317|6.988|8.743|0.926| |9b6045b26 /integration-real-legacy|103|3,039,888|3,264,966|22.707|24.743|2.424| |9b6045b26 /integration-real-selected-only|8|38,972|264,050|12.143|14.895|1.158| Separate 24 warm retained-address probes: median 17.51ms, range 15.80–26.35ms, excluded from the primary rows above. Whole-task profiles include SDK/client/server, catalog/AJV, decoding, queries, export and teardown. Engine-only costs unmeasured; fresh runtime caches, OS caches/host load uncontrolled. Baseline fc14 includes an older main, so its timing change is not attributed solely to this feature. Small input is authored, not real-engine coverage. Linux/Node 24.18.0; two-CPU affinity,3 GiB V8 heaps,8 GiB sampled process-family guard; no OOM/guard stops/retries. RSS sampled20 ms may double-count shared pages or miss brief peaks. Report budgets are not total-RSS bounds. Reference tools lived privately, outside REA dependencies. Ghidra default cold analysis is expensive: **1395.88s wall /1705.24s familyCPU /7.218 GiB sampled familyRSS**,2 CPUs/4 GiB JVM heap/8 GiB guard; no guard stop. No Ghidra speedup is claimed. </details> Coverage limits: generic instances/thunks/invokers,32-bit images, packed ELF relocations and Mach-O chained fixups remain unsupported. Original contributor-reported non-v29/platform runs remain credited in the discussion/docs and were not rerun here. Reference omission does not prove no-body/unknown semantics. Fallback bytes do not prove instruction boundaries/runtime use. No game executed or executable modified. ## Author readiness Human owner: @gensokyo-dev Original contribution: @8rulerstar. The human owner authorized the work, delegated technical review/validation to Codex and directed these declarations and ready submission. This records that instruction; it does not assert an independent human review of follow-ups or review of later commits by the original contributor. - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:43:48 +00:00
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#6883
No description provided.