455 lines
12 KiB
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)
|
|
}
|