Skip to content

Sato-Isolated/ReforgeIL

Repository files navigation

ReforgeIL

ReforgeIL is a IL2CPP analysis and managed-code recovery pipeline. It does not translate x64 instructions mechanically into CIL. It first reconstructs stable managed identities and signatures from IL2CPP metadata and runtime registrations, then treats native code as evidence of the effects that were executed.

0.23 unbounded best-effort recovery

ReforgeIL 0.23 starts from the validated 0.22 corpus baseline of 73,809 recovered bodies, 33,222 stubs, and 3,684 abstract methods. The remaining stub count is the progress metric. Recovery explores reachable native functions, fragments, CFG facts, SSA facts, and semantic evidence to a fixed point. Stable visited identities prevent cycles; numeric byte, depth, work, sample, and tier ceilings are not part of the recovery contract.

Complete encodable bodies are emitted directly into the main reference assembly. Each recovered manifest row records exact, strong_heuristic, or heuristic confidence, all examined managed candidates, the retained candidate, and ranking reasons. The first 0.23 campaign recognizes the exact il2cpp_value_box export/thunk ABI and emits ECMA-335 box with a type relocation.

GameAssembly.dll                 global-metadata.dat
        |                                  |
        v                                  v
   PE32+ AMD64                  versioned metadata frontend
        |                                  |
        +---------------+------------------+
                        v
              immutable IL2CPP model
                        v
        CodeGenModules + MetadataRegistration
                        v
 image + RID + token + metadata index -> native address
                        v
 complete reachable native body + structured x64 disassembly
                        v
        CFG -> micro-IR -> ABI-typed SSA
                        v
 calls / fields / locals / conditions -> Managed IR
                        v
       best-effort CIL + symbolic token relocations

Names are labels, never identities. Unsupported instructions remain NativeOpaque. Shared native candidates are ranked deterministically from TypeInfo, ABI, receiver, arguments, MethodInfo, thunk target, and effects; a final metadata-identity tie is visible as heuristic evidence.

Current implementation status

Implemented:

  • PE32+ AMD64 parsing, exports, sections, relocations, and .pdata runtime-function bounds;
  • Unity 6000.3 compact metadata v39 profile;
  • immutable images, types, fields, methods, parameters, properties, events, and generic metadata;
  • discovery of Il2CppCodeGenModule and Il2CppMetadataRegistration;
  • exact image + MethodDef RID -> native method pointer mapping;
  • reverse native-address index that preserves all shared-body aliases;
  • complete native reading to runtime-function, next-entry, or executable-section boundaries;
  • structured x64 operands and conservative micro-IR;
  • SSA for registers, flags, stack slots, constants, copies, and phi nodes;
  • conditional-edge splitting for phi assignments during CIL stackification;
  • Windows x64 ABI modeling for this, normal arguments, hidden RuntimeMethod*, hidden value-type return buffers, integer returns, floating-point returns, and generic-instance value/reference classification;
  • direct managed-call resolution, runtime export classification, and cycle-safe thunk following;
  • register-indirect and static-slot-indirect call preservation;
  • automatic virtual-dispatch layout calibration without hard-coded Unity offsets;
  • compact-v39 vtable decoding and exact MethodDef dispatch resolution;
  • parsing of Il2CppMethodSpec, Il2CppGenericInst, and generic method function definitions;
  • RGCTX owner-range indexing with runtime type, MethodSpec, and constrained-method payload resolution;
  • exact MethodRef linking to definitions and generic arguments;
  • ABI specialization when VAR or MVAR resolves to a concrete type;
  • metadata interface dispatch through interface_offsets, including MethodSpec slots;
  • global metadata usage decoding for TypeInfo, Il2CppType, MethodDef, FieldInfo, StringLiteral, and MethodRef;
  • native recognition of merged interface fast paths and slow paths;
  • exact recovery of receiver, declaring interface, declared slot, and VirtualInvokeData { methodPtr, method };
  • dispatch-helper absorption and one managed callvirt operation per proven interface call;
  • complete-signature recognition of five high-volume interface-dispatch helper ABIs, including slot-plus-TypeInfo, MethodInfo-derived identity, no-argument, and one-GPR variants;
  • explicit MethodSpec CIL relocations, separate from MethodDef and MemberRef;
  • terminal external jumps represented as native tail calls;
  • inherited field-offset recovery from runtime registrations, including unboxed value-type offset normalization;
  • Managed IR for arguments, locals, fields, arithmetic, comparisons, calls, branches, and returns;
  • strict CIL emission for proven operations;
  • batch Managed IR recovery with one shared workspace and an optional in-process LLVM C disassembler;
  • fixed-point exploration of reachable native chunks, cold fragments, CFG, SSA, and evidence;
  • explicit opaque-native rejection with complete diagnostic evidence;
  • exact pruning of unreachable trap/padding blocks that have no predecessors;
  • explicit memory loads for comparisons and narrow integer conversions, preserving value-versus-address semantics;
  • evidence-gated setcc, integer comparisons, signed 32-to-64 conversion, and typed 8/16-to-32 extension;
  • exact register cmovcc lowering through CFG diamonds and SSA phi nodes;
  • general test masks, register bt, exact register bts/btr, logical shr.un, and exact unary integer operations;
  • scalar SSE arithmetic, ordered floating comparisons, and exact float32 to float64 widening;
  • explicit SSA loads for integer memory arithmetic and exact three-operand imul;
  • 32-bit affine lea lowering and exact instance-field recovery through typed managed arguments;
  • opaque-native mnemonic telemetry with method counts and representative evidence;
  • compiler-frame spill/restore filtering for proven Windows x64 nonvolatile-register artifacts;
  • structural narrowing of identical-code-folded base-constructor calls through exact metadata inheritance;
  • exact subsumption of ret-only native helpers, including preservation of live volatile-register values and terminal void tail-return lowering;
  • linked ECMA-335 StandAloneSig local-variable signatures and fat bodies with initlocals;
  • symbolic cross-assembly field and method MemberRef relocations plus local/external type-token relocation;
  • versioned JSON method reports;
  • an IL-only PE/CLI reference-assembly writer;
  • fast native pattern recovery for empty methods, wrapper chains, direct field accessors, metadata-initialized field chains, null-guarded field comparisons, compound field initializers, blittable value-type block copies, type-token returns, constant returns, and direct argument returns;
  • direct injection of proven tiny and fat CIL bodies into generated assemblies;
  • conservative generic-instance field-layout synthesis and exact external-field MemberRef linking;
  • strict complete-export refusal when any concrete body remains unresolved;
  • per-method JSONL body manifests with separate source and output identities;
  • independent manifest-to-assembly verification for every emitted MethodDef;
  • reconstruction of types, inheritance, interfaces, fields, methods, parameters, properties, events, generic parameters, and generic constraints;
  • structural verification of PE/CLI headers, streams, tables, heaps, row lists, signatures, and method bodies;
  • fast method search by image and name.

Validated 0.22 Core.dll baseline:

  • 73,809 recovered methods;
  • 33,222 visible throwing stubs;
  • 3,684 abstract methods;
  • zero recovered-to-stub RID regression in the 0.22 campaign.

The 0.23 corpus replay will replace these figures only after a complete manifest comparison and all structural verifiers pass.

Project organization in this release:

  • domain-owned public APIs under include/reforge/<module> and matching implementations under src/<module>;
  • a five-line executable entry point backed by Application, CommandArguments, command groups, formatters, and output services;
  • recovery implementation grouped by domain under src/recovery, with private headers co-located in each domain;
  • test suites split by metadata, native, managed, recovery, and assembly domains;
  • documented dependency boundaries in docs/PROJECT_STRUCTURE.md.

Still in progress:

  • assigning shared native bodies to exact generic instantiations;
  • generic virtual and generic interface invokers;
  • native RGCTX helper recognition and lowering;
  • general memory SSA and alias analysis;
  • structured exception regions and exception-object recovery;
  • MethodSpec and TypeSpec table emission for generic general calls;
  • broader multiblock memory semantics, exception regions, and elimination of every unresolved concrete body before 1.0.

Build

Requirements: CMake 3.20+, a C++20 compiler, and either a loadable LLVM shared library or llvm-objdump for native method analysis. On Windows x86_64, CMake and PowerShell are the only system build prerequisites: the pinned UCRT LLVM-MinGW and Ninja toolchain is bootstrapped locally under out/tools/windows. The in-process decoder still searches the explicit --llvm-library path first, then C:\Program Files\LLVM\bin\LLVM-C.dll, and finally LLVM-C.dll through the system loader.

Recommended Release validation:

cmake --preset release
cmake --build --preset release
ctest --preset release

The stricter CI-equivalent preset disables compiler caches and enables warnings as errors for production modules:

cmake --preset ci
cmake --build --preset ci
ctest --preset ci

Debug iteration with tests:

cmake --preset debug
cmake --build --preset debug
ctest --preset debug

Fast executable-only iteration keeps tests disabled and auto-detects a compiler cache:

cmake --preset dev
cmake --build --preset dev --target reforge

Windows x86_64 with the standalone LLVM-MinGW and Ninja toolchain:

pwsh -File scripts/build_windows.ps1 -Configuration Release -Test

The first invocation downloads LLVM-MinGW 20260616 UCRT and Ninja 1.13.2, verifies both SHA-256 hashes, and caches them under ignored out/ paths. Later invocations reuse that cache. After the bootstrap, direct CMake commands are also available:

cmake --preset windows-release
cmake --build --preset windows-release
ctest --preset windows-release

windows-debug builds all tests with non-fatal warnings. windows-dev builds only reforge. Ninja uses its automatic parallelism; set CMAKE_BUILD_PARALLEL_LEVEL to override it. The Windows baseline is UCRT on Windows 10 or newer, and the executables statically link the LLVM-MinGW C++ runtimes. On the reference 16-thread machine, a clean Release build decreased from 8.58 minutes to 84 seconds, followed by 7/7 tests in about two seconds.

The build creates separate ReforgeIL::<module> libraries for common, pe, il2cpp, native, managed, recovery, reporting, assembly, and app. Module ownership and dependency rules are documented in docs/PROJECT_STRUCTURE.md and checked automatically by CTest.

Downstream include and target changes are summarized in docs/MODULARIZATION.md.

Commands

Inspect the complete input pair:

reforge inspect --assembly GameAssembly.dll --metadata global-metadata.dat --out report.json

Full recovery pass:

reforge emit-assembly --assembly C:\Users\...\GameAssembly.dll --metadata C:\Users\...\global-metadata.dat --image Core.dll --output Core.dll --body-mode reference --general-recovery-limit all --general-parallel-methods 8 --timing-summary true --report recovery.json

Recovery-only (no rebuilt PE output, same binary), useful for iterative timing runs:

reforge emit-assembly --assembly C:\Users\...\GameAssembly.dll --metadata C:\Users\...\global-metadata.dat --image Core.dll --output Core.dll --body-mode reference --general-recovery-limit all --general-parallel-methods 8 --timing-summary true --general-recovery-only true --report recovery-only.json

List metadata images:

reforge images --metadata global-metadata.dat --out images.json

Search methods without disassembling them:

reforge methods --metadata global-metadata.dat --image Core.dll --name MoveNext --limit 50 --out methods.json

Calibrate native dispatch layout:

reforge layout --assembly GameAssembly.dll --metadata global-metadata.dat --out layout.json

Inspect a generic instantiation and specialized ABI:

reforge method-spec --assembly GameAssembly.dll --metadata global-metadata.dat --index 8395 --out method-spec.json

Inspect one image's RGCTX owners and typed payloads:

reforge rgctx --assembly GameAssembly.dll --metadata global-metadata.dat --image Core.dll --limit 100 --entry-limit 256 --out rgctx.json

Select one exact TypeDef or MethodDef owner token:

reforge rgctx --assembly GameAssembly.dll --metadata global-metadata.dat --image Core.dll --token 0x06000001 --out method-rgctx.json

Inspect the native RGCTX accesses of one generic body. Runtime offsets are accepted only after independent native calibration:

reforge rgctx \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --image Core.dll \
  --method-spec 58248 \
  --out native-rgctx.json

Collect generic-linking statistics:

reforge generic-stats --assembly GameAssembly.dll --metadata global-metadata.dat --out generic-stats.json

Resolve a virtual or interface metadata slot:

reforge dispatch-slot \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --type-index 10 \
  --interface-type-index 90177 \
  --slot 0 \
  --out dispatch.json

Audit all interface offset tables:

reforge interface-stats --assembly GameAssembly.dll --metadata global-metadata.dat --out interface-stats.json

Decode a global metadata slot referenced by native code:

reforge metadata-usage \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --slot-va 0x18626c750 \
  --out metadata-usage.json

Enable interface-pattern trace output without changing JSON reports:

REFORGE_TRACE_INTERFACE=1 reforge method ...

Analyze one method:

reforge method \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --image mscorlib.dll \
  --rid 2199 \
  --out method.json

Generate a managed reference assembly:

reforge emit-assembly \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --image Core.dll \
  --output Core.dll \
  --body-mode reference \
  --general-recovery-limit all \
  --body-manifest Core.bodies.jsonl \
  --report Core.report.json

Verify its PE/CLI and ECMA-335 structure:

reforge verify-assembly --assembly Core.dll --out Core.verification.json

Body modes are explicit:

  • complete is the default and refuses to leave an output DLL while any concrete method remains unresolved;
  • reference injects proven bodies and uses explicit ldnull; throw stubs for unresolved concrete methods;
  • stubs emits a metadata-focused reference assembly without attempting body recovery.

General recovery is opt-in with --general-recovery-limit N|all. The limit selects methods for a diagnostic campaign; it does not limit analysis depth. Parallel worker selection remains available and does not alter semantics.

Use --general-recovery-scope public-structs to select concrete methods declared by public, non-enum value types. An explicit RID selector is additive, so a stable canary range can be scanned together with the public structs:

reforge emit-assembly \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --image Core.dll \
  --output Core.public-structs.dll \
  --body-mode reference \
  --general-recovery-limit all \
  --general-recovery-scope public-structs \
  --general-source-rid-start 1 \
  --general-source-rid-end 5000 \
  --body-manifest Core.public-structs.bodies.jsonl \
  --report Core.public-structs.report.json

Use --general-recovery-scope public-types to extend the focused pass to every method declared by a Public or NestedPublic type. The selector intentionally does not filter method visibility or return type: public, private, instance, static, void, scalar, reference, struct, enum, and interface members all remain eligible when they have a concrete native body. Explicit RID selectors are additive, as with public-structs.

The default scope is all, which preserves the existing RID-limit and RID-range behavior.

A DLL produced in reference mode is a partial reference assembly, not a complete behavioral reconstruction. The body manifest identifies every output MethodDef as recovered, stub, or abstract, records the native evidence for recovered methods, and attaches a stable terminal failure category and diagnostic to every attempted method that remains unresolved.

Analyze the remaining reconstruction barriers with stable blocker families and exact replay samples:

reforge failures \
  --manifest Core.bodies.jsonl \
  --assembly GameAssembly.dll \
  --metadata global-metadata.dat \
  --out Core.blockers.json

Use --family native_instruction, --family value_provenance, --family call_resolution, or another reported family to isolate one proof obligation. Reports retain complete candidate and failure evidence without partition or sample quotas.

Recovery status rules

For managed_cil reports:

  • status=recovered means every traversed Managed IR operation has CIL emission;
  • requires_linking=true means one or more symbolic tokens must be materialized in output metadata;
  • verifiable=true requires complete linking, known local types, and convergent stack analysis;
  • partial or unsupported is preferred over a plausible but fabricated body.

Safety and scope

The frontend rejects unknown metadata profiles. GameAssembly.dll is never loaded or executed; it is treated only as bytes. Native disassembly remains inside proven executable/runtime-function boundaries.

Use ReforgeIL only on binaries you are authorized to analyze.

Documentation

About

ReforgeIL is a IL2CPP analysis and managed-code recovery pipeline

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages