split packet parser into modules
This commit is contained in:
parent
62149041fc
commit
d6f0ab6784
214
internal/packets/parser/array_parser.go
Normal file
214
internal/packets/parser/array_parser.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readArray handles array parsing with full substruct support
|
||||||
|
func (p *Parser) readArray(field reflect.Value, fieldTag *FieldTag) error {
|
||||||
|
var arraySize int
|
||||||
|
if fieldTag.ArraySizeVar != "" {
|
||||||
|
arraySize = p.getDynamicLength(fieldTag.ArraySizeVar)
|
||||||
|
} else {
|
||||||
|
size, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
arraySize = int(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arraySize == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.Kind() == reflect.Slice {
|
||||||
|
elemType := field.Type().Elem()
|
||||||
|
slice := reflect.MakeSlice(field.Type(), arraySize, arraySize)
|
||||||
|
|
||||||
|
p.arrayStack = append(p.arrayStack, ArrayContext{
|
||||||
|
elementType: elemType,
|
||||||
|
totalSize: arraySize,
|
||||||
|
sizeVariable: fieldTag.ArraySizeVar,
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < arraySize; i++ {
|
||||||
|
p.arrayStack[len(p.arrayStack)-1].currentIndex = i
|
||||||
|
elem := slice.Index(i)
|
||||||
|
|
||||||
|
if elemType.Kind() == reflect.Struct {
|
||||||
|
if err := p.parseStructElement(elem, elemType); err != nil {
|
||||||
|
p.arrayStack = p.arrayStack[:len(p.arrayStack)-1]
|
||||||
|
return fmt.Errorf("array element %d: %w", i, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := p.readPrimitiveArrayElement(elem, elemType); err != nil {
|
||||||
|
p.arrayStack = p.arrayStack[:len(p.arrayStack)-1]
|
||||||
|
return fmt.Errorf("array element %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.arrayStack = p.arrayStack[:len(p.arrayStack)-1]
|
||||||
|
field.Set(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readSubstruct handles substruct parsing
|
||||||
|
func (p *Parser) readSubstruct(field reflect.Value, length int) error {
|
||||||
|
if field.Kind() == reflect.Slice {
|
||||||
|
elemType := field.Type().Elem()
|
||||||
|
slice := reflect.MakeSlice(field.Type(), length, length)
|
||||||
|
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
elem := slice.Index(i)
|
||||||
|
if err := p.parseStructElement(elem, elemType); err != nil {
|
||||||
|
return fmt.Errorf("substruct element %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Set(slice)
|
||||||
|
return nil
|
||||||
|
} else if field.Kind() == reflect.Struct {
|
||||||
|
return p.parseStructElement(field, field.Type())
|
||||||
|
} else if field.Kind() == reflect.Ptr && field.Type().Elem().Kind() == reflect.Struct {
|
||||||
|
structType := field.Type().Elem()
|
||||||
|
newStruct := reflect.New(structType)
|
||||||
|
if err := p.parseStructElement(newStruct.Elem(), structType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.Set(newStruct)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("substruct field must be struct, slice of structs, or pointer to struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStructElement parses a single struct element
|
||||||
|
func (p *Parser) parseStructElement(elem reflect.Value, elemType reflect.Type) error {
|
||||||
|
oldStruct := p.currentStruct
|
||||||
|
oldCache := p.fieldCache
|
||||||
|
|
||||||
|
p.currentStruct = elem
|
||||||
|
p.fieldCache = make(map[string]any)
|
||||||
|
p.structStack = append(p.structStack, elem)
|
||||||
|
|
||||||
|
for i := 0; i < elem.NumField(); i++ {
|
||||||
|
field := elem.Field(i)
|
||||||
|
fieldType := elemType.Field(i)
|
||||||
|
|
||||||
|
if !field.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := fieldType.Tag.Get("eq2")
|
||||||
|
if tag == "" || tag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.parseField(field, tag); err != nil {
|
||||||
|
p.structStack = p.structStack[:len(p.structStack)-1]
|
||||||
|
p.currentStruct = oldStruct
|
||||||
|
p.fieldCache = oldCache
|
||||||
|
return fmt.Errorf("field %s: %w", fieldType.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.CanInterface() {
|
||||||
|
p.fieldCache[fieldType.Name] = field.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.structStack = p.structStack[:len(p.structStack)-1]
|
||||||
|
p.currentStruct = oldStruct
|
||||||
|
p.fieldCache = oldCache
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPrimitiveArrayElement reads primitive array elements
|
||||||
|
func (p *Parser) readPrimitiveArrayElement(elem reflect.Value, elemType reflect.Type) error {
|
||||||
|
switch elemType.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetUint(uint64(val))
|
||||||
|
case reflect.Uint16:
|
||||||
|
val, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetUint(uint64(val))
|
||||||
|
case reflect.Uint32:
|
||||||
|
val, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetUint(uint64(val))
|
||||||
|
case reflect.Uint64:
|
||||||
|
val, err := p.readUint64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetUint(val)
|
||||||
|
case reflect.Int8:
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetInt(int64(int8(val)))
|
||||||
|
case reflect.Int16:
|
||||||
|
val, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetInt(int64(int16(val)))
|
||||||
|
case reflect.Int32:
|
||||||
|
val, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetInt(int64(int32(val)))
|
||||||
|
case reflect.Int64:
|
||||||
|
val, err := p.readUint64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetInt(int64(val))
|
||||||
|
case reflect.Float32:
|
||||||
|
val, err := p.readFloat32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetFloat(float64(val))
|
||||||
|
case reflect.Float64:
|
||||||
|
val, err := p.readFloat64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
elem.SetFloat(val)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported primitive array element type: %v", elemType.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentArrayIndex returns current array index for nested parsing
|
||||||
|
func (p *Parser) GetCurrentArrayIndex() int {
|
||||||
|
if len(p.arrayStack) > 0 {
|
||||||
|
return p.arrayStack[len(p.arrayStack)-1].currentIndex
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentArraySize returns current array size
|
||||||
|
func (p *Parser) GetCurrentArraySize() int {
|
||||||
|
if len(p.arrayStack) > 0 {
|
||||||
|
return p.arrayStack[len(p.arrayStack)-1].totalSize
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
135
internal/packets/parser/conditions.go
Normal file
135
internal/packets/parser/conditions.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// evaluateAllConditions checks all conditional logic
|
||||||
|
func (p *Parser) evaluateAllConditions(fieldTag *FieldTag, field reflect.Value) bool {
|
||||||
|
if fieldTag.Condition != nil && !p.evaluateCondition(fieldTag.Condition) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldTag.IfVariableSet != "" && !p.isVariableSet(fieldTag.IfVariableSet) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldTag.IfVariableNotSet != "" && p.isVariableSet(fieldTag.IfVariableNotSet) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldTag.IfFlag != "" && !p.flags[fieldTag.IfFlag] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldTag.IfFlagNotSet != "" && p.flags[fieldTag.IfFlagNotSet] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldTag.IfEquals != "" {
|
||||||
|
parts := strings.Split(fieldTag.IfEquals, "=")
|
||||||
|
if len(parts) == 2 && !p.evaluateEqualsCondition(parts[0], parts[1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldTag.IfNotEquals != "" {
|
||||||
|
parts := strings.Split(fieldTag.IfNotEquals, "=")
|
||||||
|
if len(parts) == 2 && p.evaluateEqualsCondition(parts[0], parts[1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluateCondition evaluates conditions for conditional fields
|
||||||
|
func (p *Parser) evaluateCondition(condition *FieldCondition) bool {
|
||||||
|
if condition.Type == "simple" {
|
||||||
|
return p.isVariableSet(condition.Variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedValue, exists := p.fieldCache[condition.Variable]
|
||||||
|
if !exists {
|
||||||
|
if p.currentStruct.IsValid() {
|
||||||
|
if structField := p.currentStruct.FieldByName(condition.Variable); structField.IsValid() {
|
||||||
|
cachedValue = structField.Interface()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareValue, err := p.convertValue(condition.Value, cachedValue)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.compareValues(cachedValue, compareValue, condition.Operator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isVariableSet checks if a variable exists and has a truthy value
|
||||||
|
func (p *Parser) isVariableSet(variable string) bool {
|
||||||
|
if cachedValue, exists := p.fieldCache[variable]; exists {
|
||||||
|
return p.isTruthy(cachedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.currentStruct.IsValid() {
|
||||||
|
if field := p.currentStruct.FieldByName(variable); field.IsValid() {
|
||||||
|
return p.isTruthy(field.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluateEqualsCondition checks field equality
|
||||||
|
func (p *Parser) evaluateEqualsCondition(variable, value string) bool {
|
||||||
|
cachedValue, exists := p.fieldCache[variable]
|
||||||
|
if !exists && p.currentStruct.IsValid() {
|
||||||
|
if field := p.currentStruct.FieldByName(variable); field.IsValid() {
|
||||||
|
cachedValue = field.Interface()
|
||||||
|
exists = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
compareValue, err := p.convertValue(value, cachedValue)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.compareValues(cachedValue, compareValue, "==")
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluateType2Criteria evaluates type2 criteria for alternative types
|
||||||
|
func (p *Parser) evaluateType2Criteria(criteria string) bool {
|
||||||
|
operators := []string{"!=", "==", ">=", "<=", ">", "<"}
|
||||||
|
|
||||||
|
for _, op := range operators {
|
||||||
|
if idx := strings.Index(criteria, op); idx > 0 {
|
||||||
|
fieldName := strings.TrimSpace(criteria[:idx])
|
||||||
|
valueStr := strings.TrimSpace(criteria[idx+len(op):])
|
||||||
|
|
||||||
|
cachedValue, exists := p.fieldCache[fieldName]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
compareValue, err := p.convertValue(valueStr, cachedValue)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.compareValues(cachedValue, compareValue, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
123
internal/packets/parser/field_tag.go
Normal file
123
internal/packets/parser/field_tag.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FieldTag represents parsed tag parameters
|
||||||
|
type FieldTag struct {
|
||||||
|
Type string
|
||||||
|
Length int
|
||||||
|
DynamicLen string
|
||||||
|
Condition *FieldCondition
|
||||||
|
MaxSize int
|
||||||
|
SkipOversized bool
|
||||||
|
OversizedValue int
|
||||||
|
OversizedByte byte
|
||||||
|
Type2 string
|
||||||
|
Type2Criteria string
|
||||||
|
ArraySizeVar string
|
||||||
|
IfVariableSet string
|
||||||
|
IfVariableNotSet string
|
||||||
|
IfFlag string
|
||||||
|
IfFlagNotSet string
|
||||||
|
IfEquals string
|
||||||
|
IfNotEquals string
|
||||||
|
Optional bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldCondition represents XML conditional logic
|
||||||
|
type FieldCondition struct {
|
||||||
|
Type string // "if_set", "if_not_set", "if_flag", etc.
|
||||||
|
Variable string
|
||||||
|
Value string
|
||||||
|
Operator string
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFieldTag extracts all tag parameters
|
||||||
|
func (p *Parser) parseFieldTag(tag string) *FieldTag {
|
||||||
|
parts := strings.Split(tag, ",")
|
||||||
|
fieldTag := &FieldTag{
|
||||||
|
Type: parts[0],
|
||||||
|
Length: 1,
|
||||||
|
MaxSize: -1,
|
||||||
|
OversizedValue: -1,
|
||||||
|
OversizedByte: 127,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range parts[1:] {
|
||||||
|
p.parseTagParameter(fieldTag, part)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTagParameter processes individual tag parameters
|
||||||
|
func (p *Parser) parseTagParameter(fieldTag *FieldTag, part string) {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(part, "len="):
|
||||||
|
lenVal := part[4:]
|
||||||
|
if l, err := strconv.Atoi(lenVal); err == nil {
|
||||||
|
fieldTag.Length = l
|
||||||
|
} else {
|
||||||
|
fieldTag.DynamicLen = lenVal
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(part, "if="):
|
||||||
|
fieldTag.Condition = p.parseCondition(part[3:])
|
||||||
|
case strings.HasPrefix(part, "maxsize="):
|
||||||
|
if m, err := strconv.Atoi(part[8:]); err == nil {
|
||||||
|
fieldTag.MaxSize = m
|
||||||
|
}
|
||||||
|
case part == "skipoversized":
|
||||||
|
fieldTag.SkipOversized = true
|
||||||
|
case strings.HasPrefix(part, "oversized="):
|
||||||
|
if o, err := strconv.Atoi(part[10:]); err == nil {
|
||||||
|
fieldTag.OversizedValue = o
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(part, "oversizedbyte="):
|
||||||
|
if o, err := strconv.Atoi(part[14:]); err == nil {
|
||||||
|
fieldTag.OversizedByte = byte(o)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(part, "type2="):
|
||||||
|
fieldTag.Type2 = part[6:]
|
||||||
|
case strings.HasPrefix(part, "type2criteria="):
|
||||||
|
fieldTag.Type2Criteria = part[14:]
|
||||||
|
case strings.HasPrefix(part, "arraysize="):
|
||||||
|
fieldTag.ArraySizeVar = part[10:]
|
||||||
|
case strings.HasPrefix(part, "ifvariableset="):
|
||||||
|
fieldTag.IfVariableSet = part[14:]
|
||||||
|
case strings.HasPrefix(part, "ifvariablenotset="):
|
||||||
|
fieldTag.IfVariableNotSet = part[17:]
|
||||||
|
case strings.HasPrefix(part, "ifflag="):
|
||||||
|
fieldTag.IfFlag = part[7:]
|
||||||
|
case strings.HasPrefix(part, "ifflagnotset="):
|
||||||
|
fieldTag.IfFlagNotSet = part[13:]
|
||||||
|
case strings.HasPrefix(part, "ifequals="):
|
||||||
|
fieldTag.IfEquals = part[9:]
|
||||||
|
case strings.HasPrefix(part, "ifnotequals="):
|
||||||
|
fieldTag.IfNotEquals = part[12:]
|
||||||
|
case part == "optional":
|
||||||
|
fieldTag.Optional = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCondition creates FieldCondition from condition string
|
||||||
|
func (p *Parser) parseCondition(condition string) *FieldCondition {
|
||||||
|
operators := []string{"!=", "==", ">=", "<=", ">", "<", "&", "|"}
|
||||||
|
|
||||||
|
for _, op := range operators {
|
||||||
|
if idx := strings.Index(condition, op); idx > 0 {
|
||||||
|
return &FieldCondition{
|
||||||
|
Variable: strings.TrimSpace(condition[:idx]),
|
||||||
|
Value: strings.TrimSpace(condition[idx+len(op):]),
|
||||||
|
Operator: op,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FieldCondition{
|
||||||
|
Type: "simple",
|
||||||
|
Variable: condition,
|
||||||
|
}
|
||||||
|
}
|
191
internal/packets/parser/helpers.go
Normal file
191
internal/packets/parser/helpers.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getDynamicLength gets length from another field with stack support
|
||||||
|
func (p *Parser) getDynamicLength(fieldName string) int {
|
||||||
|
if cachedValue, exists := p.fieldCache[fieldName]; exists {
|
||||||
|
return p.valueToInt(cachedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.currentStruct.IsValid() {
|
||||||
|
if field := p.currentStruct.FieldByName(fieldName); field.IsValid() {
|
||||||
|
return p.valueToInt(field.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(p.structStack) - 1; i >= 0; i-- {
|
||||||
|
if field := p.structStack[i].FieldByName(fieldName); field.IsValid() {
|
||||||
|
return p.valueToInt(field.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertValue converts string to appropriate type for comparison
|
||||||
|
func (p *Parser) convertValue(valueStr string, reference any) (any, error) {
|
||||||
|
switch reference.(type) {
|
||||||
|
case uint8, int8:
|
||||||
|
if val, err := strconv.ParseInt(valueStr, 0, 8); err == nil {
|
||||||
|
return uint8(val), nil
|
||||||
|
}
|
||||||
|
case uint16, int16:
|
||||||
|
if val, err := strconv.ParseInt(valueStr, 0, 16); err == nil {
|
||||||
|
return uint16(val), nil
|
||||||
|
}
|
||||||
|
case uint32, int32:
|
||||||
|
if val, err := strconv.ParseInt(valueStr, 0, 32); err == nil {
|
||||||
|
return uint32(val), nil
|
||||||
|
}
|
||||||
|
case uint64, int64:
|
||||||
|
if val, err := strconv.ParseInt(valueStr, 0, 64); err == nil {
|
||||||
|
return uint64(val), nil
|
||||||
|
}
|
||||||
|
case float32:
|
||||||
|
if val, err := strconv.ParseFloat(valueStr, 32); err == nil {
|
||||||
|
return float32(val), nil
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if val, err := strconv.ParseFloat(valueStr, 64); err == nil {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
return valueStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, err := strconv.ParseInt(valueStr, 0, 32); err == nil {
|
||||||
|
return int(val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return valueStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareValues performs comparison between two values
|
||||||
|
func (p *Parser) compareValues(a, b any, op string) bool {
|
||||||
|
aVal := p.valueToInt64(a)
|
||||||
|
bVal := p.valueToInt64(b)
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case "==":
|
||||||
|
return aVal == bVal
|
||||||
|
case "!=":
|
||||||
|
return aVal != bVal
|
||||||
|
case ">":
|
||||||
|
return aVal > bVal
|
||||||
|
case ">=":
|
||||||
|
return aVal >= bVal
|
||||||
|
case "<":
|
||||||
|
return aVal < bVal
|
||||||
|
case "<=":
|
||||||
|
return aVal <= bVal
|
||||||
|
case "&":
|
||||||
|
return (aVal & bVal) != 0
|
||||||
|
case "|":
|
||||||
|
return (aVal | bVal) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueToInt converts various types to int
|
||||||
|
func (p *Parser) valueToInt(v any) int {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case uint8:
|
||||||
|
return int(val)
|
||||||
|
case int8:
|
||||||
|
return int(val)
|
||||||
|
case uint16:
|
||||||
|
return int(val)
|
||||||
|
case int16:
|
||||||
|
return int(val)
|
||||||
|
case uint32:
|
||||||
|
return int(val)
|
||||||
|
case int32:
|
||||||
|
return int(val)
|
||||||
|
case uint64:
|
||||||
|
return int(val)
|
||||||
|
case int64:
|
||||||
|
return int(val)
|
||||||
|
case int:
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueToInt64 converts various types to int64 for comparison
|
||||||
|
func (p *Parser) valueToInt64(v any) int64 {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case uint8:
|
||||||
|
return int64(val)
|
||||||
|
case int8:
|
||||||
|
return int64(val)
|
||||||
|
case uint16:
|
||||||
|
return int64(val)
|
||||||
|
case int16:
|
||||||
|
return int64(val)
|
||||||
|
case uint32:
|
||||||
|
return int64(val)
|
||||||
|
case int32:
|
||||||
|
return int64(val)
|
||||||
|
case uint64:
|
||||||
|
return int64(val)
|
||||||
|
case int64:
|
||||||
|
return val
|
||||||
|
case int:
|
||||||
|
return int64(val)
|
||||||
|
case float32:
|
||||||
|
return int64(val)
|
||||||
|
case float64:
|
||||||
|
return int64(val)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTruthy checks if a value is truthy
|
||||||
|
func (p *Parser) isTruthy(v any) bool {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case bool:
|
||||||
|
return val
|
||||||
|
case uint8, int8, uint16, int16, uint32, int32, uint64, int64, int:
|
||||||
|
return p.valueToInt64(val) != 0
|
||||||
|
case string:
|
||||||
|
return val != ""
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleOversizedField handles fields that exceed their oversized value
|
||||||
|
func (p *Parser) handleOversizedField(field reflect.Value, oversizedByte byte, length int) error {
|
||||||
|
if field.Kind() == reflect.Slice {
|
||||||
|
slice := reflect.MakeSlice(field.Type(), 1, 1)
|
||||||
|
if slice.Len() > 0 {
|
||||||
|
elem := slice.Index(0)
|
||||||
|
if elem.CanSet() {
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
elem.SetUint(uint64(oversizedByte))
|
||||||
|
case reflect.Int8:
|
||||||
|
elem.SetInt(int64(int8(oversizedByte)))
|
||||||
|
default:
|
||||||
|
elem.SetUint(uint64(oversizedByte))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.Set(slice)
|
||||||
|
} else {
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
field.SetUint(uint64(oversizedByte))
|
||||||
|
case reflect.Int8:
|
||||||
|
field.SetInt(int64(int8(oversizedByte)))
|
||||||
|
default:
|
||||||
|
field.SetUint(uint64(oversizedByte))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
424
internal/packets/parser/parser_test.go
Normal file
424
internal/packets/parser/parser_test.go
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"eq2emu/internal/common"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicPacket struct {
|
||||||
|
Channel uint8 `eq2:"int8"`
|
||||||
|
Count uint16 `eq2:"int16"`
|
||||||
|
MessageType uint8 `eq2:"int8,if=Channel==1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArrayPacket struct {
|
||||||
|
ItemCount uint16 `eq2:"int16"`
|
||||||
|
Items []ItemDescription `eq2:"array,arraysize=ItemCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemDescription struct {
|
||||||
|
InfoHeader WS_ExamineInfoHeader `eq2:"substruct"`
|
||||||
|
Info BaseItemDescription `eq2:"substruct"`
|
||||||
|
ItemType uint8 `eq2:"int8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WS_ExamineInfoHeader struct {
|
||||||
|
Unknown1 uint32 `eq2:"int32"`
|
||||||
|
Unknown2 uint16 `eq2:"int16"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseItemDescription struct {
|
||||||
|
Name common.EQ2String16 `eq2:"string16"`
|
||||||
|
Category common.EQ2String8 `eq2:"string8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConditionalPacket struct {
|
||||||
|
HasRewards uint8 `eq2:"int8"`
|
||||||
|
RewardData *RewardInfo `eq2:"substruct,ifvariableset=HasRewards"`
|
||||||
|
CompleteFlag uint8 `eq2:"int8"`
|
||||||
|
ClassicSound uint8 `eq2:"int8,ifvariableset=CompleteFlag"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RewardInfo struct {
|
||||||
|
RewardType uint8 `eq2:"int8"`
|
||||||
|
Amount uint32 `eq2:"int32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OversizedPacket struct {
|
||||||
|
LargeDataCount uint16 `eq2:"int16"`
|
||||||
|
LargeData []byte `eq2:"char,len=LargeDataCount,maxsize=10,skipoversized"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypeSwitchPacket struct {
|
||||||
|
StatType uint8 `eq2:"int8"`
|
||||||
|
StatValue any `eq2:"int32,type2=float,type2criteria=StatType!=6"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagPacket struct {
|
||||||
|
Equipment []common.EQ2EquipmentItem `eq2:"equipment,len=2,ifflag=has_equipment"`
|
||||||
|
Colors []common.EQ2Color `eq2:"color,len=3,ifflagnotset=no_colors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComplexPacket struct {
|
||||||
|
GroupCount uint8 `eq2:"int8"`
|
||||||
|
Groups []Group `eq2:"array,arraysize=GroupCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
MemberCount uint16 `eq2:"int16"`
|
||||||
|
Members []Member `eq2:"array,arraysize=MemberCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Member struct {
|
||||||
|
Name common.EQ2String16 `eq2:"string16"`
|
||||||
|
Level uint8 `eq2:"int8"`
|
||||||
|
Class uint8 `eq2:"int8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version-specific structs
|
||||||
|
type PacketV1 struct {
|
||||||
|
Field1 uint8 `eq2:"int8"`
|
||||||
|
Field2 uint16 `eq2:"int16"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketV2 struct {
|
||||||
|
Field1 uint8 `eq2:"int8"`
|
||||||
|
Field2 uint16 `eq2:"int16"`
|
||||||
|
NewField uint32 `eq2:"int32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicParsing(t *testing.T) {
|
||||||
|
// Basic packet: Channel=1, Count=100, MessageType=5
|
||||||
|
data := []byte{0x01, 0x64, 0x00, 0x05}
|
||||||
|
parser := NewParser(data)
|
||||||
|
|
||||||
|
var packet BasicPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Channel != 1 {
|
||||||
|
t.Errorf("Expected Channel=1, got %d", packet.Channel)
|
||||||
|
}
|
||||||
|
if packet.Count != 100 {
|
||||||
|
t.Errorf("Expected Count=100, got %d", packet.Count)
|
||||||
|
}
|
||||||
|
if packet.MessageType != 5 {
|
||||||
|
t.Errorf("Expected MessageType=5, got %d", packet.MessageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConditionalSkip(t *testing.T) {
|
||||||
|
// Channel=2 (not 1), so MessageType should be skipped
|
||||||
|
data := []byte{0x02, 0x64, 0x00}
|
||||||
|
parser := NewParser(data)
|
||||||
|
|
||||||
|
var packet BasicPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Channel != 2 {
|
||||||
|
t.Errorf("Expected Channel=2, got %d", packet.Channel)
|
||||||
|
}
|
||||||
|
if packet.MessageType != 0 {
|
||||||
|
t.Errorf("Expected MessageType=0 (skipped), got %d", packet.MessageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayParsing(t *testing.T) {
|
||||||
|
// ItemCount=2, then 2 items
|
||||||
|
data := []byte{
|
||||||
|
0x02, 0x00, // ItemCount = 2
|
||||||
|
// Item 1
|
||||||
|
0x01, 0x00, 0x00, 0x00, // InfoHeader.Unknown1 = 1
|
||||||
|
0x02, 0x00, // InfoHeader.Unknown2 = 2
|
||||||
|
0x04, 0x00, 't', 'e', 's', 't', // Name: "test"
|
||||||
|
0x03, 'c', 'a', 't', // Category: "cat"
|
||||||
|
0x05, // ItemType = 5
|
||||||
|
// Item 2
|
||||||
|
0x03, 0x00, 0x00, 0x00, // InfoHeader.Unknown1 = 3
|
||||||
|
0x04, 0x00, // InfoHeader.Unknown2 = 4
|
||||||
|
0x05, 0x00, 'i', 't', 'e', 'm', '2', // Name: "item2"
|
||||||
|
0x04, 't', 'y', 'p', 'e', // Category: "type"
|
||||||
|
0x06, // ItemType = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser(data)
|
||||||
|
var packet ArrayPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.ItemCount != 2 {
|
||||||
|
t.Errorf("Expected ItemCount=2, got %d", packet.ItemCount)
|
||||||
|
}
|
||||||
|
if len(packet.Items) != 2 {
|
||||||
|
t.Errorf("Expected 2 items, got %d", len(packet.Items))
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Items[0].Info.Name.Data != "test" {
|
||||||
|
t.Errorf("Expected first item name 'test', got '%s'", packet.Items[0].Info.Name.Data)
|
||||||
|
}
|
||||||
|
if packet.Items[1].ItemType != 6 {
|
||||||
|
t.Errorf("Expected second item type 6, got %d", packet.Items[1].ItemType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConditionalFields(t *testing.T) {
|
||||||
|
// HasRewards=1, RewardData present, CompleteFlag=1, ClassicSound present
|
||||||
|
data := []byte{
|
||||||
|
0x01, // HasRewards = 1
|
||||||
|
0x02, // RewardType = 2
|
||||||
|
0x64, 0x00, 0x00, 0x00, // Amount = 100
|
||||||
|
0x01, // CompleteFlag = 1
|
||||||
|
0x05, // ClassicSound = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser(data)
|
||||||
|
var packet ConditionalPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.HasRewards != 1 {
|
||||||
|
t.Errorf("Expected HasRewards=1, got %d", packet.HasRewards)
|
||||||
|
}
|
||||||
|
if packet.RewardData == nil {
|
||||||
|
t.Error("Expected RewardData to be present")
|
||||||
|
} else {
|
||||||
|
if packet.RewardData.RewardType != 2 {
|
||||||
|
t.Errorf("Expected RewardType=2, got %d", packet.RewardData.RewardType)
|
||||||
|
}
|
||||||
|
if packet.RewardData.Amount != 100 {
|
||||||
|
t.Errorf("Expected Amount=100, got %d", packet.RewardData.Amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if packet.ClassicSound != 5 {
|
||||||
|
t.Errorf("Expected ClassicSound=5, got %d", packet.ClassicSound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOversizedHandling(t *testing.T) {
|
||||||
|
// LargeDataCount=15 (exceeds maxsize=10), should be truncated
|
||||||
|
data := []byte{
|
||||||
|
0x0F, 0x00, // LargeDataCount = 15
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
|
||||||
|
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 15 bytes of data
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser(data)
|
||||||
|
var packet OversizedPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.LargeDataCount != 15 {
|
||||||
|
t.Errorf("Expected LargeDataCount=15, got %d", packet.LargeDataCount)
|
||||||
|
}
|
||||||
|
if len(packet.LargeData) != 10 {
|
||||||
|
t.Errorf("Expected LargeData length=10 (truncated), got %d", len(packet.LargeData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeSwitching(t *testing.T) {
|
||||||
|
// StatType=3 (not 6), so StatValue should be parsed as float
|
||||||
|
data := []byte{
|
||||||
|
0x03, // StatType = 3
|
||||||
|
0x00, 0x00, 0x80, 0x3F, // float32: 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser(data)
|
||||||
|
var packet TypeSwitchPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.StatType != 3 {
|
||||||
|
t.Errorf("Expected StatType=3, got %d", packet.StatType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be parsed as float32 since StatType != 6
|
||||||
|
floatVal, ok := packet.StatValue.(float32)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Expected StatValue to be float32, got %T", packet.StatValue)
|
||||||
|
} else if floatVal != 1.0 {
|
||||||
|
t.Errorf("Expected StatValue=1.0, got %f", floatVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagBasedConditionals(t *testing.T) {
|
||||||
|
// Equipment data (2 items)
|
||||||
|
data := []byte{
|
||||||
|
// Equipment item 1
|
||||||
|
0x01, 0x00, // Type = 1
|
||||||
|
0xFF, 0x00, 0x00, // Color: Red=255, Green=0, Blue=0
|
||||||
|
0x00, 0xFF, 0x00, // Highlight: Red=0, Green=255, Blue=0
|
||||||
|
// Equipment item 2
|
||||||
|
0x02, 0x00, // Type = 2
|
||||||
|
0x00, 0x00, 0xFF, // Color: Red=0, Green=0, Blue=255
|
||||||
|
0xFF, 0xFF, 0x00, // Highlight: Red=255, Green=255, Blue=0
|
||||||
|
// Colors (3 items)
|
||||||
|
0x80, 0x80, 0x80, // Color 1: Gray
|
||||||
|
0x40, 0x40, 0x40, // Color 2: Dark gray
|
||||||
|
0xC0, 0xC0, 0xC0, // Color 3: Light gray
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser(data)
|
||||||
|
parser.SetFlag("has_equipment", true)
|
||||||
|
// no_colors flag is not set, so colors should be parsed
|
||||||
|
|
||||||
|
var packet FlagPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packet.Equipment) != 2 {
|
||||||
|
t.Errorf("Expected 2 equipment items, got %d", len(packet.Equipment))
|
||||||
|
}
|
||||||
|
if len(packet.Colors) != 3 {
|
||||||
|
t.Errorf("Expected 3 colors, got %d", len(packet.Colors))
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Equipment[0].Type != 1 {
|
||||||
|
t.Errorf("Expected first equipment type 1, got %d", packet.Equipment[0].Type)
|
||||||
|
}
|
||||||
|
if packet.Colors[0].Red != 0x80 {
|
||||||
|
t.Errorf("Expected first color red=128, got %d", packet.Colors[0].Red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComplexNestedArrays(t *testing.T) {
|
||||||
|
// GroupCount=1, Group has MemberCount=2
|
||||||
|
data := []byte{
|
||||||
|
0x01, // GroupCount = 1
|
||||||
|
0x02, 0x00, // MemberCount = 2
|
||||||
|
// Member 1
|
||||||
|
0x05, 0x00, 'A', 'l', 'i', 'c', 'e', // Name: "Alice"
|
||||||
|
0x0A, // Level = 10
|
||||||
|
0x01, // Class = 1
|
||||||
|
// Member 2
|
||||||
|
0x03, 0x00, 'B', 'o', 'b', // Name: "Bob"
|
||||||
|
0x0F, // Level = 15
|
||||||
|
0x02, // Class = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := NewParser(data)
|
||||||
|
var packet ComplexPacket
|
||||||
|
err := parser.ParseStruct(&packet)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.GroupCount != 1 {
|
||||||
|
t.Errorf("Expected GroupCount=1, got %d", packet.GroupCount)
|
||||||
|
}
|
||||||
|
if len(packet.Groups) != 1 {
|
||||||
|
t.Errorf("Expected 1 group, got %d", len(packet.Groups))
|
||||||
|
}
|
||||||
|
if len(packet.Groups[0].Members) != 2 {
|
||||||
|
t.Errorf("Expected 2 members, got %d", len(packet.Groups[0].Members))
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Groups[0].Members[0].Name.Data != "Alice" {
|
||||||
|
t.Errorf("Expected first member 'Alice', got '%s'", packet.Groups[0].Members[0].Name.Data)
|
||||||
|
}
|
||||||
|
if packet.Groups[0].Members[1].Level != 15 {
|
||||||
|
t.Errorf("Expected second member level 15, got %d", packet.Groups[0].Members[1].Level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionRegistry(t *testing.T) {
|
||||||
|
registry := NewVersionRegistry()
|
||||||
|
|
||||||
|
// Register different versions
|
||||||
|
registry.RegisterStruct("TestPacket", "1.0", reflect.TypeOf(PacketV1{}))
|
||||||
|
registry.RegisterStruct("TestPacket", "2.0", reflect.TypeOf(PacketV2{}))
|
||||||
|
|
||||||
|
// Test version 1.0
|
||||||
|
data1 := []byte{0x01, 0x64, 0x00} // Field1=1, Field2=100
|
||||||
|
parser1 := NewParser(data1)
|
||||||
|
|
||||||
|
result1, err := parser1.ParseWithVersion(registry, "TestPacket", "1.0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse v1.0 error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v1Packet, ok := result1.(PacketV1)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected PacketV1 type")
|
||||||
|
}
|
||||||
|
if v1Packet.Field1 != 1 || v1Packet.Field2 != 100 {
|
||||||
|
t.Errorf("V1 packet values incorrect: Field1=%d, Field2=%d", v1Packet.Field1, v1Packet.Field2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test version 2.0
|
||||||
|
data2 := []byte{0x02, 0xC8, 0x00, 0x90, 0x01, 0x00, 0x00} // Field1=2, Field2=200, NewField=400
|
||||||
|
parser2 := NewParser(data2)
|
||||||
|
|
||||||
|
result2, err := parser2.ParseWithVersion(registry, "TestPacket", "2.0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse v2.0 error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2Packet, ok := result2.(PacketV2)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected PacketV2 type")
|
||||||
|
}
|
||||||
|
if v2Packet.Field1 != 2 || v2Packet.Field2 != 200 || v2Packet.NewField != 400 {
|
||||||
|
t.Errorf("V2 packet values incorrect: Field1=%d, Field2=%d, NewField=%d",
|
||||||
|
v2Packet.Field1, v2Packet.Field2, v2Packet.NewField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersionFallback(t *testing.T) {
|
||||||
|
registry := NewVersionRegistry()
|
||||||
|
registry.RegisterStruct("TestPacket", "1.0", reflect.TypeOf(PacketV1{}))
|
||||||
|
registry.RegisterStruct("TestPacket", "2.0", reflect.TypeOf(PacketV2{}))
|
||||||
|
|
||||||
|
// Request version 1.5 (doesn't exist), should fall back to 1.0
|
||||||
|
data := []byte{0x01, 0x64, 0x00}
|
||||||
|
parser := NewParser(data)
|
||||||
|
|
||||||
|
result, err := parser.ParseWithVersion(registry, "TestPacket", "1.5")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse with fallback error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should get v1.0 (nearest lower version)
|
||||||
|
_, ok := result.(PacketV1)
|
||||||
|
if !ok {
|
||||||
|
t.Error("Expected fallback to PacketV1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserPosition(t *testing.T) {
|
||||||
|
data := []byte{0x01, 0x02, 0x03, 0x04}
|
||||||
|
parser := NewParser(data)
|
||||||
|
|
||||||
|
if parser.Offset() != 0 {
|
||||||
|
t.Errorf("Initial offset should be 0, got %d", parser.Offset())
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.readUint8() // Read one byte
|
||||||
|
if parser.Offset() != 1 {
|
||||||
|
t.Errorf("After reading 1 byte, offset should be 1, got %d", parser.Offset())
|
||||||
|
}
|
||||||
|
|
||||||
|
if parser.Remaining() != 3 {
|
||||||
|
t.Errorf("After reading 1 byte, remaining should be 3, got %d", parser.Remaining())
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.SetOffset(2)
|
||||||
|
if parser.Offset() != 2 {
|
||||||
|
t.Errorf("After SetOffset(2), offset should be 2, got %d", parser.Offset())
|
||||||
|
}
|
||||||
|
}
|
435
internal/packets/parser/type_readers.go
Normal file
435
internal/packets/parser/type_readers.go
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"eq2emu/internal/common"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readInt8 handles uint8/int8 reading
|
||||||
|
func (p *Parser) readInt8(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]uint8, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = val
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readInt16(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]uint16, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = val
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readInt32(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle interface{} fields
|
||||||
|
if field.Kind() == reflect.Interface {
|
||||||
|
field.Set(reflect.ValueOf(val))
|
||||||
|
} else {
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]uint32, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = val
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readInt64(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]uint64, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = val
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readSInt8(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetInt(int64(int8(val)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]int8, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = int8(val)
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readSInt16(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetInt(int64(int16(val)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]int16, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = int16(val)
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readSInt32(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetInt(int64(int32(val)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]int32, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = int32(val)
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readSInt64(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetInt(int64(val))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]int64, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readUint64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = int64(val)
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readChar(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetUint(uint64(val))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]byte, length)
|
||||||
|
err := p.readBytes(slice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetBytes(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readFloat(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readFloat32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle interface{} fields
|
||||||
|
if field.Kind() == reflect.Interface {
|
||||||
|
field.Set(reflect.ValueOf(val))
|
||||||
|
} else {
|
||||||
|
field.SetFloat(float64(val))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]float32, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readFloat32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = val
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readDouble(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
val, err := p.readFloat64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetFloat(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]float64, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val, err := p.readFloat64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i] = val
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readString8(field reflect.Value) error {
|
||||||
|
size, err := p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, size)
|
||||||
|
err = p.readBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
str8 := common.EQ2String8{
|
||||||
|
Size: size,
|
||||||
|
Data: string(data),
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(str8))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readString16(field reflect.Value) error {
|
||||||
|
size, err := p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, size)
|
||||||
|
err = p.readBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
str16 := common.EQ2String16{
|
||||||
|
Size: size,
|
||||||
|
Data: string(data),
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(str16))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readString32(field reflect.Value) error {
|
||||||
|
size, err := p.readUint32()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, size)
|
||||||
|
err = p.readBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
str32 := common.EQ2String32{
|
||||||
|
Size: size,
|
||||||
|
Data: string(data),
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(str32))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readColor(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
color := common.EQ2Color{}
|
||||||
|
var err error
|
||||||
|
color.Red, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
color.Green, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
color.Blue, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(color))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]common.EQ2Color, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
var err error
|
||||||
|
slice[i].Red, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i].Green, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i].Blue, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) readEquipment(field reflect.Value, length int) error {
|
||||||
|
if length == 1 {
|
||||||
|
equipment := common.EQ2EquipmentItem{}
|
||||||
|
var err error
|
||||||
|
equipment.Type, err = p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
equipment.Color.Red, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
equipment.Color.Green, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
equipment.Color.Blue, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
equipment.Highlight.Red, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
equipment.Highlight.Green, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
equipment.Highlight.Blue, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Set(reflect.ValueOf(equipment))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := make([]common.EQ2EquipmentItem, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
var err error
|
||||||
|
slice[i].Type, err = p.readUint16()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slice[i].Color.Red, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i].Color.Green, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i].Color.Blue, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slice[i].Highlight.Red, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i].Highlight.Green, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
slice[i].Highlight.Blue, err = p.readUint8()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field.Set(reflect.ValueOf(slice))
|
||||||
|
return nil
|
||||||
|
}
|
71
internal/packets/parser/version_registry.go
Normal file
71
internal/packets/parser/version_registry.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VersionRegistry manages version-specific struct types
|
||||||
|
type VersionRegistry struct {
|
||||||
|
structs map[string]map[string]reflect.Type // [structName][version] = Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVersionRegistry creates a new version registry
|
||||||
|
func NewVersionRegistry() *VersionRegistry {
|
||||||
|
return &VersionRegistry{
|
||||||
|
structs: make(map[string]map[string]reflect.Type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterStruct registers a struct type for a specific version
|
||||||
|
func (vr *VersionRegistry) RegisterStruct(name, version string, structType reflect.Type) {
|
||||||
|
if vr.structs[name] == nil {
|
||||||
|
vr.structs[name] = make(map[string]reflect.Type)
|
||||||
|
}
|
||||||
|
vr.structs[name][version] = structType
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStruct returns the appropriate struct type for a version
|
||||||
|
func (vr *VersionRegistry) GetStruct(name, version string) (reflect.Type, bool) {
|
||||||
|
if versions, exists := vr.structs[name]; exists {
|
||||||
|
if structType, exists := versions[version]; exists {
|
||||||
|
return structType, true
|
||||||
|
}
|
||||||
|
return vr.findNearestVersion(name, version)
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// findNearestVersion finds the closest version <= requested version
|
||||||
|
func (vr *VersionRegistry) findNearestVersion(name, targetVersion string) (reflect.Type, bool) {
|
||||||
|
versions := vr.structs[name]
|
||||||
|
var bestVersion string
|
||||||
|
var bestType reflect.Type
|
||||||
|
|
||||||
|
for version, structType := range versions {
|
||||||
|
if version <= targetVersion && version > bestVersion {
|
||||||
|
bestVersion = version
|
||||||
|
bestType = structType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestType, bestVersion != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseWithVersion parses using version-specific struct
|
||||||
|
func (p *Parser) ParseWithVersion(registry *VersionRegistry, structName, version string) (any, error) {
|
||||||
|
structType, exists := registry.GetStruct(structName, version)
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("no struct found for %s version %s", structName, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := reflect.New(structType)
|
||||||
|
elem := ptr.Elem()
|
||||||
|
|
||||||
|
err := p.ParseStruct(ptr.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem.Interface(), nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user