diff --git a/internal/packets/PARSER.md b/internal/packets/PARSER.md
index d80270c..9a8db77 100644
--- a/internal/packets/PARSER.md
+++ b/internal/packets/PARSER.md
@@ -82,12 +82,6 @@ Fast XML-like parser for binary packet structures with versioning and conditiona
Organize related fields with automatic prefixing:
```xml
-
-
-
-
-
-
@@ -101,10 +95,8 @@ Organize related fields with automatic prefixing:
```xml
-
-
-
-
+
+
```
diff --git a/internal/packets/parser/context.go b/internal/packets/parser/context.go
index f7ca851..e063575 100644
--- a/internal/packets/parser/context.go
+++ b/internal/packets/parser/context.go
@@ -207,13 +207,13 @@ func (ctx *ParseContext) evaluateCondition(condition string) bool {
return (ctx.flags & ctx.getFlagValue(flagName)) == 0
}
- // Variable conditions: var:name or !var:name
+ // Variable conditions: var:name or !var:name (with %i support)
if strings.HasPrefix(condition, "var:") {
- varName := condition[4:]
+ varName := ctx.resolveVariableName(condition[4:])
return ctx.hasVar(varName)
}
if strings.HasPrefix(condition, "!var:") {
- varName := condition[5:]
+ varName := ctx.resolveVariableName(condition[5:])
return !ctx.hasVar(varName)
}
@@ -222,27 +222,36 @@ func (ctx *ParseContext) evaluateCondition(condition string) bool {
return ctx.evaluateVersionCondition(condition)
}
- // String length operators: name!>5, name!<=10
+ // 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 := condition[:idx]
+ varName := ctx.resolveVariableName(condition[:idx])
valueStr := condition[idx+len(op):]
return ctx.evaluateStringLength(varName, valueStr, op)
}
}
- // Comparison operators: >=, <=, >, <, ==, !=
+ // Comparison operators: >=, <=, >, <, ==, != (with %i support)
compOps := []string{">=", "<=", ">", "<", "==", "!="}
for _, op := range compOps {
if idx := strings.Index(condition, op); idx > 0 {
- varName := condition[:idx]
+ varName := ctx.resolveVariableName(condition[:idx])
valueStr := condition[idx+len(op):]
return ctx.evaluateComparison(varName, valueStr, op)
}
}
- // Simple variable existence
+ // Simple variable existence (with %i support)
resolvedName := ctx.resolveVariableName(condition)
return ctx.hasVar(resolvedName)
}
@@ -394,3 +403,15 @@ func (ctx *ParseContext) resolveVariableName(name string) string {
}
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
+ }
+}
diff --git a/internal/packets/parser/parser.go b/internal/packets/parser/parser.go
index 0066df4..3c0efde 100644
--- a/internal/packets/parser/parser.go
+++ b/internal/packets/parser/parser.go
@@ -520,7 +520,7 @@ func (p *Parser) parseGroup(packetDef *PacketDef, fieldOrder *[]string, prefix s
return nil
}
-// Handles array elements
+// Handles array elements - FIXED: Remove redundant substruct wrapper
func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
attrs := p.current.Attributes
@@ -565,7 +565,8 @@ func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix s
}
}
- if p.current.Type == TokenSelfCloseTag {
+ // Arrays with substruct references or explicit self-closing syntax are self-closing
+ if p.current.Type == TokenSelfCloseTag || fieldDesc.SubDef != nil {
p.advance()
packetDef.Fields[arrayName] = fieldDesc
*fieldOrder = append(*fieldOrder, arrayName)
@@ -575,30 +576,9 @@ func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix s
p.pushTag("array")
p.advance()
- // If we have a substruct reference and no inline content
- if fieldDesc.SubDef != nil && (p.current.Type == TokenCloseTag ||
- (p.current.Type == TokenOpenTag && p.current.Tag(p.input) != "substruct")) {
-
- if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "array" {
- if err := p.popTag("array"); err != nil {
- return err
- }
- p.advance()
- } else {
- p.tagStack = p.tagStack[:len(p.tagStack)-1]
- }
- packetDef.Fields[arrayName] = fieldDesc
- *fieldOrder = append(*fieldOrder, arrayName)
- return nil
- }
-
- // Handle inline substruct
- if fieldDesc.SubDef == nil && p.current.Type == TokenOpenTag && p.current.Tag(p.input) == "substruct" {
+ // Handle direct child elements as substruct fields (no wrapper needed)
+ if fieldDesc.SubDef == nil {
subDef := NewPacketDef(16)
-
- p.pushTag("substruct")
- p.advance()
-
subOrder := fieldOrderPool.Get().(*[]string)
*subOrder = (*subOrder)[:0]
defer fieldOrderPool.Put(subOrder)
@@ -608,18 +588,12 @@ func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix s
return err
}
- if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "substruct" {
- if err := p.popTag("substruct"); err != nil {
- return err
- }
- p.advance()
- } else {
- return fmt.Errorf("expected closing tag for substruct at line %d", p.current.Line)
+ // Only create substruct if we actually have fields
+ if len(*subOrder) > 0 {
+ subDef.Orders[1] = make([]string, len(*subOrder))
+ copy(subDef.Orders[1], *subOrder)
+ fieldDesc.SubDef = subDef
}
-
- subDef.Orders[1] = make([]string, len(*subOrder))
- copy(subDef.Orders[1], *subOrder)
- fieldDesc.SubDef = subDef
}
if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "array" {
diff --git a/internal/packets/parser/parser_test.go b/internal/packets/parser/parser_test.go
index 54907a7..e1afb25 100644
--- a/internal/packets/parser/parser_test.go
+++ b/internal/packets/parser/parser_test.go
@@ -182,9 +182,7 @@ func TestArrayMaxSize(t *testing.T) {
-
-
-
+
`
@@ -207,9 +205,7 @@ func TestArrayOptionalAttributes(t *testing.T) {
-
-
-
+
`
@@ -275,10 +271,8 @@ func TestArrayParsing(t *testing.T) {
-
-
-
-
+
+
`
@@ -541,9 +535,7 @@ func TestBinaryParsingArrayMaxSize(t *testing.T) {
-
-
-
+
`
@@ -567,6 +559,146 @@ func TestBinaryParsingArrayMaxSize(t *testing.T) {
}
}
+func TestArrayIndexConditions(t *testing.T) {
+ // Test array index substitution in conditions
+ pml := `
+
+
+
+
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+
+
+ `
+
+ 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"].(uint8) != 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"].(uint8) != 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 := `
+
+
+
+
+
+
+
+
+
+ `
+
+ 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
@@ -616,21 +748,17 @@ func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
`
diff --git a/internal/packets/parser/structs.go b/internal/packets/parser/structs.go
index de12dba..356db6c 100644
--- a/internal/packets/parser/structs.go
+++ b/internal/packets/parser/structs.go
@@ -31,7 +31,7 @@ func (def *PacketDef) parseStruct(ctx *ParseContext) (map[string]any, error) {
value := def.parseField(ctx, field, fieldType, fieldName)
result[fieldName] = value
- ctx.setVar(fieldName, value)
+ ctx.setVarWithArrayIndex(fieldName, value)
}
return result, nil
diff --git a/internal/packets/substructs/AASpellInfo.xml b/internal/packets/substructs/AASpellInfo.xml
new file mode 100644
index 0000000..257d8b3
--- /dev/null
+++ b/internal/packets/substructs/AASpellInfo.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/internal/packets/substructs/BaseItemDescription.xml b/internal/packets/substructs/BaseItemDescription.xml
new file mode 100644
index 0000000..e1ccf37
--- /dev/null
+++ b/internal/packets/substructs/BaseItemDescription.xml
@@ -0,0 +1,484 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file