This technical specification provides an exhaustive, code-level analysis of **`StaticCmdIdDumper`** (by Hiro420), an ultra-minimalist, zero-dependency C# (.NET 8.0) static binary analysis utility. The tool is engineered to extract network packet Command IDs (`CmdId` / `Opcode`) directly from compiled IL2CPP game executables (such as `YuanShen.exe`, `StarRail.exe`, or `GameAssembly.dll`) on disk without launching the game process or relying on third-party disassemblers (such as Capstone or Iced).
---
## 1. Executive Summary & Architectural Purpose
### 1.1 The Protocol Command ID Problem
In modern anime titles developed on Unity IL2CPP (Genshin Impact, Honkai: Star Rail, Zenless Zone Zero), client-server communication relies on an enumerated command opcode (`CmdId`) identifying each Protobuf packet.
Inside the game client assemblies, each packet class contains a getter method returning its unique opcode:
```csharp
publicclassGetAvatarDataCsReq:IMessage{
publicoverrideushortGetCmdId(){
return384;// 0x0180
}
}
```
In compiled releases:
1. Method names are stripped or obfuscated (e.g., `GetCmdId()` $\rightarrow$ `KOOKJNLBAMH()`).
2. Tools like `Firefly-Static-Parser` or `honkai-dumper` can identify the **Relative Virtual Address (RVA)** of these getter methods.
3. However, obtaining the actual integer value of the opcode traditionally required:
- Attaching a live debugger / Frida script to invoke the method at runtime, or
- Opening a 100MB+ binary in IDA Pro / Ghidra and manually decompiling thousands of tiny stub functions.
### 1.2 The `StaticCmdIdDumper` Solution
`StaticCmdIdDumper` demonstrates an elegant, lightweight alternative:
- It exploits the deterministic machine-code pattern emitted by C++ compilers when compiling a constant 16-bit integer getter.
- It parses PE (Portable Executable) headers using pure, hand-written C# structs.
- It maps the getter method's RVA directly to disk file offset, reads **exactly 4 bytes**, and extracts the embedded immediate integer via a micro-disassembler.
- **`0x66` (Operand-Size Override Prefix)**: The default operand size in 64-bit mode is 32-bit. The `0x66` prefix instructs the processor to decode the instruction for the 16-bit register `ax` instead of 32-bit `eax`.
var fileoffset = il2cpp_section.ptrToRawData + (CmdIDTestAddr - il2cpp_section.virtualAddr);
byte[] buffer = new byte[4];
using (FileStream fs = new FileStream(args[0], FileMode.Open, FileAccess.Read))
{
fs.Seek((long)fileoffset, SeekOrigin.Begin);
fs.Read(buffer, 0, 4);
}
int cmdid = Disassembler.Disassemble(buffer);
packetIds[pair.Key] = cmdid;
```
---
## 5. Execution Workflow & Serialization
### 5.1 Input Format (`list.txt`)
The tool accepts a plain whitespace-delimited text file containing pairs of function identifiers and hexadecimal RVAs:
```text
KOOKJNLBAMH 0x0CCA3780
MGDFLHGODLH 0x0BE5AA60
CGKBCOJMLPC 0x082AB170
OOMIIJFHPFN 0x0D16A280
```
> [!NOTE] Origin of the Input List
> This list is directly obtained from the output of `Firefly-Static-Parser` or `honkai-dumper`. In `methods.json`, getter methods for packet IDs appear as:
> `"KOOKJNLBAMH": "0xCCA3780"`
### 5.2 Output Format (`PacketIds.json`)
The resulting dictionary is serialized to formatted JSON:
```json
{
"KOOKJNLBAMH": 101,
"MGDFLHGODLH": 102,
"CGKBCOJMLPC": 143,
"OOMIIJFHPFN": 172
}
```
---
## 6. The Complete 5-Stage Protocol Reverse-Engineering Toolchain
By studying `LunarCore`, `C# NativeAOT Reference Server`, `FireflyProtoc`, `honkai-dumper`, `Firefly-Static-Parser`, and `StaticCmdIdDumper`, we can now construct the entire open-source reverse-engineering pipeline:
## 7. Technical Critique & Improvements for `GaC-hkrpg-go`
While `StaticCmdIdDumper` is compact and effective, an industrial-grade implementation (e.g. ported to Go for `GaC-hkrpg-go`) requires resolving several architectural limitations:
### 7.1 Limitation 1: Hardcoded Section Name (`"il2cpp"`)
In `Misc.cs`:
```csharp
if (sections![idx].name != "il2cpp") continue;
```
- **Issue**: In *Honkai: Star Rail* and standard Unity builds, code is placed in the standard `.text` section, **not** a section named `il2cpp`.
- **Solution**: Implement dynamic RVA-to-Section resolution across **all** sections:
- **Issue**: If the method return type is `int` (32-bit signed integer) rather than `ushort`, compilers emit `mov eax, imm32` without the `0x66` prefix:
`B8 xx xx 00 00` (5 bytes: `0xB8` followed by 4-byte integer).
- **Solution**: Support both instruction patterns:
return 0, fmt.Errorf("RVA 0x%X not found in any section", rva)
}
```
---
## 8. Compliance with Repository Rules (`AGENTS.md`)
- **Documentation & Protocol Integrity**: `StaticCmdIdDumper` extracts Command IDs purely from the machine code representation of the target client binary. It adheres strictly to the rule requiring handlers to resolve `CmdID` from verified client protocol tables without guessing or hardcoding multi-version aliases.
- **No Machine-Specific Hardcoding**: All file paths and offsets are read dynamically from command-line arguments and PE headers.
- **Zero Runtime Interference**: Operates entirely offline on the executable file, with zero memory injection, hooks, or heartbeat overhead.