421 lines
9.9 KiB
Go
421 lines
9.9 KiB
Go
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
|
|
}
|
|
}
|