# EQ2 Packet Parser A dead-simple way to turn EverQuest 2's binary packet data into Go structs. Just add some tags to your struct fields and let reflection do the heavy lifting. ## How it works ```go // Your packet structure type LoginPacket struct { UserID uint32 `eq2:"int32"` Username common.EQ2String16 `eq2:"string16"` Level uint8 `eq2:"int8"` } // Parse some bytes data := []byte{...} // whatever bytes you got parser := parser.NewParser(data) var packet LoginPacket err := parser.ParseStruct(&packet) // boom, packet is now filled with data ``` ## Tag Syntax Just slap `eq2:"type,options"` on your fields and you're good to go. ### The usual suspects ```go PlayerID uint32 `eq2:"int32"` // 32-bit integer Health uint16 `eq2:"int16"` // 16-bit integer Alive uint8 `eq2:"int8"` // 8-bit integer Damage float32 `eq2:"float"` // 32-bit float Name common.EQ2String16 `eq2:"string16"` // EQ2's weird string format Color common.EQ2Color `eq2:"color"` // RGB color ``` ### Arrays (because everything's an array in EQ2) ```go // Fixed size - always 5 items Stats []uint16 `eq2:"int16,len=5"` // Dynamic size - read ItemCount first, then that many items ItemCount uint8 `eq2:"int8"` Items []Item `eq2:"array,arraysize=ItemCount"` ``` ### Conditionals (the fun stuff) ```go // Only parse this if Channel equals 1 MessageType uint8 `eq2:"int8,if=Channel==1"` // Only parse if HasRewards is truthy HasRewards uint8 `eq2:"int8"` RewardData *RewardInfo `eq2:"substruct,ifvariableset=HasRewards"` // Only parse if we set the "has_equipment" flag Equipment []Equipment `eq2:"equipment,ifflag=has_equipment"` ``` ### Type switching (when EQ2 reuses the same bytes for different things) ```go // Normally parse as int32, but if StatType != 6, parse as float instead StatType uint8 `eq2:"int8"` StatValue any `eq2:"int32,type2=float,type2criteria=StatType!=6"` ``` ### Size limits (because EQ2 packets can get weird) ```go // If the data is bigger than 1000 bytes, just truncate it DataSize uint16 `eq2:"int16"` Data []byte `eq2:"char,len=DataSize,maxsize=1000,skipoversized"` ``` ## Multiple client versions EQ2 has like 50 different client versions, each with slightly different packet layouts. Handle it like this: ```go registry := parser.NewVersionRegistry() registry.RegisterStruct("LoginReply", "1.0", reflect.TypeOf(LoginReplyV1{})) registry.RegisterStruct("LoginReply", "2.0", reflect.TypeOf(LoginReplyV2{})) // Parser picks the right version (or falls back to closest match) result, err := parser.ParseWithVersion(registry, "LoginReply", clientVersion) ``` ## Real example ```go type CharacterData struct { // Basic stuff CharID uint32 `eq2:"int32"` Name common.EQ2String16 `eq2:"string16"` Level uint8 `eq2:"int8"` // Guild info (only if they're in a guild) HasGuild uint8 `eq2:"int8"` GuildID uint32 `eq2:"int32,ifvariableset=HasGuild"` // Variable number of items ItemCount uint16 `eq2:"int16"` Items []InventoryItem `eq2:"array,arraysize=ItemCount"` // Nested stuff Stats PlayerStats `eq2:"substruct"` } type InventoryItem struct { ItemID uint32 `eq2:"int32"` Quantity uint16 `eq2:"int16"` Color common.EQ2Color `eq2:"color"` } type PlayerStats struct { Health uint32 `eq2:"int32"` Mana uint32 `eq2:"int32"` Stamina uint32 `eq2:"int32"` } ``` ## Converting from XML If you've got EQ2's XML packet definitions, the conversion is pretty straightforward: | XML | Go Tag | |-----|--------| | `Type="int32"` | `eq2:"int32"` | | `ArraySizeVariable="count"` | `arraysize=Count` | | `IfVariableSet="flag"` | `ifvariableset=Flag` | | `Size="5"` | `len=5` |