package packets import ( "bytes" "encoding/binary" "eq2emu/internal/common" "eq2emu/internal/packets/parser" "fmt" "math" ) // PacketBuilder constructs packets from data using parsed packet definitions type PacketBuilder struct { def *parser.PacketDef version uint32 flags uint64 buf *bytes.Buffer } // NewPacketBuilder creates a new packet builder for the given packet definition func NewPacketBuilder(def *parser.PacketDef, version uint32, flags uint64) *PacketBuilder { return &PacketBuilder{ def: def, version: version, flags: flags, buf: new(bytes.Buffer), } } // Build constructs a packet from the provided data map func (b *PacketBuilder) Build(data map[string]any) ([]byte, error) { b.buf.Reset() err := b.buildStruct(data, b.def) if err != nil { return nil, err } return b.buf.Bytes(), nil } // buildStruct builds a struct according to the packet definition field order func (b *PacketBuilder) buildStruct(data map[string]any, def *parser.PacketDef) error { order := b.getVersionOrder(def) for _, fieldName := range order { field, exists := def.Fields[fieldName] if !exists { continue } // Skip fields based on conditions if !b.checkCondition(field.Condition, data) { continue } fieldType := field.Type if field.Type2 != 0 && b.checkCondition(field.Type2Cond, data) { fieldType = field.Type2 } value, hasValue := data[fieldName] if !hasValue && !field.Optional { return fmt.Errorf("required field '%s' not found in data", fieldName) } if hasValue { err := b.buildField(value, field, fieldType, fieldName) if err != nil { return fmt.Errorf("error building field '%s': %w", fieldName, err) } } else if field.Optional { // Write default value for optional fields err := b.writeDefaultValue(field, fieldType) if err != nil { return fmt.Errorf("error writing default value for field '%s': %w", fieldName, err) } } } return nil } // buildField writes a single field to the packet buffer func (b *PacketBuilder) buildField(value any, field parser.FieldDesc, fieldType common.EQ2DataType, fieldName string) error { switch fieldType { case common.TypeInt8: v, ok := value.(uint8) if !ok { return fmt.Errorf("field '%s' expected uint8, got %T", fieldName, value) } if field.Oversized > 0 { return b.writeOversizedUint8(v, field.Oversized) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeInt16: v, ok := value.(uint16) if !ok { return fmt.Errorf("field '%s' expected uint16, got %T", fieldName, value) } if field.Oversized > 0 { return b.writeOversizedUint16(v, field.Oversized) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeInt32: v, ok := value.(uint32) if !ok { return fmt.Errorf("field '%s' expected uint32, got %T", fieldName, value) } if field.Oversized > 0 { return b.writeOversizedUint32(v, field.Oversized) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeInt64: v, ok := value.(uint64) if !ok { return fmt.Errorf("field '%s' expected uint64, got %T", fieldName, value) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeSInt8: v, ok := value.(int8) if !ok { return fmt.Errorf("field '%s' expected int8, got %T", fieldName, value) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeSInt16: v, ok := value.(int16) if !ok { return fmt.Errorf("field '%s' expected int16, got %T", fieldName, value) } if field.Oversized > 0 { return b.writeOversizedSint16(v, field.Oversized) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeSInt32: v, ok := value.(int32) if !ok { return fmt.Errorf("field '%s' expected int32, got %T", fieldName, value) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeSInt64: v, ok := value.(int64) if !ok { return fmt.Errorf("field '%s' expected int64, got %T", fieldName, value) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeString8: v, ok := value.(string) if !ok { return fmt.Errorf("field '%s' expected string, got %T", fieldName, value) } return b.writeEQ2String8(v) case common.TypeString16: v, ok := value.(string) if !ok { return fmt.Errorf("field '%s' expected string, got %T", fieldName, value) } return b.writeEQ2String16(v) case common.TypeString32: v, ok := value.(string) if !ok { return fmt.Errorf("field '%s' expected string, got %T", fieldName, value) } return b.writeEQ2String32(v) case common.TypeChar: v, ok := value.([]byte) if !ok { return fmt.Errorf("field '%s' expected []byte, got %T", fieldName, value) } return b.writeBytes(v, field.Length) case common.TypeFloat: v, ok := value.(float32) if !ok { return fmt.Errorf("field '%s' expected float32, got %T", fieldName, value) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeDouble: v, ok := value.(float64) if !ok { return fmt.Errorf("field '%s' expected float64, got %T", fieldName, value) } return binary.Write(b.buf, binary.LittleEndian, v) case common.TypeColor: v, ok := value.(common.EQ2Color) if !ok { return fmt.Errorf("field '%s' expected EQ2Color, got %T", fieldName, value) } return b.writeEQ2Color(v) case common.TypeEquipment: v, ok := value.(common.EQ2EquipmentItem) if !ok { return fmt.Errorf("field '%s' expected EQ2EquipmentItem, got %T", fieldName, value) } return b.writeEQ2Equipment(v) case common.TypeArray: v, ok := value.([]map[string]any) if !ok { return fmt.Errorf("field '%s' expected []map[string]any, got %T", fieldName, value) } return b.buildArray(v, field) default: return fmt.Errorf("unsupported field type %d for field '%s'", fieldType, fieldName) } } // buildArray builds an array field func (b *PacketBuilder) buildArray(items []map[string]any, field parser.FieldDesc) error { if field.SubDef == nil { return fmt.Errorf("array field missing sub-definition") } size := len(items) if field.MaxArraySize > 0 && size > field.MaxArraySize { size = field.MaxArraySize } for i := 0; i < size; i++ { err := b.buildStruct(items[i], field.SubDef) if err != nil { return fmt.Errorf("error building array item %d: %w", i, err) } } return nil } // Helper methods for writing specific data types func (b *PacketBuilder) writeEQ2String8(s string) error { data := []byte(s) size := len(data) if size > math.MaxUint8 { size = math.MaxUint8 } err := binary.Write(b.buf, binary.LittleEndian, uint8(size)) if err != nil { return err } _, err = b.buf.Write(data[:size]) return err } func (b *PacketBuilder) writeEQ2String16(s string) error { data := []byte(s) size := len(data) if size > math.MaxUint16 { size = math.MaxUint16 } err := binary.Write(b.buf, binary.LittleEndian, uint16(size)) if err != nil { return err } _, err = b.buf.Write(data[:size]) return err } func (b *PacketBuilder) writeEQ2String32(s string) error { data := []byte(s) size := len(data) if size > math.MaxUint32 { size = math.MaxUint32 } err := binary.Write(b.buf, binary.LittleEndian, uint32(size)) if err != nil { return err } _, err = b.buf.Write(data[:size]) return err } func (b *PacketBuilder) writeBytes(data []byte, length int) error { if length > 0 && len(data) > length { data = data[:length] } _, err := b.buf.Write(data) return err } func (b *PacketBuilder) writeEQ2Color(color common.EQ2Color) error { err := binary.Write(b.buf, binary.LittleEndian, color.Red) if err != nil { return err } err = binary.Write(b.buf, binary.LittleEndian, color.Green) if err != nil { return err } return binary.Write(b.buf, binary.LittleEndian, color.Blue) } func (b *PacketBuilder) writeEQ2Equipment(item common.EQ2EquipmentItem) error { err := binary.Write(b.buf, binary.LittleEndian, item.Type) if err != nil { return err } err = b.writeEQ2Color(item.Color) if err != nil { return err } return b.writeEQ2Color(item.Highlight) } func (b *PacketBuilder) writeOversizedUint8(value uint8, threshold int) error { if int(value) >= threshold { err := binary.Write(b.buf, binary.LittleEndian, uint8(255)) if err != nil { return err } return binary.Write(b.buf, binary.LittleEndian, uint16(value)) } return binary.Write(b.buf, binary.LittleEndian, value) } func (b *PacketBuilder) writeOversizedUint16(value uint16, threshold int) error { if int(value) >= threshold { err := binary.Write(b.buf, binary.LittleEndian, uint16(65535)) if err != nil { return err } return binary.Write(b.buf, binary.LittleEndian, uint32(value)) } return binary.Write(b.buf, binary.LittleEndian, value) } func (b *PacketBuilder) writeOversizedUint32(value uint32, threshold int) error { if int64(value) >= int64(threshold) { err := binary.Write(b.buf, binary.LittleEndian, uint32(4294967295)) if err != nil { return err } return binary.Write(b.buf, binary.LittleEndian, uint64(value)) } return binary.Write(b.buf, binary.LittleEndian, value) } func (b *PacketBuilder) writeOversizedSint16(value int16, threshold int) error { if int(value) >= threshold { err := binary.Write(b.buf, binary.LittleEndian, int16(-1)) if err != nil { return err } return binary.Write(b.buf, binary.LittleEndian, int32(value)) } return binary.Write(b.buf, binary.LittleEndian, value) } func (b *PacketBuilder) writeDefaultValue(field parser.FieldDesc, fieldType common.EQ2DataType) error { switch fieldType { case common.TypeInt8: return binary.Write(b.buf, binary.LittleEndian, uint8(field.DefaultValue)) case common.TypeInt16: return binary.Write(b.buf, binary.LittleEndian, uint16(field.DefaultValue)) case common.TypeInt32: return binary.Write(b.buf, binary.LittleEndian, uint32(field.DefaultValue)) case common.TypeInt64: return binary.Write(b.buf, binary.LittleEndian, uint64(field.DefaultValue)) case common.TypeSInt8: return binary.Write(b.buf, binary.LittleEndian, field.DefaultValue) case common.TypeSInt16: return binary.Write(b.buf, binary.LittleEndian, int16(field.DefaultValue)) case common.TypeSInt32: return binary.Write(b.buf, binary.LittleEndian, int32(field.DefaultValue)) case common.TypeSInt64: return binary.Write(b.buf, binary.LittleEndian, int64(field.DefaultValue)) case common.TypeString8: return b.writeEQ2String8("") case common.TypeString16: return b.writeEQ2String16("") case common.TypeString32: return b.writeEQ2String32("") case common.TypeFloat: return binary.Write(b.buf, binary.LittleEndian, float32(field.DefaultValue)) case common.TypeDouble: return binary.Write(b.buf, binary.LittleEndian, float64(field.DefaultValue)) case common.TypeColor: color := common.EQ2Color{Red: field.DefaultValue, Green: field.DefaultValue, Blue: field.DefaultValue} return b.writeEQ2Color(color) case common.TypeChar: if field.Length > 0 { defaultBytes := make([]byte, field.Length) return b.writeBytes(defaultBytes, field.Length) } } return nil } func (b *PacketBuilder) getVersionOrder(def *parser.PacketDef) []string { var bestVersion uint32 for v := range def.Orders { if v <= b.version && v > bestVersion { bestVersion = v } } return def.Orders[bestVersion] } func (b *PacketBuilder) checkCondition(condition string, data map[string]any) bool { if condition == "" { return true } // Simple condition evaluation - this would need to be expanded // for complex expressions used in the packet definitions if value, exists := data[condition]; exists { switch v := value.(type) { case bool: return v case int, int8, int16, int32, int64: return v != 0 case uint, uint8, uint16, uint32, uint64: return v != 0 case string: return v != "" default: return true } } return false } // BuildPacket is a convenience function that creates a builder and builds a packet in one call func BuildPacket(packetName string, data map[string]any, version uint32, flags uint64) ([]byte, error) { def, exists := GetPacket(packetName) if !exists { return nil, fmt.Errorf("packet definition '%s' not found", packetName) } builder := NewPacketBuilder(def, version, flags) return builder.Build(data) }