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

361 lines
26 KiB
Markdown

# รายงานการวิเคราะห์สถาปัตยกรรมและการทำงานของ 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)
ในสถาปัตยกรรมเซิร์ฟเวอร์ เช่น **GaC-hkrpg-go** หรือ **March7thHoney**:
```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`:
- `<<` ผกผันด้วย `>>`
- `>>` ผกผันด้วย `<<`