diff --git a/internal/packets/parser/context.go b/internal/packets/parser/context.go
new file mode 100644
index 0000000..f7ca851
--- /dev/null
+++ b/internal/packets/parser/context.go
@@ -0,0 +1,396 @@
+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),
+ }
+}
+
+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
+}
+
+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) readString8() string {
+ length := ctx.readUint8()
+ str := string(ctx.data[ctx.offset : ctx.offset+int(length)])
+ ctx.offset += int(length)
+ return str
+}
+
+func (ctx *ParseContext) readString16() string {
+ length := ctx.readUint16()
+ str := string(ctx.data[ctx.offset : ctx.offset+int(length)])
+ ctx.offset += int(length)
+ return str
+}
+
+func (ctx *ParseContext) readEQ2String8() common.EQ2String8 {
+ size := ctx.readUint8()
+ 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.readUint16()
+ 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.readUint32()
+ 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.readUint8(),
+ Green: ctx.readUint8(),
+ Blue: ctx.readUint8(),
+ }
+}
+
+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
+ if strings.HasPrefix(condition, "var:") {
+ varName := condition[4:]
+ return ctx.hasVar(varName)
+ }
+ if strings.HasPrefix(condition, "!var:") {
+ varName := condition[5:]
+ return !ctx.hasVar(varName)
+ }
+
+ // Version comparisons
+ if strings.HasPrefix(condition, "version") {
+ return ctx.evaluateVersionCondition(condition)
+ }
+
+ // String length operators: name!>5, name!<=10
+ stringOps := []string{"!>=", "!<=", "!>", "!<", "!="}
+ for _, op := range stringOps {
+ if idx := strings.Index(condition, op); idx > 0 {
+ varName := condition[:idx]
+ valueStr := condition[idx+len(op):]
+ return ctx.evaluateStringLength(varName, valueStr, op)
+ }
+ }
+
+ // Comparison operators: >=, <=, >, <, ==, !=
+ compOps := []string{">=", "<=", ">", "<", "==", "!="}
+ for _, op := range compOps {
+ if idx := strings.Index(condition, op); idx > 0 {
+ varName := condition[:idx]
+ valueStr := condition[idx+len(op):]
+ return ctx.evaluateComparison(varName, valueStr, op)
+ }
+ }
+
+ // Simple variable existence
+ 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:
+ 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
+ }
+ }
+ 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
+}
diff --git a/internal/packets/parser/lexer.go b/internal/packets/parser/lexer.go
index a22ab01..8a992c7 100644
--- a/internal/packets/parser/lexer.go
+++ b/internal/packets/parser/lexer.go
@@ -63,7 +63,7 @@ func (l *Lexer) next() byte {
// Checks if a tag should be treated as self-closing (using byte comparison)
func (l *Lexer) isSelfClosingTag(start, end int) bool {
length := end - start
- if length < 2 || length > 5 {
+ if length < 2 || length > 6 {
return false
}
@@ -103,6 +103,10 @@ func (l *Lexer) isSelfClosingTag(start, end int) bool {
(l.input[start] == 's' && l.input[start+1] == 't' &&
l.input[start+2] == 'r' && l.input[start+3] == '3' &&
l.input[start+4] == '2')
+ case 6:
+ return (l.input[start] == 'd' && l.input[start+1] == 'o' &&
+ l.input[start+2] == 'u' && l.input[start+3] == 'b' &&
+ l.input[start+4] == 'l' && l.input[start+5] == 'e')
}
return false
}
diff --git a/internal/packets/parser/parser.go b/internal/packets/parser/parser.go
index ddfd145..0066df4 100644
--- a/internal/packets/parser/parser.go
+++ b/internal/packets/parser/parser.go
@@ -58,23 +58,24 @@ type Parser struct {
// Type mapping for efficient lookup
var typeMap = map[string]common.EQ2DataType{
- "i8": common.TypeInt8,
- "i16": common.TypeInt16,
- "i32": common.TypeInt32,
- "i64": common.TypeInt64,
- "si8": common.TypeSInt8,
- "si16": common.TypeSInt16,
- "si32": common.TypeSInt32,
- "si64": common.TypeSInt64,
- "f32": common.TypeFloat,
- "f64": common.TypeDouble,
- "str8": common.TypeString8,
- "str16": common.TypeString16,
- "str32": common.TypeString32,
- "char": common.TypeChar,
- "color": common.TypeColor,
- "equip": common.TypeEquipment,
- "array": common.TypeArray,
+ "i8": common.TypeInt8,
+ "i16": common.TypeInt16,
+ "i32": common.TypeInt32,
+ "i64": common.TypeInt64,
+ "si8": common.TypeSInt8,
+ "si16": common.TypeSInt16,
+ "si32": common.TypeSInt32,
+ "si64": common.TypeSInt64,
+ "f32": common.TypeFloat,
+ "f64": common.TypeDouble,
+ "double": common.TypeDouble, // XML compatibility
+ "str8": common.TypeString8,
+ "str16": common.TypeString16,
+ "str32": common.TypeString32,
+ "char": common.TypeChar,
+ "color": common.TypeColor,
+ "equip": common.TypeEquipment,
+ "array": common.TypeArray,
}
// Creates a new parser
@@ -183,6 +184,10 @@ func getDataType(tag string) (common.EQ2DataType, bool) {
case "f64":
return common.TypeDouble, true
}
+ case 'd':
+ if tag == "double" {
+ return common.TypeDouble, true
+ }
case 'c':
switch tag {
case "char":
@@ -527,14 +532,32 @@ func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix s
}
fieldDesc := FieldDesc{
- Type: common.TypeArray,
- Condition: attrs["count"],
+ Type: common.TypeArray,
+ Condition: attrs["count"],
+ AddToStruct: true, // Default to true
}
if ifCond := attrs["if"]; ifCond != "" {
fieldDesc.Condition = combineConditions(fieldDesc.Condition, ifCond)
}
+ // Parse additional attributes
+ if maxSize := attrs["max_size"]; maxSize != "" {
+ if m, err := strconv.Atoi(maxSize); err == nil {
+ fieldDesc.MaxArraySize = m
+ } else {
+ return fmt.Errorf("invalid max_size value '%s' at line %d: %v", maxSize, p.current.Line, err)
+ }
+ }
+
+ if optional := attrs["optional"]; optional == "true" {
+ fieldDesc.Optional = true
+ }
+
+ if addToStruct := attrs["add_to_struct"]; addToStruct == "false" {
+ fieldDesc.AddToStruct = false
+ }
+
// Handle substruct reference
if substruct := attrs["substruct"]; substruct != "" {
if subDef, exists := p.substructs[substruct]; exists {
@@ -640,13 +663,70 @@ func (p *Parser) parseField(packetDef *PacketDef, fieldOrder *[]string, prefix s
}
fieldDesc := FieldDesc{
- Type: dataType,
- Condition: attrs["if"],
+ Type: dataType,
+ Condition: attrs["if"],
+ AddToStruct: true, // Default to true
+ AddType: dataType,
}
+ // Parse size attribute
if size := attrs["size"]; size != "" {
if s, err := strconv.Atoi(size); err == nil {
fieldDesc.Length = s
+ } else {
+ return fmt.Errorf("invalid size value '%s' at line %d: %v", size, p.current.Line, err)
+ }
+ }
+
+ // Parse oversized attribute
+ if oversized := attrs["oversized"]; oversized != "" {
+ if o, err := strconv.Atoi(oversized); err == nil {
+ fieldDesc.Oversized = o
+ } else {
+ return fmt.Errorf("invalid oversized value '%s' at line %d: %v", oversized, p.current.Line, err)
+ }
+ }
+
+ // Parse type2 attributes
+ if type2 := attrs["type2"]; type2 != "" {
+ if t2, exists := getDataType(type2); exists {
+ fieldDesc.Type2 = t2
+ fieldDesc.Type2Cond = attrs["type2_if"]
+ }
+ }
+
+ // Parse default value
+ if defaultVal := attrs["default"]; defaultVal != "" {
+ if d, err := strconv.ParseInt(defaultVal, 10, 8); err == nil {
+ fieldDesc.DefaultValue = int8(d)
+ } else {
+ return fmt.Errorf("invalid default value '%s' at line %d: %v", defaultVal, p.current.Line, err)
+ }
+ }
+
+ // Parse max_size
+ if maxSize := attrs["max_size"]; maxSize != "" {
+ if m, err := strconv.Atoi(maxSize); err == nil {
+ fieldDesc.MaxArraySize = m
+ } else {
+ return fmt.Errorf("invalid max_size value '%s' at line %d: %v", maxSize, p.current.Line, err)
+ }
+ }
+
+ // Parse optional
+ if optional := attrs["optional"]; optional == "true" {
+ fieldDesc.Optional = true
+ }
+
+ // Parse add_to_struct
+ if addToStruct := attrs["add_to_struct"]; addToStruct == "false" {
+ fieldDesc.AddToStruct = false
+ }
+
+ // Parse add_type
+ if addType := attrs["add_type"]; addType != "" {
+ if at, exists := getDataType(addType); exists {
+ fieldDesc.AddType = at
}
}
@@ -663,4 +743,4 @@ func Parse(content string) (map[string]*PacketDef, error) {
parser := NewParser(content)
defer parser.cleanup()
return parser.Parse()
-}
\ No newline at end of file
+}
diff --git a/internal/packets/parser/parser_test.go b/internal/packets/parser/parser_test.go
index 5a08023..54907a7 100644
--- a/internal/packets/parser/parser_test.go
+++ b/internal/packets/parser/parser_test.go
@@ -49,6 +49,187 @@ func TestBasicParsing(t *testing.T) {
}
}
+func TestFloat64Support(t *testing.T) {
+ pml := `
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+ `
+
+ 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.TypeInt32 {
+ t.Error("stat_value primary type should be TypeInt32")
+ }
+ 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.TypeInt32 {
+ t.Error("another_field type2 should be TypeInt32")
+ }
+ if anotherField.Type2Cond != "" {
+ t.Error("another_field should have empty type2_if")
+ }
+}
+
+func TestAdvancedFieldAttributes(t *testing.T) {
+ pml := `
+
+
+
+
+
+ `
+
+ 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.TypeInt16 {
+ t.Error("hidden_field add_type should be TypeInt16")
+ }
+}
+
+func TestArrayMaxSize(t *testing.T) {
+ pml := `
+
+
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+
+
+ `
+
+ 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 := `
@@ -265,6 +446,127 @@ func TestComments(t *testing.T) {
}
}
+func TestBinaryParsingFloat64(t *testing.T) {
+ // Test binary parsing with float64
+ pml := `
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+ `
+
+ 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"].(uint16); val != 100 {
+ t.Errorf("Expected normal_value 100, got %d", val)
+ }
+
+ if val := result["oversized_value"].(uint16); val != 1000 {
+ t.Errorf("Expected oversized_value 1000, got %d", val)
+ }
+}
+
+func TestBinaryParsingType2(t *testing.T) {
+ // Test type2 switching in binary parsing
+ pml := `
+
+
+
+
+ `
+
+ 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"].(uint8); 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 := `
+
+
+
+
+
+
+
+
+ `
+
+ 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 TestErrorHandling(t *testing.T) {
testCases := []struct {
name string
@@ -273,6 +575,7 @@ func TestErrorHandling(t *testing.T) {
{"Unclosed tag", ""},
{"Invalid XML", ""},
{"Missing quotes", ""},
+ {"Invalid oversized", ""},
}
for _, tc := range testCases {
@@ -295,8 +598,7 @@ func BenchmarkSimplePacket(b *testing.B) {
`
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
@@ -304,7 +606,7 @@ func BenchmarkSimplePacket(b *testing.B) {
}
}
-func BenchmarkComplexPacket(b *testing.B) {
+func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
pml := `
@@ -313,27 +615,27 @@ func BenchmarkComplexPacket(b *testing.B) {
-
+
+
-
+
-
+
`
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := Parse(pml)
if err != nil {
b.Fatal(err)
@@ -341,47 +643,6 @@ func BenchmarkComplexPacket(b *testing.B) {
}
}
-func BenchmarkLargePacket(b *testing.B) {
- // Generate a large packet definition
- pmlBuilder := ``
- for i := 0; i < 100; i++ {
- pmlBuilder += ``
- }
- pmlBuilder += ``
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- _, err := Parse(pmlBuilder)
- if err != nil {
- b.Fatal(err)
- }
- }
-}
-
-func BenchmarkWithSubstructs(b *testing.B) {
- pml := `
-
-
-
-
-
-
-
-
-
-
- `
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- parser := NewParser(pml)
- _, err := parser.Parse()
- if err != nil {
- b.Fatal(err)
- }
- }
-}
-
// Helper function to compare slices
func equalSlices(a, b []string) bool {
if len(a) != len(b) {
diff --git a/internal/packets/parser/structs.go b/internal/packets/parser/structs.go
index 78ee506..de12dba 100644
--- a/internal/packets/parser/structs.go
+++ b/internal/packets/parser/structs.go
@@ -8,13 +8,109 @@ type PacketDef struct {
Orders map[uint32][]string // Field order by version number
}
+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.setVar(fieldName, value)
+ }
+
+ return result, nil
+}
+
+func (def *PacketDef) parseField(ctx *ParseContext, field FieldDesc, fieldType common.EQ2DataType, fieldName string) any {
+ switch fieldType {
+ case common.TypeInt8, common.TypeSInt8:
+ if field.Oversized > 0 {
+ return ctx.readOversizedUint8(field.Oversized)
+ }
+ return ctx.readUint8()
+ case common.TypeInt16, common.TypeSInt16:
+ if field.Oversized > 0 {
+ return ctx.readOversizedUint16(field.Oversized)
+ }
+ return ctx.readUint16()
+ case common.TypeInt32, common.TypeSInt32:
+ if field.Oversized > 0 {
+ return ctx.readOversizedUint32(field.Oversized)
+ }
+ return ctx.readUint32()
+ case common.TypeInt64, common.TypeSInt64:
+ return ctx.readUint64()
+ 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
+ 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
}