# Dumpcs2Protobuf Architectural Specification & Schema Reconstruction Deep Dive This technical specification provides an exhaustive, code-level analysis of **`Dumpcs2Protobuf`** (by Hiro420), a specialized C# (.NET 8.0) schema recovery utility engineered to reconstruct Protocol Buffers v3 (`.proto`) definition files directly from the decompiled C# source files (`dump.cs`) emitted by IL2CPP metadata dumpers (such as `honkai-dumper` or `Firefly-Static-Parser`). --- ## 1. Executive Summary & Architectural Role ### 1.1 The Protocol Recovery Gap In modern IL2CPP game research, obtaining usable network protocol schemas involves bridging a critical semantic gap: 1. IL2CPP dumpers extract decompiled C# classes (`dump.cs`). 2. Server implementations (`LunarCore`, `March7thHoney`, `GaC-hkrpg-go`) require `.proto` schema definitions to serialize/deserialize packets and generate strongly typed message stubs. 3. Manually translating thousands of obfuscated C# packet classes into `.proto` files is infeasible. ### 1.2 The De-Compilation Inverse `Dumpcs2Protobuf` operates as the **exact inverse** of Google's Protocol Buffers C# compiler (`protoc --csharp_out`): - When Google's `protoc` compiles a `.proto` file into C#, it generates deterministic structural signatures (constant field numbers, `RepeatedField`, `MapField`, `OneofCase` enums, and `OriginalNameAttribute`). - `Dumpcs2Protobuf` analyzes these exact code generation patterns inside `dump.cs`, extracts field tags, names, and types, and **reconstructs valid `proto3` syntax**. ``` +----------------------------------------------------------------------------------------------------+ | Dumpcs2Protobuf Pipeline | +----------------------------------------------------------------------------------------------------+ | | | [ Input: dump.cs (Decompiled IL2CPP Output) ] | | | | // Module: RPG.Network.Proto.dll | | public sealed class IEFELCIJJDA : IMessage | | { | | public const int CPBHCOPAGBD = 13; | | public const int NEFBNAELMLD = 14; | | public uint EGPKLBOKFNF { get; set; } | | public RepeatedField MDDLGJHLLAI { get; } | | } | | | | +----------------------------------------------------------------------------------------------+ | | | 1. Assembly & Class Filter | | | | Filters segments for 'RPG.Network.Proto.dll' and classes implementing 'IMessage' | | | +----------------------------------------------+-----------------------------------------------+ | | | | | v | | +----------------------------------------------------------------------------------------------+ | | | 2. Tag & Property Pairing Engine | | | | - Extracts field tags from 'public const int = ;' | | | | - Extracts properties: Scalar, RepeatedField, MapField | | | | - Pairs tags with properties in declaration order | | | +----------------------------------------------+-----------------------------------------------+ | | | | | v | | +----------------------------------------------------------------------------------------------+ | | | 3. OneOf Union Detection | | | | Detects count mismatch -> Resolves discriminator enum -> Groups fields into oneof block | | | +----------------------------------------------+-----------------------------------------------+ | | | | | v | | +----------------------------------------------------------------------------------------------+ | | | 4. Type Translation & Name Normalization | | | | - CLR types -> Protobuf types (uint -> uint32, ByteString -> bytes) | | | | - CamelCase / PascalCase -> snake_case | | | | - Extracts [OriginalNameAttribute] for un-obfuscated Enum values | | | +----------------------------------------------+-----------------------------------------------+ | | | | | v | | [ Output: output.proto ] | | | | syntax = "proto3"; | | | | message IEFELCIJJDA { | | uint32 egpklbokfnf = 13; | | repeated NFJLPFKFDLF mddlgjhllai = 14; | | } | | | +----------------------------------------------------------------------------------------------------+ ``` --- ## 2. Inverting Google.Protobuf C# Code Generation To understand how `Dumpcs2Protobuf` works, we must analyze the exact contract emitted by Google's `protoc` C# code generator. ### 2.1 The Message Contract Every generated Protobuf message in C# adheres to strict patterns: 1. **Class Declaration**: ```csharp public sealed class MessageName : IMessage, IMessage, ... ``` 2. **Field Tag Constants**: For every protobuf field, `protoc` generates a public constant integer under `// Fields`: ```csharp public const int FieldNameFieldNumber = 13; ``` In obfuscated game dumps, the name is mangled (`CPBHCOPAGBD`), but the integer value (`13`) is the **immutable wire tag number**. 3. **Public Non-Static Properties**: Under `// Properties`, `protoc` generates accessors corresponding to each field: - Scalar fields: `public uint FieldName { get; set; }` - Repeated fields: `public RepeatedField FieldName { get; }` - Map fields: `public MapField FieldName { get; }` - Byte sequences: `public ByteString FieldName { get; set; }` ### 2.2 The OneOf Union Contract When a `.proto` file contains a `oneof` construct: ```protobuf oneof choice { uint32 avatar_id = 1; string nickname = 2; } ``` Google's `protoc` generates: 1. Normal field constants: ```csharp public const int AvatarIdFieldNumber = 1; public const int NicknameFieldNumber = 2; ``` 2. Normal properties for each field: ```csharp public uint AvatarId { get; set; } public string Nickname { get; set; } ``` 3. An internal discriminator enum and an extra case property: ```csharp public enum ChoiceOneofCase { None = 0, AvatarId = 1, Nickname = 2 } public ChoiceOneofCase ChoiceCase { get; } ``` Notice the invariant: **The number of properties is greater than the number of field constants by exactly the number of `oneof` blocks!** ### 2.3 The Enum Contract & `OriginalNameAttribute` Protobuf enums in C# retain their original un-obfuscated names via attributes: ```csharp public enum FGEMFOPIADP { public int value__; [OriginalNameAttribute(Name = "CmdCityShopInfoScNotify", PreferredAlias = True)] public const FGEMFOPIADP PNKKDKOGMOM = 1533; } ``` Even if the class name (`FGEMFOPIADP`) and field constant (`PNKKDKOGMOM`) are obfuscated, **the metadata attribute preserves the original protocol identifier `CmdCityShopInfoScNotify` and its integer value `1533`!** --- ## 3. Codebase Architecture & Data Models The project is structured into two source files: ``` Dumpcs2Protobuf/ ├── Proto.cs # Abstract Syntax Tree (AST) data models └── MainApp.cs # File chunker, regex extractors, type mapper, and proto serializer ``` ### 3.1 AST Data Models (`Proto.cs`) ```csharp namespace DumpFileParser; public class ProtoMessage { public string protoName = ""; public List importNameList = new(); public List fieldList = new(); public List oneOfList = new(); } public class ProtoField { public string fieldName = ""; public string fieldType = ""; public string? mapKey; public string? mapValue; public bool isRepeated; public uint val; // Protobuf wire tag number } public class OneOf { public string oneOfName = ""; public List fieldList = new(); } public class ProtoEnum { public string enumName = ""; public Dictionary valDict = new(); } ``` --- ## 4. Reverse Engineering Algorithms & Implementation (`MainApp.cs`) ### 4.1 Chunking & Assembly Filtering `dump.cs` files can be hundreds of megabytes in size. `Dumpcs2Protobuf` splits the raw text into class segments using the IL2CPP dump delimiter: ```csharp string[] segments = fileText.Split(new string[] { "// AssemblyIndex:" }, StringSplitOptions.None); ``` Each segment is checked for target assembly ownership: ```csharp string asmName = "RPG.Network.Proto.dll"; if (!segment.Split("\n")[2].Trim().EndsWith(asmName)) { continue; } ``` This restricts schema extraction exclusively to the network protocol assembly, ignoring engine, UI, and gameplay logic. ### 4.2 Field Tag Extraction (`GetFieldNums`) Field numbers are extracted exclusively between `// Fields` and `// Properties` to avoid picking up arbitrary constants: ```csharp static List GetFieldNums(string[] lines) { List result = new(); int fieldsStartIndex = -1; int propertiesStartIndex = -1; for (int i = 0; i < lines.Length; i++) { if (lines[i].Trim().StartsWith("// Fields")) fieldsStartIndex = i + 1; else if (lines[i].Trim().StartsWith("// Properties")) { propertiesStartIndex = i; break; } } if (fieldsStartIndex >= 0 && propertiesStartIndex > fieldsStartIndex) { for (int i = fieldsStartIndex; i < propertiesStartIndex; i++) { string line = lines[i].Trim(); var match = Regex.Match(line, @"public\s+const\s+int\s+(\w+)\s*=\s*(\d+);"); if (match.Success && uint.TryParse(match.Groups[2].Value, out uint fieldNum)) { result.Add(fieldNum); } } } return result; } ``` ### 4.3 Property & Type Extraction (`GetFieldStrings`) Extracts public non-static properties using regex: ```csharp var match = Regex.Match(trimmedLine, @"public\s+([\w\.]+(\s*<.*?>)?)\s+(\w+)\s*\{( get;(\s+set;)?| set;(\s+get;)?| get;| set;) \}"); ``` #### Type Mapping (`parseType`): Maps C# CLR types to `proto3` scalar types: | C# CLR Type | Protobuf Wire Type | | :--- | :--- | | `int` | `int32` | | `uint` | `uint32` | | `long` | `int64` | | `ulong` | `uint64` | | `string` | `string` | | `ByteString` | `bytes` | | `RepeatedField` | `repeated ` | | `MapField` | `map` | | Custom Class (`Namespace.Type`) | Statically stripped to `Type` | ### 4.4 OneOf Detection & Resolution Algorithm When the count of field tags does not match the count of properties: ```csharp if (fieldNums.Count != fieldStrings.Count) { // The trailing property is the OneOf discriminator enum accessor! string trimmed = fieldStrings.Last().Replace(" { get; }", "").Replace("public ", ""); oneOfName = trimmed.Split(" ")[1]; oneOfEnumName = trimmed.Split(" ")[0]; blackListEnumNames.Add(oneOfEnumName.Split(".").Last()); oneofEnum = FindAndParseEnum(oneOfEnumName, segments); } ``` 1. It identifies the discriminator property (e.g. `public ChoiceOneofCase ChoiceCase { get; }`). 2. It locates and parses the enum declaration (`ChoiceOneofCase`) across all segments. 3. For each field, if its wire tag number matches an entry in `oneofEnum.valDict`, it is placed inside `oneof { ... }`. 4. The discriminator enum is added to `blackListEnumNames` so it is not emitted as a standalone top-level enum in the `.proto` file. ### 4.5 De-Obfuscating Enums via `OriginalNameAttribute` In `parseEnum`: ```csharp if (lines[i-1].Contains("[OriginalNameAttribute(Name = \"")) { string upperLine = lines[i-1]; name = upperLine.Split("[OriginalNameAttribute(Name = \"").Last().Split("\"").First(); } else { name = line.Split($"{enumName} ").Last().Split("=").First().Trim(); } ``` If an enum constant is obfuscated (e.g. `IHJPPFIKELH`), it inspects the preceding line for `[OriginalNameAttribute]`, effortlessly restoring the original identifier (`CmdShopTypeNone = 0;`). --- ## 5. Output Serialization `Dumpcs2Protobuf` serializes the AST into clean `proto3` syntax: ```protobuf // Extracted from dump.cs using Dumpcs2Protobuf syntax = "proto3"; message SceneEntityInfo { uint32 entity_id = 1; uint32 group_id = 2; uint32 inst_id = 3; MotionInfo motion = 4; oneof entity { ActorInfo actor = 5; NpcInfo npc = 6; MonsterInfo monster = 7; } } enum CmdCityShopInfoScNotify { CmdShopTypeNone = 0; CmdCityShopInfoScNotify = 1533; } ``` --- ## 6. The Complete 6-Tool Unified Star Rail Protocol Toolchain We can now map how all 6 repositories analyzed in this session interact to form an end-to-end, automated protocol engineering pipeline: ``` +----------------------------------------------------------------------------------------------------+ | THE COMPLETE PROTOCOL REVERSE-ENGINEERING LIFECYCLE | +----------------------------------------------------------------------------------------------------+ | | | [ Step 1: Binary & Metadata Extraction ] | | Tools: Firefly-Static-Parser (Offline) OR honkai-dumper (In-Process) | | Action: Decrypts GameAssembly.dll + global-metadata.dat | | Output: dump.cs (All C# class definitions) + methods.json (All method RVAs) | | | | [ Step 2: Protocol Schema Reconstruction ] | | Tool: Dumpcs2Protobuf | | Action: Parses dump.cs (RPG.Network.Proto.dll), inverts IMessage code patterns, | | resolves OneOfs, extracts OriginalNameAttribute | | Output: Un-annotated .proto schema files | | | | [ Step 3: Opcode & CmdId Resolution ] | | Tool: StaticCmdIdDumper | | Action: Takes method RVAs of GetCmdId() from methods.json -> reads 4 bytes (66 B8 imm16) | | Output: PacketIds.json (Map of PacketName -> Integer Opcode) | | | | [ Step 4: Schema Annotation & Wire Mask Resolution ] | | Action: Combines PacketIds.json with .proto definitions: | | - Adds: meta uint32 cmd_id = 1533; | | - Adds: uint32 id = 1 [get: (id ^ 1) + 2]; | | | | [ Step 5: Advanced Protobuf Compilation ] | | Tool: FireflyProtoc | | Action: Compiles .proto into high-performance, NativeAOT C# stubs | | - Solves wire masks via ExpressionInverter | | - Generates O(1) jump table MessageFactory.Create(cmdId) | | | | [ Step 6: Server Implementation & Routing ] | | Tools: March7thHoney (.NET 10) / LunarCore (Java) / GaC-hkrpg-go (Go) | | Action: Instant zero-reflection packet routing on KCP network layer | | | +----------------------------------------------------------------------------------------------------+ ``` --- ## 7. Technical Critique & Production Blueprint for `GaC-hkrpg-go` While `Dumpcs2Protobuf` is concise and functional, a production-grade schema generator for `GaC-hkrpg-go` should address several structural limitations: ### 7.1 Limitations of the Regex Implementation 1. **Single Output File**: Dumps everything into a single monolithic `output.proto` file, leading to circular dependency and compilation issues. 2. **Missing Imports**: Does not generate `import "OtherMessage.proto";` statements required when messages reference other message types. 3. **Rigid Field Ordering Assumption**: Relies on field numbers appearing sequentially in the exact line order of properties. If the decompiler or compiler re-orders field definitions, tag pairing can desynchronize. ### 7.2 Production Go Implementation Blueprint A robust schema extractor in Go for `GaC-hkrpg-go` can parse the C# definitions and emit properly modularized proto files: ```go package proto_generator import ( "fmt" "os" "path/filepath" "strings" ) type ProtoField struct { Name string Type string Tag uint32 IsRepeated bool IsMap bool MapKey string MapValue string } type ProtoMessage struct { Name string CmdId uint32 Fields []ProtoField Imports map[string]bool OneOfs map[string][]ProtoField } func (m *ProtoMessage) GenerateProtoFile(outDir string) error { var sb strings.Builder sb.WriteString("syntax = \"proto3\";\n\n") // 1. Emit Imports for imp := range m.Imports { sb.WriteString(fmt.Sprintf("import \"%s.proto\";\n", imp)) } if len(m.Imports) > 0 { sb.WriteString("\n") } // 2. Emit Message Block sb.WriteString(fmt.Sprintf("message %s {\n", m.Name)) // Option: Meta CmdId if m.CmdId > 0 { sb.WriteString(fmt.Sprintf("\t// CmdId: %d\n", m.CmdId)) } // Normal Fields for _, f := range m.Fields { if f.IsRepeated { sb.WriteString(fmt.Sprintf("\trepeated %s %s = %d;\n", f.Type, f.Name, f.Tag)) } else if f.IsMap { sb.WriteString(fmt.Sprintf("\tmap<%s, %s> %s = %d;\n", f.MapKey, f.MapValue, f.Name, f.Tag)) } else { sb.WriteString(fmt.Sprintf("\t%s %s = %d;\n", f.Type, f.Name, f.Tag)) } } // OneOf Blocks for oneOfName, fields := range m.OneOfs { sb.WriteString(fmt.Sprintf("\toneof %s {\n", oneOfName)) for _, f := range fields { sb.WriteString(fmt.Sprintf("\t\t%s %s = %d;\n", f.Type, f.Name, f.Tag)) } sb.WriteString("\t}\n") } sb.WriteString("}\n") filePath := filepath.Join(outDir, m.Name+".proto") return os.WriteFile(filePath, []byte(sb.String()), 0644) } ``` --- ## 8. Compliance with Repository Rules (`AGENTS.md`) - **Explicit Protocol Revision & Schema Integrity**: Reconstructing schemas directly from client assemblies prevents guesswork or arbitrary schema patching. - **No Hardcoded Machine Paths**: Operates dynamically from CLI arguments and local files. - **Zero Runtime Interference**: Operates strictly on text and source files without runtime injection or client hooks.