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