Packet Structure Parser
This package provides a Go implementation of the EQ2 packet structure system, compatible with the C++ ConfigReader and PacketStruct classes. It parses XML packet definitions and provides runtime serialization/deserialization capabilities.
Features
- XML Parsing: Reads packet and substruct definitions from XML files
- Version Management: Handles multiple versions of packet structures
- Type Support: All EQ2 data types including integers, floats, strings, and colors
- Conditional Fields: Support for if-set/if-not-set field conditions
- Zero Allocation: Optimized for performance with minimal allocations
- ConfigReader Compatible: Drop-in replacement for C++ ConfigReader
Usage
Loading Packet Definitions
// Create a config reader
cr := structs.NewConfigReader("./")
// Load all XML files from structs/xml directory
err := cr.LoadStructs()
// Or load specific files
err := cr.LoadFiles([]string{"structs/xml/login/LoginRequest.xml"})
Creating and Using Packet Structs
// Get a packet struct for a specific version
ps, err := cr.GetStruct("LoginRequest", 1193)
// Set field values
ps.Set("username", "player123")
ps.Set("password", "secret")
ps.Set("version", uint16(1193))
// Serialize to bytes
data, err := ps.Serialize()
// Deserialize from bytes
ps2, err := cr.GetStruct("LoginRequest", 1193)
err = ps2.Deserialize(data)
// Get field values
username, _ := ps2.Get("username")
Direct Parser Usage
// Create a parser
parser := structs.NewParser("structs")
// Load all XML files
err := parser.LoadAll()
// Get a specific packet version
version, err := parser.GetPacketVersion("LoginRequest", 1193)
// Create packet struct directly
ps := structs.NewPacketStruct(version)
XML Format
Packet definitions use the following XML format:
<packet name="LoginRequest">
<version number="1">
<str16 name="username"/>
<str16 name="password"/>
<u32 name="acctNum"/>
<u16 name="version"/>
</version>
<version number="562">
<str16 name="accesscode"/>
<str16 name="username"/>
<str16 name="password"/>
<u8 name="unknown" size="8"/>
<u32 name="version"/>
</version>
</packet>
Substructs use a similar format:
<substruct name="Item">
<version number="1">
<u32 name="unique_id"/>
<u32 name="bag_id"/>
<u8 name="slot_id"/>
<char name="name" size="64"/>
</version>
</substruct>
Supported Field Types
- Integers: u8, u16, u32, u64, i8, i16, i32, i64
- Floats: float (32-bit), double (64-bit)
- Strings: str8, str16, str32 (length-prefixed), char (fixed-size or null-terminated)
- Special: color, eq2color
- Complex: substruct, array
Field Attributes
name
: Field name (required)size
: Array size or fixed string lengthsizevar
: Variable containing the array sizedefault
: Default valueifset
: Only include if specified field is setifnotset
: Only include if specified field is not set
Performance
The parser is optimized for performance with minimal allocations:
- Serialization: ~312 ns/op, 144 B/op, 10 allocs/op
- Deserialization: ~315 ns/op, 112 B/op, 13 allocs/op
Compatibility
This implementation maintains compatibility with the C++ ConfigReader and PacketStruct classes, allowing seamless migration of existing packet definitions and code.
Example Integration
// Initialize config reader
configReader := structs.NewConfigReader("./")
configReader.LoadStructs()
// In your packet handler
func HandleLoginRequest(data []byte, clientVersion uint16) {
// Get the appropriate packet struct
ps, err := configReader.GetStruct("LoginRequest", clientVersion)
if err != nil {
log.Printf("Failed to get LoginRequest struct: %v", err)
return
}
// Deserialize the packet
if err := ps.Deserialize(data); err != nil {
log.Printf("Failed to deserialize LoginRequest: %v", err)
return
}
// Access fields
username, _ := ps.Get("username")
password, _ := ps.Get("password")
// Process login...
}
Directory Structure
structs/
├── xml/
│ ├── common/
│ ├── login/
│ ├── item/
│ ├── spawn/
│ └── world/
├── parser.go # XML parser
├── packet_struct.go # Runtime packet structure
├── config_reader.go # ConfigReader compatible interface
└── README.md