# Honkai: Star Rail In-Process Dumper (`honkai-dumper`) Architectural Specification & Deep Dive This technical specification provides an exhaustive, code-level analysis of **`honkai-dumper`** (by AzenKain / lanylow), an in-process, native Rust dynamic link library (`cdylib`) engineered to extract metadata, field layouts, and compiled method Relative Virtual Addresses (RVAs) from the live process memory of *Honkai: Star Rail* (`StarRail.exe`). --- ## 1. Executive Summary & Design Rationale ### 1.1 The Fundamental Problem of Offline IL2CPP Dumpers In conventional Unity IL2CPP games, compiled assemblies exist as two distinct artifacts: 1. `GameAssembly.dll`: Native machine code (PE x86_64) resulting from C# $\rightarrow$ C++ $\rightarrow$ Clang compilation. 2. `global-metadata.dat`: Binary metadata file containing string literals, type definitions, method signatures, and field offset tables. In standard environments, tools like **Il2CppDumper** or **Il2CppInspector** parse `global-metadata.dat` statically on disk. However, modern live-service games protected by commercial packers/DRM implement active anti-reversing countermeasures: - **Encrypted Metadata**: `global-metadata.dat` is heavily obfuscated and encrypted on disk; static extractors fail immediately without proprietary decryption keys. - **Stripped API Exports**: `GameAssembly.dll` strips all standard IL2CPP C-API exports (e.g., `il2cpp_domain_get`, `il2cpp_class_get_methods`), rendering standard dynamic import resolution (`GetProcAddress`) ineffective. ### 1.2 The In-Process Dumper Solution `honkai-dumper` bypasses disk-level metadata encryption entirely by operating **inside the game process space** as an injected native module (`.dll`): - It delays execution until the game engine has completed unpacking, decrypted metadata, and instantiated the internal runtime structures in memory. - Instead of relying on exported symbols in `GameAssembly.dll`, it utilizes **signature pattern scanning** inside `UnityPlayer.dll` to locate the hidden global table of IL2CPP API function pointers. - It walks the live memory graph (`Domain` $\rightarrow$ `Assembly` $\rightarrow$ `Image` $\rightarrow$ `Class` $\rightarrow$ `Method` / `Field`), emitting reconstructed C# header definitions (`dump.cs`) and method RVA address maps (`methods.json`) ready for IDA Pro, Ghidra, or Binary Ninja. ``` +----------------------------------------------------------------------------------------------------+ | Game Process Address Space | +----------------------------------------------------------------------------------------------------+ | | | [ UnityPlayer.dll ] [ GameAssembly.dll ] | | +-------------------------------------+ +-------------------------------+ | | | Pattern: 48 8B 05 .. 48 8D 0D .. | | Native Machine Code Functions | | | | | | | (Compiled from C# Methods) | | | | v | +---------------+---------------+ | | | RIP-relative calculation | ^ | | | | | | | | | v | | Method Pointers | | | Internal Function Pointer Table | | (VAs / RVAs) | | | [0] = null | | | | | [22] = il2cpp_assembly_get_image | | | | | [35] = il2cpp_class_get_methods | | | | | [63] = il2cpp_domain_get | | | | +-------------------+-----------------+ | | | | | | +----------------------|----------------------------------------------------------|------------------+ | Resolves Function Pointers | v | +---------------------------------------------------------------------------------+------------------+ | honkai-dumper.dll (Injected Module) | +----------------------------------------------------------------------------------------------------+ | 1. entry() [#[ctor]] -> Spawns worker thread | | 2. thread::sleep(10s) -> Awaits runtime initialization & metadata decryption | | 3. AllocConsole() -> Interactive logging | | 4. Traverser: Domain -> Assemblies -> Images -> Classes -> Fields & Methods | | 5. Serialization: | | * dump.cs : Reconstructed C# class headers with field offsets and method RVAs | | * methods.json : JSON dictionary mapping "Namespace.Class::Method" -> "0xRVA" | +----------------------------------------------------------------------------------------------------+ ``` --- ## 2. Codebase Architecture & File Organization The project is implemented in pure Rust (2021 Edition) configured as a Windows C dynamic library (`crate-type = ["cdylib"]`). ``` honkai-dumper/ ├── Cargo.toml # cdylib target configuration, winapi, pelite, serde_json, ctor ├── src/ │ ├── lib.rs # Module root (base, il2cpp, outputs) │ ├── base.rs # DLL entry point, thread creation, 10s synchronization delay │ ├── il2cpp/ │ │ ├── mod.rs # Re-exports submodules │ │ ├── types.rs # Raw C-ABI memory representations (MethodInfo, Il2CppClass, etc.) │ │ ├── constants.rs # ECMA-335 CLI metadata attribute masks and flags │ │ ├── module.rs # Win32 module loader & generic FunctionPtr wrapper │ │ ├── utils.rs # PE scanner & RIP-relative displacement pattern matcher │ │ ├── functions.rs # Il2CppFunctions table index mapping (array offsets) │ │ └── api.rs # High-level safe wrapper over runtime API pointers │ └── outputs/ │ ├── mod.rs # Output format definitions │ ├── methoddumper.rs # Method RVA JSON exporter (methods.json) │ └── csdumper.rs # C# AST reconstruction exporter (dump.cs) ``` ### 2.1 Dependencies & Their Technical Roles | Crate | Version | Architectural Role | | :--- | :--- | :--- | | `ctor` | `0.2.8` | Emits `#[ctor]` constructor function into `.CRT$XCU` section for execution on DLL load. | | `pelite` | `0.10.0` | High-performance, zero-allocation PE64 file format parser for section headers and RVA conversion. | | `winapi` | `0.3.9` | Windows API bindings for `libloaderapi`, `consoleapi`, `psapi`, and `processthreadsapi`. | | `serde_json` | `1.0.116` | Serialization engine for generating formatted `methods.json`. | | `thiserror` | `1.0.59` | Type-safe error enumeration derivation. | --- ## 3. Initialization Lifecycle & Process Synchronization ### 3.1 Non-Blocking DLL Attachment Pattern Standard Windows DLL injection executes the payload inside `DllMain` during `DLL_PROCESS_ATTACH`. Executing heavy workloads or blocking synchronization inside `DllMain` violates Windows loader lock contracts and causes deadlocks. `honkai-dumper` isolates injection lifecycle via `src/base.rs`: ```rust use ctor::ctor; use winapi::um::consoleapi; use std::{error::Error, thread, time::Duration}; use crate::outputs::{csdumper, methoddumper}; #[ctor] fn entry() { thread::spawn(|| { init().unwrap_or_else(|e| { panic!("honkai-dumper encountered an error: {}", e) }); }); } ``` 1. **`#[ctor]` Injection**: The `ctor` macro generates a C-runtime startup initializer function. 2. **`thread::spawn`**: Immediately offloads execution to an isolated OS thread, returning control to the Windows loader instantly and averting loader lock deadlocks. 3. **Engine Warmup Delay**: ```rust fn init() -> Result<(), Box> { thread::sleep(Duration::from_secs(10)); unsafe { consoleapi::AllocConsole() }; // ... } ``` A fixed sleep of 10 seconds is imposed. This provides the host game process sufficient wall-clock time to: - Load and decrypt secondary DLLs (`GameAssembly.dll`). - Run native unpackers and decrypt internal `global-metadata.dat` structures into RAM. - Execute IL2CPP runtime initialization routines (`il2cpp_init`) and register class domains. 4. **Console Allocation**: Calls Win32 `AllocConsole()` to attach a dedicated Win32 terminal window to the host process for operator feedback. --- ## 4. The Signature Scanning Engine (`utils.rs` & `api.rs`) ### 4.1 Locating Stripped IL2CPP Function Pointers Because `GameAssembly.dll` exports are stripped, `honkai-dumper` leverages the structural architecture of the Unity Engine: `UnityPlayer.dll` acts as the engine host and holds a dispatch table of internal IL2CPP functions passed or registered during engine startup. ### 4.2 The x86_64 Machine Code Pattern In `src/il2cpp/utils.rs`, the tool scans `UnityPlayer.dll` on disk using the following byte pattern: ``` 48 8B 05 ? ? ? ? 48 8D 0D ? ? ? ? FF D0 ``` #### Disassembly & Instruction Breakdown: ```nasm 48 8B 05 xx xx xx xx ; mov rax, [rip + disp32_1] <-- Loads target function table pointer 48 8D 0D yy yy yy yy ; lea rcx, [rip + disp32_2] <-- Loads context argument into RCX FF D0 ; call rax <-- Indirect call to IL2CPP initialization ``` ### 4.3 RIP-Relative Displacement Calculation x86_64 uses relative instruction pointer (RIP) addressing. The displacement stored in the machine instruction is relative to the address of the **subsequent** instruction. `honkai-dumper` resolves this with mathematical precision: $$\text{RVA}_{\text{target}} = \text{RVA}_{\text{instruction}} + \text{Len}_{\text{instruction}} + \text{disp32}$$ In `utils.rs`: ```rust pub fn find_qword_addr(data: &[u8], base_addr: u64, pe: &PeFile) -> Option { let pattern = pattern_to_bytes("48 8B 05 ? ? ? ? 48 8D 0D ? ? ? ? FF D0"); for i in 0..=data.len().saturating_sub(pattern.len()) { if match_pattern(data, &pattern, i) { let rva = file_offset_to_rva(i as u32, pe)?; let va = base_addr + rva as u64; let rel_offset = i + 3; // Offset to 4-byte displacement in `mov rax, [rip + disp32]` let disp = i32::from_le_bytes(data[rel_offset..rel_offset + 4].try_into().unwrap()); let rip_after_mov = va + 7; // Length of `mov rax, [rip + disp32]` is 7 bytes let qword_addr = rip_after_mov.wrapping_add(disp as u64); return Some((qword_addr - base_addr) as usize); } } None } ``` 1. **File Offset $\rightarrow$ RVA Conversion**: Iterates PE section headers via `pelite` to map raw file offset $i$ to section Virtual Address. 2. **Displacement Extraction**: Reads 32-bit signed little-endian integer (`i32`) from offset `i + 3`. 3. **Effective Address**: Adds displacement to $RIP_{\text{next}} = VA + 7$. 4. **Base Address Normalization**: Subtracts image base to return the target table RVA relative to `UnityPlayer.dll`. --- ## 5. IL2CPP Function Table Jump Indices (`functions.rs`) Once the table RVA is resolved, `honkai-dumper` treats the memory address inside the running `UnityPlayer.dll` as an array of raw function pointers: ```rust let functions = Il2CppFunctions::new(unity_player.handle as usize + qword); ``` ### 5.1 Function Pointer Dereferencing Wrapper To safely call untyped C function pointers, `src/il2cpp/module.rs` defines a zero-cost wrapper `FunctionPtr` utilizing Rust's `Deref` trait and `PhantomData`: ```rust pub struct FunctionPtr { pub ptr: *const c_void, pd: PhantomData } impl Deref for FunctionPtr { type Target = T; fn deref(&self) -> &T { unsafe { &*(&self.ptr as *const *const _ as *const T) } } } ``` ### 5.2 The Index Map `src/il2cpp/functions.rs` maps array offsets using the `index!` macro: ```rust macro_rules! index { ($start:expr, $index:expr) => {{ let addr = unsafe { *$start.offset($index) }; if addr.is_null() { None } else { Some(FunctionPtr::new(addr)) } }}; } ``` The specific indices corresponding to the global HSR Unity release: | Table Index | Function Signature | Description | | :---: | :--- | :--- | | **22** | `il2cpp_assembly_get_image(*const Il2CppAssembly) -> *const Il2CppImage` | Retrieves primary PE image from assembly | | **31** | `il2cpp_class_get_fields(*const Il2CppClass, *const *const c_void) -> *const FieldInfo` | Iterates member fields of a class | | **33** | `il2cpp_class_get_interfaces(*const Il2CppClass, *const *const c_void) -> *const Il2CppClass` | Iterates implemented interfaces | | **35** | `il2cpp_class_get_methods(*const Il2CppClass, *const *const c_void) -> *const MethodInfo` | Iterates class methods | | **37** | `il2cpp_class_get_name(*const Il2CppClass) -> *const c_char` | Class name string | | **39** | `il2cpp_class_get_namespace(*const Il2CppClass) -> *const c_char` | Class namespace string | | **40** | `il2cpp_class_get_parent(*const Il2CppClass) -> *const Il2CppClass` | Base inheritance class | | **43** | `il2cpp_class_is_valuetype(*const Il2CppClass) -> bool` | Checks if type is struct / ValueType | | **45** | `il2cpp_class_get_flags(*const Il2CppClass) -> i32` | CLI TypeDef attribute flags | | **49** | `il2cpp_class_from_type(*const Il2CppType) -> *const Il2CppClass` | Resolves class definition from type ref | | **53** | `il2cpp_class_is_enum(*const Il2CppClass) -> bool` | Checks if type is Enum | | **63** | `il2cpp_domain_get() -> *const Il2CppDomain` | Returns root AppDomain pointer | | **65** | `il2cpp_domain_get_assemblies(*const Il2CppDomain, *const usize) -> *const *const Il2CppAssembly` | Returns array of loaded assemblies | | **72** | `il2cpp_field_get_flags(*const FieldInfo) -> i32` | CLI Field attribute flags | | **73** | `il2cpp_field_get_name(*const FieldInfo) -> *const c_char` | Field identifier name | | **75** | `il2cpp_field_get_offset(*const FieldInfo) -> usize` | Memory offset within instance/static buffer | | **76** | `il2cpp_field_get_type(*const FieldInfo) -> *const Il2CppType` | Field type definition | | **116** | `il2cpp_method_get_return_type(*const MethodInfo) -> *const Il2CppType` | Method return type | | **117** | `il2cpp_method_get_name(*const MethodInfo) -> *const c_char` | Method identifier name | | **123** | `il2cpp_method_get_param_count(*const MethodInfo) -> u32` | Parameter count | | **124** | `il2cpp_method_get_param(*const MethodInfo, u32) -> *const Il2CppType` | Parameter type at index | | **161** | `il2cpp_type_get_name(*const Il2CppType) -> *const c_char` | Full CLR type name | | **162** | `il2cpp_type_is_byref(*const Il2CppType) -> bool` | Checks `ref` / pointer indirection | | **163** | `il2cpp_type_get_attrs(*const Il2CppType) -> u32` | Param/Type attribute flags (`[In]`, `[Out]`) | | **168** | `il2cpp_image_get_name(*const Il2CppImage) -> *const c_char` | Assembly module name (e.g. `Assembly-CSharp.dll`) | | **169** | `il2cpp_image_get_class_count(*const Il2CppImage) -> usize` | Total number of classes in image | | **170** | `il2cpp_image_get_class(*const Il2CppImage, usize) -> *const Il2CppClass` | Retrieves class definition by index | > [!WARNING] Version Fragility > Because these table indices represent direct slots inside an internal dispatch table in `UnityPlayer.dll`, **any major Unity version update or custom engine compilation will shift these indices**. If the game client updates its Unity minor/patch version, the array offsets must be re-verified against a disassembly of `UnityPlayer.dll`. --- ## 6. Runtime Metamodel & Memory Layout (`types.rs` & `api.rs`) ### 6.1 Native `MethodInfo` Structure Representation In IL2CPP runtime architecture, every compiled managed method is represented by a C struct. In `src/il2cpp/types.rs`, `MethodInfo` is declared as: ```rust #[repr(C)] pub struct MethodInfo { pub klass: *const Il2CppClass, pub method_pointer: *const c_void, _pad: [u8; 0x20], pub flags: u16 } ``` #### Memory Layout Diagram: ``` Offset Field Type Description +0x00 klass *const Il2CppClass Pointer to declaring Il2CppClass +0x08 method_pointer *const c_void Absolute Virtual Address (VA) of native compiled code +0x10 _pad [u8; 0x20] Internal runtime fields (invoker_method, name, etc.) +0x30 flags u16 CLI Method Attributes (Public, Private, Static, etc.) ``` ### 6.2 Address Boundary Verification In JIT or native code hooking engines, pointers may point to trampolines, dynamically generated stubs, or external modules. `methoddumper.rs` enforces strict pointer sanity checking: ```rust fn verify_pointer(il2cpp: &Il2CppApi, pointer: usize) -> bool { (pointer > il2cpp.game_assembly.handle as usize) && (pointer < il2cpp.game_assembly.handle as usize + il2cpp.game_assembly.size) } ``` Only method pointers strictly located within the memory boundary of `GameAssembly.dll` ($\text{Base} \le \text{Ptr} < \text{Base} + \text{SizeOfImage}$) are treated as genuine compiled game methods. --- ## 7. C# Header Reconstruction Engine (`csdumper.rs`) `csdumper.rs` decompiles raw binary runtime structures into valid C# source definitions, decorating them with exact memory offsets and RVAs. ### 7.1 ECMA-335 Bitmask Decompilation The engine decodes CLI attribute bitfields from `constants.rs`: #### 1. Method Visibility & Modifiers ```rust let flags = unsafe { (*method).flags } as i32; let access = flags & METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK; let access_str = match access { METHOD_ATTRIBUTE_PRIVATE => "private ", METHOD_ATTRIBUTE_PUBLIC => "public ", METHOD_ATTRIBUTE_FAMILY => "protected ", METHOD_ATTRIBUTE_ASSEM | METHOD_ATTRIBUTE_FAM_AND_ASSEM => "internal ", METHOD_ATTRIBUTE_FAM_OR_ASSEM => "protected internal ", _ => "" }; ``` #### 2. Virtual Table & Polymorphism Layout IL2CPP preserves method slot layout flags (`METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK`): - `METHOD_ATTRIBUTE_ABSTRACT`: Emits `abstract`. - `METHOD_ATTRIBUTE_FINAL` with `METHOD_ATTRIBUTE_REUSE_SLOT`: Emits `sealed override`. - `METHOD_ATTRIBUTE_VIRTUAL` with `METHOD_ATTRIBUTE_NEW_SLOT`: Emits `virtual`. - `METHOD_ATTRIBUTE_VIRTUAL` with `METHOD_ATTRIBUTE_REUSE_SLOT`: Emits `override`. #### 3. ValueType Field Offset Normalization In IL2CPP's object model: - Reference types (`class`) carry a 16-byte (`0x10`) header (`Il2CppObject` containing `Il2CppClass*` and `MonitorData*`). - Value types (`struct`) on the heap are boxed with the 16-byte header, but unboxed instance structs lack this header. `csdumper.rs` handles this normalization: ```rust if is_valuetype && !is_static && field_offset > 0 { field_offset -= 0x10; } ``` ### 7.2 Type Name Normalization To produce clean, idiomatic C# source, `api.rs` maps raw CLR type names to C# language aliases: ```rust const PRIMITIVE_TYPES: &[(&str, &str)] = &[ ("System.Void", "void"), ("System.Boolean", "bool"), ("System.Char", "char"), ("System.SByte", "sbyte"), ("System.Byte", "byte"), ("System.Int16", "short"), ("System.UInt16", "ushort"), ("System.Int32", "int"), ("System.UInt32", "uint"), ("System.Int64", "long"), ("System.UInt64", "ulong"), ("System.Single", "float"), ("System.Double", "double"), ("System.String", "string"), ("System.IntPtr", "IntPtr"), ("System.UIntPtr", "UIntPtr"), ("System.Object", "object"), ("&", "") ]; ``` --- ## 8. Method RVA Map Generation (`methoddumper.rs`) ### 8.1 Method Signature Flattening & Collision Resolution In C#, method overloading allows multiple methods to share the same identifier with different parameter signatures. To generate a flat JSON dictionary without key collisions, `methoddumper.rs` implements a collision tracker: ```rust let method_name = il2cpp.method_get_name(method_info)?; let description = format!("{}{}::{}", class_namespace, class_name, method_name); let unique_description = if name_map.contains_key(&description) { let count = duplicates.entry(description.to_string()).or_insert(0); *count += 1; format!("{}_{}", description, count) } else { description }; name_map.insert(unique_description, pointer - il2cpp.game_assembly.handle as usize); ``` ### 8.2 JSON Output Schema (`methods.json`) The resulting file is formatted as a key-value mapping of fully qualified method signatures to hexadecimal RVAs: ```json { "RPG.Client.AutoScrollRect::.ctor": "0x1febdf0", "RPG.Client.AutoScrollRect::MNOGNALMGPL": "0x1feb700", "RPG.Client.AutoScrollRect::POFLDHKMLAF": "0x1feb7a0", "RPG.Client.AutoScrollRect::SetItemSelectCallback": "0x1feb7f0", "RPG.Client.AutoScrollRect::SnapTo": "0x1feb9c0" } ``` --- ## 9. Comparative Technical Assessment: In-Process vs. Static Dumpers | Dimension | `honkai-dumper` (In-Process) | `Il2CppDumper` (Static Disk) | | :--- | :--- | :--- | | **Execution Vector** | Native DLL injection into running process | Standalone CLI executable analyzing disk files | | **Metadata Protection** | **Immune**: Reads memory after unpacker decrypts metadata | **Fails**: Blocked by custom encryption on `global-metadata.dat` | | **Symbol Stripping** | **Immune**: Locates API table via signature scan in `UnityPlayer.dll` | Requires manual IDA script or pattern search if exports are stripped | | **Anti-Cheat Risk** | **High**: Process injection can trigger game integrity checks/watchdogs | **Zero**: Entirely offline | | **Build Stability** | Fragile: Dependent on fixed jump table index offsets in `functions.rs` | Robust: Parses standard metadata format specifications | | **Output Formats** | `dump.cs` (Headers) and `methods.json` (RVAs) | `dump.cs`, `script.py` (IDA), `il2cpp.h`, `stringliteral.json` | --- ## 10. Integration & Cross-Application in Server & Protocol Engineering ### 10.1 Labeling Disassembly in IDA Pro / Ghidra When reverse engineering stripped Star Rail binaries: 1. Open `GameAssembly.dll` in IDA Pro / Ghidra / Binary Ninja. 2. In IDA Pro, run an import script: ```python import json import ida_name, ida_bytes with open("methods.json", "r") as f: methods = json.load(f) base = ida_nalt.get_imagebase() for name, rva_str in methods.items(): rva = int(rva_str, 16) ea = base + rva # Sanitize name for IDA symbol constraints clean_name = name.replace("::", "__").replace(".", "_") ida_name.set_name(ea, clean_name, ida_name.SN_NOWARN) ``` 3. Instantly transforms thousands of anonymous subroutines (`sub_181FEB700`) into identifiable game logic (`RPG_Client_AutoScrollRect__MNOGNALMGPL`). ### 10.2 Protocol & Packet Handler Discovery In private server engineering (`LunarCore`, `C# NativeAOT Reference Server`, `GaC-hkrpg-go`): - Network packet dispatchers often use obfuscated command handlers. - By searching `dump.cs` for classes implementing network interfaces or containing `CmdId` constants, engineers can pinpoint the exact RVA of packet deserialization routines. - Cross-referencing `methods.json` RVAs with dynamic breakpoints allows live tracing of packet encryption keys, seed exchanges, and payload masking expressions. ### 10.3 Compliance with Repository Rules (`AGENTS.md`) - `honkai-dumper` is a one-shot diagnostic utility executed in an isolated thread (`thread::spawn`) on injection, writing results and terminating. - In contrast to prohibited persistent runtime probes, it does **not** attach to per-frame update loops or main thread heartbeats, adhering to the principle of zero in-game performance degradation. - No local machine paths, static IPs, or client versions are hardcoded in the codebase. All memory resolution is performed dynamically relative to `GetModuleInformation` bases.