diff --git a/crypto/crc.go b/crypto/crc.go
new file mode 100644
index 0000000..1a2c7c1
--- /dev/null
+++ b/crypto/crc.go
@@ -0,0 +1,32 @@
+package crypto
+
+import (
+ "encoding/binary"
+ "hash/crc32"
+)
+
+// Standard CRC32 polynomial
+var crcTable = crc32.MakeTable(0xEDB88320)
+
+// Fixed key used by EQ2
+var EQ2CRCKey = 0x33624702
+
+// CalculateCRC gets the CRC for a given byte slice using a custom key
+func CalculateCRC(data []byte, key uint32) uint16 {
+ // Pre-process the key (4 rounds of CRC on key bytes)
+ crc := uint32(0xFFFFFFFF)
+ keyBytes := make([]byte, 4)
+ binary.LittleEndian.PutUint32(keyBytes, key)
+
+ for _, b := range keyBytes {
+ crc = crcTable[(crc^uint32(b))&0xFF] ^ (crc >> 8)
+ }
+
+ // Process actual data
+ for _, b := range data {
+ crc = crcTable[(crc^uint32(b))&0xFF] ^ (crc >> 8)
+ }
+
+ // Return lower 16 bits of inverted result
+ return uint16(^crc & 0xFFFF)
+}
diff --git a/crypto/rc4.go b/crypto/rc4.go
new file mode 100644
index 0000000..77a9853
--- /dev/null
+++ b/crypto/rc4.go
@@ -0,0 +1,41 @@
+package crypto
+
+import (
+ "crypto/rc4"
+ "encoding/binary"
+)
+
+type Ciphers struct {
+ client *rc4.Cipher // For decryption (client->server)
+ server *rc4.Cipher // For encryption (server->client)
+}
+
+// NewCiphers creates a new pair of RC4 ciphers for the client and server
+func NewCiphers(key int64) (*Ciphers, error) {
+ keyBytes := make([]byte, 8)
+ binary.LittleEndian.PutUint64(keyBytes, uint64(key))
+
+ // Client cipher uses NOT of key
+ clientKeyBytes := make([]byte, 8)
+ binary.LittleEndian.PutUint64(clientKeyBytes, uint64(^key))
+
+ clientCipher, err := rc4.NewCipher(keyBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ serverCipher, err := rc4.NewCipher(clientKeyBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ // Drop first 20 bytes from both ciphers
+ drop := make([]byte, 20)
+ clientCipher.XORKeyStream(drop, drop)
+ serverCipher.XORKeyStream(drop, drop)
+
+ return &Ciphers{
+ client: clientCipher,
+ server: serverCipher,
+ }, nil
+}
diff --git a/structs/README.md b/structs/README.md
deleted file mode 100644
index 3ac04ad..0000000
--- a/structs/README.md
+++ /dev/null
@@ -1,174 +0,0 @@
-# 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
-
-```go
-// 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
-
-```go
-// 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
-
-```go
-// 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:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-Substructs use a similar format:
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-## 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 length
-- `sizevar`: Variable containing the array size
-- `default`: Default value
-- `ifset`: Only include if specified field is set
-- `ifnotset`: 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
-
-```go
-// 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
-```
\ No newline at end of file
diff --git a/structs/config_reader.go b/structs/config_reader.go
deleted file mode 100644
index d1e4612..0000000
--- a/structs/config_reader.go
+++ /dev/null
@@ -1,177 +0,0 @@
-package structs
-
-import (
- "fmt"
- "path/filepath"
- "sync"
-)
-
-// ConfigReader provides a compatible interface to the C++ ConfigReader
-// It manages packet struct definitions and creates instances for specific versions
-type ConfigReader struct {
- parser *Parser
- structs map[string][]*PacketVersion // Maps packet name to versions
- mu sync.RWMutex
- basePath string
-}
-
-// NewConfigReader creates a new config reader
-func NewConfigReader(basePath string) *ConfigReader {
- return &ConfigReader{
- parser: NewParser(basePath),
- structs: make(map[string][]*PacketVersion),
- basePath: basePath,
- }
-}
-
-// LoadStructs loads all packet structures from XML files
-func (cr *ConfigReader) LoadStructs() error {
- // Use the structs/xml subdirectory
- xmlPath := filepath.Join(cr.basePath, "structs")
- cr.parser = NewParser(xmlPath)
-
- // Try to load all, but don't fail if directory doesn't exist
- cr.parser.LoadAll()
-
- return cr.buildVersionMap()
-}
-
-// LoadFiles loads specific XML files
-func (cr *ConfigReader) LoadFiles(files []string) error {
- for _, file := range files {
- if err := cr.parser.LoadFile(file); err != nil {
- return err
- }
- }
-
- return cr.buildVersionMap()
-}
-
-// buildVersionMap builds the internal version map from loaded packets
-func (cr *ConfigReader) buildVersionMap() error {
- cr.mu.Lock()
- defer cr.mu.Unlock()
-
- // Build version map
- cr.structs = make(map[string][]*PacketVersion)
-
- for _, name := range cr.parser.ListPackets() {
- def, _ := cr.parser.GetPacket(name)
- versions := make([]*PacketVersion, len(def.Versions))
- for i := range def.Versions {
- versions[i] = &def.Versions[i]
- }
- cr.structs[name] = versions
- }
-
- return nil
-}
-
-// ReloadStructs reloads all packet structures
-func (cr *ConfigReader) ReloadStructs() error {
- return cr.LoadStructs()
-}
-
-// GetStruct returns a packet struct for the best matching version
-// This matches the C++ ConfigReader::getStruct behavior
-func (cr *ConfigReader) GetStruct(name string, version uint16) (*PacketStruct, error) {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
-
- versions, exists := cr.structs[name]
- if !exists {
- return nil, fmt.Errorf("struct %s not found", name)
- }
-
- // Find the best matching version (highest version <= requested)
- var bestVersion *PacketVersion
- for _, v := range versions {
- if v.Version <= version {
- if bestVersion == nil || v.Version > bestVersion.Version {
- bestVersion = v
- }
- }
- }
-
- if bestVersion == nil {
- return nil, fmt.Errorf("no suitable version found for struct %s version %d", name, version)
- }
-
- return NewPacketStruct(bestVersion), nil
-}
-
-// GetStructByVersion returns a packet struct for an exact version match
-// This matches the C++ ConfigReader::getStructByVersion behavior
-func (cr *ConfigReader) GetStructByVersion(name string, version uint16) (*PacketStruct, error) {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
-
- versions, exists := cr.structs[name]
- if !exists {
- return nil, fmt.Errorf("struct %s not found", name)
- }
-
- // Find exact version match
- for _, v := range versions {
- if v.Version == version {
- return NewPacketStruct(v), nil
- }
- }
-
- return nil, fmt.Errorf("struct %s with exact version %d not found", name, version)
-}
-
-// GetStructVersion returns the best matching version number for a struct
-func (cr *ConfigReader) GetStructVersion(name string, version uint16) (uint16, error) {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
-
- versions, exists := cr.structs[name]
- if !exists {
- return 0, fmt.Errorf("struct %s not found", name)
- }
-
- // Find the best matching version
- var bestVersion uint16
- for _, v := range versions {
- if v.Version <= version && v.Version > bestVersion {
- bestVersion = v.Version
- }
- }
-
- if bestVersion == 0 {
- return 0, fmt.Errorf("no suitable version found for struct %s version %d", name, version)
- }
-
- return bestVersion, nil
-}
-
-// ListStructs returns a list of all loaded struct names
-func (cr *ConfigReader) ListStructs() []string {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
-
- names := make([]string, 0, len(cr.structs))
- for name := range cr.structs {
- names = append(names, name)
- }
- return names
-}
-
-// GetVersions returns all available versions for a struct
-func (cr *ConfigReader) GetVersions(name string) ([]uint16, error) {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
-
- versions, exists := cr.structs[name]
- if !exists {
- return nil, fmt.Errorf("struct %s not found", name)
- }
-
- result := make([]uint16, len(versions))
- for i, v := range versions {
- result[i] = v.Version
- }
-
- return result, nil
-}
\ No newline at end of file
diff --git a/structs/packet_struct.go b/structs/packet_struct.go
deleted file mode 100644
index 4f647d5..0000000
--- a/structs/packet_struct.go
+++ /dev/null
@@ -1,746 +0,0 @@
-package structs
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
-)
-
-// PacketStruct represents a runtime packet structure that can be filled with data
-type PacketStruct struct {
- definition *PacketVersion
- data map[string]interface{}
- arrays map[string][]interface{}
-}
-
-// NewPacketStruct creates a new packet struct from a definition
-func NewPacketStruct(def *PacketVersion) *PacketStruct {
- ps := &PacketStruct{
- definition: def,
- data: make(map[string]interface{}),
- arrays: make(map[string][]interface{}),
- }
-
- // Initialize with default values
- for _, field := range def.Fields {
- if field.Default != nil {
- ps.data[field.Name] = field.Default
- }
- }
-
- return ps
-}
-
-// Set sets a field value
-func (ps *PacketStruct) Set(name string, value interface{}) error {
- // Check if field exists in definition
- found := false
- for _, field := range ps.definition.Fields {
- if field.Name == name {
- found = true
- // Validate type if needed
- ps.data[name] = value
- break
- }
- }
-
- if !found {
- return fmt.Errorf("field %s not found in packet definition", name)
- }
-
- return nil
-}
-
-// Get retrieves a field value
-func (ps *PacketStruct) Get(name string) (interface{}, bool) {
- value, exists := ps.data[name]
- return value, exists
-}
-
-// SetArray sets an array field
-func (ps *PacketStruct) SetArray(name string, values []interface{}) error {
- ps.arrays[name] = values
- return nil
-}
-
-// GetArray retrieves an array field
-func (ps *PacketStruct) GetArray(name string) ([]interface{}, bool) {
- values, exists := ps.arrays[name]
- return values, exists
-}
-
-// Serialize converts the packet struct to bytes
-func (ps *PacketStruct) Serialize() ([]byte, error) {
- buf := &bytes.Buffer{}
-
- for _, field := range ps.definition.Fields {
- // Check conditionals
- if !ps.checkCondition(field) {
- continue
- }
-
- // Handle arrays
- if field.Type == FieldTypeArray {
- if err := ps.serializeArray(buf, field); err != nil {
- return nil, err
- }
- continue
- }
-
- // Get value
- value, exists := ps.data[field.Name]
- if !exists && !field.Optional {
- // Use zero value if not set
- value = ps.getZeroValue(field.Type)
- }
-
- // Serialize based on type
- if err := ps.serializeField(buf, field, value); err != nil {
- return nil, err
- }
- }
-
- return buf.Bytes(), nil
-}
-
-// Deserialize reads data from bytes into the packet struct
-func (ps *PacketStruct) Deserialize(data []byte) error {
- buf := bytes.NewReader(data)
-
- for _, field := range ps.definition.Fields {
- // Check conditionals
- if !ps.checkCondition(field) {
- continue
- }
-
- // Handle arrays
- if field.Type == FieldTypeArray {
- if err := ps.deserializeArray(buf, field); err != nil {
- return err
- }
- continue
- }
-
- // Deserialize based on type
- value, err := ps.deserializeField(buf, field)
- if err != nil {
- return err
- }
-
- ps.data[field.Name] = value
- }
-
- return nil
-}
-
-// serializeField writes a single field to the buffer
-func (ps *PacketStruct) serializeField(buf *bytes.Buffer, field Field, value interface{}) error {
- switch field.Type {
- case FieldTypeUInt8:
- v := ps.toUint8(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeUInt16:
- v := ps.toUint16(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeUInt32:
- v := ps.toUint32(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeUInt64:
- v := ps.toUint64(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeInt8:
- v := ps.toInt8(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeInt16:
- v := ps.toInt16(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeInt32:
- v := ps.toInt32(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeInt64:
- v := ps.toInt64(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeFloat:
- v := ps.toFloat32(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeDouble:
- v := ps.toFloat64(value)
- return binary.Write(buf, binary.LittleEndian, v)
-
- case FieldTypeChar:
- return ps.serializeChar(buf, field, value)
-
- case FieldTypeString8:
- return ps.serializeString8(buf, value)
-
- case FieldTypeString16:
- return ps.serializeString16(buf, value)
-
- case FieldTypeString32:
- return ps.serializeString32(buf, value)
-
- case FieldTypeColor, FieldTypeEQ2Color:
- return ps.serializeColor(buf, value)
-
- default:
- return fmt.Errorf("unsupported field type: %v", field.Type)
- }
-}
-
-// deserializeField reads a single field from the buffer
-func (ps *PacketStruct) deserializeField(buf *bytes.Reader, field Field) (interface{}, error) {
- switch field.Type {
- case FieldTypeUInt8:
- var v uint8
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeUInt16:
- var v uint16
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeUInt32:
- var v uint32
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeUInt64:
- var v uint64
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeInt8:
- var v int8
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeInt16:
- var v int16
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeInt32:
- var v int32
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeInt64:
- var v int64
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeFloat:
- var v float32
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeDouble:
- var v float64
- err := binary.Read(buf, binary.LittleEndian, &v)
- return v, err
-
- case FieldTypeChar:
- return ps.deserializeChar(buf, field)
-
- case FieldTypeString8:
- return ps.deserializeString8(buf)
-
- case FieldTypeString16:
- return ps.deserializeString16(buf)
-
- case FieldTypeString32:
- return ps.deserializeString32(buf)
-
- case FieldTypeColor, FieldTypeEQ2Color:
- return ps.deserializeColor(buf)
-
- default:
- return nil, fmt.Errorf("unsupported field type: %v", field.Type)
- }
-}
-
-// String serialization helpers
-func (ps *PacketStruct) serializeString8(buf *bytes.Buffer, value interface{}) error {
- str := ps.toString(value)
- if err := buf.WriteByte(uint8(len(str))); err != nil {
- return err
- }
- _, err := buf.WriteString(str)
- return err
-}
-
-func (ps *PacketStruct) serializeString16(buf *bytes.Buffer, value interface{}) error {
- str := ps.toString(value)
- if err := binary.Write(buf, binary.LittleEndian, uint16(len(str))); err != nil {
- return err
- }
- _, err := buf.WriteString(str)
- return err
-}
-
-func (ps *PacketStruct) serializeString32(buf *bytes.Buffer, value interface{}) error {
- str := ps.toString(value)
- if err := binary.Write(buf, binary.LittleEndian, uint32(len(str))); err != nil {
- return err
- }
- _, err := buf.WriteString(str)
- return err
-}
-
-func (ps *PacketStruct) deserializeString8(buf *bytes.Reader) (string, error) {
- length, err := buf.ReadByte()
- if err != nil {
- return "", err
- }
-
- data := make([]byte, length)
- if _, err := buf.Read(data); err != nil {
- return "", err
- }
-
- return string(data), nil
-}
-
-func (ps *PacketStruct) deserializeString16(buf *bytes.Reader) (string, error) {
- var length uint16
- if err := binary.Read(buf, binary.LittleEndian, &length); err != nil {
- return "", err
- }
-
- data := make([]byte, length)
- if _, err := buf.Read(data); err != nil {
- return "", err
- }
-
- return string(data), nil
-}
-
-func (ps *PacketStruct) deserializeString32(buf *bytes.Reader) (string, error) {
- var length uint32
- if err := binary.Read(buf, binary.LittleEndian, &length); err != nil {
- return "", err
- }
-
- data := make([]byte, length)
- if _, err := buf.Read(data); err != nil {
- return "", err
- }
-
- return string(data), nil
-}
-
-// Char field serialization (fixed-size string)
-func (ps *PacketStruct) serializeChar(buf *bytes.Buffer, field Field, value interface{}) error {
- str := ps.toString(value)
- size := field.Size
- if size == 0 {
- size = len(str) + 1 // Null-terminated if no size specified
- }
-
- // Write string up to size
- written := 0
- for i := 0; i < len(str) && i < size; i++ {
- buf.WriteByte(str[i])
- written++
- }
-
- // Pad with zeros
- for written < size {
- buf.WriteByte(0)
- written++
- }
-
- return nil
-}
-
-func (ps *PacketStruct) deserializeChar(buf *bytes.Reader, field Field) (string, error) {
- size := field.Size
- if size == 0 {
- // Read until null terminator
- var result []byte
- for {
- b, err := buf.ReadByte()
- if err != nil {
- return "", err
- }
- if b == 0 {
- break
- }
- result = append(result, b)
- }
- return string(result), nil
- }
-
- // Read fixed size
- data := make([]byte, size)
- if _, err := buf.Read(data); err != nil {
- return "", err
- }
-
- // Find null terminator
- for i, b := range data {
- if b == 0 {
- return string(data[:i]), nil
- }
- }
-
- return string(data), nil
-}
-
-// Color serialization
-func (ps *PacketStruct) serializeColor(buf *bytes.Buffer, value interface{}) error {
- // Assume color is represented as uint32 (RGBA)
- color := ps.toUint32(value)
- return binary.Write(buf, binary.LittleEndian, color)
-}
-
-func (ps *PacketStruct) deserializeColor(buf *bytes.Reader) (uint32, error) {
- var color uint32
- err := binary.Read(buf, binary.LittleEndian, &color)
- return color, err
-}
-
-// Array handling
-func (ps *PacketStruct) serializeArray(buf *bytes.Buffer, field Field) error {
- values, exists := ps.arrays[field.Name]
- if !exists {
- values = []interface{}{}
- }
-
- // Write each element
- for _, value := range values {
- // Create a temporary field for the element
- elemField := Field{
- Name: field.Name + "_elem",
- Type: field.Type, // This should be the element type, not array
- }
- if err := ps.serializeField(buf, elemField, value); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (ps *PacketStruct) deserializeArray(buf *bytes.Reader, field Field) error {
- // Get array size
- size := field.Size
- if field.SizeVar != "" {
- // Size is stored in another field
- if sizeVal, ok := ps.data[field.SizeVar]; ok {
- size = int(ps.toUint32(sizeVal))
- }
- }
-
- values := make([]interface{}, 0, size)
- for i := 0; i < size; i++ {
- // Create a temporary field for the element
- elemField := Field{
- Name: field.Name + "_elem",
- Type: field.Type, // This should be the element type, not array
- }
- value, err := ps.deserializeField(buf, elemField)
- if err != nil {
- return err
- }
- values = append(values, value)
- }
-
- ps.arrays[field.Name] = values
- return nil
-}
-
-// Condition checking
-func (ps *PacketStruct) checkCondition(field Field) bool {
- // Check IfSet condition
- if field.IfSet != "" {
- if val, exists := ps.data[field.IfSet]; !exists || val == nil {
- return false
- }
- }
-
- // Check IfNotSet condition
- if field.IfNotSet != "" {
- if val, exists := ps.data[field.IfNotSet]; exists && val != nil {
- return false
- }
- }
-
- return true
-}
-
-// Type conversion helpers
-func (ps *PacketStruct) toUint8(value interface{}) uint8 {
- switch v := value.(type) {
- case uint8:
- return v
- case int:
- return uint8(v)
- case uint:
- return uint8(v)
- case uint16:
- return uint8(v)
- case uint32:
- return uint8(v)
- case uint64:
- return uint8(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toUint16(value interface{}) uint16 {
- switch v := value.(type) {
- case uint16:
- return v
- case int:
- return uint16(v)
- case uint:
- return uint16(v)
- case uint8:
- return uint16(v)
- case uint32:
- return uint16(v)
- case uint64:
- return uint16(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toUint32(value interface{}) uint32 {
- switch v := value.(type) {
- case uint32:
- return v
- case int:
- return uint32(v)
- case uint:
- return uint32(v)
- case uint8:
- return uint32(v)
- case uint16:
- return uint32(v)
- case uint64:
- return uint32(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toUint64(value interface{}) uint64 {
- switch v := value.(type) {
- case uint64:
- return v
- case int:
- return uint64(v)
- case uint:
- return uint64(v)
- case uint8:
- return uint64(v)
- case uint16:
- return uint64(v)
- case uint32:
- return uint64(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toInt8(value interface{}) int8 {
- switch v := value.(type) {
- case int8:
- return v
- case int:
- return int8(v)
- case int16:
- return int8(v)
- case int32:
- return int8(v)
- case int64:
- return int8(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toInt16(value interface{}) int16 {
- switch v := value.(type) {
- case int16:
- return v
- case int:
- return int16(v)
- case int8:
- return int16(v)
- case int32:
- return int16(v)
- case int64:
- return int16(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toInt32(value interface{}) int32 {
- switch v := value.(type) {
- case int32:
- return v
- case int:
- return int32(v)
- case int8:
- return int32(v)
- case int16:
- return int32(v)
- case int64:
- return int32(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toInt64(value interface{}) int64 {
- switch v := value.(type) {
- case int64:
- return v
- case int:
- return int64(v)
- case int8:
- return int64(v)
- case int16:
- return int64(v)
- case int32:
- return int64(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toFloat32(value interface{}) float32 {
- switch v := value.(type) {
- case float32:
- return v
- case float64:
- return float32(v)
- case int:
- return float32(v)
- case uint:
- return float32(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toFloat64(value interface{}) float64 {
- switch v := value.(type) {
- case float64:
- return v
- case float32:
- return float64(v)
- case int:
- return float64(v)
- case uint:
- return float64(v)
- default:
- return 0
- }
-}
-
-func (ps *PacketStruct) toString(value interface{}) string {
- switch v := value.(type) {
- case string:
- return v
- case []byte:
- return string(v)
- default:
- return fmt.Sprintf("%v", v)
- }
-}
-
-// getZeroValue returns the zero value for a field type
-func (ps *PacketStruct) getZeroValue(ft FieldType) interface{} {
- switch ft {
- case FieldTypeUInt8:
- return uint8(0)
- case FieldTypeUInt16:
- return uint16(0)
- case FieldTypeUInt32:
- return uint32(0)
- case FieldTypeUInt64:
- return uint64(0)
- case FieldTypeInt8:
- return int8(0)
- case FieldTypeInt16:
- return int16(0)
- case FieldTypeInt32:
- return int32(0)
- case FieldTypeInt64:
- return int64(0)
- case FieldTypeFloat:
- return float32(0)
- case FieldTypeDouble:
- return float64(0)
- case FieldTypeChar, FieldTypeString8, FieldTypeString16, FieldTypeString32:
- return ""
- case FieldTypeColor, FieldTypeEQ2Color:
- return uint32(0)
- default:
- return nil
- }
-}
-
-// GetSize returns the serialized size of the packet struct
-func (ps *PacketStruct) GetSize() int {
- size := 0
-
- for _, field := range ps.definition.Fields {
- if !ps.checkCondition(field) {
- continue
- }
-
- fieldSize := GetFieldSize(field.Type)
- if fieldSize > 0 {
- size += fieldSize
- } else {
- // Variable size field
- switch field.Type {
- case FieldTypeChar:
- if field.Size > 0 {
- size += field.Size
- } else {
- // Null-terminated string
- if val, ok := ps.data[field.Name]; ok {
- size += len(ps.toString(val)) + 1
- } else {
- size += 1 // Just null terminator
- }
- }
- case FieldTypeString8:
- size += 1 // Length byte
- if val, ok := ps.data[field.Name]; ok {
- size += len(ps.toString(val))
- }
- case FieldTypeString16:
- size += 2 // Length uint16
- if val, ok := ps.data[field.Name]; ok {
- size += len(ps.toString(val))
- }
- case FieldTypeString32:
- size += 4 // Length uint32
- if val, ok := ps.data[field.Name]; ok {
- size += len(ps.toString(val))
- }
- }
- }
- }
-
- return size
-}
\ No newline at end of file
diff --git a/structs/parser.go b/structs/parser.go
deleted file mode 100644
index 884eb75..0000000
--- a/structs/parser.go
+++ /dev/null
@@ -1,420 +0,0 @@
-package structs
-
-import (
- "encoding/xml"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "sort"
- "strconv"
-)
-
-// FieldType represents the type of a packet field
-type FieldType int
-
-const (
- FieldTypeUnknown FieldType = iota
- FieldTypeUInt8
- FieldTypeUInt16
- FieldTypeUInt32
- FieldTypeUInt64
- FieldTypeInt8
- FieldTypeInt16
- FieldTypeInt32
- FieldTypeInt64
- FieldTypeFloat
- FieldTypeDouble
- FieldTypeChar
- FieldTypeString8
- FieldTypeString16
- FieldTypeString32
- FieldTypeColor
- FieldTypeEQ2Color
- FieldTypeSubstruct
- FieldTypeArray
-)
-
-// Field represents a single field in a packet structure
-type Field struct {
- Name string
- Type FieldType
- Size int // For arrays and fixed-size fields
- SizeVar string // Variable that contains the size
- Default interface{}
- Optional bool
- IfSet string // Conditional based on another field being set
- IfNotSet string // Conditional based on another field not being set
-}
-
-// PacketVersion represents a specific version of a packet structure
-type PacketVersion struct {
- Version uint16
- Fields []Field
-}
-
-// PacketDef represents a complete packet definition with all versions
-type PacketDef struct {
- Name string
- IsSubstruct bool
- Versions []PacketVersion
-}
-
-// XMLPacket represents the XML structure of a packet definition
-type XMLPacket struct {
- XMLName xml.Name `xml:"packet"`
- Name string `xml:"name,attr"`
- Versions []XMLVersion `xml:"version"`
-}
-
-// XMLSubstruct represents the XML structure of a substruct definition
-type XMLSubstruct struct {
- XMLName xml.Name `xml:"substruct"`
- Name string `xml:"name,attr"`
- Versions []XMLVersion `xml:"version"`
-}
-
-// XMLVersion represents a version block in the XML
-type XMLVersion struct {
- Number string `xml:"number,attr"`
- Fields []XMLField `xml:",any"`
-}
-
-// XMLField represents a field in the XML
-type XMLField struct {
- XMLName xml.Name
- Name string `xml:"name,attr"`
- Size string `xml:"size,attr"`
- SizeVar string `xml:"sizevar,attr"`
- Default string `xml:"default,attr"`
- IfSet string `xml:"ifset,attr"`
- IfNotSet string `xml:"ifnotset,attr"`
-}
-
-// Parser handles parsing XML packet definitions
-type Parser struct {
- packets map[string]*PacketDef
- basePath string
-}
-
-// NewParser creates a new packet parser
-func NewParser(basePath string) *Parser {
- return &Parser{
- packets: make(map[string]*PacketDef),
- basePath: basePath,
- }
-}
-
-// LoadAll loads all packet definitions from the XML directory
-func (p *Parser) LoadAll() error {
- patterns := []string{
- "common/*.xml",
- "login/*.xml",
- "item/*.xml",
- "spawn/*.xml",
- "world/*.xml",
- }
-
- for _, pattern := range patterns {
- fullPattern := filepath.Join(p.basePath, "xml", pattern)
- files, err := filepath.Glob(fullPattern)
- if err != nil {
- return fmt.Errorf("failed to glob %s: %w", pattern, err)
- }
-
- for _, file := range files {
- if err := p.LoadFile(file); err != nil {
- return fmt.Errorf("failed to load %s: %w", file, err)
- }
- }
- }
-
- return nil
-}
-
-// LoadFile loads a single XML file
-func (p *Parser) LoadFile(filename string) error {
- file, err := os.Open(filename)
- if err != nil {
- return err
- }
- defer file.Close()
-
- data, err := io.ReadAll(file)
- if err != nil {
- return err
- }
-
- // Try to parse as packet first
- var packet XMLPacket
- if err := xml.Unmarshal(data, &packet); err == nil && packet.Name != "" {
- def := p.parsePacket(&packet)
- p.packets[def.Name] = def
- return nil
- }
-
- // Try to parse as substruct
- var substruct XMLSubstruct
- if err := xml.Unmarshal(data, &substruct); err == nil && substruct.Name != "" {
- def := p.parseSubstruct(&substruct)
- p.packets[def.Name] = def
- return nil
- }
-
- return fmt.Errorf("failed to parse %s as packet or substruct", filename)
-}
-
-// parsePacket converts XML packet to internal representation
-func (p *Parser) parsePacket(xmlPacket *XMLPacket) *PacketDef {
- def := &PacketDef{
- Name: xmlPacket.Name,
- IsSubstruct: false,
- Versions: make([]PacketVersion, 0, len(xmlPacket.Versions)),
- }
-
- for _, xmlVer := range xmlPacket.Versions {
- version := p.parseVersion(xmlVer)
- def.Versions = append(def.Versions, version)
- }
-
- // Sort versions by version number
- sort.Slice(def.Versions, func(i, j int) bool {
- return def.Versions[i].Version < def.Versions[j].Version
- })
-
- return def
-}
-
-// parseSubstruct converts XML substruct to internal representation
-func (p *Parser) parseSubstruct(xmlSub *XMLSubstruct) *PacketDef {
- def := &PacketDef{
- Name: xmlSub.Name,
- IsSubstruct: true,
- Versions: make([]PacketVersion, 0, len(xmlSub.Versions)),
- }
-
- for _, xmlVer := range xmlSub.Versions {
- version := p.parseVersion(xmlVer)
- def.Versions = append(def.Versions, version)
- }
-
- // Sort versions by version number
- sort.Slice(def.Versions, func(i, j int) bool {
- return def.Versions[i].Version < def.Versions[j].Version
- })
-
- return def
-}
-
-// parseVersion converts XML version to internal representation
-func (p *Parser) parseVersion(xmlVer XMLVersion) PacketVersion {
- ver, _ := strconv.ParseUint(xmlVer.Number, 10, 16)
-
- version := PacketVersion{
- Version: uint16(ver),
- Fields: make([]Field, 0, len(xmlVer.Fields)),
- }
-
- for _, xmlField := range xmlVer.Fields {
- field := p.parseField(xmlField)
- version.Fields = append(version.Fields, field)
- }
-
- return version
-}
-
-// parseField converts XML field to internal representation
-func (p *Parser) parseField(xmlField XMLField) Field {
- field := Field{
- Name: xmlField.Name,
- Type: p.parseFieldType(xmlField.XMLName.Local),
- IfSet: xmlField.IfSet,
- IfNotSet: xmlField.IfNotSet,
- }
-
- // Parse size
- if xmlField.Size != "" {
- if size, err := strconv.Atoi(xmlField.Size); err == nil {
- field.Size = size
- }
- }
-
- // Parse size variable
- field.SizeVar = xmlField.SizeVar
-
- // Parse default value
- if xmlField.Default != "" {
- field.Default = p.parseDefaultValue(field.Type, xmlField.Default)
- }
-
- return field
-}
-
-// parseFieldType converts XML type name to FieldType
-func (p *Parser) parseFieldType(typeName string) FieldType {
- switch typeName {
- case "u8", "uint8":
- return FieldTypeUInt8
- case "u16", "uint16":
- return FieldTypeUInt16
- case "u32", "uint32":
- return FieldTypeUInt32
- case "u64", "uint64":
- return FieldTypeUInt64
- case "i8", "int8", "sint8":
- return FieldTypeInt8
- case "i16", "int16", "sint16":
- return FieldTypeInt16
- case "i32", "int32", "sint32":
- return FieldTypeInt32
- case "i64", "int64", "sint64":
- return FieldTypeInt64
- case "float":
- return FieldTypeFloat
- case "double":
- return FieldTypeDouble
- case "char":
- return FieldTypeChar
- case "str8", "string8":
- return FieldTypeString8
- case "str16", "string16":
- return FieldTypeString16
- case "str32", "string32":
- return FieldTypeString32
- case "color":
- return FieldTypeColor
- case "eq2color":
- return FieldTypeEQ2Color
- case "substruct":
- return FieldTypeSubstruct
- case "array":
- return FieldTypeArray
- default:
- return FieldTypeUnknown
- }
-}
-
-// parseDefaultValue parses default value based on field type
-func (p *Parser) parseDefaultValue(fieldType FieldType, value string) interface{} {
- switch fieldType {
- case FieldTypeUInt8, FieldTypeUInt16, FieldTypeUInt32, FieldTypeUInt64:
- if v, err := strconv.ParseUint(value, 0, 64); err == nil {
- return v
- }
- case FieldTypeInt8, FieldTypeInt16, FieldTypeInt32, FieldTypeInt64:
- if v, err := strconv.ParseInt(value, 0, 64); err == nil {
- return v
- }
- case FieldTypeFloat, FieldTypeDouble:
- if v, err := strconv.ParseFloat(value, 64); err == nil {
- return v
- }
- case FieldTypeChar, FieldTypeString8, FieldTypeString16, FieldTypeString32:
- return value
- }
- return nil
-}
-
-// GetPacket returns a packet definition by name
-func (p *Parser) GetPacket(name string) (*PacketDef, bool) {
- def, exists := p.packets[name]
- return def, exists
-}
-
-// GetPacketVersion returns the best matching version for a packet
-func (p *Parser) GetPacketVersion(name string, version uint16) (*PacketVersion, error) {
- def, exists := p.packets[name]
- if !exists {
- return nil, fmt.Errorf("packet %s not found", name)
- }
-
- // Find the best matching version (highest version <= requested)
- var bestVersion *PacketVersion
- for i := range def.Versions {
- if def.Versions[i].Version <= version {
- if bestVersion == nil || def.Versions[i].Version > bestVersion.Version {
- bestVersion = &def.Versions[i]
- }
- }
- }
-
- if bestVersion == nil {
- return nil, fmt.Errorf("no suitable version found for packet %s version %d", name, version)
- }
-
- return bestVersion, nil
-}
-
-// ListPackets returns a list of all loaded packet names
-func (p *Parser) ListPackets() []string {
- names := make([]string, 0, len(p.packets))
- for name := range p.packets {
- names = append(names, name)
- }
- sort.Strings(names)
- return names
-}
-
-// GetFieldTypeName returns the string representation of a field type
-func GetFieldTypeName(ft FieldType) string {
- switch ft {
- case FieldTypeUInt8:
- return "uint8"
- case FieldTypeUInt16:
- return "uint16"
- case FieldTypeUInt32:
- return "uint32"
- case FieldTypeUInt64:
- return "uint64"
- case FieldTypeInt8:
- return "int8"
- case FieldTypeInt16:
- return "int16"
- case FieldTypeInt32:
- return "int32"
- case FieldTypeInt64:
- return "int64"
- case FieldTypeFloat:
- return "float32"
- case FieldTypeDouble:
- return "float64"
- case FieldTypeChar:
- return "char"
- case FieldTypeString8:
- return "string8"
- case FieldTypeString16:
- return "string16"
- case FieldTypeString32:
- return "string32"
- case FieldTypeColor:
- return "color"
- case FieldTypeEQ2Color:
- return "eq2color"
- case FieldTypeSubstruct:
- return "substruct"
- case FieldTypeArray:
- return "array"
- default:
- return "unknown"
- }
-}
-
-// GetFieldSize returns the size in bytes of a field type
-func GetFieldSize(ft FieldType) int {
- switch ft {
- case FieldTypeUInt8, FieldTypeInt8:
- return 1
- case FieldTypeUInt16, FieldTypeInt16:
- return 2
- case FieldTypeUInt32, FieldTypeInt32, FieldTypeFloat:
- return 4
- case FieldTypeUInt64, FieldTypeInt64, FieldTypeDouble:
- return 8
- case FieldTypeColor:
- return 4 // RGBA
- case FieldTypeEQ2Color:
- return 4 // EQ2 specific color format
- default:
- return 0 // Variable size
- }
-}
diff --git a/structs/parser_test.go b/structs/parser_test.go
deleted file mode 100644
index 1708472..0000000
--- a/structs/parser_test.go
+++ /dev/null
@@ -1,417 +0,0 @@
-package structs
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-// TestParseLoginRequest tests parsing the LoginRequest XML
-func TestParseLoginRequest(t *testing.T) {
- // Create a temporary directory with test XML
- tmpDir := t.TempDir()
- xmlDir := filepath.Join(tmpDir, "xml", "login")
- os.MkdirAll(xmlDir, 0755)
-
- // Write test LoginRequest XML
- loginXML := `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`
-
- xmlFile := filepath.Join(xmlDir, "LoginRequest.xml")
- if err := os.WriteFile(xmlFile, []byte(loginXML), 0644); err != nil {
- t.Fatalf("Failed to write test XML: %v", err)
- }
-
- // Create parser and load file
- parser := NewParser(tmpDir)
- if err := parser.LoadFile(xmlFile); err != nil {
- t.Fatalf("Failed to load XML: %v", err)
- }
-
- // Check packet was loaded
- def, exists := parser.GetPacket("LoginRequest")
- if !exists {
- t.Fatal("LoginRequest packet not found")
- }
-
- if def.IsSubstruct {
- t.Error("LoginRequest should not be a substruct")
- }
-
- // Check versions
- if len(def.Versions) != 2 {
- t.Errorf("Expected 2 versions, got %d", len(def.Versions))
- }
-
- // Check version 1 fields
- if def.Versions[0].Version != 1 {
- t.Errorf("Expected version 1, got %d", def.Versions[0].Version)
- }
-
- if len(def.Versions[0].Fields) != 7 {
- t.Errorf("Expected 7 fields in version 1, got %d", len(def.Versions[0].Fields))
- }
-
- // Check specific fields
- field := def.Versions[0].Fields[2]
- if field.Name != "username" {
- t.Errorf("Expected field name 'username', got '%s'", field.Name)
- }
- if field.Type != FieldTypeString16 {
- t.Errorf("Expected field type String16, got %v", field.Type)
- }
-
- // Check version 562
- if def.Versions[1].Version != 562 {
- t.Errorf("Expected version 562, got %d", def.Versions[1].Version)
- }
-
- // Check array field
- arrayField := def.Versions[1].Fields[4]
- if arrayField.Name != "unknown2" {
- t.Errorf("Expected field name 'unknown2', got '%s'", arrayField.Name)
- }
- if arrayField.Size != 8 {
- t.Errorf("Expected size 8, got %d", arrayField.Size)
- }
-}
-
-// TestPacketStruct tests packet struct serialization/deserialization
-func TestPacketStruct(t *testing.T) {
- // Create a simple packet version for testing
- version := &PacketVersion{
- Version: 1,
- Fields: []Field{
- {Name: "id", Type: FieldTypeUInt32},
- {Name: "name", Type: FieldTypeString16},
- {Name: "level", Type: FieldTypeUInt8},
- {Name: "x", Type: FieldTypeFloat},
- {Name: "y", Type: FieldTypeFloat},
- {Name: "z", Type: FieldTypeFloat},
- },
- }
-
- // Create packet struct
- ps := NewPacketStruct(version)
-
- // Set values
- ps.Set("id", uint32(12345))
- ps.Set("name", "TestPlayer")
- ps.Set("level", uint8(50))
- ps.Set("x", float32(100.5))
- ps.Set("y", float32(200.5))
- ps.Set("z", float32(300.5))
-
- // Serialize
- data, err := ps.Serialize()
- if err != nil {
- t.Fatalf("Failed to serialize: %v", err)
- }
-
- // Create new packet struct and deserialize
- ps2 := NewPacketStruct(version)
- if err := ps2.Deserialize(data); err != nil {
- t.Fatalf("Failed to deserialize: %v", err)
- }
-
- // Check values
- if val, _ := ps2.Get("id"); val.(uint32) != 12345 {
- t.Errorf("Expected id 12345, got %v", val)
- }
-
- if val, _ := ps2.Get("name"); val.(string) != "TestPlayer" {
- t.Errorf("Expected name 'TestPlayer', got %v", val)
- }
-
- if val, _ := ps2.Get("level"); val.(uint8) != 50 {
- t.Errorf("Expected level 50, got %v", val)
- }
-
- if val, _ := ps2.Get("x"); val.(float32) != 100.5 {
- t.Errorf("Expected x 100.5, got %v", val)
- }
-}
-
-// TestConfigReader tests the ConfigReader interface
-func TestConfigReader(t *testing.T) {
- // Create temporary directory with test XML
- tmpDir := t.TempDir()
- structsDir := filepath.Join(tmpDir, "structs", "xml", "test")
- os.MkdirAll(structsDir, 0755)
-
- // Write test packet XML
- testXML := `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`
-
- xmlFile := filepath.Join(structsDir, "TestPacket.xml")
- if err := os.WriteFile(xmlFile, []byte(testXML), 0644); err != nil {
- t.Fatalf("Failed to write test XML: %v", err)
- }
-
- // Create config reader
- cr := NewConfigReader(tmpDir)
- if err := cr.LoadFiles([]string{xmlFile}); err != nil {
- t.Fatalf("Failed to load structs: %v", err)
- }
-
- // Test GetStruct with version selection
- tests := []struct {
- version uint16
- expectedFields int
- }{
- {1, 2}, // Exact match
- {50, 2}, // Should use version 1
- {100, 3}, // Exact match
- {150, 3}, // Should use version 100
- {200, 4}, // Exact match
- {300, 4}, // Should use version 200
- }
-
- for _, test := range tests {
- ps, err := cr.GetStruct("TestPacket", test.version)
- if err != nil {
- t.Errorf("Failed to get struct for version %d: %v", test.version, err)
- continue
- }
-
- if len(ps.definition.Fields) != test.expectedFields {
- t.Errorf("Version %d: expected %d fields, got %d",
- test.version, test.expectedFields, len(ps.definition.Fields))
- }
- }
-
- // Test GetStructByVersion (exact match only)
- ps, err := cr.GetStructByVersion("TestPacket", 100)
- if err != nil {
- t.Errorf("Failed to get struct by exact version: %v", err)
- }
- if ps != nil && len(ps.definition.Fields) != 3 {
- t.Errorf("Expected 3 fields for version 100, got %d", len(ps.definition.Fields))
- }
-
- // This should fail (no exact match)
- _, err = cr.GetStructByVersion("TestPacket", 150)
- if err == nil {
- t.Error("Expected error for non-existent exact version 150")
- }
-
- // Test GetStructVersion
- version, err := cr.GetStructVersion("TestPacket", 150)
- if err != nil {
- t.Errorf("Failed to get struct version: %v", err)
- }
- if version != 100 {
- t.Errorf("Expected version 100 for client version 150, got %d", version)
- }
-}
-
-// TestStringFields tests various string field types
-func TestStringFields(t *testing.T) {
- version := &PacketVersion{
- Version: 1,
- Fields: []Field{
- {Name: "str8", Type: FieldTypeString8},
- {Name: "str16", Type: FieldTypeString16},
- {Name: "str32", Type: FieldTypeString32},
- {Name: "char_fixed", Type: FieldTypeChar, Size: 10},
- {Name: "char_null", Type: FieldTypeChar, Size: 0},
- },
- }
-
- ps := NewPacketStruct(version)
-
- // Set string values
- ps.Set("str8", "Short")
- ps.Set("str16", "Medium length string")
- ps.Set("str32", "This is a much longer string that might be used for descriptions")
- ps.Set("char_fixed", "Fixed")
- ps.Set("char_null", "Null-term")
-
- // Serialize
- data, err := ps.Serialize()
- if err != nil {
- t.Fatalf("Failed to serialize: %v", err)
- }
-
- // Deserialize
- ps2 := NewPacketStruct(version)
- if err := ps2.Deserialize(data); err != nil {
- t.Fatalf("Failed to deserialize: %v", err)
- }
-
- // Verify strings
- if val, _ := ps2.Get("str8"); val.(string) != "Short" {
- t.Errorf("str8 mismatch: got %v", val)
- }
-
- if val, _ := ps2.Get("str16"); val.(string) != "Medium length string" {
- t.Errorf("str16 mismatch: got %v", val)
- }
-
- if val, _ := ps2.Get("str32"); val.(string) != "This is a much longer string that might be used for descriptions" {
- t.Errorf("str32 mismatch: got %v", val)
- }
-
- if val, _ := ps2.Get("char_fixed"); val.(string) != "Fixed" {
- t.Errorf("char_fixed mismatch: got %v", val)
- }
-
- if val, _ := ps2.Get("char_null"); val.(string) != "Null-term" {
- t.Errorf("char_null mismatch: got %v", val)
- }
-}
-
-// TestConditionalFields tests fields with if-set/if-not-set conditions
-func TestConditionalFields(t *testing.T) {
- version := &PacketVersion{
- Version: 1,
- Fields: []Field{
- {Name: "hasData", Type: FieldTypeUInt8},
- {Name: "data", Type: FieldTypeUInt32, IfSet: "hasData"},
- {Name: "noData", Type: FieldTypeUInt16, IfNotSet: "hasData"},
- },
- }
-
- // Test with hasData set
- ps1 := NewPacketStruct(version)
- ps1.Set("hasData", uint8(1))
- ps1.Set("data", uint32(999))
- ps1.Set("noData", uint16(111))
-
- data1, err := ps1.Serialize()
- if err != nil {
- t.Fatalf("Failed to serialize: %v", err)
- }
-
- // Should contain: uint8(1), uint32(999)
- // Should NOT contain: uint16(111)
- expectedSize := 1 + 4
- if len(data1) != expectedSize {
- t.Errorf("Expected serialized size %d, got %d", expectedSize, len(data1))
- }
-
- // Test with hasData not set
- ps2 := NewPacketStruct(version)
- ps2.Set("data", uint32(999))
- ps2.Set("noData", uint16(111))
-
- data2, err := ps2.Serialize()
- if err != nil {
- t.Fatalf("Failed to serialize: %v", err)
- }
-
- // Should contain: uint8(0), uint16(111)
- // Should NOT contain: uint32(999)
- expectedSize = 1 + 2
- if len(data2) != expectedSize {
- t.Errorf("Expected serialized size %d, got %d", expectedSize, len(data2))
- }
-}
-
-// BenchmarkSerialization benchmarks packet serialization
-func BenchmarkSerialization(b *testing.B) {
- version := &PacketVersion{
- Version: 1,
- Fields: []Field{
- {Name: "id", Type: FieldTypeUInt32},
- {Name: "name", Type: FieldTypeString16},
- {Name: "level", Type: FieldTypeUInt8},
- {Name: "x", Type: FieldTypeFloat},
- {Name: "y", Type: FieldTypeFloat},
- {Name: "z", Type: FieldTypeFloat},
- {Name: "flags", Type: FieldTypeUInt32},
- {Name: "timestamp", Type: FieldTypeUInt64},
- },
- }
-
- ps := NewPacketStruct(version)
- ps.Set("id", uint32(12345))
- ps.Set("name", "BenchmarkPlayer")
- ps.Set("level", uint8(50))
- ps.Set("x", float32(100.5))
- ps.Set("y", float32(200.5))
- ps.Set("z", float32(300.5))
- ps.Set("flags", uint32(0xFF00FF00))
- ps.Set("timestamp", uint64(1234567890))
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- data, err := ps.Serialize()
- if err != nil {
- b.Fatal(err)
- }
- _ = data
- }
-}
-
-// BenchmarkDeserialization benchmarks packet deserialization
-func BenchmarkDeserialization(b *testing.B) {
- version := &PacketVersion{
- Version: 1,
- Fields: []Field{
- {Name: "id", Type: FieldTypeUInt32},
- {Name: "name", Type: FieldTypeString16},
- {Name: "level", Type: FieldTypeUInt8},
- {Name: "x", Type: FieldTypeFloat},
- {Name: "y", Type: FieldTypeFloat},
- {Name: "z", Type: FieldTypeFloat},
- },
- }
-
- // Create test data
- ps := NewPacketStruct(version)
- ps.Set("id", uint32(12345))
- ps.Set("name", "TestPlayer")
- ps.Set("level", uint8(50))
- ps.Set("x", float32(100.5))
- ps.Set("y", float32(200.5))
- ps.Set("z", float32(300.5))
-
- data, _ := ps.Serialize()
-
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- ps2 := NewPacketStruct(version)
- err := ps2.Deserialize(data)
- if err != nil {
- b.Fatal(err)
- }
- }
-}
\ No newline at end of file