Files
GaCy-GO/docs/scratch/honkai_dumper_deep_dive.md
T

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:

  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<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)
    });
  });
}
  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:
    fn init() -> Result<(), Box<dyn Error>> {
      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:

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
}
  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:

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 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:

#[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: 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:
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:

  1. Open GameAssembly.dll in IDA Pro / Ghidra / Binary Ninja.
  2. 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)
    
  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.