eq2go/internal/cps/serializer.go

286 lines
6.5 KiB
Go

package cps
import (
"encoding/binary"
"fmt"
"io"
"math"
)
// Serializer handles packet serialization/deserialization
type Serializer struct {
manager *Manager
}
// NewSerializer creates a packet serializer
func NewSerializer(manager *Manager) *Serializer {
return &Serializer{manager: manager}
}
// Serialize converts packet data to bytes
func (s *Serializer) Serialize(packetName string, version int, data map[string]any) ([]byte, error) {
packet, err := s.manager.GetPacket(packetName, version)
if err != nil {
return nil, err
}
var buf []byte
for _, field := range packet.Fields {
value, exists := data[field.Name]
if !exists {
value = s.getZeroValue(field)
}
fieldBytes, err := s.serializeField(field, value)
if err != nil {
return nil, fmt.Errorf("serializing field %s: %w", field.Name, err)
}
buf = append(buf, fieldBytes...)
}
return buf, nil
}
// Deserialize parses bytes into packet data
func (s *Serializer) Deserialize(packetName string, version int, data []byte) (map[string]any, error) {
packet, err := s.manager.GetPacket(packetName, version)
if err != nil {
return nil, err
}
result := make(map[string]any)
offset := 0
for _, field := range packet.Fields {
value, consumed, err := s.deserializeField(field, data[offset:])
if err != nil {
return nil, fmt.Errorf("deserializing field %s: %w", field.Name, err)
}
result[field.Name] = value
offset += consumed
}
return result, nil
}
// serializeField serializes a single field value
func (s *Serializer) serializeField(field *CompiledField, value any) ([]byte, error) {
switch field.Type {
case TypeInt8:
v, ok := value.(int8)
if !ok {
return nil, fmt.Errorf("expected int8, got %T", value)
}
return []byte{byte(v)}, nil
case TypeInt16:
v, ok := value.(int16)
if !ok {
return nil, fmt.Errorf("expected int16, got %T", value)
}
buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(v))
return buf, nil
case TypeInt32:
v, ok := value.(int32)
if !ok {
return nil, fmt.Errorf("expected int32, got %T", value)
}
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(v))
return buf, nil
case TypeInt64:
v, ok := value.(int64)
if !ok {
return nil, fmt.Errorf("expected int64, got %T", value)
}
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(v))
return buf, nil
case TypeString8:
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T", value)
}
buf := make([]byte, 1+len(v))
buf[0] = byte(len(v))
copy(buf[1:], v)
return buf, nil
case TypeString16:
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T", value)
}
buf := make([]byte, 2+len(v))
binary.LittleEndian.PutUint16(buf[0:2], uint16(len(v)))
copy(buf[2:], v)
return buf, nil
case TypeString32:
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T", value)
}
buf := make([]byte, 4+len(v))
binary.LittleEndian.PutUint32(buf[0:4], uint32(len(v)))
copy(buf[4:], v)
return buf, nil
case TypeColor:
v, ok := value.(uint32)
if !ok {
return nil, fmt.Errorf("expected uint32, got %T", value)
}
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, v)
return buf, nil
case TypeFloat32:
if field.Size > 0 {
// Array of floats
v, ok := value.([]float32)
if !ok {
return nil, fmt.Errorf("expected []float32, got %T", value)
}
buf := make([]byte, len(v)*4)
for i, f := range v {
binary.LittleEndian.PutUint32(buf[i*4:], math.Float32bits(f))
}
return buf, nil
} else {
// Single float
v, ok := value.(float32)
if !ok {
return nil, fmt.Errorf("expected float32, got %T", value)
}
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, math.Float32bits(v))
return buf, nil
}
default:
return nil, fmt.Errorf("unsupported field type: %v", field.Type)
}
}
// deserializeField deserializes a single field value
func (s *Serializer) deserializeField(field *CompiledField, data []byte) (any, int, error) {
switch field.Type {
case TypeInt8:
if len(data) < 1 {
return nil, 0, io.ErrUnexpectedEOF
}
return int8(data[0]), 1, nil
case TypeInt16:
if len(data) < 2 {
return nil, 0, io.ErrUnexpectedEOF
}
return int16(binary.LittleEndian.Uint16(data[0:2])), 2, nil
case TypeInt32:
if len(data) < 4 {
return nil, 0, io.ErrUnexpectedEOF
}
return int32(binary.LittleEndian.Uint32(data[0:4])), 4, nil
case TypeInt64:
if len(data) < 8 {
return nil, 0, io.ErrUnexpectedEOF
}
return int64(binary.LittleEndian.Uint64(data[0:8])), 8, nil
case TypeString8:
if len(data) < 1 {
return nil, 0, io.ErrUnexpectedEOF
}
strlen := int(data[0])
if len(data) < 1+strlen {
return nil, 0, io.ErrUnexpectedEOF
}
return string(data[1 : 1+strlen]), 1 + strlen, nil
case TypeString16:
if len(data) < 2 {
return nil, 0, io.ErrUnexpectedEOF
}
strlen := int(binary.LittleEndian.Uint16(data[0:2]))
if len(data) < 2+strlen {
return nil, 0, io.ErrUnexpectedEOF
}
return string(data[2 : 2+strlen]), 2 + strlen, nil
case TypeString32:
if len(data) < 4 {
return nil, 0, io.ErrUnexpectedEOF
}
strlen := int(binary.LittleEndian.Uint32(data[0:4]))
if len(data) < 4+strlen {
return nil, 0, io.ErrUnexpectedEOF
}
return string(data[4 : 4+strlen]), 4 + strlen, nil
case TypeColor:
if len(data) < 4 {
return nil, 0, io.ErrUnexpectedEOF
}
return binary.LittleEndian.Uint32(data[0:4]), 4, nil
case TypeFloat32:
if field.Size > 0 {
// Array of floats
if len(data) < field.Size*4 {
return nil, 0, io.ErrUnexpectedEOF
}
floats := make([]float32, field.Size)
for i := 0; i < field.Size; i++ {
bits := binary.LittleEndian.Uint32(data[i*4:])
floats[i] = math.Float32frombits(bits)
}
return floats, field.Size * 4, nil
} else {
// Single float
if len(data) < 4 {
return nil, 0, io.ErrUnexpectedEOF
}
bits := binary.LittleEndian.Uint32(data[0:4])
return math.Float32frombits(bits), 4, nil
}
default:
return nil, 0, fmt.Errorf("unsupported field type: %v", field.Type)
}
}
// getZeroValue returns appropriate zero value for field type
func (s *Serializer) getZeroValue(field *CompiledField) any {
switch field.Type {
case TypeInt8:
return int8(0)
case TypeInt16:
return int16(0)
case TypeInt32:
return int32(0)
case TypeInt64:
return int64(0)
case TypeString8, TypeString16, TypeString32:
return ""
case TypeColor:
return uint32(0)
case TypeFloat32:
if field.Size > 0 {
return make([]float32, field.Size)
}
return float32(0)
default:
return nil
}
}