244 lines
5.5 KiB
Go
244 lines
5.5 KiB
Go
package parser
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"unsafe"
|
|
)
|
|
|
|
// Parser handles reading EQ2 packet data into structs
|
|
type Parser struct {
|
|
data []byte
|
|
offset int
|
|
endian binary.ByteOrder
|
|
fieldCache map[string]any
|
|
currentStruct reflect.Value
|
|
flags map[string]bool
|
|
structStack []reflect.Value
|
|
arrayStack []ArrayContext
|
|
}
|
|
|
|
// ArrayContext tracks array parsing state
|
|
type ArrayContext struct {
|
|
elementType reflect.Type
|
|
currentIndex int
|
|
totalSize int
|
|
sizeVariable string
|
|
}
|
|
|
|
// NewParser creates a new EQ2 packet parser
|
|
func NewParser(data []byte) *Parser {
|
|
return &Parser{
|
|
data: data,
|
|
offset: 0,
|
|
endian: binary.LittleEndian,
|
|
fieldCache: make(map[string]any),
|
|
flags: make(map[string]bool),
|
|
structStack: make([]reflect.Value, 0),
|
|
arrayStack: make([]ArrayContext, 0),
|
|
}
|
|
}
|
|
|
|
// SetFlag sets a flag for conditional parsing
|
|
func (p *Parser) SetFlag(name string, value bool) {
|
|
p.flags[name] = value
|
|
}
|
|
|
|
// ParseStruct reads data into a struct using reflection and tags
|
|
func (p *Parser) ParseStruct(v any) error {
|
|
val := reflect.ValueOf(v)
|
|
if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
|
|
return fmt.Errorf("target must be pointer to struct")
|
|
}
|
|
|
|
elem := val.Elem()
|
|
typ := elem.Type()
|
|
p.currentStruct = elem
|
|
p.fieldCache = make(map[string]any)
|
|
|
|
for i := 0; i < elem.NumField(); i++ {
|
|
field := elem.Field(i)
|
|
fieldType := typ.Field(i)
|
|
|
|
if !field.CanSet() {
|
|
continue
|
|
}
|
|
|
|
tag := fieldType.Tag.Get("eq2")
|
|
if tag == "" || tag == "-" {
|
|
continue
|
|
}
|
|
|
|
if err := p.parseField(field, tag); err != nil {
|
|
return fmt.Errorf("field %s: %w", fieldType.Name, err)
|
|
}
|
|
|
|
if field.CanInterface() {
|
|
p.fieldCache[fieldType.Name] = field.Interface()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseField processes individual struct fields based on their tags
|
|
func (p *Parser) parseField(field reflect.Value, tag string) error {
|
|
fieldTag := p.parseFieldTag(tag)
|
|
|
|
if !p.evaluateAllConditions(fieldTag, field) {
|
|
return nil
|
|
}
|
|
|
|
length := fieldTag.Length
|
|
if fieldTag.DynamicLen != "" {
|
|
if dynLen := p.getDynamicLength(fieldTag.DynamicLen); dynLen > 0 {
|
|
length = dynLen
|
|
if fieldTag.MaxSize > 0 && length > fieldTag.MaxSize {
|
|
if fieldTag.SkipOversized {
|
|
length = fieldTag.MaxSize
|
|
} else {
|
|
return fmt.Errorf("length %d exceeds maximum %d", length, fieldTag.MaxSize)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if fieldTag.OversizedValue > 0 && length > fieldTag.OversizedValue {
|
|
if fieldTag.SkipOversized {
|
|
length = fieldTag.OversizedValue
|
|
} else {
|
|
return p.handleOversizedField(field, fieldTag.OversizedByte, length)
|
|
}
|
|
}
|
|
|
|
actualType := fieldTag.Type
|
|
if fieldTag.Type2 != "" && fieldTag.Type2Criteria != "" {
|
|
if p.evaluateType2Criteria(fieldTag.Type2Criteria) {
|
|
actualType = fieldTag.Type2
|
|
}
|
|
}
|
|
|
|
return p.parseFieldByType(field, actualType, length, fieldTag)
|
|
}
|
|
|
|
// parseFieldByType handles type-specific parsing
|
|
func (p *Parser) parseFieldByType(field reflect.Value, fieldType string, length int, fieldTag *FieldTag) error {
|
|
switch fieldType {
|
|
case "int8":
|
|
return p.readInt8(field, length)
|
|
case "int16":
|
|
return p.readInt16(field, length)
|
|
case "int32":
|
|
return p.readInt32(field, length)
|
|
case "int64":
|
|
return p.readInt64(field, length)
|
|
case "sint8":
|
|
return p.readSInt8(field, length)
|
|
case "sint16":
|
|
return p.readSInt16(field, length)
|
|
case "sint32":
|
|
return p.readSInt32(field, length)
|
|
case "sint64":
|
|
return p.readSInt64(field, length)
|
|
case "char":
|
|
return p.readChar(field, length)
|
|
case "float":
|
|
return p.readFloat(field, length)
|
|
case "double":
|
|
return p.readDouble(field, length)
|
|
case "string8", "EQ2_8BitString", "EQ2_8Bit_String":
|
|
return p.readString8(field)
|
|
case "string16", "EQ2_16BitString", "EQ2_16Bit_String":
|
|
return p.readString16(field)
|
|
case "string32", "EQ2_32BitString", "EQ2_32Bit_String":
|
|
return p.readString32(field)
|
|
case "color", "EQ2_Color":
|
|
return p.readColor(field, length)
|
|
case "equipment", "EQ2_EquipmentItem":
|
|
return p.readEquipment(field, length)
|
|
case "array":
|
|
return p.readArray(field, fieldTag)
|
|
case "substruct":
|
|
return p.readSubstruct(field, length)
|
|
default:
|
|
return fmt.Errorf("unknown type: %s", fieldType)
|
|
}
|
|
}
|
|
|
|
// Low-level read methods
|
|
func (p *Parser) readUint8() (uint8, error) {
|
|
if p.offset >= len(p.data) {
|
|
return 0, io.EOF
|
|
}
|
|
val := p.data[p.offset]
|
|
p.offset++
|
|
return val, nil
|
|
}
|
|
|
|
func (p *Parser) readUint16() (uint16, error) {
|
|
if p.offset+2 > len(p.data) {
|
|
return 0, io.EOF
|
|
}
|
|
val := p.endian.Uint16(p.data[p.offset:])
|
|
p.offset += 2
|
|
return val, nil
|
|
}
|
|
|
|
func (p *Parser) readUint32() (uint32, error) {
|
|
if p.offset+4 > len(p.data) {
|
|
return 0, io.EOF
|
|
}
|
|
val := p.endian.Uint32(p.data[p.offset:])
|
|
p.offset += 4
|
|
return val, nil
|
|
}
|
|
|
|
func (p *Parser) readUint64() (uint64, error) {
|
|
if p.offset+8 > len(p.data) {
|
|
return 0, io.EOF
|
|
}
|
|
val := p.endian.Uint64(p.data[p.offset:])
|
|
p.offset += 8
|
|
return val, nil
|
|
}
|
|
|
|
func (p *Parser) readFloat32() (float32, error) {
|
|
val, err := p.readUint32()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return *(*float32)(unsafe.Pointer(&val)), nil
|
|
}
|
|
|
|
func (p *Parser) readFloat64() (float64, error) {
|
|
val, err := p.readUint64()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return *(*float64)(unsafe.Pointer(&val)), nil
|
|
}
|
|
|
|
func (p *Parser) readBytes(buf []byte) error {
|
|
if p.offset+len(buf) > len(p.data) {
|
|
return io.EOF
|
|
}
|
|
copy(buf, p.data[p.offset:])
|
|
p.offset += len(buf)
|
|
return nil
|
|
}
|
|
|
|
// Position tracking
|
|
func (p *Parser) Offset() int {
|
|
return p.offset
|
|
}
|
|
|
|
func (p *Parser) SetOffset(offset int) {
|
|
p.offset = offset
|
|
}
|
|
|
|
func (p *Parser) Remaining() int {
|
|
return len(p.data) - p.offset
|
|
}
|