1
0

delete old struct parser

This commit is contained in:
Sky Johnson 2025-08-31 21:03:55 -05:00
parent 0b8c794692
commit f8236efffd
12 changed files with 0 additions and 4797 deletions

View File

@ -1,284 +0,0 @@
# Packet Definition Parser
Fast XML-like parser for binary packet structures with versioning and conditional fields.
## Basic Syntax
```xml
<packet name="PacketName">
<version number="1">
<i32 name="player_id">
<str16 name="player_name">
<color name="skin_color">
</version>
</packet>
```
## Field Types
| Type | Size | Description |
|------|------|-------------|
| `u8`, `u16`, `u32`, `u64` | 1-8 bytes | Unsigned integers |
| `i8`, `i16`, `i32`, `i64` | 1-8 bytes | Signed integers |
| `f32`, `f64`, `double` | 4-8 bytes | Floating point |
| `str8`, `str16`, `str32` | Variable | Length-prefixed strings |
| `char` | Fixed | Fixed-size byte array |
| `color` | 3 bytes | RGB color (r,g,b) |
| `equip` | 8 bytes | Equipment item |
| `array` | Variable | Array of substructures |
## Multiple Field Names
```xml
<u32 name="player_id,account_id">
<f32 name="pos_x,pos_y,pos_z">
```
## Conditional Fields
```xml
<str16 name="guild_name" if="flag:has_guild">
<u8 name="enhancement" if="item_type!=0">
<color name="aura" if="special_flags&0x01">
<str16 name="description" if="description!>0">
```
### Condition Types
**Flag Conditions:**
- `flag:name` - Flag is set
- `!flag:name` - Flag not set
**Variable Conditions:**
- `var:name` - Variable exists and is non-zero
- `!var:name` - Variable doesn't exist or is zero
**Version Conditions:**
- `version>=562` - Version comparisons
- `version<1200` - Supports `>=`, `<=`, `>`, `<`
**Value Comparisons:**
- `field>=value` - Numeric comparisons
- `field!=0` - Supports `>=`, `<=`, `>`, `<`, `==`, `!=`
**String Length:**
- `name!>5` - String longer than 5 chars
- `name!<=100` - String 100 chars or less
- Supports `!>`, `!<`, `!>=`, `!<=`, `!=`
**Bitwise Operations:**
- `field&0x01` - Bitwise AND with hex value
**Complex Logic:**
- `cond1,cond2` - OR logic (comma-separated)
- `cond1&cond2` - AND logic (ampersand)
- `version>=562&level>10` - Multiple conditions
**Array Context:**
- `item_type_%i!=0` - `%i` substitutes current array index
## Groups
Organize related fields with automatic prefixing:
```xml
<group name="appearance">
<color name="skin_color,hair_color,eye_color">
<str16 name="face_file,hair_file">
</group>
<!-- Creates: appearance_skin_color, appearance_hair_color, etc. -->
```
## Templates
Define reusable field groups that can be injected into packets:
```xml
<!-- Define template -->
<template name="position">
<f32 name="x,y,z">
<f32 name="heading">
</template>
<template name="appearance">
<color name="skin_color,hair_color,eye_color">
<str16 name="face_file,hair_file">
</template>
<!-- Use templates in packets -->
<packet name="PlayerUpdate">
<version number="1">
<u32 name="player_id">
<template use="position">
<u8 name="level">
<template use="appearance">
</version>
</packet>
```
Templates work with groups for prefixing:
```xml
<group name="current">
<template use="position">
</group>
<!-- Creates: current_x, current_y, current_z, current_heading -->
```
## Arrays
```xml
<u8 name="item_count">
<array name="items" count="var:item_count" max_size="100">
<u32 name="item_id">
<str16 name="item_name">
</array>
```
## Advanced Field Attributes
### Type Switching
```xml
<u32 name="stat_value" type2="f32" type2_if="stat_type==6">
```
### Oversized Fields
```xml
<u16 name="large_count" oversized="255">
<u32 name="huge_value" oversized="65535">
```
### Field Modifiers
```xml
<u8 name="data_array" size="10" default="5">
<str16 name="optional_text" optional="true">
<u32 name="hidden_field" add_to_struct="false" add_type="i16">
```
## Complete Attribute Reference
| Attribute | Description | Example |
|-----------|-------------|---------|
| `name` | Field name(s), comma-separated | `"id,account_id"` |
| `use` | Template name to inject | `"position"` |
| `if` | Conditional parsing expression | `"flag:has_guild"` |
| `size` | Fixed array size for `char` type | `"10"` |
| `count` | Array size variable | `"var:item_count"` |
| `substruct` | Reference to substruct | `"ItemInfo"` |
| `oversized` | Threshold for oversized handling | `"255"` |
| `type2` | Alternative field type | `"f32"` |
| `type2_if` | Condition for using type2 | `"stat_type!=6"` |
| `default` | Default value for initialization | `"0"` |
| `max_size` | Maximum array size limit | `"100"` |
| `optional` | Field is optional | `"true"` |
| `add_to_struct` | Include in packet structure | `"false"` |
| `add_type` | Type when adding to packet | `"i16"` |
## Reusable Substructs
```xml
<substruct name="ItemInfo">
<u32 name="item_id">
<str16 name="item_name">
<u8 name="rarity">
</substruct>
<packet name="Inventory">
<version number="1">
<u8 name="count">
<array name="items" count="var:count" substruct="ItemInfo">
</version>
</packet>
```
## Multiple Versions
```xml
<packet name="PlayerInfo">
<version number="1">
<u32 name="id">
<str16 name="name">
</version>
<version number="562">
<u32 name="id">
<str16 name="name">
<color name="skin_color">
</version>
</packet>
```
## Comments
```xml
<!-- This is a comment -->
<packet name="Test"> <!-- Inline comment -->
<version number="1">
<u32 name="id"> <!-- Field comment -->
</version>
</packet>
```
## Usage
```go
import "eq2emu/internal/parser"
// Parse PML content
packets, err := parser.Parse(pmlContent)
if err != nil {
log.Fatal(err)
}
// Get packet definition
packet := packets["PacketName"]
// Parse binary data with version and flags
result, err := packet.Parse(data, version, flags)
if err != nil {
log.Fatal(err)
}
// Access parsed fields
playerID := result["player_id"].(uint32)
playerName := result["player_name"].(common.EQ2String16).Data
```
## Complete Example
```xml
<!-- Define reusable templates -->
<template name="position">
<f32 name="x,y,z">
<f32 name="heading">
</template>
<template name="appearance">
<color name="skin_color,hair_color,eye_color">
<str16 name="face_file,hair_file">
</template>
<substruct name="StatBonus">
<u8 name="stat_type">
<u32 name="base_value" type2="f32" type2_if="stat_type==6">
<u16 name="bonus_value" if="stat_type!=6">
</substruct>
<packet name="PlayerStats">
<version number="562">
<u32 name="player_id,account_id">
<str16 name="player_name">
<u8 name="level,race,class">
<template use="position">
<template use="appearance" if="version>=562">
<str16 name="guild_name" if="flag:has_guild">
<u32 name="guild_id" if="flag:has_guild&level>=10">
<u8 name="stat_count">
<array name="stats" count="var:stat_count" max_size="50" substruct="StatBonus">
<u32 name="special_flags">
<str16 name="special_ability" if="special_flags&0x01">
<color name="aura_color" if="special_flags&0x02">
<str16 name="description" if="description!>0">
</version>
</packet>
```

View File

@ -1,454 +0,0 @@
package packets
import (
"bytes"
"encoding/binary"
"eq2emu/internal/common"
"eq2emu/internal/packets/parser"
"fmt"
"math"
)
// PacketBuilder constructs packets from data using parsed packet definitions
type PacketBuilder struct {
def *parser.PacketDef
version uint32
flags uint64
buf *bytes.Buffer
}
// NewPacketBuilder creates a new packet builder for the given packet definition
func NewPacketBuilder(def *parser.PacketDef, version uint32, flags uint64) *PacketBuilder {
return &PacketBuilder{
def: def,
version: version,
flags: flags,
buf: new(bytes.Buffer),
}
}
// Build constructs a packet from the provided data map
func (b *PacketBuilder) Build(data map[string]any) ([]byte, error) {
b.buf.Reset()
err := b.buildStruct(data, b.def)
if err != nil {
return nil, err
}
return b.buf.Bytes(), nil
}
// buildStruct builds a struct according to the packet definition field order
func (b *PacketBuilder) buildStruct(data map[string]any, def *parser.PacketDef) error {
order := b.getVersionOrder(def)
for _, fieldName := range order {
field, exists := def.Fields[fieldName]
if !exists {
continue
}
// Skip fields based on conditions
if !b.checkCondition(field.Condition, data) {
continue
}
fieldType := field.Type
if field.Type2 != 0 && b.checkCondition(field.Type2Cond, data) {
fieldType = field.Type2
}
value, hasValue := data[fieldName]
if !hasValue && !field.Optional {
return fmt.Errorf("required field '%s' not found in data", fieldName)
}
if hasValue {
err := b.buildField(value, field, fieldType, fieldName)
if err != nil {
return fmt.Errorf("error building field '%s': %w", fieldName, err)
}
} else if field.Optional {
// Write default value for optional fields
err := b.writeDefaultValue(field, fieldType)
if err != nil {
return fmt.Errorf("error writing default value for field '%s': %w", fieldName, err)
}
}
}
return nil
}
// buildField writes a single field to the packet buffer
func (b *PacketBuilder) buildField(value any, field parser.FieldDesc, fieldType common.EQ2DataType, fieldName string) error {
switch fieldType {
case common.TypeInt8:
v, ok := value.(uint8)
if !ok {
return fmt.Errorf("field '%s' expected uint8, got %T", fieldName, value)
}
if field.Oversized > 0 {
return b.writeOversizedUint8(v, field.Oversized)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeInt16:
v, ok := value.(uint16)
if !ok {
return fmt.Errorf("field '%s' expected uint16, got %T", fieldName, value)
}
if field.Oversized > 0 {
return b.writeOversizedUint16(v, field.Oversized)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeInt32:
v, ok := value.(uint32)
if !ok {
return fmt.Errorf("field '%s' expected uint32, got %T", fieldName, value)
}
if field.Oversized > 0 {
return b.writeOversizedUint32(v, field.Oversized)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeInt64:
v, ok := value.(uint64)
if !ok {
return fmt.Errorf("field '%s' expected uint64, got %T", fieldName, value)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeSInt8:
v, ok := value.(int8)
if !ok {
return fmt.Errorf("field '%s' expected int8, got %T", fieldName, value)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeSInt16:
v, ok := value.(int16)
if !ok {
return fmt.Errorf("field '%s' expected int16, got %T", fieldName, value)
}
if field.Oversized > 0 {
return b.writeOversizedSint16(v, field.Oversized)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeSInt32:
v, ok := value.(int32)
if !ok {
return fmt.Errorf("field '%s' expected int32, got %T", fieldName, value)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeSInt64:
v, ok := value.(int64)
if !ok {
return fmt.Errorf("field '%s' expected int64, got %T", fieldName, value)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeString8:
v, ok := value.(string)
if !ok {
return fmt.Errorf("field '%s' expected string, got %T", fieldName, value)
}
return b.writeEQ2String8(v)
case common.TypeString16:
v, ok := value.(string)
if !ok {
return fmt.Errorf("field '%s' expected string, got %T", fieldName, value)
}
return b.writeEQ2String16(v)
case common.TypeString32:
v, ok := value.(string)
if !ok {
return fmt.Errorf("field '%s' expected string, got %T", fieldName, value)
}
return b.writeEQ2String32(v)
case common.TypeChar:
v, ok := value.([]byte)
if !ok {
return fmt.Errorf("field '%s' expected []byte, got %T", fieldName, value)
}
return b.writeBytes(v, field.Length)
case common.TypeFloat:
v, ok := value.(float32)
if !ok {
return fmt.Errorf("field '%s' expected float32, got %T", fieldName, value)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeDouble:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("field '%s' expected float64, got %T", fieldName, value)
}
return binary.Write(b.buf, binary.LittleEndian, v)
case common.TypeColor:
v, ok := value.(common.EQ2Color)
if !ok {
return fmt.Errorf("field '%s' expected EQ2Color, got %T", fieldName, value)
}
return b.writeEQ2Color(v)
case common.TypeEquipment:
v, ok := value.(common.EQ2EquipmentItem)
if !ok {
return fmt.Errorf("field '%s' expected EQ2EquipmentItem, got %T", fieldName, value)
}
return b.writeEQ2Equipment(v)
case common.TypeArray:
v, ok := value.([]map[string]any)
if !ok {
return fmt.Errorf("field '%s' expected []map[string]any, got %T", fieldName, value)
}
return b.buildArray(v, field)
default:
return fmt.Errorf("unsupported field type %d for field '%s'", fieldType, fieldName)
}
}
// buildArray builds an array field
func (b *PacketBuilder) buildArray(items []map[string]any, field parser.FieldDesc) error {
if field.SubDef == nil {
return fmt.Errorf("array field missing sub-definition")
}
size := len(items)
if field.MaxArraySize > 0 && size > field.MaxArraySize {
size = field.MaxArraySize
}
for i := 0; i < size; i++ {
err := b.buildStruct(items[i], field.SubDef)
if err != nil {
return fmt.Errorf("error building array item %d: %w", i, err)
}
}
return nil
}
// Helper methods for writing specific data types
func (b *PacketBuilder) writeEQ2String8(s string) error {
data := []byte(s)
size := len(data)
if size > math.MaxUint8 {
size = math.MaxUint8
}
err := binary.Write(b.buf, binary.LittleEndian, uint8(size))
if err != nil {
return err
}
_, err = b.buf.Write(data[:size])
return err
}
func (b *PacketBuilder) writeEQ2String16(s string) error {
data := []byte(s)
size := len(data)
if size > math.MaxUint16 {
size = math.MaxUint16
}
err := binary.Write(b.buf, binary.LittleEndian, uint16(size))
if err != nil {
return err
}
_, err = b.buf.Write(data[:size])
return err
}
func (b *PacketBuilder) writeEQ2String32(s string) error {
data := []byte(s)
size := len(data)
if size > math.MaxUint32 {
size = math.MaxUint32
}
err := binary.Write(b.buf, binary.LittleEndian, uint32(size))
if err != nil {
return err
}
_, err = b.buf.Write(data[:size])
return err
}
func (b *PacketBuilder) writeBytes(data []byte, length int) error {
if length > 0 && len(data) > length {
data = data[:length]
}
_, err := b.buf.Write(data)
return err
}
func (b *PacketBuilder) writeEQ2Color(color common.EQ2Color) error {
err := binary.Write(b.buf, binary.LittleEndian, color.Red)
if err != nil {
return err
}
err = binary.Write(b.buf, binary.LittleEndian, color.Green)
if err != nil {
return err
}
return binary.Write(b.buf, binary.LittleEndian, color.Blue)
}
func (b *PacketBuilder) writeEQ2Equipment(item common.EQ2EquipmentItem) error {
err := binary.Write(b.buf, binary.LittleEndian, item.Type)
if err != nil {
return err
}
err = b.writeEQ2Color(item.Color)
if err != nil {
return err
}
return b.writeEQ2Color(item.Highlight)
}
func (b *PacketBuilder) writeOversizedUint8(value uint8, threshold int) error {
if int(value) >= threshold {
err := binary.Write(b.buf, binary.LittleEndian, uint8(255))
if err != nil {
return err
}
return binary.Write(b.buf, binary.LittleEndian, uint16(value))
}
return binary.Write(b.buf, binary.LittleEndian, value)
}
func (b *PacketBuilder) writeOversizedUint16(value uint16, threshold int) error {
if int(value) >= threshold {
err := binary.Write(b.buf, binary.LittleEndian, uint16(65535))
if err != nil {
return err
}
return binary.Write(b.buf, binary.LittleEndian, uint32(value))
}
return binary.Write(b.buf, binary.LittleEndian, value)
}
func (b *PacketBuilder) writeOversizedUint32(value uint32, threshold int) error {
if int64(value) >= int64(threshold) {
err := binary.Write(b.buf, binary.LittleEndian, uint32(4294967295))
if err != nil {
return err
}
return binary.Write(b.buf, binary.LittleEndian, uint64(value))
}
return binary.Write(b.buf, binary.LittleEndian, value)
}
func (b *PacketBuilder) writeOversizedSint16(value int16, threshold int) error {
if int(value) >= threshold {
err := binary.Write(b.buf, binary.LittleEndian, int16(-1))
if err != nil {
return err
}
return binary.Write(b.buf, binary.LittleEndian, int32(value))
}
return binary.Write(b.buf, binary.LittleEndian, value)
}
func (b *PacketBuilder) writeDefaultValue(field parser.FieldDesc, fieldType common.EQ2DataType) error {
switch fieldType {
case common.TypeInt8:
return binary.Write(b.buf, binary.LittleEndian, uint8(field.DefaultValue))
case common.TypeInt16:
return binary.Write(b.buf, binary.LittleEndian, uint16(field.DefaultValue))
case common.TypeInt32:
return binary.Write(b.buf, binary.LittleEndian, uint32(field.DefaultValue))
case common.TypeInt64:
return binary.Write(b.buf, binary.LittleEndian, uint64(field.DefaultValue))
case common.TypeSInt8:
return binary.Write(b.buf, binary.LittleEndian, field.DefaultValue)
case common.TypeSInt16:
return binary.Write(b.buf, binary.LittleEndian, int16(field.DefaultValue))
case common.TypeSInt32:
return binary.Write(b.buf, binary.LittleEndian, int32(field.DefaultValue))
case common.TypeSInt64:
return binary.Write(b.buf, binary.LittleEndian, int64(field.DefaultValue))
case common.TypeString8:
return b.writeEQ2String8("")
case common.TypeString16:
return b.writeEQ2String16("")
case common.TypeString32:
return b.writeEQ2String32("")
case common.TypeFloat:
return binary.Write(b.buf, binary.LittleEndian, float32(field.DefaultValue))
case common.TypeDouble:
return binary.Write(b.buf, binary.LittleEndian, float64(field.DefaultValue))
case common.TypeColor:
color := common.EQ2Color{Red: field.DefaultValue, Green: field.DefaultValue, Blue: field.DefaultValue}
return b.writeEQ2Color(color)
case common.TypeChar:
if field.Length > 0 {
defaultBytes := make([]byte, field.Length)
return b.writeBytes(defaultBytes, field.Length)
}
}
return nil
}
func (b *PacketBuilder) getVersionOrder(def *parser.PacketDef) []string {
var bestVersion uint32
for v := range def.Orders {
if v <= b.version && v > bestVersion {
bestVersion = v
}
}
return def.Orders[bestVersion]
}
func (b *PacketBuilder) checkCondition(condition string, data map[string]any) bool {
if condition == "" {
return true
}
// Simple condition evaluation - this would need to be expanded
// for complex expressions used in the packet definitions
if value, exists := data[condition]; exists {
switch v := value.(type) {
case bool:
return v
case int, int8, int16, int32, int64:
return v != 0
case uint, uint8, uint16, uint32, uint64:
return v != 0
case string:
return v != ""
default:
return true
}
}
return false
}
// BuildPacket is a convenience function that creates a builder and builds a packet in one call
func BuildPacket(packetName string, data map[string]any, version uint32, flags uint64) ([]byte, error) {
def, exists := GetPacket(packetName)
if !exists {
return nil, fmt.Errorf("packet definition '%s' not found", packetName)
}
builder := NewPacketBuilder(def, version, flags)
return builder.Build(data)
}

View File

@ -1,173 +0,0 @@
package packets
import (
"eq2emu/internal/common"
"eq2emu/internal/packets/parser"
"testing"
)
func TestPacketBuilderSimpleFields(t *testing.T) {
// Create a simple packet definition for testing
def := parser.NewPacketDef(5)
// Add some basic fields
def.Fields["field1"] = parser.FieldDesc{
Type: common.TypeInt8,
}
def.Fields["field2"] = parser.FieldDesc{
Type: common.TypeString8,
}
def.Fields["field3"] = parser.FieldDesc{
Type: common.TypeInt32,
}
// Set field order
def.Orders[1] = []string{"field1", "field2", "field3"}
// Create test data
data := map[string]any{
"field1": uint8(42),
"field2": "hello",
"field3": uint32(12345),
}
// Build packet
builder := NewPacketBuilder(def, 1, 0)
packetData, err := builder.Build(data)
if err != nil {
t.Errorf("Failed to build simple packet: %v", err)
return
}
expectedMinLength := 1 + 1 + 5 + 4 // uint8 + string8(len+data) + uint32
if len(packetData) < expectedMinLength {
t.Errorf("Built packet too short: got %d bytes, expected at least %d", len(packetData), expectedMinLength)
return
}
t.Logf("Successfully built simple packet (%d bytes)", len(packetData))
// Verify the first byte is correct
if packetData[0] != 42 {
t.Errorf("First field incorrect: got %d, expected 42", packetData[0])
}
}
func TestPacketBuilderOptionalFields(t *testing.T) {
// Create a packet definition with optional fields
def := parser.NewPacketDef(3)
def.Fields["required"] = parser.FieldDesc{
Type: common.TypeInt8,
}
def.Fields["optional"] = parser.FieldDesc{
Type: common.TypeInt8,
Optional: true,
}
def.Orders[1] = []string{"required", "optional"}
// Test with only required field
data := map[string]any{
"required": uint8(123),
}
builder := NewPacketBuilder(def, 1, 0)
packetData, err := builder.Build(data)
if err != nil {
t.Errorf("Failed to build packet with optional fields: %v", err)
return
}
expectedLength := 2 // required + optional default
if len(packetData) != expectedLength {
t.Errorf("Built packet wrong length: got %d bytes, expected %d", len(packetData), expectedLength)
return
}
t.Logf("Successfully built packet with optional fields (%d bytes)", len(packetData))
}
func TestPacketBuilderMissingRequiredField(t *testing.T) {
// Create a packet definition
def := parser.NewPacketDef(2)
def.Fields["required"] = parser.FieldDesc{
Type: common.TypeInt8,
}
def.Fields["also_required"] = parser.FieldDesc{
Type: common.TypeInt16,
}
def.Orders[1] = []string{"required", "also_required"}
// Test with missing required field
data := map[string]any{
"required": uint8(123),
// missing "also_required"
}
builder := NewPacketBuilder(def, 1, 0)
_, err := builder.Build(data)
if err == nil {
t.Error("Expected error for missing required field, but got none")
return
}
t.Logf("Correctly detected missing required field: %v", err)
}
func TestPacketBuilderStringTypes(t *testing.T) {
// Test different string types
def := parser.NewPacketDef(3)
def.Fields["str8"] = parser.FieldDesc{
Type: common.TypeString8,
}
def.Fields["str16"] = parser.FieldDesc{
Type: common.TypeString16,
}
def.Fields["str32"] = parser.FieldDesc{
Type: common.TypeString32,
}
def.Orders[1] = []string{"str8", "str16", "str32"}
data := map[string]any{
"str8": "test8",
"str16": "test16",
"str32": "test32",
}
builder := NewPacketBuilder(def, 1, 0)
packetData, err := builder.Build(data)
if err != nil {
t.Errorf("Failed to build packet with strings: %v", err)
return
}
// Verify we have some data
if len(packetData) < 20 { // rough estimate
t.Errorf("Built packet too short for string data: %d bytes", len(packetData))
return
}
t.Logf("Successfully built packet with strings (%d bytes)", len(packetData))
}
func TestBuildPacketConvenienceFunction(t *testing.T) {
// Test the convenience function
data := map[string]any{
"username": "testuser",
"password": "testpass",
}
// This should fail since we don't have a "TestPacket" defined
_, err := BuildPacket("NonExistentPacket", data, 1, 0)
if err == nil {
t.Error("Expected error for non-existent packet, but got none")
return
}
t.Logf("Correctly detected non-existent packet: %v", err)
}

View File

@ -1,146 +0,0 @@
package packets
import (
"embed"
"eq2emu/internal/packets/parser"
"fmt"
"log"
"path"
"strings"
"sync"
)
//go:embed xml/**/*.xml
var xmlFiles embed.FS
var (
loadedPackets = make(map[string]*parser.PacketDef)
loadedMutex sync.RWMutex
loadError error
)
func init() {
err := loadAllPackets()
if err != nil {
loadError = err
log.Printf("Failed to load packet definitions: %v", err)
}
}
func loadAllPackets() error {
entries, err := xmlFiles.ReadDir("xml")
if err != nil {
return fmt.Errorf("failed to read xml directory: %w", err)
}
packets := make(map[string]*parser.PacketDef)
for _, entry := range entries {
if !entry.IsDir() {
continue
}
dirPath := path.Join("xml", entry.Name())
err := processDirectory(dirPath, packets)
if err != nil {
return fmt.Errorf("failed to process directory %s: %w", entry.Name(), err)
}
}
loadedMutex.Lock()
loadedPackets = packets
loadedMutex.Unlock()
log.Printf("Loaded %d packet definitions", len(packets))
return nil
}
func processDirectory(dirPath string, packets map[string]*parser.PacketDef) error {
entries, err := xmlFiles.ReadDir(dirPath)
if err != nil {
return err
}
for _, entry := range entries {
entryPath := path.Join(dirPath, entry.Name())
if entry.IsDir() {
err := processDirectory(entryPath, packets)
if err != nil {
return err
}
continue
}
if !strings.HasSuffix(entry.Name(), ".xml") {
continue
}
err := processXMLFile(entryPath, packets)
if err != nil {
log.Printf("Warning: %s: %v", entryPath, err)
}
}
return nil
}
func processXMLFile(filePath string, packets map[string]*parser.PacketDef) error {
content, err := xmlFiles.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
parsedPackets, err := parser.Parse(string(content))
if err != nil {
return fmt.Errorf("failed to parse packet def: %w", err)
}
for name, packet := range parsedPackets {
if existing, exists := packets[name]; exists {
log.Printf("Warning: packet '%s' already exists, overwriting", name)
_ = existing
}
packets[name] = packet
}
return nil
}
func GetPacket(name string) (*parser.PacketDef, bool) {
if loadError != nil {
return nil, false
}
loadedMutex.RLock()
defer loadedMutex.RUnlock()
packet, exists := loadedPackets[name]
return packet, exists
}
func GetPacketNames() []string {
if loadError != nil {
return nil
}
loadedMutex.RLock()
defer loadedMutex.RUnlock()
names := make([]string, 0, len(loadedPackets))
for name := range loadedPackets {
names = append(names, name)
}
return names
}
func GetPacketCount() int {
if loadError != nil {
return 0
}
loadedMutex.RLock()
defer loadedMutex.RUnlock()
return len(loadedPackets)
}

View File

@ -1,239 +0,0 @@
package packets
import (
"fmt"
"sync"
"database/sql"
"log"
)
// OpcodeManager manages opcode mappings for different client versions
type OpcodeManager struct {
versions map[int32]int32 // Maps version range start to end
opcodes map[int32]map[string]uint16 // Maps version to opcode name->value
internalOpcodes map[int32]map[uint16]InternalOpcode // Maps version to wire opcode->internal
mu sync.RWMutex
}
// NewOpcodeManager creates a new opcode manager
func NewOpcodeManager() *OpcodeManager {
return &OpcodeManager{
versions: make(map[int32]int32),
opcodes: make(map[int32]map[string]uint16),
internalOpcodes: make(map[int32]map[uint16]InternalOpcode),
}
}
// LoadVersionsFromDB loads version ranges from the database
func (om *OpcodeManager) LoadVersionsFromDB(db *sql.DB) error {
om.mu.Lock()
defer om.mu.Unlock()
query := `SELECT DISTINCT version_range1, version_range2 FROM opcodes`
rows, err := db.Query(query)
if err != nil {
return fmt.Errorf("failed to query versions: %w", err)
}
defer rows.Close()
for rows.Next() {
var start, end int32
if err := rows.Scan(&start, &end); err != nil {
return fmt.Errorf("failed to scan version row: %w", err)
}
om.versions[start] = end
}
return rows.Err()
}
// LoadOpcodesFromDB loads opcodes for all versions from the database
func (om *OpcodeManager) LoadOpcodesFromDB(db *sql.DB) error {
om.mu.Lock()
defer om.mu.Unlock()
for versionStart := range om.versions {
query := `SELECT name, opcode FROM opcodes
WHERE ? BETWEEN version_range1 AND version_range2
ORDER BY version_range1, id`
rows, err := db.Query(query, versionStart)
if err != nil {
return fmt.Errorf("failed to query opcodes for version %d: %w", versionStart, err)
}
opcodes := make(map[string]uint16)
internal := make(map[uint16]InternalOpcode)
for rows.Next() {
var name string
var opcode uint16
if err := rows.Scan(&name, &opcode); err != nil {
rows.Close()
return fmt.Errorf("failed to scan opcode row: %w", err)
}
opcodes[name] = opcode
// Map to internal opcodes
if internalOp, ok := nameToInternalOpcode[name]; ok {
internal[opcode] = internalOp
}
}
rows.Close()
om.opcodes[versionStart] = opcodes
om.internalOpcodes[versionStart] = internal
// Silent - we'll log summary later
}
log.Printf("Loaded opcodes for %d client versions from database", len(om.versions))
return nil
}
// LoadDefaultOpcodes loads hardcoded opcodes for when database is not available
func (om *OpcodeManager) LoadDefaultOpcodes() {
om.mu.Lock()
defer om.mu.Unlock()
// Default version range (1119 is a common EQ2 client version)
defaultVersion := int32(1119)
om.versions[defaultVersion] = defaultVersion
// These are the default opcodes from the C++ implementation
opcodes := map[string]uint16{
"OP_LoginRequestMsg": 0x0001,
"OP_LoginByNumRequestMsg": 0x0002,
"OP_WSLoginRequestMsg": 0x0003,
"OP_ESLoginRequestMsg": 0x0004,
"OP_LoginReplyMsg": 0x0005,
"OP_WorldListMsg": 0x0006,
"OP_WorldStatusChangeMsg": 0x0007,
"OP_AllWSDescRequestMsg": 0x0008,
"OP_WSStatusReplyMsg": 0x0009,
"OP_AllCharactersDescRequestMsg": 0x000A,
"OP_AllCharactersDescReplyMsg": 0x000B,
"OP_CreateCharacterRequestMsg": 0x000C,
"OP_ReskinCharacterRequestMsg": 0x000D,
"OP_CreateCharacterReplyMsg": 0x000E,
"OP_WSCreateCharacterRequestMsg": 0x000F,
"OP_WSCreateCharacterReplyMsg": 0x0010,
"OP_DeleteCharacterRequestMsg": 0x0011,
"OP_DeleteCharacterReplyMsg": 0x0012,
"OP_PlayCharacterRequestMsg": 0x0013,
"OP_PlayCharacterReplyMsg": 0x0014,
"OP_ServerPlayCharacterRequestMsg": 0x0015,
"OP_ServerPlayCharacterReplyMsg": 0x0016,
"OP_KeymapLoadMsg": 0x0017,
"OP_KeymapNoneMsg": 0x0018,
"OP_KeymapDataMsg": 0x0019,
"OP_KeymapSaveMsg": 0x001A,
"OP_LSCheckAcctLockMsg": 0x001B,
"OP_WSAcctLockStatusMsg": 0x001C,
"OP_LsRequestClientCrashLogMsg": 0x001D,
"OP_LsClientBaselogReplyMsg": 0x001E,
"OP_LsClientCrashlogReplyMsg": 0x001F,
"OP_LsClientAlertlogReplyMsg": 0x0020,
"OP_LsClientVerifylogReplyMsg": 0x0021,
"OP_BadLanguageFilter": 0x0022,
"OP_WSServerLockMsg": 0x0023,
"OP_WSServerHideMsg": 0x0024,
"OP_LSServerLockMsg": 0x0025,
"OP_UpdateCharacterSheetMsg": 0x0026,
"OP_UpdateInventoryMsg": 0x0027,
}
internal := make(map[uint16]InternalOpcode)
for name, opcode := range opcodes {
if internalOp, ok := nameToInternalOpcode[name]; ok {
internal[opcode] = internalOp
}
}
om.opcodes[defaultVersion] = opcodes
om.internalOpcodes[defaultVersion] = internal
log.Printf("Loaded default opcodes for version %d", defaultVersion)
}
// GetOpcodeVersion returns the version range start for a given client version
// This implements the same logic as the C++ GetOpcodeVersion function
func (om *OpcodeManager) GetOpcodeVersion(clientVersion int32) int32 {
om.mu.RLock()
defer om.mu.RUnlock()
for versionStart, versionEnd := range om.versions {
if clientVersion >= versionStart && clientVersion <= versionEnd {
return versionStart
}
}
// If no match found, return the client version itself
return clientVersion
}
// GetOpcodeByName returns the wire opcode value for a given opcode name and client version
func (om *OpcodeManager) GetOpcodeByName(clientVersion int32, name string) (uint16, bool) {
om.mu.RLock()
defer om.mu.RUnlock()
version := om.GetOpcodeVersion(clientVersion)
if opcodes, ok := om.opcodes[version]; ok {
if opcode, ok := opcodes[name]; ok {
return opcode, true
}
}
return 0, false
}
// GetInternalOpcode converts a wire opcode to an internal opcode for a given client version
func (om *OpcodeManager) GetInternalOpcode(clientVersion int32, wireOpcode uint16) (InternalOpcode, bool) {
om.mu.RLock()
defer om.mu.RUnlock()
version := om.GetOpcodeVersion(clientVersion)
if internal, ok := om.internalOpcodes[version]; ok {
if internalOp, ok := internal[wireOpcode]; ok {
return internalOp, true
}
}
return OP_Unknown, false
}
// GetWireOpcode converts an internal opcode to a wire opcode for a given client version
func (om *OpcodeManager) GetWireOpcode(clientVersion int32, internalOpcode InternalOpcode) (uint16, bool) {
om.mu.RLock()
defer om.mu.RUnlock()
// Get the name for this internal opcode
name := ""
for n, op := range nameToInternalOpcode {
if op == internalOpcode {
name = n
break
}
}
if name == "" {
return 0, false
}
return om.GetOpcodeByName(clientVersion, name)
}
// nameToInternalOpcode maps opcode names to internal opcodes
var nameToInternalOpcode = map[string]InternalOpcode{
"OP_LoginRequestMsg": OP_LoginRequestMsg,
"OP_LoginByNumRequestMsg": OP_LoginByNumRequestMsg,
"OP_WSLoginRequestMsg": OP_WSLoginRequestMsg,
"OP_LoginReplyMsg": OP_LoginReplyMsg,
"OP_PlayCharacterRequestMsg": OP_PlayCharacterRequest,
"OP_CreateCharacterRequestMsg": OP_CharacterCreate,
"OP_DeleteCharacterRequestMsg": OP_CharacterDelete,
"OP_AllCharactersDescRequestMsg": OP_CharacterList,
"OP_WorldListMsg": OP_ServerList,
"OP_WorldStatusChangeMsg": OP_SendServerStatus,
// Add more mappings as needed
}

View File

@ -1,162 +0,0 @@
package packets
// InternalOpcode represents the internal opcode enumeration
type InternalOpcode int32
// Internal opcode constants - these map to the C++ EmuOpcode enum
const (
OP_Unknown InternalOpcode = iota
// Login server specific opcodes (from C++ LoginServer)
OP_Login2
OP_GetLoginInfo
OP_LoginInfo
OP_SessionId
OP_SessionKey
OP_Disconnect
OP_AllFinish
OP_Ack5
OP_SendServersFragment
OP_ServerList
OP_RequestServerStatus
OP_SendServerStatus
OP_Version
OP_LoginBanner
OP_PlayCharacterRequest
OP_CharacterList
OP_CharacterCreate
OP_CharacterDelete
// Login and authentication operations
OP_LoginRequestMsg
OP_LoginReplyMsg
OP_LoginByNumRequestMsg
OP_WSLoginRequestMsg
// Server initialization and zone management
OP_ESInitMsg
OP_ESReadyForClientsMsg
OP_CreateZoneInstanceMsg
OP_ZoneInstanceCreateReplyMsg
OP_ZoneInstanceDestroyedMsg
OP_ExpectClientAsCharacterRequest
OP_ExpectClientAsCharacterReplyMs
OP_ZoneInfoMsg
// Character creation and loading
OP_AllCharactersDescRequestMsg
OP_AllCharactersDescReplyMsg
OP_CreateCharacterRequestMsg
OP_ReskinCharacterRequestMsg
OP_CreateCharacterReplyMsg
OP_DeleteCharacterRequestMsg
OP_DeleteCharacterReplyMsg
OP_PlayCharacterRequestMsg
OP_PlayCharacterReplyMsg
// World server communication
OP_WSCreateCharacterRequestMsg
OP_WSCreateCharacterReplyMsg
OP_ServerPlayCharacterRequestMsg
OP_ServerPlayCharacterReplyMsg
OP_ExpectClientAsCharacterRequ
OP_ExpectClientAsCharacterReply
OP_WorldListMsg
OP_WorldStatusChangeMsg
OP_AllWSDescRequestMsg
OP_WSStatusReplyMsg
OP_WSAcctLockStatusMsg
OP_WSServerLockMsg
OP_WSServerHideMsg
OP_LSServerLockMsg
// Keymap and configuration
OP_KeymapLoadMsg
OP_KeymapNoneMsg
OP_KeymapDataMsg
OP_KeymapSaveMsg
// Account and security management
OP_LSCheckAcctLockMsg
OP_LsRequestClientCrashLogMsg
OP_LsClientBaselogReplyMsg
OP_LsClientCrashlogReplyMsg
OP_LsClientAlertlogReplyMsg
OP_LsClientVerifylogReplyMsg
OP_BadLanguageFilter
// Character sheet and inventory
OP_UpdateCharacterSheetMsg
OP_UpdateInventoryMsg
)
// OpcodeNames provides human-readable names for internal opcodes
var OpcodeNames = map[InternalOpcode]string{
OP_Unknown: "OP_Unknown",
// Login server specific opcodes
OP_Login2: "OP_Login2",
OP_GetLoginInfo: "OP_GetLoginInfo",
OP_LoginInfo: "OP_LoginInfo",
OP_SessionId: "OP_SessionId",
OP_SessionKey: "OP_SessionKey",
OP_Disconnect: "OP_Disconnect",
OP_AllFinish: "OP_AllFinish",
OP_Ack5: "OP_Ack5",
OP_SendServersFragment: "OP_SendServersFragment",
OP_ServerList: "OP_ServerList",
OP_RequestServerStatus: "OP_RequestServerStatus",
OP_SendServerStatus: "OP_SendServerStatus",
OP_Version: "OP_Version",
OP_LoginBanner: "OP_LoginBanner",
OP_PlayCharacterRequest: "OP_PlayCharacterRequest",
OP_CharacterList: "OP_CharacterList",
OP_CharacterCreate: "OP_CharacterCreate",
OP_CharacterDelete: "OP_CharacterDelete",
// Login and authentication operations
OP_LoginRequestMsg: "OP_LoginRequestMsg",
OP_LoginReplyMsg: "OP_LoginReplyMsg",
OP_LoginByNumRequestMsg: "OP_LoginByNumRequestMsg",
OP_WSLoginRequestMsg: "OP_WSLoginRequestMsg",
// Character operations
OP_AllCharactersDescRequestMsg: "OP_AllCharactersDescRequestMsg",
OP_AllCharactersDescReplyMsg: "OP_AllCharactersDescReplyMsg",
OP_CreateCharacterRequestMsg: "OP_CreateCharacterRequestMsg",
OP_ReskinCharacterRequestMsg: "OP_ReskinCharacterRequestMsg",
OP_CreateCharacterReplyMsg: "OP_CreateCharacterReplyMsg",
OP_DeleteCharacterRequestMsg: "OP_DeleteCharacterRequestMsg",
OP_DeleteCharacterReplyMsg: "OP_DeleteCharacterReplyMsg",
OP_PlayCharacterRequestMsg: "OP_PlayCharacterRequestMsg",
OP_PlayCharacterReplyMsg: "OP_PlayCharacterReplyMsg",
// World server communication
OP_WorldListMsg: "OP_WorldListMsg",
OP_WorldStatusChangeMsg: "OP_WorldStatusChangeMsg",
OP_AllWSDescRequestMsg: "OP_AllWSDescRequestMsg",
OP_WSStatusReplyMsg: "OP_WSStatusReplyMsg",
OP_WSAcctLockStatusMsg: "OP_WSAcctLockStatusMsg",
OP_WSServerLockMsg: "OP_WSServerLockMsg",
OP_WSServerHideMsg: "OP_WSServerHideMsg",
OP_LSServerLockMsg: "OP_LSServerLockMsg",
// Keymap operations
OP_KeymapLoadMsg: "OP_KeymapLoadMsg",
OP_KeymapNoneMsg: "OP_KeymapNoneMsg",
OP_KeymapDataMsg: "OP_KeymapDataMsg",
OP_KeymapSaveMsg: "OP_KeymapSaveMsg",
// Account and security
OP_LSCheckAcctLockMsg: "OP_LSCheckAcctLockMsg",
OP_LsRequestClientCrashLogMsg: "OP_LsRequestClientCrashLogMsg",
OP_LsClientBaselogReplyMsg: "OP_LsClientBaselogReplyMsg",
OP_LsClientCrashlogReplyMsg: "OP_LsClientCrashlogReplyMsg",
OP_LsClientAlertlogReplyMsg: "OP_LsClientAlertlogReplyMsg",
OP_LsClientVerifylogReplyMsg: "OP_LsClientVerifylogReplyMsg",
OP_BadLanguageFilter: "OP_BadLanguageFilter",
// Character sheet and inventory
OP_UpdateCharacterSheetMsg: "OP_UpdateCharacterSheetMsg",
OP_UpdateInventoryMsg: "OP_UpdateInventoryMsg",
}

View File

@ -1,447 +0,0 @@
package parser
import (
"encoding/binary"
"eq2emu/internal/common"
"strconv"
"strings"
"unsafe"
)
type ParseContext struct {
data []byte
offset int
version uint32
flags uint64
vars map[string]any
arrayStack []int
}
func NewContext(data []byte, version uint32, flags uint64) *ParseContext {
return &ParseContext{
data: data,
version: version,
flags: flags,
vars: make(map[string]any),
}
}
// Unsigned integer readers
func (ctx *ParseContext) readUint8() uint8 {
val := ctx.data[ctx.offset]
ctx.offset++
return val
}
func (ctx *ParseContext) readUint16() uint16 {
val := binary.LittleEndian.Uint16(ctx.data[ctx.offset:])
ctx.offset += 2
return val
}
func (ctx *ParseContext) readUint32() uint32 {
val := binary.LittleEndian.Uint32(ctx.data[ctx.offset:])
ctx.offset += 4
return val
}
func (ctx *ParseContext) readUint64() uint64 {
val := binary.LittleEndian.Uint64(ctx.data[ctx.offset:])
ctx.offset += 8
return val
}
// Signed integer readers
func (ctx *ParseContext) readSint8() int8 {
val := int8(ctx.data[ctx.offset])
ctx.offset++
return val
}
func (ctx *ParseContext) readSint16() int16 {
val := int16(binary.LittleEndian.Uint16(ctx.data[ctx.offset:]))
ctx.offset += 2
return val
}
func (ctx *ParseContext) readSint32() int32 {
val := int32(binary.LittleEndian.Uint32(ctx.data[ctx.offset:]))
ctx.offset += 4
return val
}
func (ctx *ParseContext) readSint64() int64 {
val := int64(binary.LittleEndian.Uint64(ctx.data[ctx.offset:]))
ctx.offset += 8
return val
}
// Oversized readers
func (ctx *ParseContext) readOversizedUint8(threshold int) uint8 {
if ctx.data[ctx.offset] == byte(threshold) {
ctx.offset++
return ctx.readUint8()
}
return ctx.readUint8()
}
func (ctx *ParseContext) readOversizedUint16(threshold int) uint16 {
if ctx.data[ctx.offset] == byte(threshold) {
ctx.offset++
return ctx.readUint16()
}
return uint16(ctx.readUint8())
}
func (ctx *ParseContext) readOversizedUint32(threshold int) uint32 {
if ctx.data[ctx.offset] == byte(threshold) {
ctx.offset++
return ctx.readUint32()
}
return uint32(ctx.readUint16())
}
func (ctx *ParseContext) readOversizedSint16(threshold int) int16 {
val := int8(ctx.data[ctx.offset])
if val == int8(threshold) || val == int8(-threshold) {
ctx.offset++
return ctx.readSint16()
}
return int16(ctx.readSint8())
}
func (ctx *ParseContext) readEQ2String8() common.EQ2String8 {
size := ctx.readSint8()
data := string(ctx.data[ctx.offset : ctx.offset+int(size)])
ctx.offset += int(size)
return common.EQ2String8{
Size: size,
Data: data,
}
}
func (ctx *ParseContext) readEQ2String16() common.EQ2String16 {
size := ctx.readSint16()
data := string(ctx.data[ctx.offset : ctx.offset+int(size)])
ctx.offset += int(size)
return common.EQ2String16{
Size: size,
Data: data,
}
}
func (ctx *ParseContext) readEQ2String32() common.EQ2String32 {
size := ctx.readSint32()
data := string(ctx.data[ctx.offset : ctx.offset+int(size)])
ctx.offset += int(size)
return common.EQ2String32{
Size: size,
Data: data,
}
}
func (ctx *ParseContext) readEQ2Color() common.EQ2Color {
return common.EQ2Color{
Red: ctx.readSint8(),
Green: ctx.readSint8(),
Blue: ctx.readSint8(),
}
}
func (ctx *ParseContext) readEQ2Equipment() common.EQ2EquipmentItem {
return common.EQ2EquipmentItem{
Type: ctx.readUint16(),
Color: ctx.readEQ2Color(),
Highlight: ctx.readEQ2Color(),
}
}
func (ctx *ParseContext) readBytes(count int) []byte {
val := make([]byte, count)
copy(val, ctx.data[ctx.offset:ctx.offset+count])
ctx.offset += count
return val
}
func (ctx *ParseContext) readFloat32() float32 {
val := ctx.readUint32()
return *(*float32)(unsafe.Pointer(&val))
}
func (ctx *ParseContext) readFloat64() float64 {
val := ctx.readUint64()
return *(*float64)(unsafe.Pointer(&val))
}
func (ctx *ParseContext) setVar(name string, value any) {
ctx.vars[name] = value
}
func (ctx *ParseContext) pushArrayIndex(index int) {
ctx.arrayStack = append(ctx.arrayStack, index)
}
func (ctx *ParseContext) popArrayIndex() {
if len(ctx.arrayStack) > 0 {
ctx.arrayStack = ctx.arrayStack[:len(ctx.arrayStack)-1]
}
}
// Condition evaluation methods
func (ctx *ParseContext) checkCondition(condition string) bool {
if condition == "" {
return true
}
// Handle comma-separated OR conditions
if strings.Contains(condition, ",") {
parts := strings.Split(condition, ",")
for _, part := range parts {
if ctx.evaluateCondition(strings.TrimSpace(part)) {
return true
}
}
return false
}
// Handle AND conditions with &
if strings.Contains(condition, "&") && !strings.Contains(condition, "0x") {
parts := strings.Split(condition, "&")
for _, part := range parts {
if !ctx.evaluateCondition(strings.TrimSpace(part)) {
return false
}
}
return true
}
return ctx.evaluateCondition(condition)
}
func (ctx *ParseContext) evaluateCondition(condition string) bool {
// Flag conditions: flag:name or !flag:name
if strings.HasPrefix(condition, "flag:") {
flagName := condition[5:]
return (ctx.flags & ctx.getFlagValue(flagName)) != 0
}
if strings.HasPrefix(condition, "!flag:") {
flagName := condition[6:]
return (ctx.flags & ctx.getFlagValue(flagName)) == 0
}
// Variable conditions: var:name or !var:name (with %i support)
if strings.HasPrefix(condition, "var:") {
varName := ctx.resolveVariableName(condition[4:])
return ctx.hasVar(varName)
}
if strings.HasPrefix(condition, "!var:") {
varName := ctx.resolveVariableName(condition[5:])
return !ctx.hasVar(varName)
}
// Version comparisons
if strings.HasPrefix(condition, "version") {
return ctx.evaluateVersionCondition(condition)
}
// Bitwise AND: header_flag&0x01 (with %i support)
if strings.Contains(condition, "&0x") {
parts := strings.SplitN(condition, "&", 2)
varName := ctx.resolveVariableName(parts[0])
hexValue, _ := strconv.ParseUint(parts[1], 0, 64)
varValue := ctx.getVarValue(varName)
return (varValue & hexValue) != 0
}
// String length operators: name!>5, name!<=10 (with %i support)
stringOps := []string{"!>=", "!<=", "!>", "!<", "!="}
for _, op := range stringOps {
if idx := strings.Index(condition, op); idx > 0 {
varName := ctx.resolveVariableName(condition[:idx])
valueStr := condition[idx+len(op):]
return ctx.evaluateStringLength(varName, valueStr, op)
}
}
// Comparison operators: >=, <=, >, <, ==, != (with %i support)
compOps := []string{">=", "<=", ">", "<", "==", "!="}
for _, op := range compOps {
if idx := strings.Index(condition, op); idx > 0 {
varName := ctx.resolveVariableName(condition[:idx])
valueStr := condition[idx+len(op):]
return ctx.evaluateComparison(varName, valueStr, op)
}
}
// Simple variable existence (with %i support)
resolvedName := ctx.resolveVariableName(condition)
return ctx.hasVar(resolvedName)
}
func (ctx *ParseContext) evaluateVersionCondition(condition string) bool {
if strings.Contains(condition, ">=") {
versionStr := condition[strings.Index(condition, ">=")+2:]
targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
return ctx.version >= uint32(targetVersion)
}
if strings.Contains(condition, "<=") {
versionStr := condition[strings.Index(condition, "<=")+2:]
targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
return ctx.version <= uint32(targetVersion)
}
if strings.Contains(condition, ">") {
versionStr := condition[strings.Index(condition, ">")+1:]
targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
return ctx.version > uint32(targetVersion)
}
if strings.Contains(condition, "<") {
versionStr := condition[strings.Index(condition, "<")+1:]
targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
return ctx.version < uint32(targetVersion)
}
return false
}
func (ctx *ParseContext) evaluateStringLength(varName, valueStr, op string) bool {
resolvedName := ctx.resolveVariableName(varName)
str := ctx.getStringVar(resolvedName)
targetLen, _ := strconv.Atoi(valueStr)
strLen := len(str)
switch op {
case "!>":
return strLen > targetLen
case "!<":
return strLen < targetLen
case "!>=":
return strLen >= targetLen
case "!<=":
return strLen <= targetLen
case "!=":
return strLen != targetLen
}
return false
}
func (ctx *ParseContext) evaluateComparison(varName, valueStr, op string) bool {
resolvedName := ctx.resolveVariableName(varName)
varValue := ctx.getVarValue(resolvedName)
targetValue, _ := strconv.ParseUint(valueStr, 0, 64)
switch op {
case ">=":
return varValue >= targetValue
case "<=":
return varValue <= targetValue
case ">":
return varValue > targetValue
case "<":
return varValue < targetValue
case "==":
return varValue == targetValue
case "!=":
return varValue != targetValue
}
return false
}
func (ctx *ParseContext) hasVar(name string) bool {
if val, exists := ctx.vars[name]; exists {
switch v := val.(type) {
case uint8, uint16, uint32, uint64, int8, int16, int32, int64:
return ctx.getVarValue(name) != 0
case string:
return v != ""
case common.EQ2String8:
return v.Data != ""
case common.EQ2String16:
return v.Data != ""
case common.EQ2String32:
return v.Data != ""
default:
return true
}
}
return false
}
func (ctx *ParseContext) getVarValue(name string) uint64 {
if val, exists := ctx.vars[name]; exists {
switch v := val.(type) {
case uint8:
return uint64(v)
case uint16:
return uint64(v)
case uint32:
return uint64(v)
case uint64:
return v
case int8:
return uint64(v)
case int16:
return uint64(v)
case int32:
return uint64(v)
case int64:
return uint64(v)
}
}
return 0
}
func (ctx *ParseContext) getStringVar(name string) string {
if val, exists := ctx.vars[name]; exists {
switch v := val.(type) {
case string:
return v
case common.EQ2String8:
return v.Data
case common.EQ2String16:
return v.Data
case common.EQ2String32:
return v.Data
}
}
return ""
}
func (ctx *ParseContext) getFlagValue(flagName string) uint64 {
flagMap := map[string]uint64{
"loot": 0x01,
"has_equipment": 0x02,
"no_colors": 0x04,
}
if val, exists := flagMap[flagName]; exists {
return val
}
return 0
}
func (ctx *ParseContext) getArraySize(condition string) int {
if strings.HasPrefix(condition, "var:") {
varName := condition[4:]
return int(ctx.getVarValue(varName))
}
return 0
}
func (ctx *ParseContext) resolveVariableName(name string) string {
// Handle %i substitution for array contexts
if strings.Contains(name, "%i") && len(ctx.arrayStack) > 0 {
currentIndex := ctx.arrayStack[len(ctx.arrayStack)-1]
return strings.ReplaceAll(name, "%i", strconv.Itoa(currentIndex))
}
return name
}
func (ctx *ParseContext) setVarWithArrayIndex(name string, value any) {
// Always set the base variable name
ctx.vars[name] = value
// If we're in an array context, also set the indexed variable
if len(ctx.arrayStack) > 0 {
currentIndex := ctx.arrayStack[len(ctx.arrayStack)-1]
indexedName := name + "_" + strconv.Itoa(currentIndex)
ctx.vars[indexedName] = value
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,933 +0,0 @@
package parser
import (
"eq2emu/internal/common"
"testing"
)
func TestBasicParsing(t *testing.T) {
pml := `<packet name="Test">
<version number="1">
<i32 name="player_id">
<str16 name="player_name">
<color name="skin_color">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["Test"]
if packet == nil {
t.Fatal("Test packet not found")
}
// Check fields
if len(packet.Fields) != 3 {
t.Errorf("Expected 3 fields, got %d", len(packet.Fields))
}
if packet.Fields["player_id"].Type != common.TypeSInt32 {
t.Error("player_id should be TypeSInt32")
}
if packet.Fields["player_name"].Type != common.TypeString16 {
t.Error("player_name should be TypeString16")
}
if packet.Fields["skin_color"].Type != common.TypeColor {
t.Error("skin_color should be TypeColor")
}
// Check order
order := packet.Orders[1]
expected := []string{"player_id", "player_name", "skin_color"}
if !equalSlices(order, expected) {
t.Errorf("Expected order %v, got %v", expected, order)
}
}
func TestFloat64Support(t *testing.T) {
pml := `<packet name="FloatTest">
<version number="1">
<f32 name="position_x">
<f64 name="precise_value">
<double name="legacy_double">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["FloatTest"]
if packet.Fields["position_x"].Type != common.TypeFloat {
t.Error("position_x should be TypeFloat")
}
if packet.Fields["precise_value"].Type != common.TypeDouble {
t.Error("precise_value should be TypeDouble")
}
if packet.Fields["legacy_double"].Type != common.TypeDouble {
t.Error("legacy_double should be TypeDouble")
}
}
func TestOversizedFields(t *testing.T) {
pml := `<packet name="OversizedTest">
<version number="1">
<i8 name="small_count">
<i16 name="num_words" oversized="255">
<i32 name="large_value" oversized="65535">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["OversizedTest"]
if packet.Fields["small_count"].Oversized != 0 {
t.Error("small_count should not be oversized")
}
if packet.Fields["num_words"].Oversized != 255 {
t.Errorf("num_words oversized should be 255, got %d", packet.Fields["num_words"].Oversized)
}
if packet.Fields["large_value"].Oversized != 65535 {
t.Errorf("large_value oversized should be 65535, got %d", packet.Fields["large_value"].Oversized)
}
}
func TestType2Support(t *testing.T) {
pml := `<packet name="Type2Test">
<version number="1">
<i8 name="stat_type">
<i32 name="stat_value" type2="f32" type2_if="stat_type!=6">
<i16 name="another_field" type2="i32">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["Type2Test"]
statValue := packet.Fields["stat_value"]
if statValue.Type != common.TypeSInt32 {
t.Error("stat_value primary type should be TypeSInt32")
}
if statValue.Type2 != common.TypeFloat {
t.Error("stat_value type2 should be TypeFloat")
}
if statValue.Type2Cond != "stat_type!=6" {
t.Errorf("Expected type2_if 'stat_type!=6', got '%s'", statValue.Type2Cond)
}
anotherField := packet.Fields["another_field"]
if anotherField.Type2 != common.TypeSInt32 {
t.Error("another_field type2 should be TypeSInt32")
}
if anotherField.Type2Cond != "" {
t.Error("another_field should have empty type2_if")
}
}
func TestAdvancedFieldAttributes(t *testing.T) {
pml := `<packet name="AttributeTest">
<version number="1">
<i8 name="data_array" size="10" default="5">
<str16 name="optional_text" if="var:has_text" optional="true">
<i32 name="hidden_field" add_to_struct="false" add_type="i16">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["AttributeTest"]
dataArray := packet.Fields["data_array"]
if dataArray.Length != 10 {
t.Errorf("Expected size 10, got %d", dataArray.Length)
}
if dataArray.DefaultValue != 5 {
t.Errorf("Expected default 5, got %d", dataArray.DefaultValue)
}
optionalText := packet.Fields["optional_text"]
if optionalText.Condition != "var:has_text" {
t.Errorf("Expected condition 'var:has_text', got '%s'", optionalText.Condition)
}
if !optionalText.Optional {
t.Error("optional_text should be optional")
}
hiddenField := packet.Fields["hidden_field"]
if hiddenField.AddToStruct {
t.Error("hidden_field should not be added to struct")
}
if hiddenField.AddType != common.TypeSInt16 {
t.Error("hidden_field add_type should be TypeSInt16")
}
}
func TestArrayMaxSize(t *testing.T) {
pml := `<packet name="ArrayMaxTest">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="100">
<i32 name="item_id">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayMaxTest"]
itemsField := packet.Fields["items"]
if itemsField.MaxArraySize != 100 {
t.Errorf("Expected max_size 100, got %d", itemsField.MaxArraySize)
}
}
func TestArrayOptionalAttributes(t *testing.T) {
pml := `<packet name="ArrayOptionalTest">
<version number="1">
<i8 name="count">
<array name="optional_items" count="var:count" optional="true" add_to_struct="false">
<i32 name="id">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayOptionalTest"]
itemsField := packet.Fields["optional_items"]
if !itemsField.Optional {
t.Error("optional_items should be optional")
}
if itemsField.AddToStruct {
t.Error("optional_items should not be added to struct")
}
}
func TestMultipleVersions(t *testing.T) {
pml := `<packet name="MultiVersion">
<version number="1">
<i32 name="id">
<str16 name="name">
</version>
<version number="562">
<i32 name="id">
<str16 name="name">
<color name="color">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["MultiVersion"]
if packet == nil {
t.Fatal("MultiVersion packet not found")
}
// Check both versions exist
if len(packet.Orders) != 2 {
t.Errorf("Expected 2 versions, got %d", len(packet.Orders))
}
v1Order := packet.Orders[1]
v562Order := packet.Orders[562]
if len(v1Order) != 2 {
t.Errorf("Version 1 should have 2 fields, got %d", len(v1Order))
}
if len(v562Order) != 3 {
t.Errorf("Version 562 should have 3 fields, got %d", len(v562Order))
}
}
func TestArrayParsing(t *testing.T) {
pml := `<packet name="ArrayTest">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count">
<i32 name="item_id">
<str16 name="item_name">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayTest"]
itemsField := packet.Fields["items"]
if itemsField.Type != common.TypeArray {
t.Error("items should be TypeArray")
}
if itemsField.Condition != "var:item_count" {
t.Errorf("Expected condition 'var:item_count', got '%s'", itemsField.Condition)
}
if itemsField.SubDef == nil {
t.Fatal("SubDef should not be nil")
}
// Check substruct fields
if len(itemsField.SubDef.Fields) != 2 {
t.Errorf("Expected 2 substruct fields, got %d", len(itemsField.SubDef.Fields))
}
if itemsField.SubDef.Fields["item_id"].Type != common.TypeSInt32 {
t.Error("item_id should be TypeSInt32")
}
}
func TestConditionalParsing(t *testing.T) {
pml := `<packet name="ConditionalTest">
<version number="1">
<i32 name="player_id">
<str16 name="guild_name" if="flag:has_guild">
<i8 name="enhancement" if="item_type!=0">
<color name="aura" if="special_flags&0x01">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ConditionalTest"]
if packet.Fields["guild_name"].Condition != "flag:has_guild" {
t.Errorf("guild_name condition wrong: %s", packet.Fields["guild_name"].Condition)
}
if packet.Fields["enhancement"].Condition != "item_type!=0" {
t.Errorf("enhancement condition wrong: %s", packet.Fields["enhancement"].Condition)
}
if packet.Fields["aura"].Condition != "special_flags&0x01" {
t.Errorf("aura condition wrong: %s", packet.Fields["aura"].Condition)
}
}
func TestCommaFieldNames(t *testing.T) {
pml := `<packet name="CommaTest">
<version number="1">
<i32 name="player_id,account_id">
<f32 name="pos_x,pos_y,pos_z">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["CommaTest"]
expectedFields := []string{"player_id", "account_id", "pos_x", "pos_y", "pos_z"}
if len(packet.Fields) != len(expectedFields) {
t.Errorf("Expected %d fields, got %d", len(expectedFields), len(packet.Fields))
}
for _, field := range expectedFields {
if _, exists := packet.Fields[field]; !exists {
t.Errorf("Field %s not found", field)
}
}
}
func TestSubstructReference(t *testing.T) {
pml := `<substruct name="ItemInfo">
<i32 name="item_id">
<str16 name="item_name">
</substruct>
<packet name="SubstructTest">
<version number="1">
<i8 name="count">
<array name="items" count="var:count" substruct="ItemInfo">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["SubstructTest"]
itemsField := packet.Fields["items"]
if itemsField.SubDef == nil {
t.Fatal("SubDef should not be nil for referenced substruct")
}
if len(itemsField.SubDef.Fields) != 2 {
t.Errorf("Expected 2 substruct fields, got %d", len(itemsField.SubDef.Fields))
}
}
func TestFieldAttributes(t *testing.T) {
pml := `<packet name="AttributeTest">
<version number="1">
<i8 name="data_array" size="10">
<str16 name="optional_text" if="var:has_text">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["AttributeTest"]
if packet.Fields["data_array"].Length != 10 {
t.Errorf("Expected size 10, got %d", packet.Fields["data_array"].Length)
}
if packet.Fields["optional_text"].Condition != "var:has_text" {
t.Errorf("Expected condition 'var:has_text', got '%s'", packet.Fields["optional_text"].Condition)
}
}
func TestComments(t *testing.T) {
pml := `<!-- This is a comment -->
<packet name="CommentTest">
<!-- Another comment -->
<version number="1">
<i32 name="id"> <!-- Inline comment -->
<str16 name="name">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["CommentTest"]
if len(packet.Fields) != 2 {
t.Errorf("Comments should not affect parsing, expected 2 fields, got %d", len(packet.Fields))
}
}
func TestBinaryParsingFloat64(t *testing.T) {
// Test binary parsing with float64
pml := `<packet name="BinaryFloat64">
<version number="1">
<f64 name="precise_value">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Create test data: 8 bytes representing float64 value 123.456
testData := []byte{0x77, 0xbe, 0x9f, 0x1a, 0x2f, 0xdd, 0x5e, 0x40} // 123.456 in little-endian
result, err := packets["BinaryFloat64"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
if val, ok := result["precise_value"].(float64); !ok {
t.Error("precise_value should be float64")
} else if val < 123.0 || val > 124.0 { // Rough check
t.Errorf("Expected value around 123.456, got %f", val)
}
}
func TestBinaryParsingOversized(t *testing.T) {
// Test oversized field parsing
pml := `<packet name="BinaryOversized">
<version number="1">
<i16 name="normal_value">
<i16 name="oversized_value" oversized="255">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test data: normal 16-bit value (100), then oversized marker (255) + 16-bit value (1000)
testData := []byte{0x64, 0x00, 0xff, 0xe8, 0x03} // 100, 255, 1000
result, err := packets["BinaryOversized"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
if val := result["normal_value"].(int16); val != 100 {
t.Errorf("Expected normal_value 100, got %d", val)
}
if val := result["oversized_value"].(int16); val != 1000 {
t.Errorf("Expected oversized_value 1000, got %d", val)
}
}
func TestBinaryParsingType2(t *testing.T) {
// Test type2 switching in binary parsing
pml := `<packet name="BinaryType2">
<version number="1">
<i8 name="stat_type">
<i32 name="stat_value" type2="f32" type2_if="stat_type==6">
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test with stat_type = 6 (should use float)
testData1 := []byte{0x06, 0x00, 0x00, 0x20, 0x41} // stat_type=6, float 10.0
result1, err := packets["BinaryType2"].Parse(testData1, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
if statType := result1["stat_type"].(int8); statType != 6 {
t.Errorf("Expected stat_type 6, got %d", statType)
}
// Note: The actual type switching logic depends on conditions.go implementation
// This test verifies the parsing structure is correct
}
func TestBinaryParsingArrayMaxSize(t *testing.T) {
// Test array max size limit
pml := `<packet name="BinaryArrayMax">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="2">
<i16 name="item_id">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test data: count=5, but max_size=2 should limit to 2 items
testData := []byte{0x05, 0x01, 0x00, 0x02, 0x00} // count=5, item1=1, item2=2
result, err := packets["BinaryArrayMax"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
items := result["items"].([]map[string]any)
if len(items) != 2 {
t.Errorf("Expected 2 items due to max_size, got %d", len(items))
}
}
func TestArrayIndexConditions(t *testing.T) {
// Test array index substitution in conditions
pml := `<packet name="ArrayConditionTest">
<version number="1">
<i8 name="stat_count">
<array name="stats" count="var:stat_count">
<i8 name="stat_type">
<i32 name="base_value">
<i32 name="modified_value" if="stat_type_%i>=1&stat_type_%i<=5">
<f32 name="percentage" if="stat_type_%i==6">
<str16 name="description" if="!var:stat_type_%i">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ArrayConditionTest"]
// Verify fields were parsed correctly
statsField := packet.Fields["stats"]
if statsField.Type != common.TypeArray {
t.Error("stats should be TypeArray")
}
if statsField.SubDef == nil {
t.Fatal("SubDef should not be nil")
}
// Check that conditions were preserved
modifiedValue := statsField.SubDef.Fields["modified_value"]
if modifiedValue.Condition != "stat_type_%i>=1&stat_type_%i<=5" {
t.Errorf("Expected 'stat_type_%%i>=1&stat_type_%%i<=5', got '%s'", modifiedValue.Condition)
}
percentage := statsField.SubDef.Fields["percentage"]
if percentage.Condition != "stat_type_%i==6" {
t.Errorf("Expected 'stat_type_%%i==6', got '%s'", percentage.Condition)
}
description := statsField.SubDef.Fields["description"]
if description.Condition != "!var:stat_type_%i" {
t.Errorf("Expected '!var:stat_type_%%i', got '%s'", description.Condition)
}
}
func TestArrayIndexBinaryParsing(t *testing.T) {
// Test that array index conditions work during binary parsing
pml := `<packet name="ArrayConditionBinary">
<version number="1">
<i8 name="stat_count">
<array name="stats" count="var:stat_count">
<i8 name="stat_type">
<i32 name="base_value">
<f32 name="percentage" if="stat_type_%i==6">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test data: 2 stats, first with type=5, second with type=6
// stat_count=2, stat1: type=5, base=100, stat2: type=6, base=200, percentage=1.5
testData := []byte{
0x02, // stat_count = 2
0x05, 0x64, 0x00, 0x00, 0x00, // stat 1: type=5, base=100 (no percentage)
0x06, 0xC8, 0x00, 0x00, 0x00, // stat 2: type=6, base=200
0x00, 0x00, 0xC0, 0x3F, // percentage=1.5 (float32)
}
result, err := packets["ArrayConditionBinary"].Parse(testData, 1, 0)
if err != nil {
t.Fatalf("Binary parse failed: %v", err)
}
stats := result["stats"].([]map[string]any)
if len(stats) != 2 {
t.Fatalf("Expected 2 stats, got %d", len(stats))
}
// First stat (type=5) should not have percentage field
stat1 := stats[0]
if stat1["stat_type"].(int8) != 5 {
t.Errorf("Expected stat_type 5, got %d", stat1["stat_type"])
}
if _, hasPercentage := stat1["percentage"]; hasPercentage {
t.Error("Stat type 5 should not have percentage field")
}
// Second stat (type=6) should have percentage field
stat2 := stats[1]
if stat2["stat_type"].(int8) != 6 {
t.Errorf("Expected stat_type 6, got %d", stat2["stat_type"])
}
if percentage, hasPercentage := stat2["percentage"]; !hasPercentage {
t.Error("Stat type 6 should have percentage field")
} else if percentage.(float32) < 1.4 || percentage.(float32) > 1.6 {
t.Errorf("Expected percentage around 1.5, got %f", percentage)
}
}
func TestComplexArrayConditions(t *testing.T) {
// Test complex conditions with array indices
pml := `<packet name="ComplexArrayTest">
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count">
<i16 name="item_type">
<i32 name="item_flags">
<i8 name="enhancement" if="item_type_%i!=0&item_flags_%i&0x01">
<color name="special_color" if="item_type_%i>=100,item_flags_%i&0x02">
</array>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
packet := packets["ComplexArrayTest"]
itemsField := packet.Fields["items"]
enhancement := itemsField.SubDef.Fields["enhancement"]
if enhancement.Condition != "item_type_%i!=0&item_flags_%i&0x01" {
t.Errorf("Enhancement condition wrong: %s", enhancement.Condition)
}
specialColor := itemsField.SubDef.Fields["special_color"]
if specialColor.Condition != "item_type_%i>=100,item_flags_%i&0x02" {
t.Errorf("Special color condition wrong: %s", specialColor.Condition)
}
}
func TestErrorHandling(t *testing.T) {
testCases := []struct {
name string
pml string
}{
{"Unclosed tag", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\">"},
{"Invalid XML", "<packet><version><i32></packet>"},
{"Missing quotes", "<packet name=Test><version number=1></version></packet>"},
{"Invalid oversized", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\" oversized=\"abc\"></version></packet>"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := Parse(tc.pml)
if err == nil {
t.Error("Expected error but got none")
}
})
}
}
func TestTemplateDefinitionAndUsage(t *testing.T) {
pml := `<!-- Define reusable templates -->
<template name="position">
<f32 name="x,y,z">
<f32 name="heading">
</template>
<template name="appearance">
<color name="skin_color,hair_color,eye_color">
<str16 name="face_file,hair_file">
</template>
<!-- Use templates in packet -->
<packet name="PlayerUpdate">
<version number="1">
<i32 name="player_id">
<template use="position">
<i8 name="level">
<template use="appearance">
<str16 name="guild_name" if="flag:has_guild">
</version>
</packet>
<!-- Test template with group prefix -->
<packet name="GroupedTemplate">
<version number="1">
<i32 name="entity_id">
<group name="current">
<template use="position">
</group>
<group name="target">
<template use="position">
</group>
</version>
</packet>`
packets, err := Parse(pml)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Test basic template injection
playerPacket := packets["PlayerUpdate"]
if playerPacket == nil {
t.Fatal("PlayerUpdate packet not found")
}
// Check that template fields were injected
expectedFields := []string{"player_id", "x", "y", "z", "heading", "level", "skin_color", "hair_color", "eye_color", "face_file", "hair_file", "guild_name"}
if len(playerPacket.Fields) != len(expectedFields) {
t.Errorf("Expected %d fields, got %d", len(expectedFields), len(playerPacket.Fields))
}
// Verify specific template fields exist with correct types
if playerPacket.Fields["x"].Type != common.TypeFloat {
t.Error("x should be TypeFloat from position template")
}
if playerPacket.Fields["heading"].Type != common.TypeFloat {
t.Error("heading should be TypeFloat from position template")
}
if playerPacket.Fields["skin_color"].Type != common.TypeColor {
t.Error("skin_color should be TypeColor from appearance template")
}
if playerPacket.Fields["face_file"].Type != common.TypeString16 {
t.Error("face_file should be TypeString16 from appearance template")
}
// Check field ordering preserves template injection points
order := playerPacket.Orders[1]
expectedOrder := []string{"player_id", "x", "y", "z", "heading", "level", "skin_color", "hair_color", "eye_color", "face_file", "hair_file", "guild_name"}
if !equalSlices(order, expectedOrder) {
t.Errorf("Expected order %v, got %v", expectedOrder, order)
}
// Test template with group prefixes
groupedPacket := packets["GroupedTemplate"]
if groupedPacket == nil {
t.Fatal("GroupedTemplate packet not found")
}
// Check prefixed fields from templates
if groupedPacket.Fields["current_x"].Type != common.TypeFloat {
t.Error("current_x should exist from prefixed template")
}
if groupedPacket.Fields["target_heading"].Type != common.TypeFloat {
t.Error("target_heading should exist from prefixed template")
}
// Verify grouped template field order
groupedOrder := groupedPacket.Orders[1]
expectedGroupedOrder := []string{"entity_id", "current_x", "current_y", "current_z", "current_heading", "target_x", "target_y", "target_z", "target_heading"}
if !equalSlices(groupedOrder, expectedGroupedOrder) {
t.Errorf("Expected grouped order %v, got %v", expectedGroupedOrder, groupedOrder)
}
}
func BenchmarkSimplePacket(b *testing.B) {
pml := `<packet name="Simple">
<version number="1">
<i32 name="id">
<str16 name="name">
<i8 name="level">
<f32 name="x,y,z">
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkMediumPacket(b *testing.B) {
pml := `<packet name="Medium">
<version number="1">
<i32 name="id,account_id,session_id">
<str16 name="name,guild_name">
<i8 name="level,race,class">
<f32 name="x,y,z,heading">
<color name="skin_color,hair_color">
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkLargePacket(b *testing.B) {
pml := `<packet name="Large">
<version number="1">
<i32 name="a1,a2,a3,a4,a5,a6,a7,a8,a9,a10">
<str16 name="s1,s2,s3,s4,s5">
<f32 name="f1,f2,f3,f4,f5,f6,f7,f8">
<color name="c1,c2,c3,c4">
<i8 name="b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12">
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkArrayPacket(b *testing.B) {
pml := `<packet name="ArrayPacket">
<version number="1">
<i8 name="count">
<array name="items" count="var:count">
<i32 name="id">
<str16 name="name">
<color name="color">
<f32 name="x,y,z">
</array>
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkMultiVersionPacket(b *testing.B) {
pml := `<packet name="MultiVersion">
<version number="1">
<i32 name="id">
<str16 name="name">
</version>
<version number="100">
<i32 name="id">
<str16 name="name">
<i8 name="level">
</version>
<version number="500">
<i32 name="id">
<str16 name="name">
<i8 name="level">
<color name="color">
</version>
</packet>`
for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
}
}
}
// Helper function to compare slices
func equalSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}

View File

@ -1,135 +0,0 @@
package parser
import "eq2emu/internal/common"
// PacketDef defines a complete packet structure with versioned field ordering
type PacketDef struct {
Fields map[string]FieldDesc // Field definitions by name
Orders map[uint32][]string // Field order by version number
}
// Creates packet definition with estimated capacity
func NewPacketDef(estimatedFields int) *PacketDef {
return &PacketDef{
Fields: make(map[string]FieldDesc, estimatedFields),
Orders: make(map[uint32][]string, 4),
}
}
func (def *PacketDef) Parse(data []byte, version uint32, flags uint64) (map[string]any, error) {
ctx := NewContext(data, version, flags)
return def.parseStruct(ctx)
}
func (def *PacketDef) parseStruct(ctx *ParseContext) (map[string]any, error) {
result := make(map[string]any)
order := def.getVersionOrder(ctx.version)
for _, fieldName := range order {
field := def.Fields[fieldName]
if !ctx.checkCondition(field.Condition) {
continue
}
fieldType := field.Type
if field.Type2 != 0 && ctx.checkCondition(field.Type2Cond) {
fieldType = field.Type2
}
value := def.parseField(ctx, field, fieldType, fieldName)
result[fieldName] = value
ctx.setVarWithArrayIndex(fieldName, value)
}
return result, nil
}
func (def *PacketDef) parseField(ctx *ParseContext, field FieldDesc, fieldType common.EQ2DataType, fieldName string) any {
switch fieldType {
case common.TypeInt8:
if field.Oversized > 0 {
return ctx.readOversizedUint8(field.Oversized)
}
return ctx.readUint8()
case common.TypeInt16:
if field.Oversized > 0 {
return ctx.readOversizedUint16(field.Oversized)
}
return ctx.readUint16()
case common.TypeInt32:
if field.Oversized > 0 {
return ctx.readOversizedUint32(field.Oversized)
}
return ctx.readUint32()
case common.TypeInt64:
return ctx.readUint64()
case common.TypeSInt8:
return ctx.readSint8()
case common.TypeSInt16:
if field.Oversized > 0 {
return ctx.readOversizedSint16(field.Oversized)
}
return ctx.readSint16()
case common.TypeSInt32:
return ctx.readSint32()
case common.TypeSInt64:
return ctx.readSint64()
case common.TypeString8:
return ctx.readEQ2String8()
case common.TypeString16:
return ctx.readEQ2String16()
case common.TypeString32:
return ctx.readEQ2String32()
case common.TypeChar:
return ctx.readBytes(field.Length)
case common.TypeFloat:
return ctx.readFloat32()
case common.TypeDouble:
return ctx.readFloat64()
case common.TypeColor:
return ctx.readEQ2Color()
case common.TypeEquipment:
return ctx.readEQ2Equipment()
case common.TypeArray:
size := ctx.getArraySize(field.Condition)
if field.MaxArraySize > 0 && size > field.MaxArraySize {
size = field.MaxArraySize
}
result := make([]map[string]any, size)
for i := 0; i < size; i++ {
ctx.pushArrayIndex(i)
item, _ := field.SubDef.parseStruct(ctx)
result[i] = item
ctx.popArrayIndex()
}
return result
}
return nil
}
func (def *PacketDef) getVersionOrder(version uint32) []string {
var bestVersion uint32
for v := range def.Orders {
if v <= version && v > bestVersion {
bestVersion = v
}
}
return def.Orders[bestVersion]
}
// FieldDesc describes a single packet field
type FieldDesc struct {
Type common.EQ2DataType // Primary data type
Condition string // Conditional parsing expression
Length int // Array length or size for fixed-size fields
SubDef *PacketDef // Nested packet definition for arrays
Type2 common.EQ2DataType // Alternative data type for conditional parsing
Type2Cond string // Condition for using Type2
Oversized int // Threshold for oversized field handling
DefaultValue int8 // Default value for initialization
MaxArraySize int // Maximum allowed array size
Optional bool // Whether this field is optional
AddToStruct bool // Whether to include in packet structure
AddType common.EQ2DataType // Type to use when adding to packet
}

View File

@ -1,370 +0,0 @@
package packets
import (
"eq2emu/internal/packets/parser"
"fmt"
"path/filepath"
"sort"
"strings"
"testing"
)
func TestParseAllXMLDefinitions(t *testing.T) {
// Get all available packet names
packetNames := GetPacketNames()
if len(packetNames) == 0 {
t.Fatal("No packet definitions loaded")
}
sort.Strings(packetNames)
var failed []string
var passed []string
var skipped []string
t.Logf("Testing %d packet definitions...", len(packetNames))
for _, name := range packetNames {
t.Run(name, func(t *testing.T) {
def, exists := GetPacket(name)
if !exists {
t.Errorf("Packet definition '%s' not found", name)
failed = append(failed, name)
return
}
// Validate packet definition structure
err := validatePacketDefinition(name, def)
if err != nil {
t.Errorf("Invalid packet definition '%s': %v", name, err)
failed = append(failed, name)
return
}
// Try to create sample data and test parsing round-trip
err = testPacketRoundTrip(name, def)
if err != nil {
if strings.Contains(err.Error(), "complex condition") {
t.Logf("Skipping '%s': %v", name, err)
skipped = append(skipped, name)
return
}
t.Errorf("Round-trip test failed for '%s': %v", name, err)
failed = append(failed, name)
return
}
passed = append(passed, name)
t.Logf("Successfully validated '%s'", name)
})
}
// Summary report
t.Logf("\n=== PACKET VALIDATION SUMMARY ===")
t.Logf("Total packets: %d", len(packetNames))
t.Logf("Passed: %d", len(passed))
t.Logf("Failed: %d", len(failed))
t.Logf("Skipped (complex conditions): %d", len(skipped))
if len(failed) > 0 {
t.Logf("\nFailed packets:")
for _, name := range failed {
t.Logf(" - %s", name)
}
}
if len(skipped) > 0 {
t.Logf("\nSkipped packets (complex conditions):")
for _, name := range skipped {
t.Logf(" - %s", name)
}
}
// Report by category
categories := make(map[string]int)
for _, name := range passed {
category := getPacketCategory(name)
categories[category]++
}
t.Logf("\nPassed packets by category:")
var cats []string
for cat := range categories {
cats = append(cats, cat)
}
sort.Strings(cats)
for _, cat := range cats {
t.Logf(" %s: %d", cat, categories[cat])
}
// Only fail the test if we have actual parsing failures, not skipped ones
if len(failed) > 0 {
t.Errorf("%d packet definitions failed validation", len(failed))
}
}
func TestPacketBuilderBasicFunctionality(t *testing.T) {
// Test with a simple packet that we know should work
testPackets := []string{
"LoginRequest",
"PlayRequest",
"CreateCharacter",
}
for _, packetName := range testPackets {
t.Run(packetName, func(t *testing.T) {
def, exists := GetPacket(packetName)
if !exists {
t.Skipf("Packet '%s' not found - may not exist in current definitions", packetName)
return
}
// Create minimal test data
data := createMinimalTestData(def)
// Test builder - just ensure it can build without error
builder := NewPacketBuilder(def, 1, 0)
packetData, err := builder.Build(data)
if err != nil {
t.Errorf("Failed to build packet '%s': %v", packetName, err)
return
}
if len(packetData) == 0 {
t.Errorf("Built packet '%s' is empty", packetName)
return
}
t.Logf("Successfully built packet '%s' (%d bytes)", packetName, len(packetData))
// Skip parsing back for now due to complex conditions - just test building
})
}
}
func TestPacketDefinitionCoverage(t *testing.T) {
// Test that we have reasonable coverage of packet types
packetNames := GetPacketNames()
categories := map[string]int{
"login": 0,
"world": 0,
"item": 0,
"spawn": 0,
"common": 0,
"other": 0,
}
for _, name := range packetNames {
category := getPacketCategory(name)
if count, exists := categories[category]; exists {
categories[category] = count + 1
} else {
categories["other"]++
}
}
t.Logf("Packet coverage by category:")
for cat, count := range categories {
t.Logf(" %s: %d packets", cat, count)
}
// Ensure we have some packets in each major category
if categories["world"] == 0 {
t.Error("No world packets found")
}
if categories["login"] == 0 {
t.Error("No login packets found")
}
if categories["item"] == 0 {
t.Error("No item packets found")
}
}
// Helper functions
func validatePacketDefinition(name string, def *parser.PacketDef) error {
if def == nil {
return fmt.Errorf("packet definition is nil")
}
if len(def.Fields) == 0 {
return fmt.Errorf("packet has no fields defined")
}
if len(def.Orders) == 0 {
return fmt.Errorf("packet has no field ordering defined")
}
// Check that all fields referenced in orders exist
for version, order := range def.Orders {
for _, fieldName := range order {
if _, exists := def.Fields[fieldName]; !exists {
return fmt.Errorf("field '%s' referenced in version %d order but not defined", fieldName, version)
}
}
}
// Validate field definitions
for fieldName, field := range def.Fields {
err := validateFieldDefinition(fieldName, field)
if err != nil {
return fmt.Errorf("invalid field '%s': %w", fieldName, err)
}
}
return nil
}
func validateFieldDefinition(name string, field parser.FieldDesc) error {
// Check that field type is valid
if field.Type < 0 || field.Type > 25 { // Adjust range based on actual enum
return fmt.Errorf("invalid field type %d", field.Type)
}
// If Type2 is set, Type2Cond should also be set
if field.Type2 != 0 && field.Type2Cond == "" {
return fmt.Errorf("field has Type2 but no Type2Cond")
}
return nil
}
func testPacketRoundTrip(name string, def *parser.PacketDef) error {
// Skip packets with complex conditions that would be hard to satisfy
if hasComplexConditions(def) {
return fmt.Errorf("complex condition detected - skipping round-trip test")
}
// For now, just validate the structure without round-trip testing
// This is safer until we have better condition handling
return nil
}
func hasComplexConditions(def *parser.PacketDef) bool {
for _, field := range def.Fields {
if strings.Contains(field.Condition, ">=") ||
strings.Contains(field.Condition, "<=") ||
strings.Contains(field.Condition, "!=") ||
strings.Contains(field.Condition, "&&") ||
strings.Contains(field.Condition, "||") ||
strings.Contains(field.Type2Cond, ">=") ||
strings.Contains(field.Type2Cond, "<=") {
return true
}
}
return false
}
func createMinimalTestData(def *parser.PacketDef) map[string]any {
data := make(map[string]any)
// Get the field order for version 1 (or first available version)
var version uint32 = 1
if len(def.Orders) > 0 {
for v := range def.Orders {
version = v
break
}
}
order, exists := def.Orders[version]
if !exists && len(def.Orders) > 0 {
// Take first available version
for v, o := range def.Orders {
version = v
order = o
break
}
}
for _, fieldName := range order {
field, fieldExists := def.Fields[fieldName]
if !fieldExists {
continue
}
// Skip fields with complex conditions
if field.Condition != "" &&
(strings.Contains(field.Condition, ">=") ||
strings.Contains(field.Condition, "<=") ||
strings.Contains(field.Condition, "!=")) {
continue
}
// Create minimal test data based on field type
switch field.Type {
case 1: // TypeInt8
data[fieldName] = uint8(1)
case 2: // TypeInt16
data[fieldName] = uint16(1)
case 3: // TypeInt32
data[fieldName] = uint32(1)
case 4: // TypeInt64
data[fieldName] = uint64(1)
case 5: // TypeFloat
data[fieldName] = float32(1.0)
case 6: // TypeDouble
data[fieldName] = float64(1.0)
case 8: // TypeSInt8
data[fieldName] = int8(1)
case 9: // TypeSInt16
data[fieldName] = int16(1)
case 10: // TypeSInt32
data[fieldName] = int32(1)
case 12: // TypeChar
if field.Length > 0 {
data[fieldName] = make([]byte, field.Length)
} else {
data[fieldName] = []byte("test")
}
case 13, 14, 15: // String types
data[fieldName] = "test"
case 17: // TypeArray
data[fieldName] = []map[string]any{}
case 25: // TypeSInt64
data[fieldName] = int64(1)
}
}
return data
}
func getPacketCategory(packetName string) string {
name := strings.ToLower(packetName)
if strings.Contains(name, "login") || strings.Contains(name, "play") ||
strings.Contains(name, "world") && (strings.Contains(name, "list") || strings.Contains(name, "update")) {
return "login"
}
if strings.Contains(name, "item") || strings.Contains(name, "inventory") ||
strings.Contains(name, "merchant") || strings.Contains(name, "loot") {
return "item"
}
if strings.Contains(name, "spawn") || strings.Contains(name, "position") {
return "spawn"
}
if strings.Contains(name, "character") || strings.Contains(name, "create") {
return "common"
}
// Determine by file path if we have it
dir := filepath.Dir(packetName)
switch {
case strings.Contains(dir, "login"):
return "login"
case strings.Contains(dir, "world"):
return "world"
case strings.Contains(dir, "item"):
return "item"
case strings.Contains(dir, "spawn"):
return "spawn"
case strings.Contains(dir, "common"):
return "common"
default:
return "world" // Most packets are world packets
}
}

View File

@ -1,255 +0,0 @@
package packets
import (
"encoding/binary"
"eq2emu/internal/common"
"eq2emu/internal/packets/parser"
"fmt"
"io"
"math"
)
// PacketReader reads packet data based on packet definitions
type PacketReader struct {
data []byte
pos int
}
// NewPacketReader creates a new packet reader
func NewPacketReader(data []byte) *PacketReader {
return &PacketReader{
data: data,
pos: 0,
}
}
// ParsePacketFields parses packet data using a packet definition
func ParsePacketFields(data []byte, packetName string, version uint32) (map[string]any, error) {
def, exists := GetPacket(packetName)
if !exists {
return nil, fmt.Errorf("packet definition '%s' not found", packetName)
}
reader := NewPacketReader(data)
return reader.parseStruct(def, version)
}
// parseStruct parses a struct according to packet definition
func (r *PacketReader) parseStruct(def *parser.PacketDef, version uint32) (map[string]any, error) {
result := make(map[string]any)
// Get field order for this version
order := r.getVersionOrder(def, version)
for _, fieldName := range order {
field, exists := def.Fields[fieldName]
if !exists {
continue
}
// For simplicity, skip conditional fields for now
if field.Condition != "" {
continue
}
fieldType := field.Type
if field.Type2 != 0 {
fieldType = field.Type2
}
value, err := r.readField(field, fieldType, fieldName, result)
if err != nil {
return nil, fmt.Errorf("error reading field '%s': %w", fieldName, err)
}
if value != nil {
result[fieldName] = value
}
}
return result, nil
}
// readField reads a single field from the packet data
func (r *PacketReader) readField(field parser.FieldDesc, fieldType common.EQ2DataType, fieldName string, context map[string]any) (any, error) {
switch fieldType {
case common.TypeInt8:
return r.readUint8()
case common.TypeInt16:
return r.readUint16()
case common.TypeInt32:
return r.readUint32()
case common.TypeInt64:
return r.readUint64()
case common.TypeSInt8:
return r.readInt8()
case common.TypeSInt16:
return r.readInt16()
case common.TypeSInt32:
return r.readInt32()
case common.TypeSInt64:
return r.readInt64()
case common.TypeString8:
return r.readEQ2String8()
case common.TypeString16:
return r.readEQ2String16()
case common.TypeString32:
return r.readEQ2String32()
case common.TypeFloat:
return r.readFloat32()
case common.TypeDouble:
return r.readFloat64()
case common.TypeChar:
if field.Length > 0 {
return r.readBytes(field.Length)
}
return nil, fmt.Errorf("char field '%s' has no length specified", fieldName)
default:
// For unsupported types, skip the field
return nil, nil
}
}
// Low-level read functions
func (r *PacketReader) readUint8() (uint8, error) {
if r.pos+1 > len(r.data) {
return 0, io.EOF
}
value := r.data[r.pos]
r.pos++
return value, nil
}
func (r *PacketReader) readInt8() (int8, error) {
value, err := r.readUint8()
return int8(value), err
}
func (r *PacketReader) readUint16() (uint16, error) {
if r.pos+2 > len(r.data) {
return 0, io.EOF
}
value := binary.LittleEndian.Uint16(r.data[r.pos:])
r.pos += 2
return value, nil
}
func (r *PacketReader) readInt16() (int16, error) {
value, err := r.readUint16()
return int16(value), err
}
func (r *PacketReader) readUint32() (uint32, error) {
if r.pos+4 > len(r.data) {
return 0, io.EOF
}
value := binary.LittleEndian.Uint32(r.data[r.pos:])
r.pos += 4
return value, nil
}
func (r *PacketReader) readInt32() (int32, error) {
value, err := r.readUint32()
return int32(value), err
}
func (r *PacketReader) readUint64() (uint64, error) {
if r.pos+8 > len(r.data) {
return 0, io.EOF
}
value := binary.LittleEndian.Uint64(r.data[r.pos:])
r.pos += 8
return value, nil
}
func (r *PacketReader) readInt64() (int64, error) {
value, err := r.readUint64()
return int64(value), err
}
func (r *PacketReader) readFloat32() (float32, error) {
if r.pos+4 > len(r.data) {
return 0, io.EOF
}
bits := binary.LittleEndian.Uint32(r.data[r.pos:])
r.pos += 4
return math.Float32frombits(bits), nil
}
func (r *PacketReader) readFloat64() (float64, error) {
if r.pos+8 > len(r.data) {
return 0, io.EOF
}
bits := binary.LittleEndian.Uint64(r.data[r.pos:])
r.pos += 8
return math.Float64frombits(bits), nil
}
func (r *PacketReader) readBytes(n int) ([]byte, error) {
if r.pos+n > len(r.data) {
return nil, io.EOF
}
data := make([]byte, n)
copy(data, r.data[r.pos:r.pos+n])
r.pos += n
return data, nil
}
func (r *PacketReader) readEQ2String8() (string, error) {
length, err := r.readUint8()
if err != nil {
return "", err
}
if length == 0 {
return "", nil
}
data, err := r.readBytes(int(length))
if err != nil {
return "", err
}
return string(data), nil
}
func (r *PacketReader) readEQ2String16() (string, error) {
length, err := r.readUint16()
if err != nil {
return "", err
}
if length == 0 {
return "", nil
}
data, err := r.readBytes(int(length))
if err != nil {
return "", err
}
return string(data), nil
}
func (r *PacketReader) readEQ2String32() (string, error) {
length, err := r.readUint32()
if err != nil {
return "", err
}
if length == 0 {
return "", nil
}
data, err := r.readBytes(int(length))
if err != nil {
return "", err
}
return string(data), nil
}
// getVersionOrder returns the field order for the specified version
func (r *PacketReader) getVersionOrder(def *parser.PacketDef, version uint32) []string {
var bestVersion uint32
for v := range def.Orders {
if v <= version && v > bestVersion {
bestVersion = v
}
}
if order, exists := def.Orders[bestVersion]; exists {
return order
}
return []string{}
}