implement missing types, conditions
This commit is contained in:
parent
e310437c1b
commit
c42485f874
396
internal/packets/parser/context.go
Normal file
396
internal/packets/parser/context.go
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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) readString8() string {
|
||||||
|
length := ctx.readUint8()
|
||||||
|
str := string(ctx.data[ctx.offset : ctx.offset+int(length)])
|
||||||
|
ctx.offset += int(length)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ParseContext) readString16() string {
|
||||||
|
length := ctx.readUint16()
|
||||||
|
str := string(ctx.data[ctx.offset : ctx.offset+int(length)])
|
||||||
|
ctx.offset += int(length)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *ParseContext) readEQ2String8() common.EQ2String8 {
|
||||||
|
size := ctx.readUint8()
|
||||||
|
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.readUint16()
|
||||||
|
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.readUint32()
|
||||||
|
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.readUint8(),
|
||||||
|
Green: ctx.readUint8(),
|
||||||
|
Blue: ctx.readUint8(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if strings.HasPrefix(condition, "var:") {
|
||||||
|
varName := condition[4:]
|
||||||
|
return ctx.hasVar(varName)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(condition, "!var:") {
|
||||||
|
varName := condition[5:]
|
||||||
|
return !ctx.hasVar(varName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version comparisons
|
||||||
|
if strings.HasPrefix(condition, "version") {
|
||||||
|
return ctx.evaluateVersionCondition(condition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String length operators: name!>5, name!<=10
|
||||||
|
stringOps := []string{"!>=", "!<=", "!>", "!<", "!="}
|
||||||
|
for _, op := range stringOps {
|
||||||
|
if idx := strings.Index(condition, op); idx > 0 {
|
||||||
|
varName := condition[:idx]
|
||||||
|
valueStr := condition[idx+len(op):]
|
||||||
|
return ctx.evaluateStringLength(varName, valueStr, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison operators: >=, <=, >, <, ==, !=
|
||||||
|
compOps := []string{">=", "<=", ">", "<", "==", "!="}
|
||||||
|
for _, op := range compOps {
|
||||||
|
if idx := strings.Index(condition, op); idx > 0 {
|
||||||
|
varName := condition[:idx]
|
||||||
|
valueStr := condition[idx+len(op):]
|
||||||
|
return ctx.evaluateComparison(varName, valueStr, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple variable existence
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
@ -63,7 +63,7 @@ func (l *Lexer) next() byte {
|
|||||||
// Checks if a tag should be treated as self-closing (using byte comparison)
|
// Checks if a tag should be treated as self-closing (using byte comparison)
|
||||||
func (l *Lexer) isSelfClosingTag(start, end int) bool {
|
func (l *Lexer) isSelfClosingTag(start, end int) bool {
|
||||||
length := end - start
|
length := end - start
|
||||||
if length < 2 || length > 5 {
|
if length < 2 || length > 6 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +103,10 @@ func (l *Lexer) isSelfClosingTag(start, end int) bool {
|
|||||||
(l.input[start] == 's' && l.input[start+1] == 't' &&
|
(l.input[start] == 's' && l.input[start+1] == 't' &&
|
||||||
l.input[start+2] == 'r' && l.input[start+3] == '3' &&
|
l.input[start+2] == 'r' && l.input[start+3] == '3' &&
|
||||||
l.input[start+4] == '2')
|
l.input[start+4] == '2')
|
||||||
|
case 6:
|
||||||
|
return (l.input[start] == 'd' && l.input[start+1] == 'o' &&
|
||||||
|
l.input[start+2] == 'u' && l.input[start+3] == 'b' &&
|
||||||
|
l.input[start+4] == 'l' && l.input[start+5] == 'e')
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -58,23 +58,24 @@ type Parser struct {
|
|||||||
|
|
||||||
// Type mapping for efficient lookup
|
// Type mapping for efficient lookup
|
||||||
var typeMap = map[string]common.EQ2DataType{
|
var typeMap = map[string]common.EQ2DataType{
|
||||||
"i8": common.TypeInt8,
|
"i8": common.TypeInt8,
|
||||||
"i16": common.TypeInt16,
|
"i16": common.TypeInt16,
|
||||||
"i32": common.TypeInt32,
|
"i32": common.TypeInt32,
|
||||||
"i64": common.TypeInt64,
|
"i64": common.TypeInt64,
|
||||||
"si8": common.TypeSInt8,
|
"si8": common.TypeSInt8,
|
||||||
"si16": common.TypeSInt16,
|
"si16": common.TypeSInt16,
|
||||||
"si32": common.TypeSInt32,
|
"si32": common.TypeSInt32,
|
||||||
"si64": common.TypeSInt64,
|
"si64": common.TypeSInt64,
|
||||||
"f32": common.TypeFloat,
|
"f32": common.TypeFloat,
|
||||||
"f64": common.TypeDouble,
|
"f64": common.TypeDouble,
|
||||||
"str8": common.TypeString8,
|
"double": common.TypeDouble, // XML compatibility
|
||||||
"str16": common.TypeString16,
|
"str8": common.TypeString8,
|
||||||
"str32": common.TypeString32,
|
"str16": common.TypeString16,
|
||||||
"char": common.TypeChar,
|
"str32": common.TypeString32,
|
||||||
"color": common.TypeColor,
|
"char": common.TypeChar,
|
||||||
"equip": common.TypeEquipment,
|
"color": common.TypeColor,
|
||||||
"array": common.TypeArray,
|
"equip": common.TypeEquipment,
|
||||||
|
"array": common.TypeArray,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new parser
|
// Creates a new parser
|
||||||
@ -183,6 +184,10 @@ func getDataType(tag string) (common.EQ2DataType, bool) {
|
|||||||
case "f64":
|
case "f64":
|
||||||
return common.TypeDouble, true
|
return common.TypeDouble, true
|
||||||
}
|
}
|
||||||
|
case 'd':
|
||||||
|
if tag == "double" {
|
||||||
|
return common.TypeDouble, true
|
||||||
|
}
|
||||||
case 'c':
|
case 'c':
|
||||||
switch tag {
|
switch tag {
|
||||||
case "char":
|
case "char":
|
||||||
@ -527,14 +532,32 @@ func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix s
|
|||||||
}
|
}
|
||||||
|
|
||||||
fieldDesc := FieldDesc{
|
fieldDesc := FieldDesc{
|
||||||
Type: common.TypeArray,
|
Type: common.TypeArray,
|
||||||
Condition: attrs["count"],
|
Condition: attrs["count"],
|
||||||
|
AddToStruct: true, // Default to true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ifCond := attrs["if"]; ifCond != "" {
|
if ifCond := attrs["if"]; ifCond != "" {
|
||||||
fieldDesc.Condition = combineConditions(fieldDesc.Condition, ifCond)
|
fieldDesc.Condition = combineConditions(fieldDesc.Condition, ifCond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse additional attributes
|
||||||
|
if maxSize := attrs["max_size"]; maxSize != "" {
|
||||||
|
if m, err := strconv.Atoi(maxSize); err == nil {
|
||||||
|
fieldDesc.MaxArraySize = m
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid max_size value '%s' at line %d: %v", maxSize, p.current.Line, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if optional := attrs["optional"]; optional == "true" {
|
||||||
|
fieldDesc.Optional = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if addToStruct := attrs["add_to_struct"]; addToStruct == "false" {
|
||||||
|
fieldDesc.AddToStruct = false
|
||||||
|
}
|
||||||
|
|
||||||
// Handle substruct reference
|
// Handle substruct reference
|
||||||
if substruct := attrs["substruct"]; substruct != "" {
|
if substruct := attrs["substruct"]; substruct != "" {
|
||||||
if subDef, exists := p.substructs[substruct]; exists {
|
if subDef, exists := p.substructs[substruct]; exists {
|
||||||
@ -640,13 +663,70 @@ func (p *Parser) parseField(packetDef *PacketDef, fieldOrder *[]string, prefix s
|
|||||||
}
|
}
|
||||||
|
|
||||||
fieldDesc := FieldDesc{
|
fieldDesc := FieldDesc{
|
||||||
Type: dataType,
|
Type: dataType,
|
||||||
Condition: attrs["if"],
|
Condition: attrs["if"],
|
||||||
|
AddToStruct: true, // Default to true
|
||||||
|
AddType: dataType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse size attribute
|
||||||
if size := attrs["size"]; size != "" {
|
if size := attrs["size"]; size != "" {
|
||||||
if s, err := strconv.Atoi(size); err == nil {
|
if s, err := strconv.Atoi(size); err == nil {
|
||||||
fieldDesc.Length = s
|
fieldDesc.Length = s
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid size value '%s' at line %d: %v", size, p.current.Line, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse oversized attribute
|
||||||
|
if oversized := attrs["oversized"]; oversized != "" {
|
||||||
|
if o, err := strconv.Atoi(oversized); err == nil {
|
||||||
|
fieldDesc.Oversized = o
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid oversized value '%s' at line %d: %v", oversized, p.current.Line, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse type2 attributes
|
||||||
|
if type2 := attrs["type2"]; type2 != "" {
|
||||||
|
if t2, exists := getDataType(type2); exists {
|
||||||
|
fieldDesc.Type2 = t2
|
||||||
|
fieldDesc.Type2Cond = attrs["type2_if"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse default value
|
||||||
|
if defaultVal := attrs["default"]; defaultVal != "" {
|
||||||
|
if d, err := strconv.ParseInt(defaultVal, 10, 8); err == nil {
|
||||||
|
fieldDesc.DefaultValue = int8(d)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid default value '%s' at line %d: %v", defaultVal, p.current.Line, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse max_size
|
||||||
|
if maxSize := attrs["max_size"]; maxSize != "" {
|
||||||
|
if m, err := strconv.Atoi(maxSize); err == nil {
|
||||||
|
fieldDesc.MaxArraySize = m
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid max_size value '%s' at line %d: %v", maxSize, p.current.Line, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse optional
|
||||||
|
if optional := attrs["optional"]; optional == "true" {
|
||||||
|
fieldDesc.Optional = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse add_to_struct
|
||||||
|
if addToStruct := attrs["add_to_struct"]; addToStruct == "false" {
|
||||||
|
fieldDesc.AddToStruct = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse add_type
|
||||||
|
if addType := attrs["add_type"]; addType != "" {
|
||||||
|
if at, exists := getDataType(addType); exists {
|
||||||
|
fieldDesc.AddType = at
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,4 +743,4 @@ func Parse(content string) (map[string]*PacketDef, error) {
|
|||||||
parser := NewParser(content)
|
parser := NewParser(content)
|
||||||
defer parser.cleanup()
|
defer parser.cleanup()
|
||||||
return parser.Parse()
|
return parser.Parse()
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,187 @@ func TestBasicParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFloat64Support(t *testing.T) {
|
||||||
|
pml := `<packet name="FloatTest">
|
||||||
|
<version number="1">
|
||||||
|
<f32 name="position_x">
|
||||||
|
<f64 name="precise_value">
|
||||||
|
<double name="legacy_double">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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 := `<packet name="OversizedTest">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="small_count">
|
||||||
|
<i16 name="num_words" oversized="255">
|
||||||
|
<i32 name="large_value" oversized="65535">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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 := `<packet name="Type2Test">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="stat_type">
|
||||||
|
<i32 name="stat_value" type2="f32" type2_if="stat_type!=6">
|
||||||
|
<i16 name="another_field" type2="i32">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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.TypeInt32 {
|
||||||
|
t.Error("stat_value primary type should be TypeInt32")
|
||||||
|
}
|
||||||
|
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.TypeInt32 {
|
||||||
|
t.Error("another_field type2 should be TypeInt32")
|
||||||
|
}
|
||||||
|
if anotherField.Type2Cond != "" {
|
||||||
|
t.Error("another_field should have empty type2_if")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdvancedFieldAttributes(t *testing.T) {
|
||||||
|
pml := `<packet name="AttributeTest">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="data_array" size="10" default="5">
|
||||||
|
<str16 name="optional_text" if="var:has_text" optional="true">
|
||||||
|
<i32 name="hidden_field" add_to_struct="false" add_type="i16">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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.TypeInt16 {
|
||||||
|
t.Error("hidden_field add_type should be TypeInt16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayMaxSize(t *testing.T) {
|
||||||
|
pml := `<packet name="ArrayMaxTest">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="item_count">
|
||||||
|
<array name="items" count="var:item_count" max_size="100">
|
||||||
|
<substruct>
|
||||||
|
<i32 name="item_id">
|
||||||
|
</substruct>
|
||||||
|
</array>
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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 := `<packet name="ArrayOptionalTest">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="count">
|
||||||
|
<array name="optional_items" count="var:count" optional="true" add_to_struct="false">
|
||||||
|
<substruct>
|
||||||
|
<i32 name="id">
|
||||||
|
</substruct>
|
||||||
|
</array>
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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) {
|
func TestMultipleVersions(t *testing.T) {
|
||||||
pml := `<packet name="MultiVersion">
|
pml := `<packet name="MultiVersion">
|
||||||
<version number="1">
|
<version number="1">
|
||||||
@ -265,6 +446,127 @@ func TestComments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBinaryParsingFloat64(t *testing.T) {
|
||||||
|
// Test binary parsing with float64
|
||||||
|
pml := `<packet name="BinaryFloat64">
|
||||||
|
<version number="1">
|
||||||
|
<f64 name="precise_value">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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 := `<packet name="BinaryOversized">
|
||||||
|
<version number="1">
|
||||||
|
<i16 name="normal_value">
|
||||||
|
<i16 name="oversized_value" oversized="255">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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"].(uint16); val != 100 {
|
||||||
|
t.Errorf("Expected normal_value 100, got %d", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val := result["oversized_value"].(uint16); val != 1000 {
|
||||||
|
t.Errorf("Expected oversized_value 1000, got %d", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinaryParsingType2(t *testing.T) {
|
||||||
|
// Test type2 switching in binary parsing
|
||||||
|
pml := `<packet name="BinaryType2">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="stat_type">
|
||||||
|
<i32 name="stat_value" type2="f32" type2_if="stat_type==6">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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"].(uint8); 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 := `<packet name="BinaryArrayMax">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="item_count">
|
||||||
|
<array name="items" count="var:item_count" max_size="2">
|
||||||
|
<substruct>
|
||||||
|
<i16 name="item_id">
|
||||||
|
</substruct>
|
||||||
|
</array>
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
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 TestErrorHandling(t *testing.T) {
|
func TestErrorHandling(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@ -273,6 +575,7 @@ func TestErrorHandling(t *testing.T) {
|
|||||||
{"Unclosed tag", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\">"},
|
{"Unclosed tag", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\">"},
|
||||||
{"Invalid XML", "<packet><version><i32></packet>"},
|
{"Invalid XML", "<packet><version><i32></packet>"},
|
||||||
{"Missing quotes", "<packet name=Test><version number=1></version></packet>"},
|
{"Missing quotes", "<packet name=Test><version number=1></version></packet>"},
|
||||||
|
{"Invalid oversized", "<packet name=\"Test\"><version number=\"1\"><i32 name=\"id\" oversized=\"abc\"></version></packet>"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@ -295,8 +598,7 @@ func BenchmarkSimplePacket(b *testing.B) {
|
|||||||
</version>
|
</version>
|
||||||
</packet>`
|
</packet>`
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := Parse(pml)
|
_, err := Parse(pml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
@ -304,7 +606,7 @@ func BenchmarkSimplePacket(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkComplexPacket(b *testing.B) {
|
func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
|
||||||
pml := `<packet name="Complex">
|
pml := `<packet name="Complex">
|
||||||
<version number="562">
|
<version number="562">
|
||||||
<i32 name="player_id,account_id">
|
<i32 name="player_id,account_id">
|
||||||
@ -313,27 +615,27 @@ func BenchmarkComplexPacket(b *testing.B) {
|
|||||||
<str16 name="guild_name" if="flag:has_guild">
|
<str16 name="guild_name" if="flag:has_guild">
|
||||||
<i32 name="guild_id" if="flag:has_guild">
|
<i32 name="guild_id" if="flag:has_guild">
|
||||||
<i8 name="equipment_count">
|
<i8 name="equipment_count">
|
||||||
<array name="equipment" count="var:equipment_count">
|
<array name="equipment" count="var:equipment_count" max_size="50">
|
||||||
<substruct>
|
<substruct>
|
||||||
<i16 name="slot_id,item_type">
|
<i16 name="slot_id,item_type">
|
||||||
<color name="primary_color,secondary_color">
|
<color name="primary_color,secondary_color">
|
||||||
<i8 name="enhancement_level" if="item_type!=0">
|
<i8 name="enhancement_level" if="item_type!=0">
|
||||||
|
<i32 name="stat_value" type2="f32" type2_if="item_type==6" oversized="255">
|
||||||
</substruct>
|
</substruct>
|
||||||
</array>
|
</array>
|
||||||
<i8 name="stat_count">
|
<i8 name="stat_count">
|
||||||
<array name="stats" count="var:stat_count">
|
<array name="stats" count="var:stat_count" optional="true">
|
||||||
<substruct>
|
<substruct>
|
||||||
<i8 name="stat_type">
|
<i8 name="stat_type">
|
||||||
<i32 name="base_value">
|
<i32 name="base_value">
|
||||||
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
|
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
|
||||||
<f32 name="percentage" if="stat_type==6">
|
<f64 name="percentage" if="stat_type==6">
|
||||||
</substruct>
|
</substruct>
|
||||||
</array>
|
</array>
|
||||||
</version>
|
</version>
|
||||||
</packet>`
|
</packet>`
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := Parse(pml)
|
_, err := Parse(pml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
@ -341,47 +643,6 @@ func BenchmarkComplexPacket(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkLargePacket(b *testing.B) {
|
|
||||||
// Generate a large packet definition
|
|
||||||
pmlBuilder := `<packet name="Large"><version number="1">`
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
pmlBuilder += `<i32 name="field` + string(rune('A'+i%26)) + `">`
|
|
||||||
}
|
|
||||||
pmlBuilder += `</version></packet>`
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := Parse(pmlBuilder)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWithSubstructs(b *testing.B) {
|
|
||||||
pml := `<substruct name="Item">
|
|
||||||
<i32 name="id">
|
|
||||||
<str16 name="name">
|
|
||||||
<i16 name="quantity">
|
|
||||||
</substruct>
|
|
||||||
|
|
||||||
<packet name="WithSubstruct">
|
|
||||||
<version number="1">
|
|
||||||
<i8 name="count">
|
|
||||||
<array name="items" count="var:count" substruct="Item">
|
|
||||||
</version>
|
|
||||||
</packet>`
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
parser := NewParser(pml)
|
|
||||||
_, err := parser.Parse()
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to compare slices
|
// Helper function to compare slices
|
||||||
func equalSlices(a, b []string) bool {
|
func equalSlices(a, b []string) bool {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
|
@ -8,13 +8,109 @@ type PacketDef struct {
|
|||||||
Orders map[uint32][]string // Field order by version number
|
Orders map[uint32][]string // Field order by version number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.setVar(fieldName, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (def *PacketDef) parseField(ctx *ParseContext, field FieldDesc, fieldType common.EQ2DataType, fieldName string) any {
|
||||||
|
switch fieldType {
|
||||||
|
case common.TypeInt8, common.TypeSInt8:
|
||||||
|
if field.Oversized > 0 {
|
||||||
|
return ctx.readOversizedUint8(field.Oversized)
|
||||||
|
}
|
||||||
|
return ctx.readUint8()
|
||||||
|
case common.TypeInt16, common.TypeSInt16:
|
||||||
|
if field.Oversized > 0 {
|
||||||
|
return ctx.readOversizedUint16(field.Oversized)
|
||||||
|
}
|
||||||
|
return ctx.readUint16()
|
||||||
|
case common.TypeInt32, common.TypeSInt32:
|
||||||
|
if field.Oversized > 0 {
|
||||||
|
return ctx.readOversizedUint32(field.Oversized)
|
||||||
|
}
|
||||||
|
return ctx.readUint32()
|
||||||
|
case common.TypeInt64, common.TypeSInt64:
|
||||||
|
return ctx.readUint64()
|
||||||
|
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
|
// FieldDesc describes a single packet field
|
||||||
type FieldDesc struct {
|
type FieldDesc struct {
|
||||||
Type common.EQ2DataType // Primary data type
|
Type common.EQ2DataType // Primary data type
|
||||||
Condition string // Conditional parsing expression
|
Condition string // Conditional parsing expression
|
||||||
Length int // Array length or size for fixed-size fields
|
Length int // Array length or size for fixed-size fields
|
||||||
SubDef *PacketDef // Nested packet definition for arrays
|
SubDef *PacketDef // Nested packet definition for arrays
|
||||||
Type2 common.EQ2DataType // Alternative data type for conditional parsing
|
Type2 common.EQ2DataType // Alternative data type for conditional parsing
|
||||||
Type2Cond string // Condition for using Type2
|
Type2Cond string // Condition for using Type2
|
||||||
Oversized int // Threshold for oversized field handling
|
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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user