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.
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.
Implemented:
- PE32+ AMD64 parsing, exports, sections, relocations, and
.pdataruntime-function bounds; - Unity 6000.3 compact metadata v39 profile;
- immutable images, types, fields, methods, parameters, properties, events, and generic metadata;
- discovery of
Il2CppCodeGenModuleandIl2CppMetadataRegistration; - exact
image + MethodDef RID -> native method pointermapping; - 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
phinodes; - conditional-edge splitting for
phiassignments during CIL stackification; - Windows x64 ABI modeling for
this, normal arguments, hiddenRuntimeMethod*, 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
MethodDefdispatch resolution; - parsing of
Il2CppMethodSpec,Il2CppGenericInst, and generic method function definitions; - RGCTX owner-range indexing with runtime type,
MethodSpec, and constrained-method payload resolution; - exact
MethodReflinking to definitions and generic arguments; - ABI specialization when
VARorMVARresolves to a concrete type; - metadata interface dispatch through
interface_offsets, includingMethodSpecslots; - global metadata usage decoding for
TypeInfo,Il2CppType,MethodDef,FieldInfo,StringLiteral, andMethodRef; - 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
callvirtoperation 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
MethodSpecCIL relocations, separate fromMethodDefandMemberRef; - 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
cmovcclowering through CFG diamonds and SSAphinodes; - general
testmasks, registerbt, exact registerbts/btr, logicalshr.un, and exact unary integer operations; - scalar SSE arithmetic, ordered floating comparisons, and exact
float32tofloat64widening; - explicit SSA loads for integer memory arithmetic and exact three-operand
imul; - 32-bit affine
lealowering 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
StandAloneSiglocal-variable signatures and fat bodies withinitlocals; - symbolic cross-assembly field and method
MemberRefrelocations 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
MemberReflinking; - 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 undersrc/<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;
MethodSpecandTypeSpectable emission for generic general calls;- broader multiblock memory semantics, exception regions, and elimination of every unresolved concrete body before 1.0.
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 releaseThe stricter CI-equivalent preset disables compiler caches and enables warnings as errors for production modules:
cmake --preset ci
cmake --build --preset ci
ctest --preset ciDebug iteration with tests:
cmake --preset debug
cmake --build --preset debug
ctest --preset debugFast executable-only iteration keeps tests disabled and auto-detects a compiler cache:
cmake --preset dev
cmake --build --preset dev --target reforgeWindows x86_64 with the standalone LLVM-MinGW and Ninja toolchain:
pwsh -File scripts/build_windows.ps1 -Configuration Release -TestThe 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-releasewindows-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.
Inspect the complete input pair:
reforge inspect --assembly GameAssembly.dll --metadata global-metadata.dat --out report.jsonFull 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.jsonRecovery-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.jsonList metadata images:
reforge images --metadata global-metadata.dat --out images.jsonSearch methods without disassembling them:
reforge methods --metadata global-metadata.dat --image Core.dll --name MoveNext --limit 50 --out methods.jsonCalibrate native dispatch layout:
reforge layout --assembly GameAssembly.dll --metadata global-metadata.dat --out layout.jsonInspect a generic instantiation and specialized ABI:
reforge method-spec --assembly GameAssembly.dll --metadata global-metadata.dat --index 8395 --out method-spec.jsonInspect 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.jsonSelect one exact TypeDef or MethodDef owner token:
reforge rgctx --assembly GameAssembly.dll --metadata global-metadata.dat --image Core.dll --token 0x06000001 --out method-rgctx.jsonInspect 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.jsonCollect generic-linking statistics:
reforge generic-stats --assembly GameAssembly.dll --metadata global-metadata.dat --out generic-stats.jsonResolve 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.jsonAudit all interface offset tables:
reforge interface-stats --assembly GameAssembly.dll --metadata global-metadata.dat --out interface-stats.jsonDecode a global metadata slot referenced by native code:
reforge metadata-usage \
--assembly GameAssembly.dll \
--metadata global-metadata.dat \
--slot-va 0x18626c750 \
--out metadata-usage.jsonEnable 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.jsonGenerate 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.jsonVerify its PE/CLI and ECMA-335 structure:
reforge verify-assembly --assembly Core.dll --out Core.verification.jsonBody modes are explicit:
completeis the default and refuses to leave an output DLL while any concrete method remains unresolved;referenceinjects proven bodies and uses explicitldnull; throwstubs for unresolved concrete methods;stubsemits 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.jsonUse --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.jsonUse --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.
For managed_cil reports:
status=recoveredmeans every traversed Managed IR operation has CIL emission;requires_linking=truemeans one or more symbolic tokens must be materialized in output metadata;verifiable=truerequires complete linking, known local types, and convergent stack analysis;partialorunsupportedis preferred over a plausible but fabricated body.
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.