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
}