# Packet Parser Write packet definitions as data instead of code! This parser handles complex binary protocols with versioning, conditional fields, and nested structures. ## Quick Start ```go import "your-project/internal/common" var MyPacketDef = &PacketDef{ Fields: map[string]FieldDesc{ "count": {Type: common.TypeInt16}, "items": {Type: common.TypeArray, Condition: "var:count", SubDef: ItemDef}, "name": {Type: common.TypeString8}, }, Orders: map[uint32][]string{ 1: {"count", "items", "name"}, }, } // Parse it result, err := MyPacketDef.Parse(data, version, flags) ``` ## Field Types ### Integer Types | Type | Description | Size | Example | |------|-------------|------|---------| | `common.TypeInt8` | Unsigned 8-bit integer | 1 byte | `{Type: common.TypeInt8}` | | `common.TypeInt16` | Unsigned 16-bit integer | 2 bytes | `{Type: common.TypeInt16}` | | `common.TypeInt32` | Unsigned 32-bit integer | 4 bytes | `{Type: common.TypeInt32}` | | `common.TypeInt64` | Unsigned 64-bit integer | 8 bytes | `{Type: common.TypeInt64}` | | `common.TypeSInt8` | Signed 8-bit integer | 1 byte | `{Type: common.TypeSInt8}` | | `common.TypeSInt16` | Signed 16-bit integer | 2 bytes | `{Type: common.TypeSInt16}` | | `common.TypeSInt32` | Signed 32-bit integer | 4 bytes | `{Type: common.TypeSInt32}` | | `common.TypeSInt64` | Signed 64-bit integer | 8 bytes | `{Type: common.TypeSInt64}` | ### String Types | Type | Description | Returns | Example | |------|-------------|---------|---------| | `common.TypeString8` | 1-byte length prefix | `EQ2String8{Size, Data}` | `{Type: common.TypeString8}` | | `common.TypeString16` | 2-byte length prefix | `EQ2String16{Size, Data}` | `{Type: common.TypeString16}` | | `common.TypeString32` | 4-byte length prefix | `EQ2String32{Size, Data}` | `{Type: common.TypeString32}` | ### Other Types | Type | Description | Returns | Example | |------|-------------|---------|---------| | `common.TypeChar` | Fixed-size byte array | `[]byte` | `{Type: common.TypeChar, Length: 10}` | | `common.TypeFloat` | 32-bit float | `float32` | `{Type: common.TypeFloat}` | | `common.TypeDouble` | 64-bit float | `float64` | `{Type: common.TypeDouble}` | | `common.TypeColor` | RGB color value | `EQ2Color{Red, Green, Blue}` | `{Type: common.TypeColor}` | | `common.TypeEquipment` | Equipment item | `EQ2EquipmentItem{Type, Color, Highlight}` | `{Type: common.TypeEquipment}` | | `common.TypeArray` | Array of substructures | `[]map[string]any` | `{Type: common.TypeArray, SubDef: ItemDef}` | ## Conditions Control when fields are parsed using simple condition strings: ### Variable Checks ```go "var:item_count" // Parse if item_count exists and is non-zero "!var:item_count" // Parse if item_count doesn't exist or is zero ``` ### Flag Checks ```go "flag:loot" // Parse if loot flag is set "!flag:loot" // Parse if loot flag is not set ``` ### Version Checks ```go "version>=1188" // Parse if version 1188 or higher "version<562" // Parse if version below 562 ``` ### Comparisons ```go "stat_type!=6" // Parse if stat_type is not 6 "level>=10" // Parse if level is 10 or higher ``` ### String Length ```go "name!>5" // Parse if name is longer than 5 characters "description!<=100" // Parse if description is 100 chars or less ``` ### Bitwise Operations ```go "header_flags&0x01" // Parse if bit 1 is set in header_flags ``` ### Combining Conditions ```go "var:count,var:size" // OR: either count or size exists "version>=562&version<1188" // AND: version between 562-1187 ``` ## Version Ordering Different versions can have different field orders: ```go Orders: map[uint32][]string{ 373: {"basic_field", "name"}, 1188: {"basic_field", "new_field", "name"}, 2000: {"basic_field", "new_field", "another_field", "name"}, } ``` The parser automatically uses the highest version ≤ your target version. ## Substructures Create reusable packet definitions: ```go var ItemDef = &PacketDef{ Fields: map[string]FieldDesc{ "item_id": {Type: common.TypeInt32}, "item_name": {Type: common.TypeString16}, "quantity": {Type: common.TypeInt8, Condition: "version>=546"}, "color": {Type: common.TypeColor, Condition: "flag:has_colors"}, }, Orders: map[uint32][]string{ 1: {"item_id", "item_name", "quantity", "color"}, }, } var InventoryDef = &PacketDef{ Fields: map[string]FieldDesc{ "item_count": {Type: common.TypeInt8}, "items": {Type: common.TypeArray, Condition: "var:item_count", SubDef: ItemDef}, }, Orders: map[uint32][]string{ 1: {"item_count", "items"}, }, } ``` ## Advanced Features ### Type Switching Use different types based on conditions: ```go { Type: common.TypeInt32, Type2: common.TypeFloat, Type2Cond: "stat_type!=6", // Use float if stat_type is not 6 } ``` ### Oversized Fields Handle fields that can exceed normal size limits: ```go { Type: common.TypeInt16, Oversized: 127, // Switch to 16-bit if first byte is 127 } ``` ### Array Index Substitution Reference array indices in conditions: ```go "var:item_type_%i" // Substitutes %i with current array index ``` ## Working with Structured Types ### String Types String types return structured objects with size and data: ```go // TypeString8 returns: EQ2String8{Size: uint8, Data: string} // TypeString16 returns: EQ2String16{Size: uint16, Data: string} // TypeString32 returns: EQ2String32{Size: uint32, Data: string} result, _ := packet.Parse(data, version, flags) if nameField, ok := result["player_name"].(common.EQ2String16); ok { fmt.Printf("Name: %s (length: %d)\n", nameField.Data, nameField.Size) } ``` ### Color Types ```go // TypeColor returns: EQ2Color{Red: uint8, Green: uint8, Blue: uint8} if colorField, ok := result["shirt_color"].(common.EQ2Color); ok { fmt.Printf("RGB: %d,%d,%d\n", colorField.Red, colorField.Green, colorField.Blue) } ``` ### Equipment Types ```go // TypeEquipment returns: EQ2EquipmentItem{Type: uint16, Color: EQ2Color, Highlight: EQ2Color} if equipField, ok := result["helmet"].(common.EQ2EquipmentItem); ok { fmt.Printf("Equipment Type: %d\n", equipField.Type) fmt.Printf("Color: RGB(%d,%d,%d)\n", equipField.Color.Red, equipField.Color.Green, equipField.Color.Blue) } ``` ## Complete Example ```go import "your-project/internal/common" var QuestPacketDef = &PacketDef{ Fields: map[string]FieldDesc{ "quest_id": {Type: common.TypeInt32}, "has_rewards": {Type: common.TypeInt8}, "reward_count": {Type: common.TypeInt8, Condition: "var:has_rewards"}, "rewards": {Type: common.TypeArray, Condition: "var:reward_count", SubDef: RewardDef}, "is_complete": {Type: common.TypeInt8}, "complete_text": {Type: common.TypeString16, Condition: "var:is_complete"}, "quest_color": {Type: common.TypeColor, Condition: "version>=1200"}, "version_field": {Type: common.TypeInt32, Condition: "version>=1200"}, }, Orders: map[uint32][]string{ 1: {"quest_id", "has_rewards", "reward_count", "rewards", "is_complete", "complete_text"}, 1200: {"quest_id", "has_rewards", "reward_count", "rewards", "is_complete", "complete_text", "quest_color", "version_field"}, }, } var RewardDef = &PacketDef{ Fields: map[string]FieldDesc{ "reward_type": {Type: common.TypeInt8}, "amount": {Type: common.TypeInt32}, "bonus": {Type: common.TypeInt16, Condition: "reward_type==1"}, "item_color": {Type: common.TypeColor, Condition: "reward_type==2"}, }, Orders: map[uint32][]string{ 1: {"reward_type", "amount", "bonus", "item_color"}, }, } // Usage result, err := QuestPacketDef.Parse(packetData, 1200, questFlags) if err != nil { log.Fatal(err) } // Access structured data if questText, ok := result["complete_text"].(common.EQ2String16); ok { fmt.Printf("Quest completion text: %s\n", questText.Data) } if questColor, ok := result["quest_color"].(common.EQ2Color); ok { fmt.Printf("Quest color: #%02x%02x%02x\n", questColor.Red, questColor.Green, questColor.Blue) } ``` ## Migration from Old Types | Old Type | New Type | Breaking Change | |----------|----------|-----------------| | `Uint8` | `common.TypeInt8` | Type name only | | `Uint16` | `common.TypeInt16` | Type name only | | `Uint32` | `common.TypeInt32` | Type name only | | `String8` | `common.TypeString8` | Returns `EQ2String8` struct | | `String16` | `common.TypeString16` | Returns `EQ2String16` struct | | `ByteArray` | `common.TypeChar` | Type name only | | `Float32` | `common.TypeFloat` | Type name only | | `SubArray` | `common.TypeArray` | Type name only | **Note**: String types now return structured objects instead of plain strings. Access the string data via the `.Data` field.