24 KiB
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:
GameAssembly.dll: Native machine code (PE x86_64) resulting from C#\rightarrowC++\rightarrowClang compilation.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.datis heavily obfuscated and encrypted on disk; static extractors fail immediately without proprietary decryption keys. - Stripped API Exports:
GameAssembly.dllstrips 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 insideUnityPlayer.dllto locate the hidden global table of IL2CPP API function pointers. - It walks the live memory graph (
Domain\rightarrowAssembly\rightarrowImage\rightarrowClass\rightarrowMethod/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<T> 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:
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)
});
});
}
#[ctor]Injection: Thectormacro generates a C-runtime startup initializer function.thread::spawn: Immediately offloads execution to an isolated OS thread, returning control to the Windows loader instantly and averting loader lock deadlocks.- Engine Warmup Delay:
A fixed sleep of 10 seconds is imposed. This provides the host game process sufficient wall-clock time to:
fn init() -> Result<(), Box<dyn Error>> { thread::sleep(Duration::from_secs(10)); unsafe { consoleapi::AllocConsole() }; // ... }- Load and decrypt secondary DLLs (
GameAssembly.dll). - Run native unpackers and decrypt internal
global-metadata.datstructures into RAM. - Execute IL2CPP runtime initialization routines (
il2cpp_init) and register class domains.
- Load and decrypt secondary DLLs (
- 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:
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:
pub fn find_qword_addr(data: &[u8], base_addr: u64, pe: &PeFile) -> Option<usize> {
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
}
- File Offset
\rightarrowRVA Conversion: Iterates PE section headers viapeliteto map raw file offsetito section Virtual Address. - Displacement Extraction: Reads 32-bit signed little-endian integer (
i32) from offseti + 3. - Effective Address: Adds displacement to
RIP_{\text{next}} = VA + 7. - 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:
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<T> utilizing Rust's Deref trait and PhantomData:
pub struct FunctionPtr<T> {
pub ptr: *const c_void,
pd: PhantomData<T>
}
impl<T> Deref for FunctionPtr<T> {
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:
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 ofUnityPlayer.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:
#[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:
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
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: Emitsabstract.METHOD_ATTRIBUTE_FINALwithMETHOD_ATTRIBUTE_REUSE_SLOT: Emitssealed override.METHOD_ATTRIBUTE_VIRTUALwithMETHOD_ATTRIBUTE_NEW_SLOT: Emitsvirtual.METHOD_ATTRIBUTE_VIRTUALwithMETHOD_ATTRIBUTE_REUSE_SLOT: Emitsoverride.
3. ValueType Field Offset Normalization
In IL2CPP's object model:
- Reference types (
class) carry a 16-byte (0x10) header (Il2CppObjectcontainingIl2CppClass*andMonitorData*). - Value types (
struct) on the heap are boxed with the 16-byte header, but unboxed instance structs lack this header.csdumper.rshandles this normalization:
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:
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:
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:
{
"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:
- Open
GameAssembly.dllin IDA Pro / Ghidra / Binary Ninja. - In IDA Pro, run an import script:
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) - 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, March7thHoney, GaC-hkrpg-go):
- Network packet dispatchers often use obfuscated command handlers.
- By searching
dump.csfor classes implementing network interfaces or containingCmdIdconstants, engineers can pinpoint the exact RVA of packet deserialization routines. - Cross-referencing
methods.jsonRVAs 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-dumperis 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
GetModuleInformationbases.