Make substructs implicit in arrays

This commit is contained in:
Sky Johnson 2025-07-28 21:55:15 -05:00
parent 3f9aae51cb
commit b2a2e9366b
7 changed files with 718 additions and 80 deletions

View File

@ -82,12 +82,6 @@ Fast XML-like parser for binary packet structures with versioning and conditiona
Organize related fields with automatic prefixing:
```xml
<group>
<i32 name="player_id">
<str16 name="player_name">
</group>
<!-- Creates: player_id, player_name (no prefix added) -->
<group name="appearance">
<color name="skin_color,hair_color,eye_color">
<str16 name="face_file,hair_file">
@ -101,10 +95,8 @@ Organize related fields with automatic prefixing:
```xml
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="100">
<substruct>
<i32 name="item_id">
<str16 name="item_name">
</substruct>
<i32 name="item_id">
<str16 name="item_name">
</array>
```

View File

@ -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
}
}

View File

@ -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" {

View File

@ -182,9 +182,7 @@ func TestArrayMaxSize(t *testing.T) {
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="100">
<substruct>
<i32 name="item_id">
</substruct>
<i32 name="item_id">
</array>
</version>
</packet>`
@ -207,9 +205,7 @@ func TestArrayOptionalAttributes(t *testing.T) {
<version number="1">
<i8 name="count">
<array name="optional_items" count="var:count" optional="true" add_to_struct="false">
<substruct>
<i32 name="id">
</substruct>
<i32 name="id">
</array>
</version>
</packet>`
@ -275,10 +271,8 @@ func TestArrayParsing(t *testing.T) {
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count">
<substruct>
<i32 name="item_id">
<str16 name="item_name">
</substruct>
<i32 name="item_id">
<str16 name="item_name">
</array>
</version>
</packet>`
@ -541,9 +535,7 @@ func TestBinaryParsingArrayMaxSize(t *testing.T) {
<version number="1">
<i8 name="item_count">
<array name="items" count="var:item_count" max_size="2">
<substruct>
<i16 name="item_id">
</substruct>
<i16 name="item_id">
</array>
</version>
</packet>`
@ -567,6 +559,146 @@ func TestBinaryParsingArrayMaxSize(t *testing.T) {
}
}
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"].(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 := `<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
@ -616,21 +748,17 @@ func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
<i32 name="guild_id" if="flag:has_guild">
<i8 name="equipment_count">
<array name="equipment" count="var:equipment_count" max_size="50">
<substruct>
<i16 name="slot_id,item_type">
<color name="primary_color,secondary_color">
<i8 name="enhancement_level" if="item_type!=0">
<i32 name="stat_value" type2="f32" type2_if="item_type==6" oversized="255">
</substruct>
<i16 name="slot_id,item_type">
<color name="primary_color,secondary_color">
<i8 name="enhancement_level" if="item_type!=0">
<i32 name="stat_value" type2="f32" type2_if="item_type==6" oversized="255">
</array>
<i8 name="stat_count">
<array name="stats" count="var:stat_count" optional="true">
<substruct>
<i8 name="stat_type">
<i32 name="base_value">
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
<f64 name="percentage" if="stat_type==6">
</substruct>
<i8 name="stat_type">
<i32 name="base_value">
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
<f64 name="percentage" if="stat_type==6">
</array>
</version>
</packet>`

View File

@ -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

View File

@ -0,0 +1,39 @@
<substruct name="AASpellInfo">
<version number="1193">
<i32 name="id">
<i16 name="icon,icon2,icontype,version,sub_version">
<i8 name="type">
<i32 name="class_skill,mastery_skill">
<i16 name="min_class_skill_req" oversized="127">
<i32 name="spell_text_color">
<i8 name="unknown_605_MJ_3,tier">
<i16 name="health_req,health_upkeep,power_req,power_upkeep" oversized="127">
<i16 name="req_concentration,unknown">
<i16 name="cast_time,recovery" oversized="127">
<f32 name="recast,radius">
<i16 name="max_aoe_targets">
<i8 name="friendly_spell,num_reagents">
<array name="reagent_array" count="var:num_reagents">
<str8 name="reagent">
<i8 name="consumed">
<i32 name="qty_required">
</array>
<i8 name="num_effects">
<array name="effect_array" count="var:num_effects">
<i8 name="subbulletflag">
<str16 name="effect">
<i8 name="percentage">
</array>
<i8 name="display_spell_tier,unknown1">
<f32 name="minimum_range,range">
<i32 name="duration1,duration2">
<i8 name="unknown9,duration_flag,target,can_effect_raid,affect_only_group_members,group_spell">
<f32 name="resistibility">
<i8 name="unknown11" size="7">
<f32 name="hit_bonus">
<i8 name="unknown12">
<str8 name="name">
<str16 name="description">
<i8 name="unknown_605_MJ_6">
</version>
</substruct>

View File

@ -0,0 +1,484 @@
<substruct name="BaseItemDescription">
<version number="1">
<i8 name="creator_flag">
<str16 name="creator">
<i32 name="unique_id">
<i16 name="icon">
<str8 name="flag_names">
<i8 name="unknown8_1" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127">
<si16 name="value" oversized="127">
<str8 name="stat_name">
</array>
<i8 name="stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="adornment_flag">
<array name="adornment_array" count="var:adornment_flag">
<i8 name="adornment_unknown">
</array>
<str16 name="stat_description">
</array>
<i8 name="condition">
<i16 name="weight" oversized="127">
<i32 name="skill_req1">
<i16 name="skill_min,skill_recommended" oversized="127">
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
</version>
<version number="373">
<i8 name="creator_flag">
<str16 name="creator">
<i32 name="unique_id">
<i16 name="icon">
<i8 name="tier">
<str8 name="flag_names">
<i8 name="unknown8_1" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127">
<si16 name="value" oversized="127">
<str8 name="stat_name">
</array>
<i8 name="stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="adornment_flag">
<array name="adornment_array" count="var:adornment_flag">
<i8 name="adornment_unknown">
</array>
<str16 name="stat_description">
</array>
<i8 name="condition">
<i16 name="weight" oversized="127">
<i32 name="skill_req1,skill_req2">
<i16 name="skill_min,skill_recommended" oversized="127">
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
</version>
<version number="546">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<i16 name="icon">
<i8 name="tier">
<i32 name="flags">
<i8 name="unknown8_1" size="15">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i8 name="stat_type">
<si16 name="stat_subtype" oversized="127">
<si16 name="value" oversized="127">
<str8 name="stat_name">
</array>
<i8 name="stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
</array>
<i8 name="condition">
<i16 name="weight" oversized="127">
<i32 name="skill_req1,skill_req2">
<i16 name="skill_min">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="562">
<i8 name="creator_flag">
<str8 name="creator">
<str16 name="adornment">
<i32 name="adornment_id,unknown3">
<str16 name="first_desc">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id">
<i16 name="icon">
<i8 name="tier">
<i32 name="flags">
<i8 name="unknown8_1" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127">
<si16 name="value" oversized="127">
<str8 name="stat_name">
</array>
<i8 name="stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="adornment_flag">
<array name="adornment_array" count="var:adornment_flag">
<i8 name="adornment_unknown">
</array>
<str16 name="stat_description">
</array>
<i8 name="condition">
<i32 name="weight">
<i32 name="skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="860">
<i8 name="creator_flag">
<str8 name="creator">
<str16 name="adornment">
<i32 name="adornment_id,unknown3">
<str16 name="adornment_desc,unknown_desc,first_desc">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id">
<i16 name="icon">
<i8 name="tier">
<i32 name="flags">
<i8 name="unknown8_860" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127">
<si16 name="value" oversized="127">
<str8 name="stat_name">
</array>
<i8 name="stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="adornment_flag">
<array name="adornment_array" count="var:adornment_flag">
<i8 name="adornment_unknown">
</array>
<str16 name="stat_description">
</array>
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="1096">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_BID_1096" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127">
<f32 name="value" type2="si16" type2_if="stat_type!=6" oversized="127">
<str8 name="stat_name">
</array>
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<str16 name="stat_description">
<i8 name="stat_string_unknown">
</array>
<i16 name="unknown19,unknown20">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="1188">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_1188" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127" if="!var:stat_type_%i">
<f32 name="value" type2="si16" type2_if="stat_type!=6" oversized="127">
<i8 name="stat_unknown" if="var:stat_type_%i">
<str8 name="stat_name">
<i8 name="stat_level">
</array>
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="stat_string_unknown">
<str16 name="stat_description">
</array>
<i16 name="unknown19,unknown20">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="1208">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_1208" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127" if="!var:stat_type_%i">
<f32 name="value" type2="si16" type2_if="stat_type!=6" oversized="127">
<i8 name="stat_unknown" if="var:stat_type_%i">
<str8 name="stat_name">
<i16 name="stat_level" type2="i8" type2_if="stat_name!>2">
</array>
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="stat_string_unknown">
<str16 name="stat_description">
</array>
<i16 name="unknown19,unknown20">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="58571">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_58570" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127" if="!var:stat_type_%i">
<f32 name="value" type2="si32" type2_if="stat_type!=6" oversized="127">
<str8 name="stat_name">
<i16 name="stat_level" type2="i8" type2_if="stat_name!>2">
</array>
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="stat_string_unknown">
<str16 name="stat_description">
</array>
<i16 name="unknown19,unknown20">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="58617">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_58617" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127" if="!var:stat_type_%i">
<f32 name="value" type2="si32" type2_if="stat_type!=6" oversized="127">
<str8 name="stat_name">
<i16 name="stat_level" type2="i8" type2_if="stat_name!>2">
<f32 name="value2" type2="si32" type2_if="stat_type!=6" oversized="127">
</array>
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="stat_string_unknown">
<str16 name="stat_description">
</array>
<i16 name="unknown19,unknown20">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="60174">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_60174" size="17">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127" if="!var:stat_type_%i">
<f32 name="value" type2="si32" type2_if="stat_type!=6" oversized="127">
<str8 name="stat_name">
<i16 name="stat_level" type2="i8" type2_if="stat_name!>2">
</array>
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="stat_string_unknown">
<str16 name="stat_description">
</array>
<i16 name="unknown19,unknown20">
<i32 name="unknown21">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
<version number="63119">
<i8 name="creator_flag">
<str8 name="creator">
<i32 name="unique_id">
<i64 name="broker_item_id">
<si32 name="item_id,item_crc">
<i16 name="icon">
<i8 name="tier">
<i16 name="flags,flags2">
<i8 name="unknown8_63119" size="17">
<i8 name="stat_unknown,stat_string_count">
<array name="stat_string_array" count="var:stat_string_count">
<str8 name="stat_string">
<i8 name="stat_string_unknown">
<str16 name="stat_description">
</array>
<i16 name="unknown19">
<i8 name="stat_count">
<array name="stat_array" count="var:stat_count">
<i16 name="stat_type" oversized="127">
<si16 name="stat_subtype" oversized="127" if="!var:stat_type_%i">
<f32 name="value" type2="si32" type2_if="stat_type!=6" oversized="127">
<str8 name="stat_name">
<i16 name="stat_level" type2="i8" type2_if="stat_name!>2">
<f32 name="value2" type2="si32" type2_if="stat_type!=6" oversized="127">
</array>
<i8 name="unknown20,mod_count">
<array name="mod_array" count="var:mod_count">
<str8 name="mod_string">
<i8 name="mod_need">
<i8 name="mod_have" if="var:header_info_mod_need_0">
<i8 name="mod_unknown" size="2">
</array>
<i8 name="unknown21" size="4">
<i8 name="condition">
<i32 name="weight,skill_req1,skill_req2">
<i16 name="skill_min" oversized="127">
<i8 name="class_count">
<array name="class_array" count="var:class_count">
<i8 name="adventure_class,tradeskill_class">
<i16 name="level">
</array>
<i8 name="slot_count">
<array name="slot_array" count="var:slot_count">
<i8 name="slot">
</array>
<i32 name="footer_type">
</version>
</substruct>