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