1
0
Protocol/compression.go
2025-09-01 13:45:40 -05:00

209 lines
4.7 KiB
Go

package eq2net
import (
"bytes"
"compress/zlib"
"encoding/binary"
"io"
)
const (
// Compression flags
CompressionFlagZlib = 0x5A // Zlib compression
CompressionFlagSimple = 0xA5 // Simple encoding (no actual compression)
// Compression threshold - packets larger than this use zlib
CompressionThreshold = 30
)
// CompressPacket compresses a packet using zlib or simple encoding
func CompressPacket(data []byte) ([]byte, error) {
if len(data) < 2 {
return data, nil
}
// Determine opcode size
flagOffset := 1
if data[0] == 0 {
flagOffset = 2 // Two-byte opcode
}
// Don't compress if too small
if len(data) <= flagOffset {
return data, nil
}
result := make([]byte, 0, len(data)+1)
// Copy opcode bytes
result = append(result, data[:flagOffset]...)
if len(data) > CompressionThreshold {
// Use zlib compression for larger packets
result = append(result, CompressionFlagZlib)
// Compress the data after opcode
var compressed bytes.Buffer
w := zlib.NewWriter(&compressed)
if _, err := w.Write(data[flagOffset:]); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
result = append(result, compressed.Bytes()...)
} else {
// Use simple encoding for smaller packets
result = append(result, CompressionFlagSimple)
result = append(result, data[flagOffset:]...)
}
return result, nil
}
// DecompressPacket decompresses a packet
func DecompressPacket(data []byte) ([]byte, error) {
if len(data) < 3 {
return data, nil
}
// Determine opcode size and compression flag position
flagOffset := 1
if data[0] == 0 {
flagOffset = 2
}
if len(data) <= flagOffset {
return data, nil
}
compressionFlag := data[flagOffset]
// Check compression type
switch compressionFlag {
case CompressionFlagZlib:
// Zlib decompression
result := make([]byte, 0, len(data)*2)
// Copy opcode
result = append(result, data[:flagOffset]...)
// Decompress data (skip flag byte)
r, err := zlib.NewReader(bytes.NewReader(data[flagOffset+1:]))
if err != nil {
return nil, err
}
defer r.Close()
decompressed, err := io.ReadAll(r)
if err != nil {
return nil, err
}
result = append(result, decompressed...)
return result, nil
case CompressionFlagSimple:
// Simple encoding - just remove the flag byte
result := make([]byte, 0, len(data)-1)
result = append(result, data[:flagOffset]...)
result = append(result, data[flagOffset+1:]...)
return result, nil
default:
// No compression
return data, nil
}
}
// IsCompressed checks if a packet is compressed
func IsCompressed(data []byte) bool {
if len(data) < 2 {
return false
}
flagOffset := 1
if data[0] == 0 {
flagOffset = 2
}
if len(data) <= flagOffset {
return false
}
flag := data[flagOffset]
return flag == CompressionFlagZlib || flag == CompressionFlagSimple
}
// ChatEncode encodes chat data using XOR encryption with rolling key
func ChatEncode(data []byte, encodeKey uint32) []byte {
// Skip certain packet types
if len(data) >= 2 && (data[1] == 0x01 || data[0] == 0x02 || data[0] == 0x1d) {
return data
}
// Work with data after opcode
if len(data) <= 2 {
return data
}
result := make([]byte, len(data))
copy(result[:2], data[:2]) // Copy opcode
key := encodeKey
offset := 2
// Process 4-byte blocks with rolling key
for i := offset; i+4 <= len(data); i += 4 {
block := binary.LittleEndian.Uint32(data[i : i+4])
encrypted := block ^ key
binary.LittleEndian.PutUint32(result[i:i+4], encrypted)
key = encrypted // Update key with encrypted data
}
// Handle remaining bytes
keyByte := byte(key & 0xFF)
alignedEnd := offset + ((len(data)-offset)/4)*4
for i := alignedEnd; i < len(data); i++ {
result[i] = data[i] ^ keyByte
}
return result
}
// ChatDecode decodes chat data using XOR encryption with rolling key
func ChatDecode(data []byte, decodeKey uint32) []byte {
// Skip certain packet types
if len(data) >= 2 && (data[1] == 0x01 || data[0] == 0x02 || data[0] == 0x1d) {
return data
}
// Work with data after opcode
if len(data) <= 2 {
return data
}
result := make([]byte, len(data))
copy(result[:2], data[:2]) // Copy opcode
key := decodeKey
offset := 2
// Process 4-byte blocks with rolling key
for i := offset; i+4 <= len(data); i += 4 {
encrypted := binary.LittleEndian.Uint32(data[i : i+4])
decrypted := encrypted ^ key
binary.LittleEndian.PutUint32(result[i:i+4], decrypted)
key = encrypted // Update key with encrypted data (before decryption)
}
// Handle remaining bytes
keyByte := byte(key & 0xFF)
alignedEnd := offset + ((len(data)-offset)/4)*4
for i := alignedEnd; i < len(data); i++ {
result[i] = data[i] ^ keyByte
}
return result
}