diff --git a/structs/PARSER.md b/structs/PARSER.md
new file mode 100644
index 0000000..8d2e394
--- /dev/null
+++ b/structs/PARSER.md
@@ -0,0 +1,284 @@
+# Packet Definition Parser
+
+Fast XML-like parser for binary packet structures with versioning and conditional fields.
+
+## Basic Syntax
+
+```xml
+
+
+
+
+
+
+
+```
+
+## 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
+
+
+```
+
+## Conditional Fields
+
+```xml
+
+
+
+
+```
+
+### 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
+
+
+
+
+
+
+```
+
+## Templates
+
+Define reusable field groups that can be injected into packets:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Templates work with groups for prefixing:
+
+```xml
+
+
+
+
+```
+
+## Arrays
+
+```xml
+
+
+
+
+
+```
+
+## Advanced Field Attributes
+
+### Type Switching
+```xml
+
+```
+
+### Oversized Fields
+```xml
+
+
+```
+
+### Field Modifiers
+```xml
+
+
+
+```
+
+## 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
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Multiple Versions
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Comments
+
+```xml
+
+
+
+
+
+
+```
+
+## 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
\ No newline at end of file
diff --git a/structs/builder.go b/structs/builder.go
new file mode 100644
index 0000000..8f17cf4
--- /dev/null
+++ b/structs/builder.go
@@ -0,0 +1,454 @@
+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)
+}
diff --git a/structs/builder_test.go b/structs/builder_test.go
new file mode 100644
index 0000000..e4dbbb9
--- /dev/null
+++ b/structs/builder_test.go
@@ -0,0 +1,173 @@
+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)
+}
diff --git a/structs/loader.go b/structs/loader.go
new file mode 100644
index 0000000..acb86b1
--- /dev/null
+++ b/structs/loader.go
@@ -0,0 +1,146 @@
+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)
+}
diff --git a/structs/opcode_manager.go b/structs/opcode_manager.go
new file mode 100644
index 0000000..679b6ec
--- /dev/null
+++ b/structs/opcode_manager.go
@@ -0,0 +1,239 @@
+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
+}
\ No newline at end of file
diff --git a/structs/opcodes.go b/structs/opcodes.go
new file mode 100644
index 0000000..cf9aea1
--- /dev/null
+++ b/structs/opcodes.go
@@ -0,0 +1,162 @@
+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",
+}
\ No newline at end of file
diff --git a/structs/parser/context.go b/structs/parser/context.go
new file mode 100644
index 0000000..9eeb027
--- /dev/null
+++ b/structs/parser/context.go
@@ -0,0 +1,447 @@
+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
+ }
+}
diff --git a/structs/parser/parser.go b/structs/parser/parser.go
new file mode 100644
index 0000000..8b54f34
--- /dev/null
+++ b/structs/parser/parser.go
@@ -0,0 +1,1199 @@
+package parser
+
+import (
+ "eq2emu/internal/common"
+ "fmt"
+ "strconv"
+ "strings"
+ "unicode"
+)
+
+// Parser is a single-pass recursive descent parser for packet definitions
+type Parser struct {
+ input string
+ pos int
+ line int
+ col int
+ substructs map[string]*PacketDef
+ templates map[string]*PacketDef
+ tagStack []string
+}
+
+// NewParser creates a new string-based parser
+func NewParser(input string) *Parser {
+ return &Parser{
+ input: input,
+ line: 1,
+ col: 1,
+ substructs: make(map[string]*PacketDef),
+ templates: make(map[string]*PacketDef),
+ tagStack: make([]string, 0, 16),
+ }
+}
+
+// peek returns the current character without advancing
+func (p *Parser) peek() rune {
+ if p.pos >= len(p.input) {
+ return 0
+ }
+ return rune(p.input[p.pos])
+}
+
+// peekN looks ahead n characters
+func (p *Parser) peekN(n int) rune {
+ pos := p.pos + n
+ if pos >= len(p.input) {
+ return 0
+ }
+ return rune(p.input[pos])
+}
+
+// advance moves to the next character and returns it
+func (p *Parser) advance() rune {
+ if p.pos >= len(p.input) {
+ return 0
+ }
+
+ ch := rune(p.input[p.pos])
+ p.pos++
+
+ if ch == '\n' {
+ p.line++
+ p.col = 1
+ } else {
+ p.col++
+ }
+
+ return ch
+}
+
+// skipWhitespace skips whitespace characters
+func (p *Parser) skipWhitespace() {
+ for p.pos < len(p.input) && unicode.IsSpace(p.peek()) {
+ p.advance()
+ }
+}
+
+// consumeChar consumes the expected character, returns true if found
+func (p *Parser) consumeChar(expected rune) bool {
+ if p.peek() == expected {
+ p.advance()
+ return true
+ }
+ return false
+}
+
+// consumeString consumes the expected string, returns true if found
+func (p *Parser) consumeString(expected string) bool {
+ if p.pos+len(expected) > len(p.input) {
+ return false
+ }
+
+ if p.input[p.pos:p.pos+len(expected)] == expected {
+ for range expected {
+ p.advance()
+ }
+ return true
+ }
+ return false
+}
+
+// parseIdentifier parses an identifier (alphanumeric + underscore)
+func (p *Parser) parseIdentifier() (string, error) {
+ start := p.pos
+
+ if p.pos >= len(p.input) || (!unicode.IsLetter(p.peek()) && p.peek() != '_') {
+ return "", fmt.Errorf("expected identifier at line %d, col %d", p.line, p.col)
+ }
+
+ for p.pos < len(p.input) {
+ ch := p.peek()
+ if unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '-' {
+ p.advance()
+ } else {
+ break
+ }
+ }
+
+ return p.input[start:p.pos], nil
+}
+
+// parseQuotedString parses a quoted string value
+func (p *Parser) parseQuotedString() (string, error) {
+ quote := p.peek()
+ if quote != '"' && quote != '\'' {
+ return "", fmt.Errorf("expected quoted string at line %d, col %d", p.line, p.col)
+ }
+
+ p.advance() // consume opening quote
+ start := p.pos
+
+ for p.pos < len(p.input) && p.peek() != quote {
+ if p.peek() == '\\' {
+ p.advance() // consume backslash
+ if p.pos < len(p.input) {
+ p.advance() // consume escaped character
+ }
+ } else {
+ p.advance()
+ }
+ }
+
+ if p.pos >= len(p.input) {
+ return "", fmt.Errorf("unclosed quoted string at line %d", p.line)
+ }
+
+ value := p.input[start:p.pos]
+ p.advance() // consume closing quote
+ return value, nil
+}
+
+// parseAttributes parses tag attributes
+func (p *Parser) parseAttributes() (map[string]string, error) {
+ attrs := make(map[string]string)
+
+ for {
+ p.skipWhitespace()
+
+ // Check for end of tag
+ if p.pos >= len(p.input) || p.peek() == '>' || (p.peek() == '/' && p.peekN(1) == '>') {
+ break
+ }
+
+ // Parse attribute name
+ name, err := p.parseIdentifier()
+ if err != nil {
+ return nil, err
+ }
+
+ p.skipWhitespace()
+ if !p.consumeChar('=') {
+ return nil, fmt.Errorf("expected '=' after attribute name '%s' at line %d", name, p.line)
+ }
+
+ p.skipWhitespace()
+
+ // Parse attribute value
+ value, err := p.parseQuotedString()
+ if err != nil {
+ return nil, err
+ }
+
+ attrs[name] = strings.TrimSpace(value)
+ }
+
+ return attrs, nil
+}
+
+// parseComment skips over comment content
+func (p *Parser) parseComment() error {
+ // We're at the start of
+ for p.pos+2 < len(p.input) {
+ if p.consumeString("-->") {
+ return nil
+ }
+ p.advance()
+ }
+
+ return fmt.Errorf("unclosed comment at line %d", p.line)
+}
+
+// Tag represents a parsed XML tag
+type Tag struct {
+ Name string
+ Attributes map[string]string
+ SelfClosing bool
+ IsClosing bool
+}
+
+// parseTag parses an opening, closing, or self-closing tag
+func (p *Parser) parseTag() (*Tag, error) {
+ if !p.consumeChar('<') {
+ return nil, fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ // Check for comment
+ if p.peek() == '!' && p.peekN(1) == '-' && p.peekN(2) == '-' {
+ // We already consumed the '<', so parse comment from here
+ if !p.consumeString("!--") {
+ return nil, fmt.Errorf("expected '
+ for p.pos+2 < len(p.input) {
+ if p.consumeString("-->") {
+ return p.parseTag() // recursively get the next tag
+ }
+ p.advance()
+ }
+
+ return nil, fmt.Errorf("unclosed comment at line %d", p.line)
+ }
+
+ tag := &Tag{}
+
+ // Check for closing tag
+ if p.consumeChar('/') {
+ tag.IsClosing = true
+ name, err := p.parseIdentifier()
+ if err != nil {
+ return nil, err
+ }
+ tag.Name = name
+
+ p.skipWhitespace()
+ if !p.consumeChar('>') {
+ return nil, fmt.Errorf("expected '>' after closing tag '%s' at line %d", name, p.line)
+ }
+ return tag, nil
+ }
+
+ // Parse tag name
+ name, err := p.parseIdentifier()
+ if err != nil {
+ return nil, err
+ }
+ tag.Name = name
+
+ // Parse attributes
+ attrs, err := p.parseAttributes()
+ if err != nil {
+ return nil, err
+ }
+ tag.Attributes = attrs
+
+ p.skipWhitespace()
+
+ // Check for self-closing
+ if p.consumeString("/>") {
+ tag.SelfClosing = true
+ } else if p.consumeChar('>') {
+ // Regular opening tag
+ } else {
+ return nil, fmt.Errorf("expected '>' or '/>' after tag '%s' at line %d", name, p.line)
+ }
+
+ return tag, nil
+}
+
+// pushTag pushes a tag name onto the stack for validation
+func (p *Parser) pushTag(tag string) {
+ p.tagStack = append(p.tagStack, tag)
+}
+
+// popTag pops and validates the closing tag
+func (p *Parser) popTag(expectedTag string) error {
+ if len(p.tagStack) == 0 {
+ return fmt.Errorf("unexpected closing tag '%s' at line %d", expectedTag, p.line)
+ }
+
+ lastTag := p.tagStack[len(p.tagStack)-1]
+ if lastTag != expectedTag {
+ return fmt.Errorf("mismatched closing tag: expected '%s', got '%s' at line %d", lastTag, expectedTag, p.line)
+ }
+
+ p.tagStack = p.tagStack[:len(p.tagStack)-1]
+ return nil
+}
+
+// validateAllTagsClosed checks for unclosed tags
+func (p *Parser) validateAllTagsClosed() error {
+ if len(p.tagStack) > 0 {
+ return fmt.Errorf("unclosed tag '%s'", p.tagStack[len(p.tagStack)-1])
+ }
+ return nil
+}
+
+// Helper functions for robust attribute parsing
+func (p *Parser) parseIntAttribute(value string, attributeName string) (int, error) {
+ if value == "" {
+ return 0, fmt.Errorf("empty %s attribute at line %d", attributeName, p.line)
+ }
+
+ cleanValue := strings.TrimSpace(value)
+ if cleanValue == "" {
+ return 0, fmt.Errorf("empty %s attribute value '%s' at line %d", attributeName, value, p.line)
+ }
+
+ result, err := strconv.Atoi(cleanValue)
+ if err != nil {
+ return 0, fmt.Errorf("invalid %s value '%s' at line %d: %v", attributeName, value, p.line, err)
+ }
+
+ return result, nil
+}
+
+func (p *Parser) parseUintAttribute(value string, attributeName string) (uint32, error) {
+ if value == "" {
+ return 0, fmt.Errorf("empty %s attribute at line %d", attributeName, p.line)
+ }
+
+ cleanValue := strings.TrimSpace(value)
+ if cleanValue == "" {
+ return 0, fmt.Errorf("empty %s attribute value '%s' at line %d", attributeName, value, p.line)
+ }
+
+ result, err := strconv.ParseUint(cleanValue, 10, 32)
+ if err != nil {
+ return 0, fmt.Errorf("invalid %s value '%s' at line %d: %v", attributeName, value, p.line, err)
+ }
+
+ return uint32(result), nil
+}
+
+func (p *Parser) parseInt8Attribute(value string, attributeName string) (int8, error) {
+ if value == "" {
+ return 0, fmt.Errorf("empty %s attribute at line %d", attributeName, p.line)
+ }
+
+ cleanValue := strings.TrimSpace(value)
+ if cleanValue == "" {
+ return 0, fmt.Errorf("empty %s attribute value '%s' at line %d", attributeName, value, p.line)
+ }
+
+ result, err := strconv.ParseInt(cleanValue, 10, 8)
+ if err != nil {
+ return 0, fmt.Errorf("invalid %s value '%s' at line %d: %v", attributeName, value, p.line, err)
+ }
+
+ return int8(result), nil
+}
+
+// parseFieldNames parses comma-separated field names
+func (p *Parser) parseFieldNames(nameAttr string) []string {
+ if nameAttr == "" {
+ return nil
+ }
+
+ // Fast path for single name
+ if !strings.Contains(nameAttr, ",") {
+ name := strings.TrimSpace(nameAttr)
+ if name != "" {
+ return []string{name}
+ }
+ return nil
+ }
+
+ // Parse multiple names
+ names := strings.Split(nameAttr, ",")
+ result := make([]string, 0, len(names))
+ for _, name := range names {
+ if trimmed := strings.TrimSpace(name); trimmed != "" {
+ result = append(result, trimmed)
+ }
+ }
+
+ return result
+}
+
+// Parse parses the entire packet definition document
+func (p *Parser) Parse() (map[string]*PacketDef, error) {
+ packets := make(map[string]*PacketDef)
+
+ for p.pos < len(p.input) {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ break
+ }
+
+ if p.peek() != '<' {
+ return nil, fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ // Handle comments at the top level
+ if p.peek() == '<' && p.peekN(1) == '!' && p.peekN(2) == '-' && p.peekN(3) == '-' {
+ err := p.parseComment()
+ if err != nil {
+ return nil, err
+ }
+ continue
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return nil, err
+ }
+
+ switch tag.Name {
+ case "packet":
+ name := tag.Attributes["name"]
+ packet, err := p.parsePacket(tag)
+ if err != nil {
+ return nil, err
+ }
+ if name != "" {
+ packets[name] = packet
+ }
+
+ case "substruct":
+ name := tag.Attributes["name"]
+ substruct, err := p.parseSubstruct(tag)
+ if err != nil {
+ return nil, err
+ }
+ if name != "" {
+ p.substructs[name] = substruct
+ }
+
+ case "template":
+ name := tag.Attributes["name"]
+ if name != "" {
+ err := p.parseTemplateDefinition(tag, name)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ default:
+ return nil, fmt.Errorf("unexpected top-level tag '%s' at line %d", tag.Name, p.line)
+ }
+ }
+
+ if err := p.validateAllTagsClosed(); err != nil {
+ return nil, err
+ }
+
+ return packets, nil
+}
+
+// parsePacket parses a packet element
+func (p *Parser) parsePacket(openTag *Tag) (*PacketDef, error) {
+ packetDef := NewPacketDef(16)
+
+ if openTag.SelfClosing {
+ return packetDef, nil
+ }
+
+ p.pushTag("packet")
+
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ return nil, fmt.Errorf("unexpected end of input in packet")
+ }
+
+ if p.peek() != '<' {
+ return nil, fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ // Handle comments
+ if p.peek() == '<' && p.peekN(1) == '!' && p.peekN(2) == '-' && p.peekN(3) == '-' {
+ err := p.parseComment()
+ if err != nil {
+ return nil, err
+ }
+ continue
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return nil, err
+ }
+
+ if tag.IsClosing {
+ if tag.Name != "packet" {
+ return nil, fmt.Errorf("expected closing tag 'packet', got '%s' at line %d", tag.Name, p.line)
+ }
+ if err := p.popTag("packet"); err != nil {
+ return nil, err
+ }
+ break
+ }
+
+ if tag.Name == "version" {
+ err := p.parseVersion(tag, packetDef)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ return nil, fmt.Errorf("unexpected tag '%s' in packet at line %d", tag.Name, p.line)
+ }
+ }
+
+ return packetDef, nil
+}
+
+// parseSubstruct parses a substruct element
+func (p *Parser) parseSubstruct(openTag *Tag) (*PacketDef, error) {
+ packetDef := NewPacketDef(16)
+
+ if openTag.SelfClosing {
+ return packetDef, nil
+ }
+
+ p.pushTag("substruct")
+
+ // Check if this substruct contains version elements
+ hasVersions := false
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ return nil, fmt.Errorf("unexpected end of input in substruct")
+ }
+
+ if p.peek() != '<' {
+ return nil, fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return nil, err
+ }
+
+ if tag.IsClosing {
+ if tag.Name != "substruct" {
+ return nil, fmt.Errorf("expected closing tag 'substruct', got '%s' at line %d", tag.Name, p.line)
+ }
+ if err := p.popTag("substruct"); err != nil {
+ return nil, err
+ }
+ break
+ }
+
+ if tag.Name == "version" {
+ hasVersions = true
+ err := p.parseVersion(tag, packetDef)
+ if err != nil {
+ return nil, err
+ }
+ } else if hasVersions {
+ return nil, fmt.Errorf("unexpected tag '%s' after version in substruct at line %d", tag.Name, p.line)
+ } else {
+ // No versions found, parse as direct elements
+ fieldOrder := make([]string, 0)
+ err := p.parseElement(tag, packetDef, &fieldOrder, "")
+ if err != nil {
+ return nil, err
+ }
+
+ // Continue parsing remaining elements
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) || p.peek() != '<' {
+ break
+ }
+
+ nextTag, err := p.parseTag()
+ if err != nil {
+ return nil, err
+ }
+
+ if nextTag.IsClosing {
+ if nextTag.Name != "substruct" {
+ return nil, fmt.Errorf("expected closing tag 'substruct', got '%s' at line %d", nextTag.Name, p.line)
+ }
+ if err := p.popTag("substruct"); err != nil {
+ return nil, err
+ }
+ break
+ }
+
+ err = p.parseElement(nextTag, packetDef, &fieldOrder, "")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Set field order for version 1
+ packetDef.Orders[1] = make([]string, len(fieldOrder))
+ copy(packetDef.Orders[1], fieldOrder)
+ break
+ }
+ }
+
+ return packetDef, nil
+}
+
+// parseVersion parses a version element
+func (p *Parser) parseVersion(openTag *Tag, packetDef *PacketDef) error {
+ version := uint32(1)
+ if v := openTag.Attributes["number"]; v != "" {
+ if parsed, err := p.parseUintAttribute(v, "number"); err == nil {
+ version = parsed
+ }
+ // Don't fail on invalid version numbers, just use default
+ }
+
+ fieldOrder := make([]string, 0)
+
+ if openTag.SelfClosing {
+ packetDef.Orders[version] = fieldOrder
+ return nil
+ }
+
+ p.pushTag("version")
+
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ return fmt.Errorf("unexpected end of input in version")
+ }
+
+ if p.peek() != '<' {
+ return fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ // Handle comments
+ if p.peek() == '<' && p.peekN(1) == '!' && p.peekN(2) == '-' && p.peekN(3) == '-' {
+ err := p.parseComment()
+ if err != nil {
+ return err
+ }
+ continue
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return err
+ }
+
+ if tag.IsClosing {
+ if tag.Name != "version" {
+ return fmt.Errorf("expected closing tag 'version', got '%s' at line %d", tag.Name, p.line)
+ }
+ if err := p.popTag("version"); err != nil {
+ return err
+ }
+ break
+ }
+
+ err = p.parseElement(tag, packetDef, &fieldOrder, "")
+ if err != nil {
+ return err
+ }
+ }
+
+ packetDef.Orders[version] = make([]string, len(fieldOrder))
+ copy(packetDef.Orders[version], fieldOrder)
+ return nil
+}
+
+// parseElement parses any element (field, array, group, template, substruct reference)
+func (p *Parser) parseElement(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ switch tag.Name {
+ case "group":
+ return p.parseGroup(tag, packetDef, fieldOrder, prefix)
+ case "array":
+ return p.parseArray(tag, packetDef, fieldOrder, prefix)
+ case "template":
+ return p.parseTemplateUsage(tag, packetDef, fieldOrder, prefix)
+ case "substruct":
+ return p.parseSubstructReference(tag, packetDef, fieldOrder, prefix)
+ case "item":
+ return p.parseItemField(tag, packetDef, fieldOrder, prefix)
+ default:
+ // Try to parse as a field
+ return p.parseField(tag, packetDef, fieldOrder, prefix)
+ }
+}
+
+// parseGroup parses a group element
+func (p *Parser) parseGroup(openTag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ groupPrefix := prefix
+ if name := openTag.Attributes["name"]; name != "" {
+ if prefix == "" {
+ groupPrefix = name + "_"
+ } else {
+ groupPrefix = prefix + name + "_"
+ }
+ }
+
+ if openTag.SelfClosing {
+ return nil
+ }
+
+ p.pushTag("group")
+
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ return fmt.Errorf("unexpected end of input in group")
+ }
+
+ if p.peek() != '<' {
+ return fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return err
+ }
+
+ if tag.IsClosing {
+ if tag.Name != "group" {
+ return fmt.Errorf("expected closing tag 'group', got '%s' at line %d", tag.Name, p.line)
+ }
+ if err := p.popTag("group"); err != nil {
+ return err
+ }
+ break
+ }
+
+ err = p.parseElement(tag, packetDef, fieldOrder, groupPrefix)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// parseArray parses an array element
+func (p *Parser) parseArray(openTag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ var arrayName string
+ if prefix == "" {
+ arrayName = openTag.Attributes["name"]
+ } else {
+ arrayName = prefix + openTag.Attributes["name"]
+ }
+
+ fieldDesc := FieldDesc{
+ Type: common.TypeArray,
+ Condition: openTag.Attributes["count"],
+ AddToStruct: true, // Default to true
+ }
+
+ if ifCond := openTag.Attributes["if"]; ifCond != "" {
+ fieldDesc.Condition = combineConditions(fieldDesc.Condition, ifCond)
+ }
+
+ // Parse additional attributes
+ if maxSize := openTag.Attributes["max_size"]; maxSize != "" {
+ if m, err := p.parseIntAttribute(maxSize, "max_size"); err == nil {
+ fieldDesc.MaxArraySize = m
+ } else {
+ return err
+ }
+ }
+
+ if optional := openTag.Attributes["optional"]; optional == "true" {
+ fieldDesc.Optional = true
+ }
+
+ if addToStruct := openTag.Attributes["add_to_struct"]; addToStruct == "false" {
+ fieldDesc.AddToStruct = false
+ }
+
+ // Handle substruct reference
+ if substruct := openTag.Attributes["substruct"]; substruct != "" {
+ if subDef, exists := p.substructs[substruct]; exists {
+ fieldDesc.SubDef = subDef
+ }
+ }
+
+ // Arrays with substruct references or explicit self-closing syntax are self-closing
+ if openTag.SelfClosing || fieldDesc.SubDef != nil {
+ packetDef.Fields[arrayName] = fieldDesc
+ *fieldOrder = append(*fieldOrder, arrayName)
+ return nil
+ }
+
+ p.pushTag("array")
+
+ // Handle direct child elements as substruct fields
+ if fieldDesc.SubDef == nil {
+ subDef := NewPacketDef(16)
+ subOrder := make([]string, 0)
+
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ return fmt.Errorf("unexpected end of input in array")
+ }
+
+ if p.peek() != '<' {
+ return fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return err
+ }
+
+ if tag.IsClosing {
+ if tag.Name != "array" {
+ return fmt.Errorf("expected closing tag 'array', got '%s' at line %d", tag.Name, p.line)
+ }
+ if err := p.popTag("array"); err != nil {
+ return err
+ }
+ break
+ }
+
+ err = p.parseElement(tag, subDef, &subOrder, "")
+ if err != nil {
+ return err
+ }
+ }
+
+ // 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
+ }
+ }
+
+ packetDef.Fields[arrayName] = fieldDesc
+ *fieldOrder = append(*fieldOrder, arrayName)
+ return nil
+}
+
+// combineConditions combines two conditions with AND logic - using existing function
+
+// parseField parses a field element
+func (p *Parser) parseField(openTag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ dataType, exists := getDataType(openTag.Name)
+ if !exists {
+ return fmt.Errorf("unknown field type '%s' at line %d", openTag.Name, p.line)
+ }
+
+ nameAttr := openTag.Attributes["name"]
+ if nameAttr == "" {
+ return fmt.Errorf("field missing name attribute at line %d", p.line)
+ }
+
+ names := p.parseFieldNames(nameAttr)
+ for _, name := range names {
+ var fullName string
+ if prefix == "" {
+ fullName = name
+ } else {
+ fullName = prefix + name
+ }
+
+ fieldDesc := FieldDesc{
+ Type: dataType,
+ Condition: openTag.Attributes["if"],
+ AddToStruct: true, // Default to true
+ AddType: dataType,
+ }
+
+ // Parse size attribute
+ if size := openTag.Attributes["size"]; size != "" {
+ if s, err := p.parseIntAttribute(size, "size"); err == nil {
+ fieldDesc.Length = s
+ } else {
+ return err
+ }
+ }
+
+ // Parse oversized attribute
+ if oversized := openTag.Attributes["oversized"]; oversized != "" {
+ if o, err := p.parseIntAttribute(oversized, "oversized"); err == nil {
+ fieldDesc.Oversized = o
+ } else {
+ return err
+ }
+ }
+
+ // Parse type2 attributes
+ if type2 := openTag.Attributes["type2"]; type2 != "" {
+ if t2, exists := getDataType(type2); exists {
+ fieldDesc.Type2 = t2
+ fieldDesc.Type2Cond = openTag.Attributes["type2_if"]
+ }
+ }
+
+ // Parse default value
+ if defaultVal := openTag.Attributes["default"]; defaultVal != "" {
+ if d, err := p.parseInt8Attribute(defaultVal, "default"); err == nil {
+ fieldDesc.DefaultValue = d
+ } else {
+ return err
+ }
+ }
+
+ // Parse max_size
+ if maxSize := openTag.Attributes["max_size"]; maxSize != "" {
+ if m, err := p.parseIntAttribute(maxSize, "max_size"); err == nil {
+ fieldDesc.MaxArraySize = m
+ } else {
+ return err
+ }
+ }
+
+ // Parse optional
+ if optional := openTag.Attributes["optional"]; optional == "true" {
+ fieldDesc.Optional = true
+ }
+
+ // Parse add_to_struct
+ if addToStruct := openTag.Attributes["add_to_struct"]; addToStruct == "false" {
+ fieldDesc.AddToStruct = false
+ }
+
+ // Parse add_type
+ if addType := openTag.Attributes["add_type"]; addType != "" {
+ if at, exists := getDataType(addType); exists {
+ fieldDesc.AddType = at
+ }
+ }
+
+ packetDef.Fields[fullName] = fieldDesc
+ *fieldOrder = append(*fieldOrder, fullName)
+ }
+
+ return nil
+}
+
+// parseTemplateDefinition parses a template definition
+func (p *Parser) parseTemplateDefinition(openTag *Tag, templateName string) error {
+ templateDef := NewPacketDef(16)
+ fieldOrder := make([]string, 0)
+
+ if openTag.SelfClosing {
+ templateDef.Orders[1] = fieldOrder
+ p.templates[templateName] = templateDef
+ return nil
+ }
+
+ p.pushTag("template")
+
+ for {
+ p.skipWhitespace()
+ if p.pos >= len(p.input) {
+ return fmt.Errorf("unexpected end of input in template")
+ }
+
+ if p.peek() != '<' {
+ return fmt.Errorf("expected '<' at line %d", p.line)
+ }
+
+ tag, err := p.parseTag()
+ if err != nil {
+ return err
+ }
+
+ if tag.IsClosing {
+ if tag.Name != "template" {
+ return fmt.Errorf("expected closing tag 'template', got '%s' at line %d", tag.Name, p.line)
+ }
+ if err := p.popTag("template"); err != nil {
+ return err
+ }
+ break
+ }
+
+ err = p.parseElement(tag, templateDef, &fieldOrder, "")
+ if err != nil {
+ return err
+ }
+ }
+
+ templateDef.Orders[1] = make([]string, len(fieldOrder))
+ copy(templateDef.Orders[1], fieldOrder)
+ p.templates[templateName] = templateDef
+ return nil
+}
+
+// parseTemplateUsage parses template usage
+func (p *Parser) parseTemplateUsage(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ // Template usage:
+ if templateUse := tag.Attributes["use"]; templateUse != "" {
+ template, exists := p.templates[templateUse]
+ if !exists {
+ return fmt.Errorf("undefined template '%s' at line %d", templateUse, p.line)
+ }
+
+ // Inject all template fields into current packet
+ if templateOrder, exists := template.Orders[1]; exists {
+ for _, fieldName := range templateOrder {
+ if fieldDesc, exists := template.Fields[fieldName]; exists {
+ // Apply prefix if provided
+ fullName := fieldName
+ if prefix != "" {
+ fullName = prefix + fieldName
+ }
+
+ packetDef.Fields[fullName] = fieldDesc
+ *fieldOrder = append(*fieldOrder, fullName)
+ }
+ }
+ }
+
+ return nil
+ }
+
+ return fmt.Errorf("template missing 'use' attribute at line %d", p.line)
+}
+
+// parseSubstructReference parses a substruct reference like
+func (p *Parser) parseSubstructReference(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ nameAttr := tag.Attributes["name"]
+ useAttr := tag.Attributes["use"]
+
+ if nameAttr == "" {
+ return fmt.Errorf("substruct reference missing 'name' attribute at line %d", p.line)
+ }
+
+ var fullName string
+ if prefix == "" {
+ fullName = nameAttr
+ } else {
+ fullName = prefix + nameAttr
+ }
+
+ fieldDesc := FieldDesc{
+ Type: common.TypeArray, // Substructs are treated as arrays
+ AddToStruct: true,
+ }
+
+ // Handle substruct reference with 'use' attribute
+ if useAttr != "" {
+ if subDef, exists := p.substructs[useAttr]; exists {
+ fieldDesc.SubDef = subDef
+ } else {
+ // If substruct doesn't exist yet, it might be defined later
+ // For now, we'll create a placeholder
+ }
+ }
+
+ packetDef.Fields[fullName] = fieldDesc
+ *fieldOrder = append(*fieldOrder, fullName)
+ return nil
+}
+
+// parseItemField parses an item field like -
+func (p *Parser) parseItemField(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
+ nameAttr := tag.Attributes["name"]
+ if nameAttr == "" {
+ return fmt.Errorf("item field missing 'name' attribute at line %d", p.line)
+ }
+
+ var fullName string
+ if prefix == "" {
+ fullName = nameAttr
+ } else {
+ fullName = prefix + nameAttr
+ }
+
+ fieldDesc := FieldDesc{
+ Type: common.TypeItem,
+ Condition: tag.Attributes["if"],
+ AddToStruct: true,
+ AddType: common.TypeItem,
+ }
+
+ packetDef.Fields[fullName] = fieldDesc
+ *fieldOrder = append(*fieldOrder, fullName)
+ return nil
+}
+
+// Parse function that uses the new string parser
+func Parse(content string) (map[string]*PacketDef, error) {
+ parser := NewParser(content)
+ return parser.Parse()
+}
+
+// Type mapping using corrected type constants
+var typeMap = map[string]common.EQ2DataType{
+ "u8": common.TypeInt8,
+ "u16": common.TypeInt16,
+ "u32": common.TypeInt32,
+ "u64": common.TypeInt64,
+ "i8": common.TypeSInt8,
+ "i16": common.TypeSInt16,
+ "i32": common.TypeSInt32,
+ "i64": common.TypeSInt64,
+ "f32": common.TypeFloat,
+ "f64": common.TypeDouble,
+ "double": common.TypeDouble,
+ "str8": common.TypeString8,
+ "str16": common.TypeString16,
+ "str32": common.TypeString32,
+ "char": common.TypeChar,
+ "color": common.TypeColor,
+ "equip": common.TypeEquipment,
+ "array": common.TypeArray,
+}
+
+// combineConditions combines two conditions with AND logic
+func combineConditions(cond1, cond2 string) string {
+ if cond1 == "" {
+ return cond2
+ }
+ if cond2 == "" {
+ return cond1
+ }
+ return cond1 + "&" + cond2
+}
+
+// Fast type lookup using first character
+func getDataType(tag string) (common.EQ2DataType, bool) {
+ if len(tag) == 0 {
+ return 0, false
+ }
+
+ switch tag[0] {
+ case 'u':
+ switch tag {
+ case "u8":
+ return common.TypeInt8, true
+ case "u16":
+ return common.TypeInt16, true
+ case "u32":
+ return common.TypeInt32, true
+ case "u64":
+ return common.TypeInt64, true
+ }
+ case 'i':
+ switch tag {
+ case "i8":
+ return common.TypeSInt8, true
+ case "i16":
+ return common.TypeSInt16, true
+ case "i32":
+ return common.TypeSInt32, true
+ case "i64":
+ return common.TypeSInt64, true
+ }
+ case 's':
+ switch tag {
+ case "str8":
+ return common.TypeString8, true
+ case "str16":
+ return common.TypeString16, true
+ case "str32":
+ return common.TypeString32, true
+ }
+ case 'f':
+ switch tag {
+ case "f32":
+ return common.TypeFloat, true
+ case "f64":
+ return common.TypeDouble, true
+ }
+ case 'd':
+ if tag == "double" {
+ return common.TypeDouble, true
+ }
+ case 'c':
+ switch tag {
+ case "char":
+ return common.TypeChar, true
+ case "color":
+ return common.TypeColor, true
+ }
+ case 'e':
+ if tag == "equip" {
+ return common.TypeEquipment, true
+ }
+ case 'a':
+ if tag == "array" {
+ return common.TypeArray, true
+ }
+ }
+
+ // Fallback to map lookup
+ if dataType, exists := typeMap[tag]; exists {
+ return dataType, true
+ }
+
+ return 0, false
+}
diff --git a/structs/parser/parser_test.go b/structs/parser/parser_test.go
new file mode 100644
index 0000000..cd43902
--- /dev/null
+++ b/structs/parser/parser_test.go
@@ -0,0 +1,933 @@
+package parser
+
+import (
+ "eq2emu/internal/common"
+ "testing"
+)
+
+func TestBasicParsing(t *testing.T) {
+ pml := `
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+ `
+
+ 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.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 := `
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+ `
+
+ 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"].(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 := `
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+
+
+
+
+ `
+
+ 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"].(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 := `
+
+
+
+
+
+
+
+
+
+ `
+
+ 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", ""},
+ {"Invalid XML", ""},
+ {"Missing quotes", ""},
+ {"Invalid oversized", ""},
+ }
+
+ 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 := `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+
+ 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 := `
+
+
+
+
+
+
+ `
+
+ for b.Loop() {
+ _, err := Parse(pml)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkMediumPacket(b *testing.B) {
+ pml := `
+
+
+
+
+
+
+
+ `
+
+ for b.Loop() {
+ _, err := Parse(pml)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkLargePacket(b *testing.B) {
+ pml := `
+
+
+
+
+
+
+
+ `
+
+ for b.Loop() {
+ _, err := Parse(pml)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkArrayPacket(b *testing.B) {
+ pml := `
+
+
+
+
+
+
+
+
+
+ `
+
+ for b.Loop() {
+ _, err := Parse(pml)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkMultiVersionPacket(b *testing.B) {
+ pml := `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+
+ 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
+}
diff --git a/structs/parser/structs.go b/structs/parser/structs.go
new file mode 100644
index 0000000..a5a7741
--- /dev/null
+++ b/structs/parser/structs.go
@@ -0,0 +1,135 @@
+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
+}
diff --git a/structs/parser_test.go b/structs/parser_test.go
new file mode 100644
index 0000000..630b5c9
--- /dev/null
+++ b/structs/parser_test.go
@@ -0,0 +1,370 @@
+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
+ }
+}
diff --git a/structs/reader.go b/structs/reader.go
new file mode 100644
index 0000000..e0b444e
--- /dev/null
+++ b/structs/reader.go
@@ -0,0 +1,255 @@
+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{}
+}
\ No newline at end of file
diff --git a/structs/xml/common/BadLanguageFilter.xml b/structs/xml/common/BadLanguageFilter.xml
new file mode 100644
index 0000000..fa0de1e
--- /dev/null
+++ b/structs/xml/common/BadLanguageFilter.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/common/CreateCharacter.xml b/structs/xml/common/CreateCharacter.xml
new file mode 100644
index 0000000..5497443
--- /dev/null
+++ b/structs/xml/common/CreateCharacter.xml
@@ -0,0 +1,744 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/common/CreateCharacterReply.xml b/structs/xml/common/CreateCharacterReply.xml
new file mode 100644
index 0000000..ffc498d
--- /dev/null
+++ b/structs/xml/common/CreateCharacterReply.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/BaseItemDescription.xml b/structs/xml/item/BaseItemDescription.xml
new file mode 100644
index 0000000..da884a8
--- /dev/null
+++ b/structs/xml/item/BaseItemDescription.xml
@@ -0,0 +1,1058 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/BaseItemDescriptionGeneric.xml b/structs/xml/item/BaseItemDescriptionGeneric.xml
new file mode 100644
index 0000000..fdc4ee7
--- /dev/null
+++ b/structs/xml/item/BaseItemDescriptionGeneric.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/BaseItemDescriptionInspect.xml b/structs/xml/item/BaseItemDescriptionInspect.xml
new file mode 100644
index 0000000..ddfcf59
--- /dev/null
+++ b/structs/xml/item/BaseItemDescriptionInspect.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/BaseMerchantItemDescription.xml b/structs/xml/item/BaseMerchantItemDescription.xml
new file mode 100644
index 0000000..0164f5e
--- /dev/null
+++ b/structs/xml/item/BaseMerchantItemDescription.xml
@@ -0,0 +1,597 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/Item.xml b/structs/xml/item/Item.xml
new file mode 100644
index 0000000..fafe29b
--- /dev/null
+++ b/structs/xml/item/Item.xml
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemAdornment.xml b/structs/xml/item/ItemAdornment.xml
new file mode 100644
index 0000000..0db1882
--- /dev/null
+++ b/structs/xml/item/ItemAdornment.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemArmor.xml b/structs/xml/item/ItemArmor.xml
new file mode 100644
index 0000000..e9e5596
--- /dev/null
+++ b/structs/xml/item/ItemArmor.xml
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemArmorDetails.xml b/structs/xml/item/ItemArmorDetails.xml
new file mode 100644
index 0000000..22564d3
--- /dev/null
+++ b/structs/xml/item/ItemArmorDetails.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemArmorSet.xml b/structs/xml/item/ItemArmorSet.xml
new file mode 100644
index 0000000..cf3415a
--- /dev/null
+++ b/structs/xml/item/ItemArmorSet.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemBag.xml b/structs/xml/item/ItemBag.xml
new file mode 100644
index 0000000..61b4025
--- /dev/null
+++ b/structs/xml/item/ItemBag.xml
@@ -0,0 +1,325 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemBagDetails.xml b/structs/xml/item/ItemBagDetails.xml
new file mode 100644
index 0000000..54e4fb3
--- /dev/null
+++ b/structs/xml/item/ItemBagDetails.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemBauble.xml b/structs/xml/item/ItemBauble.xml
new file mode 100644
index 0000000..99bc66f
--- /dev/null
+++ b/structs/xml/item/ItemBauble.xml
@@ -0,0 +1,375 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemBook.xml b/structs/xml/item/ItemBook.xml
new file mode 100644
index 0000000..46e5e5b
--- /dev/null
+++ b/structs/xml/item/ItemBook.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemDecoration.xml b/structs/xml/item/ItemDecoration.xml
new file mode 100644
index 0000000..466c48e
--- /dev/null
+++ b/structs/xml/item/ItemDecoration.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemDescription.xml b/structs/xml/item/ItemDescription.xml
new file mode 100644
index 0000000..a57cdd3
--- /dev/null
+++ b/structs/xml/item/ItemDescription.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemDescriptionGeneric.xml b/structs/xml/item/ItemDescriptionGeneric.xml
new file mode 100644
index 0000000..c6e4427
--- /dev/null
+++ b/structs/xml/item/ItemDescriptionGeneric.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemDescriptionInspect.xml b/structs/xml/item/ItemDescriptionInspect.xml
new file mode 100644
index 0000000..88bd3a4
--- /dev/null
+++ b/structs/xml/item/ItemDescriptionInspect.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemDungeonMaker.xml b/structs/xml/item/ItemDungeonMaker.xml
new file mode 100644
index 0000000..4ffa332
--- /dev/null
+++ b/structs/xml/item/ItemDungeonMaker.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemFood.xml b/structs/xml/item/ItemFood.xml
new file mode 100644
index 0000000..bfba983
--- /dev/null
+++ b/structs/xml/item/ItemFood.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemFooter.xml b/structs/xml/item/ItemFooter.xml
new file mode 100644
index 0000000..9a65bd6
--- /dev/null
+++ b/structs/xml/item/ItemFooter.xml
@@ -0,0 +1,1787 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemFooterInspect.xml b/structs/xml/item/ItemFooterInspect.xml
new file mode 100644
index 0000000..1090c65
--- /dev/null
+++ b/structs/xml/item/ItemFooterInspect.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemGeneric.xml b/structs/xml/item/ItemGeneric.xml
new file mode 100644
index 0000000..862d8be
--- /dev/null
+++ b/structs/xml/item/ItemGeneric.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemHouse.xml b/structs/xml/item/ItemHouse.xml
new file mode 100644
index 0000000..398f41e
--- /dev/null
+++ b/structs/xml/item/ItemHouse.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemHouseContainer.xml b/structs/xml/item/ItemHouseContainer.xml
new file mode 100644
index 0000000..ce8cf0a
--- /dev/null
+++ b/structs/xml/item/ItemHouseContainer.xml
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemInspect.xml b/structs/xml/item/ItemInspect.xml
new file mode 100644
index 0000000..c4c0378
--- /dev/null
+++ b/structs/xml/item/ItemInspect.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemMarketplace.xml b/structs/xml/item/ItemMarketplace.xml
new file mode 100644
index 0000000..9a84927
--- /dev/null
+++ b/structs/xml/item/ItemMarketplace.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemPattern.xml b/structs/xml/item/ItemPattern.xml
new file mode 100644
index 0000000..ace4926
--- /dev/null
+++ b/structs/xml/item/ItemPattern.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemProfile.xml b/structs/xml/item/ItemProfile.xml
new file mode 100644
index 0000000..b9df500
--- /dev/null
+++ b/structs/xml/item/ItemProfile.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemRange.xml b/structs/xml/item/ItemRange.xml
new file mode 100644
index 0000000..bd48f89
--- /dev/null
+++ b/structs/xml/item/ItemRange.xml
@@ -0,0 +1,398 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemRangeDetails.xml b/structs/xml/item/ItemRangeDetails.xml
new file mode 100644
index 0000000..137fe1b
--- /dev/null
+++ b/structs/xml/item/ItemRangeDetails.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemRecipeBook.xml b/structs/xml/item/ItemRecipeBook.xml
new file mode 100644
index 0000000..a75ae33
--- /dev/null
+++ b/structs/xml/item/ItemRecipeBook.xml
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemRecipeBookDetails.xml b/structs/xml/item/ItemRecipeBookDetails.xml
new file mode 100644
index 0000000..a05e2e3
--- /dev/null
+++ b/structs/xml/item/ItemRecipeBookDetails.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemSet.xml b/structs/xml/item/ItemSet.xml
new file mode 100644
index 0000000..d6dd96b
--- /dev/null
+++ b/structs/xml/item/ItemSet.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemShield.xml b/structs/xml/item/ItemShield.xml
new file mode 100644
index 0000000..9cf2c97
--- /dev/null
+++ b/structs/xml/item/ItemShield.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemShieldDetails.xml b/structs/xml/item/ItemShieldDetails.xml
new file mode 100644
index 0000000..b6d0661
--- /dev/null
+++ b/structs/xml/item/ItemShieldDetails.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemSkill.xml b/structs/xml/item/ItemSkill.xml
new file mode 100644
index 0000000..0fd2bc5
--- /dev/null
+++ b/structs/xml/item/ItemSkill.xml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemThrown.xml b/structs/xml/item/ItemThrown.xml
new file mode 100644
index 0000000..c3b94c0
--- /dev/null
+++ b/structs/xml/item/ItemThrown.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemWeapon.xml b/structs/xml/item/ItemWeapon.xml
new file mode 100644
index 0000000..7ffb69c
--- /dev/null
+++ b/structs/xml/item/ItemWeapon.xml
@@ -0,0 +1,376 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/ItemWeaponDetails.xml b/structs/xml/item/ItemWeaponDetails.xml
new file mode 100644
index 0000000..6173d43
--- /dev/null
+++ b/structs/xml/item/ItemWeaponDetails.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/LootItemDescription.xml b/structs/xml/item/LootItemDescription.xml
new file mode 100644
index 0000000..49d6e72
--- /dev/null
+++ b/structs/xml/item/LootItemDescription.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/LootItemGeneric.xml b/structs/xml/item/LootItemGeneric.xml
new file mode 100644
index 0000000..9315bbb
--- /dev/null
+++ b/structs/xml/item/LootItemGeneric.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/Loot_ItemFooter.xml b/structs/xml/item/Loot_ItemFooter.xml
new file mode 100644
index 0000000..ecb87d0
--- /dev/null
+++ b/structs/xml/item/Loot_ItemFooter.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemAdornment.xml b/structs/xml/item/MerchantItemAdornment.xml
new file mode 100644
index 0000000..0f3cff6
--- /dev/null
+++ b/structs/xml/item/MerchantItemAdornment.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemArmor.xml b/structs/xml/item/MerchantItemArmor.xml
new file mode 100644
index 0000000..120f445
--- /dev/null
+++ b/structs/xml/item/MerchantItemArmor.xml
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemArmorSet.xml b/structs/xml/item/MerchantItemArmorSet.xml
new file mode 100644
index 0000000..ecd767a
--- /dev/null
+++ b/structs/xml/item/MerchantItemArmorSet.xml
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemBag.xml b/structs/xml/item/MerchantItemBag.xml
new file mode 100644
index 0000000..e3dd82f
--- /dev/null
+++ b/structs/xml/item/MerchantItemBag.xml
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemBauble.xml b/structs/xml/item/MerchantItemBauble.xml
new file mode 100644
index 0000000..326493d
--- /dev/null
+++ b/structs/xml/item/MerchantItemBauble.xml
@@ -0,0 +1,293 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemBook.xml b/structs/xml/item/MerchantItemBook.xml
new file mode 100644
index 0000000..b8ad973
--- /dev/null
+++ b/structs/xml/item/MerchantItemBook.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemDecoration.xml b/structs/xml/item/MerchantItemDecoration.xml
new file mode 100644
index 0000000..e1ba44a
--- /dev/null
+++ b/structs/xml/item/MerchantItemDecoration.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemDescription.xml b/structs/xml/item/MerchantItemDescription.xml
new file mode 100644
index 0000000..db4a8a3
--- /dev/null
+++ b/structs/xml/item/MerchantItemDescription.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemDungeonMaker.xml b/structs/xml/item/MerchantItemDungeonMaker.xml
new file mode 100644
index 0000000..c81a264
--- /dev/null
+++ b/structs/xml/item/MerchantItemDungeonMaker.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemFood.xml b/structs/xml/item/MerchantItemFood.xml
new file mode 100644
index 0000000..f8cdb47
--- /dev/null
+++ b/structs/xml/item/MerchantItemFood.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemGeneric.xml b/structs/xml/item/MerchantItemGeneric.xml
new file mode 100644
index 0000000..51ecebb
--- /dev/null
+++ b/structs/xml/item/MerchantItemGeneric.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemHouse.xml b/structs/xml/item/MerchantItemHouse.xml
new file mode 100644
index 0000000..b3f7581
--- /dev/null
+++ b/structs/xml/item/MerchantItemHouse.xml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemHouseContainer.xml b/structs/xml/item/MerchantItemHouseContainer.xml
new file mode 100644
index 0000000..a2c6a4d
--- /dev/null
+++ b/structs/xml/item/MerchantItemHouseContainer.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemMarketplace.xml b/structs/xml/item/MerchantItemMarketplace.xml
new file mode 100644
index 0000000..e0f1648
--- /dev/null
+++ b/structs/xml/item/MerchantItemMarketplace.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemPattern.xml b/structs/xml/item/MerchantItemPattern.xml
new file mode 100644
index 0000000..52d7b86
--- /dev/null
+++ b/structs/xml/item/MerchantItemPattern.xml
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemProfile.xml b/structs/xml/item/MerchantItemProfile.xml
new file mode 100644
index 0000000..d84f612
--- /dev/null
+++ b/structs/xml/item/MerchantItemProfile.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemRange.xml b/structs/xml/item/MerchantItemRange.xml
new file mode 100644
index 0000000..60110c4
--- /dev/null
+++ b/structs/xml/item/MerchantItemRange.xml
@@ -0,0 +1,314 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemRecipeBook.xml b/structs/xml/item/MerchantItemRecipeBook.xml
new file mode 100644
index 0000000..5201f88
--- /dev/null
+++ b/structs/xml/item/MerchantItemRecipeBook.xml
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemSet.xml b/structs/xml/item/MerchantItemSet.xml
new file mode 100644
index 0000000..e739ab7
--- /dev/null
+++ b/structs/xml/item/MerchantItemSet.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemShield.xml b/structs/xml/item/MerchantItemShield.xml
new file mode 100644
index 0000000..4362f27
--- /dev/null
+++ b/structs/xml/item/MerchantItemShield.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemSkill.xml b/structs/xml/item/MerchantItemSkill.xml
new file mode 100644
index 0000000..bd57861
--- /dev/null
+++ b/structs/xml/item/MerchantItemSkill.xml
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemThrown.xml b/structs/xml/item/MerchantItemThrown.xml
new file mode 100644
index 0000000..a7edbc2
--- /dev/null
+++ b/structs/xml/item/MerchantItemThrown.xml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/MerchantItemWeapon.xml b/structs/xml/item/MerchantItemWeapon.xml
new file mode 100644
index 0000000..280eeb5
--- /dev/null
+++ b/structs/xml/item/MerchantItemWeapon.xml
@@ -0,0 +1,294 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/QuestItemDescription.xml b/structs/xml/item/QuestItemDescription.xml
new file mode 100644
index 0000000..132db40
--- /dev/null
+++ b/structs/xml/item/QuestItemDescription.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/QuestItemRewards.xml b/structs/xml/item/QuestItemRewards.xml
new file mode 100644
index 0000000..8af1d9d
--- /dev/null
+++ b/structs/xml/item/QuestItemRewards.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/SkillItemFooter.xml b/structs/xml/item/SkillItemFooter.xml
new file mode 100644
index 0000000..456b1af
--- /dev/null
+++ b/structs/xml/item/SkillItemFooter.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/item/UpdateInventory.xml b/structs/xml/item/UpdateInventory.xml
new file mode 100644
index 0000000..1cde507
--- /dev/null
+++ b/structs/xml/item/UpdateInventory.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/CharSelectProfile.xml b/structs/xml/login/CharSelectProfile.xml
new file mode 100644
index 0000000..3d8b293
--- /dev/null
+++ b/structs/xml/login/CharSelectProfile.xml
@@ -0,0 +1,398 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/CreateCharacterReply.xml b/structs/xml/login/CreateCharacterReply.xml
new file mode 100644
index 0000000..a4c3ebd
--- /dev/null
+++ b/structs/xml/login/CreateCharacterReply.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/DeleteCharacterRequest.xml b/structs/xml/login/DeleteCharacterRequest.xml
new file mode 100644
index 0000000..9153992
--- /dev/null
+++ b/structs/xml/login/DeleteCharacterRequest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/DeleteCharacterResponse.xml b/structs/xml/login/DeleteCharacterResponse.xml
new file mode 100644
index 0000000..adb936a
--- /dev/null
+++ b/structs/xml/login/DeleteCharacterResponse.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/LoginReplyMsg.xml b/structs/xml/login/LoginReplyMsg.xml
new file mode 100644
index 0000000..49e9a24
--- /dev/null
+++ b/structs/xml/login/LoginReplyMsg.xml
@@ -0,0 +1,490 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/LoginRequest.xml b/structs/xml/login/LoginRequest.xml
new file mode 100644
index 0000000..dd25c6d
--- /dev/null
+++ b/structs/xml/login/LoginRequest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/PlayRequest.xml b/structs/xml/login/PlayRequest.xml
new file mode 100644
index 0000000..8ec86cc
--- /dev/null
+++ b/structs/xml/login/PlayRequest.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/PlayResponse.xml b/structs/xml/login/PlayResponse.xml
new file mode 100644
index 0000000..2d8a21c
--- /dev/null
+++ b/structs/xml/login/PlayResponse.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/WorldList.xml b/structs/xml/login/WorldList.xml
new file mode 100644
index 0000000..5d0c49d
--- /dev/null
+++ b/structs/xml/login/WorldList.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/login/WorldUpdate.xml b/structs/xml/login/WorldUpdate.xml
new file mode 100644
index 0000000..5700609
--- /dev/null
+++ b/structs/xml/login/WorldUpdate.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SignWidgetSpawnStruct_Footer.xml b/structs/xml/spawn/SignWidgetSpawnStruct_Footer.xml
new file mode 100644
index 0000000..c92ca7b
--- /dev/null
+++ b/structs/xml/spawn/SignWidgetSpawnStruct_Footer.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SpawnInfoStruct.xml b/structs/xml/spawn/SpawnInfoStruct.xml
new file mode 100644
index 0000000..ee2de99
--- /dev/null
+++ b/structs/xml/spawn/SpawnInfoStruct.xml
@@ -0,0 +1,2098 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SpawnPositionStruct.xml b/structs/xml/spawn/SpawnPositionStruct.xml
new file mode 100644
index 0000000..1328dc5
--- /dev/null
+++ b/structs/xml/spawn/SpawnPositionStruct.xml
@@ -0,0 +1,300 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SpawnStruct.xml b/structs/xml/spawn/SpawnStruct.xml
new file mode 100644
index 0000000..715d347
--- /dev/null
+++ b/structs/xml/spawn/SpawnStruct.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SpawnStruct_Footer.xml b/structs/xml/spawn/SpawnStruct_Footer.xml
new file mode 100644
index 0000000..4d4cfc6
--- /dev/null
+++ b/structs/xml/spawn/SpawnStruct_Footer.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SpawnStruct_Header.xml b/structs/xml/spawn/SpawnStruct_Header.xml
new file mode 100644
index 0000000..35b30e4
--- /dev/null
+++ b/structs/xml/spawn/SpawnStruct_Header.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/SpawnVisualizationInfoStruct.xml b/structs/xml/spawn/SpawnVisualizationInfoStruct.xml
new file mode 100644
index 0000000..3a8278f
--- /dev/null
+++ b/structs/xml/spawn/SpawnVisualizationInfoStruct.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/spawn/WidgetSpawnStruct_Footer.xml b/structs/xml/spawn/WidgetSpawnStruct_Footer.xml
new file mode 100644
index 0000000..abe4a4c
--- /dev/null
+++ b/structs/xml/spawn/WidgetSpawnStruct_Footer.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/AchievementSpellInfo.xml b/structs/xml/world/AchievementSpellInfo.xml
new file mode 100644
index 0000000..71d964a
--- /dev/null
+++ b/structs/xml/world/AchievementSpellInfo.xml
@@ -0,0 +1,810 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/AchievementUpdate.xml b/structs/xml/world/AchievementUpdate.xml
new file mode 100644
index 0000000..4e1d7e9
--- /dev/null
+++ b/structs/xml/world/AchievementUpdate.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/AdvancementRequest.xml b/structs/xml/world/AdvancementRequest.xml
new file mode 100644
index 0000000..cac84f4
--- /dev/null
+++ b/structs/xml/world/AdvancementRequest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/AdventureList.xml b/structs/xml/world/AdventureList.xml
new file mode 100644
index 0000000..ce65f95
--- /dev/null
+++ b/structs/xml/world/AdventureList.xml
@@ -0,0 +1,3764 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/AfterInvSpellUpdate.xml b/structs/xml/world/AfterInvSpellUpdate.xml
new file mode 100644
index 0000000..74f9024
--- /dev/null
+++ b/structs/xml/world/AfterInvSpellUpdate.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/ArenaGameTypes.xml b/structs/xml/world/ArenaGameTypes.xml
new file mode 100644
index 0000000..bf0c7c0
--- /dev/null
+++ b/structs/xml/world/ArenaGameTypes.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/AvailWorldChannels.xml b/structs/xml/world/AvailWorldChannels.xml
new file mode 100644
index 0000000..4d74ba0
--- /dev/null
+++ b/structs/xml/world/AvailWorldChannels.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BagOptions.xml b/structs/xml/world/BagOptions.xml
new file mode 100644
index 0000000..e166c86
--- /dev/null
+++ b/structs/xml/world/BagOptions.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BeginItemCreation.xml b/structs/xml/world/BeginItemCreation.xml
new file mode 100644
index 0000000..17c351e
--- /dev/null
+++ b/structs/xml/world/BeginItemCreation.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BeginTracking.xml b/structs/xml/world/BeginTracking.xml
new file mode 100644
index 0000000..436134f
--- /dev/null
+++ b/structs/xml/world/BeginTracking.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BioUpdate.xml b/structs/xml/world/BioUpdate.xml
new file mode 100644
index 0000000..414ad41
--- /dev/null
+++ b/structs/xml/world/BioUpdate.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BrokerBags.xml b/structs/xml/world/BrokerBags.xml
new file mode 100644
index 0000000..e508a1b
--- /dev/null
+++ b/structs/xml/world/BrokerBags.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BrokerItems.xml b/structs/xml/world/BrokerItems.xml
new file mode 100644
index 0000000..8762e6e
--- /dev/null
+++ b/structs/xml/world/BrokerItems.xml
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/BuyHouse.xml b/structs/xml/world/BuyHouse.xml
new file mode 100644
index 0000000..0572946
--- /dev/null
+++ b/structs/xml/world/BuyHouse.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CSTicketHeaderRequestMsg.xml b/structs/xml/world/CSTicketHeaderRequestMsg.xml
new file mode 100644
index 0000000..ceae91c
--- /dev/null
+++ b/structs/xml/world/CSTicketHeaderRequestMsg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CSToolsRequest.xml b/structs/xml/world/CSToolsRequest.xml
new file mode 100644
index 0000000..34a4bfa
--- /dev/null
+++ b/structs/xml/world/CSToolsRequest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/Camp.xml b/structs/xml/world/Camp.xml
new file mode 100644
index 0000000..0582c9f
--- /dev/null
+++ b/structs/xml/world/Camp.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CampAbortedMsg.xml b/structs/xml/world/CampAbortedMsg.xml
new file mode 100644
index 0000000..a908ac6
--- /dev/null
+++ b/structs/xml/world/CampAbortedMsg.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CancelMoveObjectMode.xml b/structs/xml/world/CancelMoveObjectMode.xml
new file mode 100644
index 0000000..cc93f18
--- /dev/null
+++ b/structs/xml/world/CancelMoveObjectMode.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CannedEmote.xml b/structs/xml/world/CannedEmote.xml
new file mode 100644
index 0000000..449ca6f
--- /dev/null
+++ b/structs/xml/world/CannedEmote.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterAchievements.xml b/structs/xml/world/CharacterAchievements.xml
new file mode 100644
index 0000000..4763a37
--- /dev/null
+++ b/structs/xml/world/CharacterAchievements.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterCreatedDungeons.xml b/structs/xml/world/CharacterCreatedDungeons.xml
new file mode 100644
index 0000000..7cf12d5
--- /dev/null
+++ b/structs/xml/world/CharacterCreatedDungeons.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterCurrency.xml b/structs/xml/world/CharacterCurrency.xml
new file mode 100644
index 0000000..e639511
--- /dev/null
+++ b/structs/xml/world/CharacterCurrency.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterHousingList.xml b/structs/xml/world/CharacterHousingList.xml
new file mode 100644
index 0000000..9118e93
--- /dev/null
+++ b/structs/xml/world/CharacterHousingList.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterMerc.xml b/structs/xml/world/CharacterMerc.xml
new file mode 100644
index 0000000..a91835e
--- /dev/null
+++ b/structs/xml/world/CharacterMerc.xml
@@ -0,0 +1,421 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterMounts.xml b/structs/xml/world/CharacterMounts.xml
new file mode 100644
index 0000000..8633636
--- /dev/null
+++ b/structs/xml/world/CharacterMounts.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterPet.xml b/structs/xml/world/CharacterPet.xml
new file mode 100644
index 0000000..8cd8dcb
--- /dev/null
+++ b/structs/xml/world/CharacterPet.xml
@@ -0,0 +1,230 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/structs/xml/world/CharacterSheet.xml b/structs/xml/world/CharacterSheet.xml
new file mode 100644
index 0000000..2c33348
--- /dev/null
+++ b/structs/xml/world/CharacterSheet.xml
@@ -0,0 +1,1528 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+