package structs import ( "encoding/xml" "fmt" "io" "os" "path/filepath" "sort" "strconv" ) // FieldType represents the type of a packet field type FieldType int const ( FieldTypeUnknown FieldType = iota FieldTypeUInt8 FieldTypeUInt16 FieldTypeUInt32 FieldTypeUInt64 FieldTypeInt8 FieldTypeInt16 FieldTypeInt32 FieldTypeInt64 FieldTypeFloat FieldTypeDouble FieldTypeChar FieldTypeString8 FieldTypeString16 FieldTypeString32 FieldTypeColor FieldTypeEQ2Color FieldTypeSubstruct FieldTypeArray ) // Field represents a single field in a packet structure type Field struct { Name string Type FieldType Size int // For arrays and fixed-size fields SizeVar string // Variable that contains the size Default interface{} Optional bool IfSet string // Conditional based on another field being set IfNotSet string // Conditional based on another field not being set } // PacketVersion represents a specific version of a packet structure type PacketVersion struct { Version uint16 Fields []Field } // PacketDef represents a complete packet definition with all versions type PacketDef struct { Name string IsSubstruct bool Versions []PacketVersion } // XMLPacket represents the XML structure of a packet definition type XMLPacket struct { XMLName xml.Name `xml:"packet"` Name string `xml:"name,attr"` Versions []XMLVersion `xml:"version"` } // XMLSubstruct represents the XML structure of a substruct definition type XMLSubstruct struct { XMLName xml.Name `xml:"substruct"` Name string `xml:"name,attr"` Versions []XMLVersion `xml:"version"` } // XMLVersion represents a version block in the XML type XMLVersion struct { Number string `xml:"number,attr"` Fields []XMLField `xml:",any"` } // XMLField represents a field in the XML type XMLField struct { XMLName xml.Name Name string `xml:"name,attr"` Size string `xml:"size,attr"` SizeVar string `xml:"sizevar,attr"` Default string `xml:"default,attr"` IfSet string `xml:"ifset,attr"` IfNotSet string `xml:"ifnotset,attr"` } // Parser handles parsing XML packet definitions type Parser struct { packets map[string]*PacketDef basePath string } // NewParser creates a new packet parser func NewParser(basePath string) *Parser { return &Parser{ packets: make(map[string]*PacketDef), basePath: basePath, } } // LoadAll loads all packet definitions from the XML directory func (p *Parser) LoadAll() error { patterns := []string{ "common/*.xml", "login/*.xml", "item/*.xml", "spawn/*.xml", "world/*.xml", } for _, pattern := range patterns { fullPattern := filepath.Join(p.basePath, "xml", pattern) files, err := filepath.Glob(fullPattern) if err != nil { return fmt.Errorf("failed to glob %s: %w", pattern, err) } for _, file := range files { if err := p.LoadFile(file); err != nil { return fmt.Errorf("failed to load %s: %w", file, err) } } } return nil } // LoadFile loads a single XML file func (p *Parser) LoadFile(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() data, err := io.ReadAll(file) if err != nil { return err } // Try to parse as packet first var packet XMLPacket if err := xml.Unmarshal(data, &packet); err == nil && packet.Name != "" { def := p.parsePacket(&packet) p.packets[def.Name] = def return nil } // Try to parse as substruct var substruct XMLSubstruct if err := xml.Unmarshal(data, &substruct); err == nil && substruct.Name != "" { def := p.parseSubstruct(&substruct) p.packets[def.Name] = def return nil } return fmt.Errorf("failed to parse %s as packet or substruct", filename) } // parsePacket converts XML packet to internal representation func (p *Parser) parsePacket(xmlPacket *XMLPacket) *PacketDef { def := &PacketDef{ Name: xmlPacket.Name, IsSubstruct: false, Versions: make([]PacketVersion, 0, len(xmlPacket.Versions)), } for _, xmlVer := range xmlPacket.Versions { version := p.parseVersion(xmlVer) def.Versions = append(def.Versions, version) } // Sort versions by version number sort.Slice(def.Versions, func(i, j int) bool { return def.Versions[i].Version < def.Versions[j].Version }) return def } // parseSubstruct converts XML substruct to internal representation func (p *Parser) parseSubstruct(xmlSub *XMLSubstruct) *PacketDef { def := &PacketDef{ Name: xmlSub.Name, IsSubstruct: true, Versions: make([]PacketVersion, 0, len(xmlSub.Versions)), } for _, xmlVer := range xmlSub.Versions { version := p.parseVersion(xmlVer) def.Versions = append(def.Versions, version) } // Sort versions by version number sort.Slice(def.Versions, func(i, j int) bool { return def.Versions[i].Version < def.Versions[j].Version }) return def } // parseVersion converts XML version to internal representation func (p *Parser) parseVersion(xmlVer XMLVersion) PacketVersion { ver, _ := strconv.ParseUint(xmlVer.Number, 10, 16) version := PacketVersion{ Version: uint16(ver), Fields: make([]Field, 0, len(xmlVer.Fields)), } for _, xmlField := range xmlVer.Fields { field := p.parseField(xmlField) version.Fields = append(version.Fields, field) } return version } // parseField converts XML field to internal representation func (p *Parser) parseField(xmlField XMLField) Field { field := Field{ Name: xmlField.Name, Type: p.parseFieldType(xmlField.XMLName.Local), IfSet: xmlField.IfSet, IfNotSet: xmlField.IfNotSet, } // Parse size if xmlField.Size != "" { if size, err := strconv.Atoi(xmlField.Size); err == nil { field.Size = size } } // Parse size variable field.SizeVar = xmlField.SizeVar // Parse default value if xmlField.Default != "" { field.Default = p.parseDefaultValue(field.Type, xmlField.Default) } return field } // parseFieldType converts XML type name to FieldType func (p *Parser) parseFieldType(typeName string) FieldType { switch typeName { case "u8", "uint8": return FieldTypeUInt8 case "u16", "uint16": return FieldTypeUInt16 case "u32", "uint32": return FieldTypeUInt32 case "u64", "uint64": return FieldTypeUInt64 case "i8", "int8", "sint8": return FieldTypeInt8 case "i16", "int16", "sint16": return FieldTypeInt16 case "i32", "int32", "sint32": return FieldTypeInt32 case "i64", "int64", "sint64": return FieldTypeInt64 case "float": return FieldTypeFloat case "double": return FieldTypeDouble case "char": return FieldTypeChar case "str8", "string8": return FieldTypeString8 case "str16", "string16": return FieldTypeString16 case "str32", "string32": return FieldTypeString32 case "color": return FieldTypeColor case "eq2color": return FieldTypeEQ2Color case "substruct": return FieldTypeSubstruct case "array": return FieldTypeArray default: return FieldTypeUnknown } } // parseDefaultValue parses default value based on field type func (p *Parser) parseDefaultValue(fieldType FieldType, value string) interface{} { switch fieldType { case FieldTypeUInt8, FieldTypeUInt16, FieldTypeUInt32, FieldTypeUInt64: if v, err := strconv.ParseUint(value, 0, 64); err == nil { return v } case FieldTypeInt8, FieldTypeInt16, FieldTypeInt32, FieldTypeInt64: if v, err := strconv.ParseInt(value, 0, 64); err == nil { return v } case FieldTypeFloat, FieldTypeDouble: if v, err := strconv.ParseFloat(value, 64); err == nil { return v } case FieldTypeChar, FieldTypeString8, FieldTypeString16, FieldTypeString32: return value } return nil } // GetPacket returns a packet definition by name func (p *Parser) GetPacket(name string) (*PacketDef, bool) { def, exists := p.packets[name] return def, exists } // GetPacketVersion returns the best matching version for a packet func (p *Parser) GetPacketVersion(name string, version uint16) (*PacketVersion, error) { def, exists := p.packets[name] if !exists { return nil, fmt.Errorf("packet %s not found", name) } // Find the best matching version (highest version <= requested) var bestVersion *PacketVersion for i := range def.Versions { if def.Versions[i].Version <= version { if bestVersion == nil || def.Versions[i].Version > bestVersion.Version { bestVersion = &def.Versions[i] } } } if bestVersion == nil { return nil, fmt.Errorf("no suitable version found for packet %s version %d", name, version) } return bestVersion, nil } // ListPackets returns a list of all loaded packet names func (p *Parser) ListPackets() []string { names := make([]string, 0, len(p.packets)) for name := range p.packets { names = append(names, name) } sort.Strings(names) return names } // GetFieldTypeName returns the string representation of a field type func GetFieldTypeName(ft FieldType) string { switch ft { case FieldTypeUInt8: return "uint8" case FieldTypeUInt16: return "uint16" case FieldTypeUInt32: return "uint32" case FieldTypeUInt64: return "uint64" case FieldTypeInt8: return "int8" case FieldTypeInt16: return "int16" case FieldTypeInt32: return "int32" case FieldTypeInt64: return "int64" case FieldTypeFloat: return "float32" case FieldTypeDouble: return "float64" case FieldTypeChar: return "char" case FieldTypeString8: return "string8" case FieldTypeString16: return "string16" case FieldTypeString32: return "string32" case FieldTypeColor: return "color" case FieldTypeEQ2Color: return "eq2color" case FieldTypeSubstruct: return "substruct" case FieldTypeArray: return "array" default: return "unknown" } } // GetFieldSize returns the size in bytes of a field type func GetFieldSize(ft FieldType) int { switch ft { case FieldTypeUInt8, FieldTypeInt8: return 1 case FieldTypeUInt16, FieldTypeInt16: return 2 case FieldTypeUInt32, FieldTypeInt32, FieldTypeFloat: return 4 case FieldTypeUInt64, FieldTypeInt64, FieldTypeDouble: return 8 case FieldTypeColor: return 4 // RGBA case FieldTypeEQ2Color: return 4 // EQ2 specific color format default: return 0 // Variable size } }