build crypto package
This commit is contained in:
parent
44b0b86a2e
commit
fc8c72b1d5
32
crypto/crc.go
Normal file
32
crypto/crc.go
Normal file
@ -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)
|
||||||
|
}
|
41
crypto/rc4.go
Normal file
41
crypto/rc4.go
Normal file
@ -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
|
||||||
|
}
|
@ -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
|
|
||||||
<packet name="LoginRequest">
|
|
||||||
<version number="1">
|
|
||||||
<str16 name="username"/>
|
|
||||||
<str16 name="password"/>
|
|
||||||
<u32 name="acctNum"/>
|
|
||||||
<u16 name="version"/>
|
|
||||||
</version>
|
|
||||||
<version number="562">
|
|
||||||
<str16 name="accesscode"/>
|
|
||||||
<str16 name="username"/>
|
|
||||||
<str16 name="password"/>
|
|
||||||
<u8 name="unknown" size="8"/>
|
|
||||||
<u32 name="version"/>
|
|
||||||
</version>
|
|
||||||
</packet>
|
|
||||||
```
|
|
||||||
|
|
||||||
Substructs use a similar format:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<substruct name="Item">
|
|
||||||
<version number="1">
|
|
||||||
<u32 name="unique_id"/>
|
|
||||||
<u32 name="bag_id"/>
|
|
||||||
<u8 name="slot_id"/>
|
|
||||||
<char name="name" size="64"/>
|
|
||||||
</version>
|
|
||||||
</substruct>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Supported Field Types
|
|
||||||
|
|
||||||
- **Integers**: u8, u16, u32, u64, i8, i16, i32, i64
|
|
||||||
- **Floats**: float (32-bit), double (64-bit)
|
|
||||||
- **Strings**: str8, str16, str32 (length-prefixed), char (fixed-size or null-terminated)
|
|
||||||
- **Special**: color, eq2color
|
|
||||||
- **Complex**: substruct, array
|
|
||||||
|
|
||||||
## Field Attributes
|
|
||||||
|
|
||||||
- `name`: Field name (required)
|
|
||||||
- `size`: Array size or fixed string 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
|
|
||||||
```
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 := `<packet name="LoginRequest">
|
|
||||||
<version number="1">
|
|
||||||
<str16 name="sessionID"/>
|
|
||||||
<str16 name="sessionRecycleToken"/>
|
|
||||||
<str16 name="username"/>
|
|
||||||
<str16 name="password"/>
|
|
||||||
<u32 name="acctNum"/>
|
|
||||||
<u32 name="passCode"/>
|
|
||||||
<u16 name="version"/>
|
|
||||||
</version>
|
|
||||||
<version number="562">
|
|
||||||
<str16 name="accesscode"/>
|
|
||||||
<str16 name="unknown1"/>
|
|
||||||
<str16 name="username"/>
|
|
||||||
<str16 name="password"/>
|
|
||||||
<u8 name="unknown2" size="8"/>
|
|
||||||
<u8 name="unknown3" size="2"/>
|
|
||||||
<u32 name="version"/>
|
|
||||||
<u16 name="unknown3b"/>
|
|
||||||
<u32 name="unknown4"/>
|
|
||||||
</version>
|
|
||||||
</packet>`
|
|
||||||
|
|
||||||
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 := `<packet name="TestPacket">
|
|
||||||
<version number="1">
|
|
||||||
<u32 name="id"/>
|
|
||||||
<str16 name="name"/>
|
|
||||||
</version>
|
|
||||||
<version number="100">
|
|
||||||
<u32 name="id"/>
|
|
||||||
<str16 name="name"/>
|
|
||||||
<u8 name="level"/>
|
|
||||||
</version>
|
|
||||||
<version number="200">
|
|
||||||
<u32 name="id"/>
|
|
||||||
<str16 name="name"/>
|
|
||||||
<u8 name="level"/>
|
|
||||||
<u16 name="flags"/>
|
|
||||||
</version>
|
|
||||||
</packet>`
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user