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 }