eq2go/internal/packets/builder.go

455 lines
12 KiB
Go

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)
}