2026-10-31 20:47:36 +07:00
|
|
|
# รายงานการวิเคราะห์สถาปัตยกรรมและการทำงานของ FireflyProtoc (Deep-Dive Technical Specification)
|
|
|
|
|
|
|
|
|
|
> **เอกสารอ้างอิงทางเทคนิคระดับลึกสำหรับวิศวกรซอฟต์แวร์และโมเดลปัญญาประดิษฐ์ (AI Systems)**
|
|
|
|
|
> วิเคราะห์จากซอร์สโค้ดของโครงการ: [gatokeeper/FireflyProtoc](https://github.com/gatokeeper/FireflyProtoc)
|
|
|
|
|
> ครอบคลุมสถาปัตยกรรมคอมไพเลอร์ Protobuf ยุคใหม่, การถอดรหัสความคลุมเครือของข้อมูล (Wire De-obfuscation), ระบบคณิตศาสตร์เชิงสัญลักษณ์ (Symbolic Expression Inverter), Meta Fields, Compile-Time Message Factory และรันไทม์ประสิทธิภาพสูง
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## สารบัญ (Table of Contents)
|
|
|
|
|
1. [บทนำและวัตถุประสงค์ของโครงการ (Introduction & Problem Statement)](#1-บทนำและวัตถุประสงค์ของโครงการ-introduction--problem-statement)
|
|
|
|
|
2. [สถาปัตยกรรมคอมไพเลอร์แบบ Multi-Tiered IR (Compiler Architecture)](#2-สถาปัตยกรรมคอมไพเลอร์แบบ-multi-tiered-ir-compiler-architecture)
|
|
|
|
|
- 2.1 ภาพรวมของไปป์ไลน์การคอมไพล์ (Frontend $\rightarrow$ IR $\rightarrow$ Backend)
|
|
|
|
|
- 2.2 โครงสร้างไวยากรณ์เสริม (Custom Syntax Extensions)
|
|
|
|
|
3. [ระบบคณิตศาสตร์เชิงสัญลักษณ์ (Symbolic Expression Inverter Engine)](#3-ระบบคณิตศาสตร์เชิงสัญลักษณ์-symbolic-expression-inverter-engine)
|
|
|
|
|
- 3.1 ปัญหาการพรางค่าข้อมูลบนสายส่ง (Wire Obfuscation / Masking)
|
|
|
|
|
- 3.2 อัลกอริทึมการแปลงนิพจน์ผกผันพีชคณิตอัตโนมัติ (Algebraic Inversion)
|
|
|
|
|
4. [ระบบ Meta Fields และ Message Factory แบบ Zero-Reflection](#4-ระบบ-meta-fields-และ-message-factory-แบบ-zero-reflection)
|
|
|
|
|
- 4.1 การฝัง Metadata (CmdId/Opcode) โดยไม่เสีย Overhead บนสายส่ง
|
|
|
|
|
- 4.2 การสร้าง Compile-Time Factory Jump Table (`IFactoryMessage`)
|
|
|
|
|
5. [การสร้างโค้ด C# และการปรับปรุงประสิทธิภาพ (CodeGen & Performance Optimizations)](#5-การสร้างโค้ด-c-และการปรับปรุงประสิทธิภาพ-codegen--performance-optimizations)
|
|
|
|
|
6. [รันไทม์ภายในของ Firefly (Firefly.Protobuf Runtime Architecture)](#6-รันไทม์ภายในของ-firefly-fireflyprotobuf-runtime-architecture)
|
|
|
|
|
7. [ตัวอย่างจริงจากการคอมไพล์ (Real-World Compilation Walkthrough)](#7-ตัวอย่างจริงจากการคอมไพล์-real-world-compilation-walkthrough)
|
|
|
|
|
8. [การนำไปประยุกต์ใช้ในระบบเซิร์ฟเวอร์เกม (Integration with HSR/Genshin Private Servers)](#8-การนำไปประยุกต์ใช้ในระบบเซิร์ฟเวอร์เกม-integration-with-hsrgenshin-private-servers)
|
|
|
|
|
9. [คู่มือวิศวกรรมสำหรับ AI ในการขยายขีดความสามารถ (AI Extension & Engineering Guide)](#9-คู่มือวิศวกรรมสำหรับ-ai-ในการขยายขีดความสามารถ-ai-extension--engineering-guide)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 1. บทนำและวัตถุประสงค์ของโครงการ (Introduction & Problem Statement)
|
|
|
|
|
|
|
|
|
|
**FireflyProtoc** (พัฒนาโดย `gatokeeper`) คือคอมไพเลอร์ Protocol Buffers ทางเลือกที่ถูกออกแบบขึ้นมาโดยเฉพาะสำหรับ **Protocol วิศวกรรมย้อนกลับ (Reverse Engineering)** และ **เซิร์ฟเวอร์จำลองเกม (Game Emulators)** เช่น *Honkai: Star Rail*, *Genshin Impact*, และ *Zenless Zone Zero*
|
|
|
|
|
|
|
|
|
|
### ข้อจำกัดของ `protoc` ดั้งเดิมของ Google:
|
|
|
|
|
1. **ไม่รองรับ Wire Data Masking / Obfuscation**: เกมค่าย HoYoverse มักจะพรางค่าฟิลด์ตัวเลขบนสายส่ง (Wire Format) ด้วยการทำ XOR, บวก, ลบ หรือบิตชิฟต์ เช่น ค่า ID จริงคือ `1001` แต่ในสายส่งกลับถูกเข้ารหัสเป็น `(1001 ^ 0x5A) + 7` คอมไพเลอร์ปกติของ Google ทำได้แค่อ่านค่าดิบ ทำให้วิศวกรต้องมาเขียนโค้ดแปลงค่าซ้ำซ้อนในทุก ๆ ฟิลด์
|
|
|
|
|
2. **ไม่มีแนวคิดเรื่อง Packet Opcode (CmdId) ในตัว**: Protobuf มาตรฐานไม่มีช่องทางในการผูกหมายเลข Opcode เข้ากับ Class โดยตรง หากสร้างฟิลด์ธรรมดา ข้อมูล Opcode จะถูกส่งซ้ำซ้อนลงไปใน Payload สิ้นเปลืองแบนด์วิดท์
|
|
|
|
|
3. **การสร้างอินสแตนซ์พึ่งพา Reflection หรือ Dictionary**: การแมป Opcode เป็น C# Object มักต้องใช้ Reflection หรือ Dictionary Lookup ขนาดใหญ่ ซึ่งช้าและไม่รองรับการทำ NativeAOT
|
|
|
|
|
|
|
|
|
|
FireflyProtoc แก้ปัญหาทั้งหมดนี้ที่ **ระดับคอมไพเลอร์ (Compiler Level)** ทำให้ไฟล์ `.proto` มีความสามารถในการแปลงค่าพรางข้อมูลอัตโนมัติและสร้าง O(1) Factory สำเร็จรูปทันที
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. สถาปัตยกรรมคอมไพเลอร์แบบ Multi-Tiered IR (Compiler Architecture)
|
|
|
|
|
|
|
|
|
|
FireflyProtoc ถูกสร้างขึ้นด้วยสถาปัตยกรรมคอมไพเลอร์ระดับมืออาชีพ โดยแบ่งขั้นตอนการทำงานออกเป็น **3 ชั้น Intermediate Representation (IR)**:
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
flowchart TD
|
|
|
|
|
ProtoFile[".proto Source Code<br/>(พร้อมไวยากรณ์เสริม meta, [get: ...])"] --> Lexer["Lexer (Token Stream)"]
|
|
|
|
|
Lexer --> Parser["Parser (AST: Node / Decl / Expr)"]
|
|
|
|
|
Parser --> TypeChecker["TypeChecker (Semantic Validation)"]
|
|
|
|
|
|
|
|
|
|
subgraph IR_Pipeline["Multi-Tiered IR Lowering"]
|
|
|
|
|
TypeChecker --> HIR["HIR Builder<br/>(High-Level IR: Messages, Enums, Scopes)"]
|
|
|
|
|
HIR --> MIR["MIR Builder<br/>(Mid-Level IR: Type Resolution, MessageFactory, PascalCase)"]
|
|
|
|
|
MIR --> LIR["LIR Builder<br/>(Low-Level IR: Classes, Methods, Body, Byte Stmts)"]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
LIR --> CodeGen["CSharpGenerator (Parallel.ForEach File Emit)"]
|
|
|
|
|
CodeGen --> CSharpFiles["Generated High-Performance C# Files<br/>(Zero Reflection, AggressiveInlining, NativeAOT)"]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 2.1 หน้าที่ของแต่ละเลเยอร์ในไปป์ไลน์:
|
|
|
|
|
1. **AST (Abstract Syntax Tree)**:
|
|
|
|
|
- แปลงไวยากรณ์ข้อความเป็นโครงสร้างข้อมูลเบื้องต้น (`Decl.Message`, `Decl.Field`, `Decl.Enum`, `Expr.Binary`)
|
|
|
|
|
2. **HIR (High-Level Intermediate Representation)**:
|
|
|
|
|
- รวบรวม Scope และ Namespace ของไฟล์
|
|
|
|
|
- จัดระเบียบความสัมพันธ์ของ Message ซ้อน Message (Nested Types)
|
|
|
|
|
- สกัดนิพจน์ `[get: ...]` และ `[set: ...]` เข้าสู่ `AttributeSet`
|
|
|
|
|
3. **MIR (Mid-Level Intermediate Representation)**:
|
|
|
|
|
- แมปประเภทข้อมูลของ Protobuf ไปเป็นประเภทข้อมูล C# (`MirTypeRef`)
|
|
|
|
|
- แปลงชื่อแบบ snake_case เป็น PascalCase
|
|
|
|
|
- รวบรวมฟิลด์ที่เป็น `meta` ที่ตรงกับคำสั่ง `option message_factory` เพื่อเตรียมสร้าง Factory Class
|
|
|
|
|
4. **LIR (Low-Level Intermediate Representation)**:
|
|
|
|
|
- กำหนดโครงสร้างโค้ดระดับล่าง: Constructors, Property Getters/Setters, ฟิลด์ Backing Field (`_Field`), เมธอด `WriteTo`, `MergeFrom`, `CalculateSize`, `Clone`, และ Switch-Case ใน `MessageFactory.Create`
|
|
|
|
|
5. **CodeGen (C# Code Generator)**:
|
|
|
|
|
- แปลง LIR ออกมาเป็นโค้ด C# ข้อความจริง โดยรันกระบวนการเขียนไฟล์แบบคู่ขนาน (`Parallel.ForEach`)
|
|
|
|
|
|
|
|
|
|
### 2.2 โครงสร้างไวยากรณ์เสริม (Custom Syntax Extensions)
|
|
|
|
|
|
|
|
|
|
| ไวยากรณ์ | รูปแบบการเขียน | ผลลัพธ์ที่คอมไพเลอร์สร้างให้ |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| **Meta Field** | `meta int32 cmd_id = 1;` | สร้างค่าคงที่และ Getter ในคลาส **แต่จะไม่ถูก Serialize ออกทางสายส่ง** และสามารถใช้ Tag ชนกับฟิลด์ธรรมดาได้ |
|
|
|
|
|
| **Masked Field** | `[get: (id ^ 1) + 2] int32 id = 3;` | ตัวแปร `Id` ภายนอกจะมองเห็นค่าจริง แต่ค่าที่ถูกส่ง/รับบนสายส่งจะผ่านสูตรคณิตศาสตร์ผกผัน |
|
|
|
|
|
| **Message Factory** | `option message_factory = int32 cmd_id;` | สร้างคลาส `MessageFactory` พร้อมเมธอด `Create(int cmd_id)` คืนค่าอินสแตนซ์ของ Message นั้นทันที |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. ระบบคณิตศาสตร์เชิงสัญลักษณ์ (Symbolic Expression Inverter Engine)
|
|
|
|
|
|
|
|
|
|
จุดที่อัจฉริยะที่สุดของ FireflyProtoc คือคลาส **`ExpressionInverter.cs`** ซึ่งทำหน้าที่เป็น Symbolic Algebra Solver ในตัวเอง
|
|
|
|
|
|
|
|
|
|
### 3.1 ปัญหาการพรางค่าข้อมูลบนสายส่ง (Wire Obfuscation / Masking)
|
|
|
|
|
สมมุติว่าในเกม ฟิลด์ `hp` มีสูตรการพรางข้อมูลทางสายส่งคือ:
|
|
|
|
|
$$\text{WireValue} = (\text{LogicalHP} \oplus 1) + 2$$
|
|
|
|
|
|
|
|
|
|
เมื่อเขียนโค้ดฝั่งอ่าน (Deserialization / `MergeFrom`), เราจำเป็นต้องถอดรหัสค่าจากสายส่งกลับมาเป็นค่าจริง:
|
|
|
|
|
$$\text{LogicalHP} = (\text{WireValue} - 2) \oplus 1$$
|
|
|
|
|
|
|
|
|
|
หากใช้คอมไพเลอร์ทั่วไป ผู้พัฒนาจะต้องมานั่งคำนวณและเขียนสูตรทั้งขาไป (`get`) และขากลับ (`set`) ด้วยตัวเอง หากมีฟิลด์แบบนี้เป็นร้อยๆ ฟิลด์ จะเกิดข้อผิดพลาดได้ง่ายมาก
|
|
|
|
|
|
|
|
|
|
### 3.2 อัลกอริทึมการแปลงนิพจน์ผกผันพีชคณิตอัตโนมัติ (Algebraic Inversion)
|
|
|
|
|
ใน FireflyProtoc ผู้ใช้สามารถเขียนเพียงขาเดียว:
|
|
|
|
|
```protobuf
|
|
|
|
|
[get: (id ^ 1) + 2] int32 id = 3;
|
|
|
|
|
```
|
|
|
|
|
`ExpressionInverter` จะทำการคลายโหนด (Unwind) ย้อนกลับจาก Root ของ AST Expression Tree:
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
flowchart TD
|
|
|
|
|
Input["Input: (id ^ 1) + 2"] --> Root["Root Node: Operator (+) with Constant (2)"]
|
|
|
|
|
Root --> InvertAdd["Invert (+) to (-)<br/>New Accumulator: (WireValue - 2)"]
|
|
|
|
|
InvertAdd --> NextNode["Next Node: Operator (^) with Constant (1)"]
|
|
|
|
|
NextNode --> InvertXor["Invert (^) to (^)<br/>Self-inverse Operator<br/>New Accumulator: ((WireValue - 2) ^ 1)"]
|
|
|
|
|
InvertXor --> ReachTarget["Reached Target Identifier 'id'"]
|
|
|
|
|
ReachTarget --> Output["Setter Expression: value - 2 ^ 1"]
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### ตารางการสลับตัวดำเนินการพีชคณิต (`ExpressionInverter.cs`):
|
|
|
|
|
- บวก ($+$) $\Longleftrightarrow$ ลบ ($-$)
|
|
|
|
|
- คูณ ($*$) $\Longleftrightarrow$ หาร ($/$)
|
|
|
|
|
- บิตไวส์ XOR ($\oplus$) $\Longleftrightarrow$ บิตไวส์ XOR ($\oplus$) *(XOR เป็น Self-Inverse Operator: $A \oplus B \oplus B = A$)*
|
|
|
|
|
- ลบเอกภาพ (Unary $-$) $\Longleftrightarrow$ ลบเอกภาพ (Unary $-$)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 4. ระบบ Meta Fields และ Message Factory แบบ Zero-Reflection
|
|
|
|
|
|
|
|
|
|
### 4.1 การฝัง Metadata (CmdId/Opcode) โดยไม่เสีย Overhead บนสายส่ง
|
|
|
|
|
ในไฟล์ `.proto` สามารถกำหนดฟิลด์เป็น `meta`:
|
|
|
|
|
```protobuf
|
|
|
|
|
message PlayerLoginCsReq {
|
|
|
|
|
meta int32 cmd_id = 11;
|
|
|
|
|
string token = 1;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
เมื่อคอมไพล์:
|
|
|
|
|
- `cmd_id` จะไม่ถูกนับรวมใน `CalculateSize()`
|
|
|
|
|
- `cmd_id` จะไม่ถูกเขียนลง `WriteTo(CodedOutputStream output)`
|
|
|
|
|
- `cmd_id` จะไม่ถูกอ่านใน `MergeFrom(CodedInputStream input)`
|
|
|
|
|
- คลาสจะมีเมธอด:
|
|
|
|
|
```csharp
|
|
|
|
|
public const int CmdId = 11;
|
|
|
|
|
public int GetCmdId() => CmdId;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4.2 การสร้าง Compile-Time Factory Jump Table (`IFactoryMessage`)
|
|
|
|
|
เมื่อกำหนด `option message_factory = int32 cmd_id;` ที่หัวไฟล์ `.proto`
|
|
|
|
|
คอมไพเลอร์จะรวบรวมทุก Message ที่มีฟิลด์ `meta int32 cmd_id = X;` แล้วสร้างไฟล์ `MessageFactory.cs`:
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
public static class MessageFactory
|
|
|
|
|
{
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
public static IFactoryMessage Create(int cmdId)
|
|
|
|
|
{
|
|
|
|
|
switch (cmdId)
|
|
|
|
|
{
|
|
|
|
|
case 11:
|
|
|
|
|
return new PlayerLoginCsReq();
|
|
|
|
|
case 12:
|
|
|
|
|
return new PlayerLoginScRsp();
|
|
|
|
|
default:
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> [!TIP]
|
|
|
|
|
> **ความได้เปรียบระดับสูงด้านประสิทธิภาพ:**
|
|
|
|
|
> การสร้าง Switch-Case สำหรับ Type Instantiation ในขั้นตอน Compile-Time ทำให้คอมไพลเลอร์ C# และ JIT/AOT สามารถปรับให้เป็น **Jump Table (O(1) Direct Branching)** ในระดับ Assembly CPU Instruction ได้โดยตรง ปราศจากการทำ Boxing/Unboxing, แฮชแมป หรือ Reflection ใดๆ ทั้งสิ้น
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. การสร้างโค้ด C# และการปรับปรุงประสิทธิภาพ (CodeGen & Performance Optimizations)
|
|
|
|
|
|
|
|
|
|
โค้ด C# ที่ FireflyProtoc สร้างขึ้นมานั้น มีการใส่ Attribute พิเศษเพื่อรีดประสิทธิภาพฮาร์ดแวร์สูงสุด:
|
|
|
|
|
|
|
|
|
|
1. **`[MethodImpl(MethodImplOptions.AggressiveInlining)]`**:
|
|
|
|
|
- บังคับให้ JIT และ NativeAOT นำโค้ดของเมธอด (เช่น Getter/Setter, Serialization routines) ไปวางแทนที่จุดเรียกโดยตรง (Inline Expansion) เพื่อตัดค่าใช้จ่ายในการ Call Stack ของ CPU
|
|
|
|
|
2. **`[DebuggerNonUserCode]`**:
|
|
|
|
|
- ป้องกันไม่ให้ Debugger กระโดดเข้าไปในโค้ดที่สร้างขึ้นโดยอัตโนมัติ เพิ่มความสะดวกในการดีบักระบบของผู้พัฒนา
|
|
|
|
|
3. **Partial Class Architecture (`public partial class`)**:
|
|
|
|
|
- คลาสที่สร้างขึ้นทั้งหมดเป็น `partial class` ทำให้ผู้พัฒนาสามารถเขียนโค้ดส่วนขยาย (Extension Logic, Custom Operators, Helper Methods) แยกไฟล์ได้ โดยที่โค้ดจะไม่สูญหายเมื่อคอมไพล์ `.proto` ใหม่
|
|
|
|
|
4. **Zero-Allocation Stack Buffer ใน Varint Encoding**:
|
|
|
|
|
- เมธอดสำหรับเขียนตัวเลข Varint ใช้ `Span<byte> buffer = stackalloc byte[10];` บน Stack Memory ล้วนๆ ไม่มีการจอง Heap ใดๆ
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. รันไทม์ภายในของ Firefly (Firefly.Protobuf Runtime Architecture)
|
|
|
|
|
|
|
|
|
|
FireflyProtoc ไม่ได้พึ่งพาไลบรารี `Google.Protobuf.dll` แต่มาพร้อมรันไทม์ขนาดเล็กของตัวเองชื่อ **`Firefly.Protobuf`** ซึ่งอยู่ในโฟลเดอร์ `runtime/`:
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
classDiagram
|
|
|
|
|
class IMessage {
|
|
|
|
|
<<interface>>
|
|
|
|
|
+CalculateSize() int
|
|
|
|
|
+MergeFrom(CodedInputStream input)
|
|
|
|
|
+WriteTo(CodedOutputStream output)
|
|
|
|
|
+ToByteArray() byte[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class IFactoryMessage {
|
|
|
|
|
<<interface>>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class CodedInputStream {
|
|
|
|
|
-byte[] m_Buffer
|
|
|
|
|
-int m_Position
|
|
|
|
|
+ReadTag() int
|
|
|
|
|
+ReadString() string
|
|
|
|
|
+ReadInt32() int
|
|
|
|
|
+ReadUInt32() uint
|
|
|
|
|
+ReadBytes() ByteString
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class CodedOutputStream {
|
|
|
|
|
-Stream m_Stream
|
|
|
|
|
+WriteRawTag(int tag)
|
|
|
|
|
+WriteString(string value)
|
|
|
|
|
+WriteInt32(int value)
|
|
|
|
|
+WriteUInt32(uint value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MessageParser~T~ {
|
|
|
|
|
+ParseFrom(byte[] data) T
|
|
|
|
|
+ParseFrom(CodedInputStream input) T
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IMessage <|.. IFactoryMessage
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### รายละเอียดส่วนประกอบหลักของรันไทม์:
|
|
|
|
|
- **`CodedInputStream` / `CodedOutputStream`**: ควบคุมการอ่านและเขียนข้อมูลระดับบิตและไบต์ตามมาตรฐาน Wire Type (Varint, Fixed32, Fixed64, Length-Delimited)
|
|
|
|
|
- **`ByteString`**: ตัวแทนของ Immutable Byte Array สำหรับฟิลด์ประเภท `bytes`
|
|
|
|
|
- **`RepeatedField<T>`**: คอลเลกชันสำหรับฟิลด์ซ้ำ (`repeated`) ที่ปรับแต่งให้ขยายบัฟเฟอร์อย่างมีประสิทธิภาพ
|
|
|
|
|
- **`MapField<TKey, TValue>`**: โครงสร้างข้อมูล Dictionary ประสิทธิภาพสูงสำหรับฟิลด์ประเภท `map<K, V>`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 7. ตัวอย่างจริงจากการคอมไพล์ (Real-World Compilation Walkthrough)
|
|
|
|
|
|
|
|
|
|
### 7.1 โค้ดต้นฉบับ Protobuf (`example.proto`):
|
|
|
|
|
```protobuf
|
|
|
|
|
syntax = "proto3";
|
|
|
|
|
|
|
|
|
|
option csharp_namespace = "FireflyProtoc.Example";
|
|
|
|
|
option message_factory = int32 department_id;
|
|
|
|
|
|
|
|
|
|
message Person {
|
|
|
|
|
meta int32 department_id = 1;
|
|
|
|
|
string name = 1;
|
|
|
|
|
string email = 2;
|
|
|
|
|
[get: (id ^ 1) + 2] int32 id = 3;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 7.2 โค้ด C# ที่คอมไพล์ได้จริง (`Person.cs`):
|
|
|
|
|
```csharp
|
|
|
|
|
// Generated by FireflyProtoc! do not modify!
|
|
|
|
|
#pragma warning disable CS8618
|
|
|
|
|
|
|
|
|
|
namespace FireflyProtoc.Example;
|
|
|
|
|
|
|
|
|
|
[global::System.Diagnostics.DebuggerNonUserCode]
|
|
|
|
|
public partial class Person : global::Firefly.Protobuf.IMessage<Person>, global::Firefly.Protobuf.IFactoryMessage
|
|
|
|
|
{
|
|
|
|
|
public const global::System.Int32 DepartmentId = 1;
|
|
|
|
|
public const global::System.Int32 NameFieldNumber = 1;
|
|
|
|
|
public const global::System.Int32 EmailFieldNumber = 2;
|
|
|
|
|
public const global::System.Int32 IdFieldNumber = 3;
|
|
|
|
|
|
|
|
|
|
private global::System.Int32 _Id;
|
|
|
|
|
|
|
|
|
|
[global::System.Diagnostics.DebuggerNonUserCode]
|
|
|
|
|
public global::System.Int32 Id
|
|
|
|
|
{
|
|
|
|
|
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return (_Id ^ 1) + 2; // คำนวณค่าเพื่อส่งออก
|
|
|
|
|
}
|
|
|
|
|
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_Id = value - 2 ^ 1; // คำนวณค่าย้อนกลับอัตโนมัติจากการ Invert นิพจน์!
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
public global::System.Int32 GetDepartmentId()
|
|
|
|
|
{
|
|
|
|
|
return DepartmentId; // Meta Field คืนค่าได้ แต่ไม่ถูกเขียนลง Wire
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteTo(global::Firefly.Protobuf.CodedOutputStream output)
|
|
|
|
|
{
|
|
|
|
|
if (Name != default)
|
|
|
|
|
{
|
|
|
|
|
output.WriteRawTag(10);
|
|
|
|
|
output.WriteString(_Name);
|
|
|
|
|
}
|
|
|
|
|
if (Email != default)
|
|
|
|
|
{
|
|
|
|
|
output.WriteRawTag(18);
|
|
|
|
|
output.WriteString(_Email);
|
|
|
|
|
}
|
|
|
|
|
if (Id != default)
|
|
|
|
|
{
|
|
|
|
|
output.WriteRawTag(24);
|
|
|
|
|
output.WriteInt32(_Id); // ส่งค่าดิบที่ผ่านการพรางแล้ว
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 8. การนำไปประยุกต์ใช้ในระบบเซิร์ฟเวอร์เกม (Integration with HSR/Genshin Private Servers)
|
|
|
|
|
|
2026-10-31 23:27:27 +07:00
|
|
|
ในสถาปัตยกรรมเซิร์ฟเวอร์ เช่น **GaC-hkrpg-go** หรือ **C# NativeAOT Reference Server**:
|
2026-10-31 20:47:36 +07:00
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
autonumber
|
|
|
|
|
actor Client as Game Client (Obfuscated Traffic)
|
|
|
|
|
participant KCP as Server KCP Layer
|
|
|
|
|
participant Factory as MessageFactory.Create(cmdId)
|
|
|
|
|
participant Proto as Generated IFactoryMessage
|
|
|
|
|
|
|
|
|
|
Client->>KCP: UDP Packet (CmdId: 11, Obfuscated Payload)
|
|
|
|
|
KCP->>Factory: MessageFactory.Create(11) [O(1) Switch Jump]
|
|
|
|
|
Factory-->>KCP: Return new PlayerGetTokenCsReq()
|
|
|
|
|
KCP->>Proto: message.MergeFrom(stream)
|
|
|
|
|
Note over Proto: ฟิลด์ที่พรางค่าจะถูกคำนวณย้อนกลับเป็นค่าจริงอัตโนมัติใน Setter!
|
|
|
|
|
KCP->>ServerHandler: Handler.Handle(session, message)
|
|
|
|
|
Note over ServerHandler: Server ใช้งานฟิลด์ได้ตามปกติโดยไม่ต้องรู้สูตรพรางข้อมูล!
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
1. **ขจัด Boilerplate Code**: เซิร์ฟเวอร์ไม่จำเป็นต้องมีโค้ดแปลงค่า (Converter Functions) นับร้อยจุด เพราะการพรางและถอดรหัสถูกฝังอยู่ในตัว Class ของ Protobuf โดยตรง
|
|
|
|
|
2. **ความเข้ากันได้กับโปรโตคอลหลายเวอร์ชัน**: สามารถกำหนดสูตรการพรางค่าที่แตกต่างกันในแต่ละแพตช์ได้โดยเพียงแค่แก้สัญลักษณ์ในไฟล์ `.proto`
|
|
|
|
|
3. **การทำงานร่วมกับระบบ Router**: สามารถเชื่อมโยง Opcode Dispatcher เข้ากับ `MessageFactory.Create(cmdId)` ได้ทันที
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 9. คู่มือวิศวกรรมสำหรับ AI ในการขยายขีดความสามารถ (AI Extension & Engineering Guide)
|
|
|
|
|
|
|
|
|
|
หากโมเดล AI อื่นต้องการปรับแต่งหรือขยายขีดความสามารถของ FireflyProtoc ต่อไป:
|
|
|
|
|
|
|
|
|
|
1. **ข้อควรระวังเรื่อง CLI Output Path**:
|
|
|
|
|
ใน `src/FireflyProtoc.cs` บรรทัดที่ 21 มีการฮาร์ดโค้ด `$"./{outputFolder}"`
|
|
|
|
|
หากส่ง Path แบบ Absolute (เช่น `C:\Users\...`) จะทำให้ระบบพาธขัดข้อง (`./C:\...`)
|
|
|
|
|
**คำแนะนำในการแก้:** เปลี่ยนให้ใช้ `Path.GetFullPath(outputFolder)` หรือตรวจสอบ `Path.IsPathRooted(outputFolder)` ก่อนต่อสตริง
|
|
|
|
|
2. **การคอมไพล์ในสภาพแวดล้อม .NET 10**:
|
|
|
|
|
ไฟล์ `FireflyProtoc.csproj` มีการเปิดแฟล็ก `<PublishAot>true</PublishAot>` สำหรับ `net9.0` ซึ่งหากรันคำสั่ง `dotnet run` ภายใต้ .NET 10 SDK จะเกิดข้อผิดพลาดในการหา ILCompiler Targets
|
|
|
|
|
**คำแนะนำในการรัน:** ให้ส่งแฟล็ก `-c Release -p:PublishAot=false` ขณะทดสอบ หรือปรับ `<TargetFramework>` ให้ตรงกับเวอร์ชันของ SDK ที่ติดตั้ง
|
|
|
|
|
3. **การเพิ่มตัวดำเนินการพรางข้อมูล (Bitshift, Modulo)**:
|
|
|
|
|
หากต้องการให้รองรับการเลื่อนบิต (`<<`, `>>`) หรือเศษเหลือ (`%`) ให้ไปเพิ่ม Mapping ผกผันใน `InvertWithRight` และ `InvertWithLeft` ของ `src/Semantic/ExpressionInverter.cs`:
|
|
|
|
|
- `<<` ผกผันด้วย `>>`
|
|
|
|
|
- `>>` ผกผันด้วย `<<`
|