eq2go/internal/packets/PARSER.md
2025-07-27 22:16:36 -05:00

8.6 KiB

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

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

"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

"flag:loot"             // Parse if loot flag is set
"!flag:loot"            // Parse if loot flag is not set

Version Checks

"version>=1188"         // Parse if version 1188 or higher
"version<562"           // Parse if version below 562

Comparisons

"stat_type!=6"          // Parse if stat_type is not 6
"level>=10"             // Parse if level is 10 or higher

String Length

"name!>5"               // Parse if name is longer than 5 characters
"description!<=100"     // Parse if description is 100 chars or less

Bitwise Operations

"header_flags&0x01"     // Parse if bit 1 is set in header_flags

Combining Conditions

"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:

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:

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:

{
	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:

{
	Type: common.TypeInt16,
	Oversized: 127,  // Switch to 16-bit if first byte is 127
}

Array Index Substitution

Reference array indices in conditions:

"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:

// 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

// 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

// 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

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.