| buffer | ||
| builder | ||
| packet | ||
| parser | ||
| xml | ||
| .gitignore | ||
| go.mod | ||
| README.md | ||
PacketStruct
This package is the packet structure reader and serde for the special XML format the emulator uses. Meticulously engineered to be byte-for-byte compatible with the C++ implementation.
Current features:
- Parse XML packet definitions
- Serialize Go data structures to binary EQ2 packets
- Deserialize binary packets back to Go structures
- Support versioned packet definitions for different client versions
- Handle complex features like arrays, conditional fields, and nested structures
Meh
Personally, not a big fan of the current XML-based versioned packet structure. I spent a lot of time trying to figure out how or whether it was even possible to modify it, but aside from cleaning up the syntax and inconsistencies I couldn't come up with anything. Therefore, this is going to be as hyper-performant as I can make it, but does not reflect my personal taste.
Package Structure
packetstruct/
├── buffer/ Binary I/O operations (little-endian)
├── builder/ XML parsing and template management
├── packet/ Core packet structure and serialization
└── parser/ SAX-style XML parser
Quick Start
Loading Packet Definitions
import "git.sharkk.net/eq2go/packetstruct/builder"
// Create a builder and load XML definitions
sb := builder.NewStructBuilder()
err := sb.ProcessXMLFile("metadata/TestStructs.xml")
if err != nil {
log.Fatal(err)
}
// Get a packet structure for a specific client version
ps, err := sb.GetStruct("LS_LoginReply", 1096)
if err != nil {
log.Fatal(err)
}
Setting Field Values
// Set individual fields
ps.SetDataByName("session_id", uint32(12345))
ps.SetDataByName("server_name", "TestServer")
ps.SetDataByName("player_level", uint8(50))
// Set array data
ps.SetArrayDataByName("item_id", int32(1001), 0) // First element
ps.SetArrayDataByName("item_id", int32(1002), 1) // Second element
Serializing to Binary
// Serialize to bytes
data, err := ps.Serialize()
if err != nil {
log.Fatal(err)
}
// Send over network
conn.Write(data)
Deserializing from Binary
// Receive packet data
data := make([]byte, 1024)
n, _ := conn.Read(data)
// Get packet structure template
ps, _ := sb.GetStruct("LS_LoginReply", 1096)
// Deserialize
err := ps.LoadPacketData(data[:n])
if err != nil {
log.Fatal(err)
}
// Read field values
sessionID, _ := ps.GetUint32ByName("session_id")
serverName, _ := ps.GetStringByName("server_name")
XML Packet Definitions
Basic Structure
<Struct Name="LS_LoginReply" ClientVersion="1096" OpcodeName="LS_LoginReply">
<Data ElementName="session_id" Type="int32" />
<Data ElementName="server_name" Type="EQ2_16BitString" />
<Data ElementName="player_level" Type="int8" />
</Struct>
Arrays
Arrays require a size variable and element template:
<Struct Name="ItemList" ClientVersion="1">
<Data ElementName="item_count" Type="int8" />
<Data ElementName="items" Type="Array" ArraySize="item_count">
<Data ElementName="item_id" Type="int32" />
<Data ElementName="item_name" Type="EQ2_16BitString" />
<Data ElementName="quantity" Type="int16" />
</Data>
</Struct>
Conditional Fields
Fields can be conditionally included based on other field values:
<Data ElementName="has_guild" Type="int8" />
<Data ElementName="guild_name" Type="EQ2_16BitString" IfSet="has_guild" />
Supported conditionals:
IfSet- Include if variable is setIfNotSet- Include if variable is not setIfEquals- Include if variable equals valueIfNotEquals- Include if variable doesn't equal valueIfFlagSet- Include if flag is setIfFlagNotSet- Include if flag is not set
Oversized Encoding
For variable-length integer encoding:
<Data ElementName="value" Type="int16" Size="255" OversizedByte="0xFF" />
This writes 1 byte if value < 255, otherwise writes marker byte (0xFF) + 2-byte value.
Nested Structures
Reference other packet definitions as substructs:
<Data ElementName="player_position" Type="int8" Substruct="Position_Struct" />
Supported Types
All types from the types package:
- Integers:
int8,int16,int32,int64 - Signed:
sint8,sint16,sint32,sint64 - Floats:
float,double - Strings:
EQ2_8BitString,EQ2_16BitString,EQ2_32BitString - Special:
char,color,equipment,array,item
Version Management
The package supports multiple versions of the same packet structure:
// Get best matching version for client
ps, err := sb.GetStruct("LS_LoginReply", 1096)
// Check available versions
version, err := sb.GetVersion("LS_LoginReply", 1096)
Version selection follows "latest compatible" logic - finds the highest version ≤ requested version.
Thread Safety
StructBuilderis thread-safe for concurrent reads (GetStruct)- Individual
PacketStructinstances should not be shared across goroutines - Clone packets for concurrent use:
ps2 := ps.Clone()
Advanced Features
Custom Field Metadata
ds := packet.NewDataStruct()
ds.SetName("player_health")
ds.SetDataType(types.DataStructInt32)
ds.SetDefaultValue(100)
ds.SetOversized(32767)
ds.SetOversizedByte(0xFF)
ps.AddDataStruct(ds)
Array Templates
// Create array element template
template := packet.NewPacketStruct()
template.SetSubPacket(true)
idField := packet.NewDataStruct()
idField.SetName("id")
idField.SetDataType(types.DataStructInt32)
template.AddDataStruct(idField)
// Create array field
arrayField := packet.NewDataStruct()
arrayField.SetName("items")
arrayField.SetDataType(types.DataStructArray)
arrayField.SetArraySizeVariable("count")
arrayField.SetArrayTemplate(template)
ps.AddDataStruct(arrayField)
Flags and Conditionals
// Add flag
ps.AddFlag("has_advanced_stats")
// Check flag
if ps.HasFlag("has_advanced_stats") {
// Include advanced fields
}
Testing
Run the complete test suite:
cd go/packetstruct
go test ./...
Run comparison tests against C++ implementation:
cd test-harness
./compare.sh
This verifies byte-for-byte compatibility with the C++ emulator.
Performance
- Zero-copy deserialization where possible
- Efficient binary I/O with buffered operations
- Template-based packet creation minimizes allocations
- Concurrent reads from StructBuilder cache
Examples
See packet/packet_test.go for comprehensive examples including:
- Simple field serialization
- Array handling
- Oversized encoding
- Multiple type handling
- Round-trip tests
Compatibility
Fully compatible with the C++ EQ2 emulator packet system. All serialization output matches byte-for-byte with the C++ implementation.
Dependencies
git.sharkk.net/eq2go/types- EQ2 type definitions- Go 1.25.4 or later
License
Part of the EQ2 Emulator project.