1
0
Protocol/structs/parser.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
}
}