1
0
Protocol/packets/helpers.go
2025-09-03 20:41:44 -05:00

285 lines
7.8 KiB
Go

package packets
import (
"bytes"
"compress/zlib"
"encoding/binary"
"fmt"
"io"
"git.sharkk.net/EQ2/Protocol/crypto"
)
// ValidateCRC validates packet CRC using EQ2's custom CRC16
func ValidateCRC(buffer []byte, key uint32) bool {
if len(buffer) < 2 {
return false
}
// Extract CRC from last 2 bytes (EQ2 uses CRC16)
packetCRC := binary.BigEndian.Uint16(buffer[len(buffer)-2:])
// Calculate CRC on data portion (excluding CRC bytes)
data := buffer[:len(buffer)-2]
calculatedCRC := crypto.CalculateCRC(data, key)
return packetCRC == calculatedCRC
}
// AppendCRC appends CRC16 to packet buffer using EQ2's custom CRC
func AppendCRC(buffer []byte, key uint32) []byte {
crc := crypto.CalculateCRC(buffer, key)
result := make([]byte, len(buffer)+2)
copy(result, buffer)
binary.BigEndian.PutUint16(result[len(buffer):], crc)
return result
}
// StripCRC removes CRC16 from packet buffer
func StripCRC(buffer []byte) []byte {
if len(buffer) < 2 {
return buffer
}
return buffer[:len(buffer)-2]
}
// Compress compresses packet data using zlib or simple encoding (matches C++ EQProtocolPacket::Compress)
// Uses zlib for packets > 30 bytes, simple encoding for smaller packets
func Compress(src []byte) ([]byte, error) {
if len(src) == 0 {
return src, nil
}
// C++ uses 30 bytes as threshold between simple encoding and zlib
if len(src) > 30 {
// Use zlib compression for larger packets
var buf bytes.Buffer
// Write uncompressed length first (4 bytes) - EQ protocol requirement
if err := binary.Write(&buf, binary.BigEndian, uint32(len(src))); err != nil {
return nil, err
}
// Compress the data
w := zlib.NewWriter(&buf)
if _, err := w.Write(src); err != nil {
w.Close()
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// Use simple encoding for smaller packets (just return data as-is)
// The 0xa5 flag is added by the caller, not here
return src, nil
}
// Decompress decompresses packet data using zlib
// This is called after the compression flag has already been checked and removed
func Decompress(src []byte) ([]byte, error) {
if len(src) < 4 {
return nil, fmt.Errorf("compressed data too small")
}
// Read uncompressed length (first 4 bytes)
uncompressedLen := binary.BigEndian.Uint32(src[:4])
// Sanity check
if uncompressedLen > MaxPacketSize {
return nil, fmt.Errorf("uncompressed size %d exceeds max packet size", uncompressedLen)
}
// Create reader for compressed data (skip length prefix)
r, err := zlib.NewReader(bytes.NewReader(src[4:]))
if err != nil {
return nil, err
}
defer r.Close()
// Read decompressed data
decompressed := make([]byte, uncompressedLen)
if _, err := io.ReadFull(r, decompressed); err != nil {
return nil, err
}
return decompressed, nil
}
// CompressPacket adds compression with proper flag handling (matches C++ EQProtocolPacket::Compress)
// Uses zlib (0x5a) for packets > 30 bytes, simple encoding (0xa5) for smaller packets
func CompressPacket(buffer []byte) ([]byte, error) {
if len(buffer) < 2 {
return buffer, nil
}
// Determine flag offset based on opcode size
flagOffset := 1
if buffer[0] == 0x00 {
flagOffset = 2 // Two-byte opcode
}
if len(buffer) <= flagOffset {
return buffer, nil // Too small to compress
}
// Get data to compress (after opcode)
dataToCompress := buffer[flagOffset:]
// Choose compression method based on size (C++ uses 30 byte threshold)
if len(dataToCompress) > 30 {
// Use zlib compression
compressed, err := Compress(dataToCompress)
if err != nil {
return nil, err
}
// Only use if compression actually reduced size
if len(compressed) < len(dataToCompress) {
result := make([]byte, flagOffset+1+len(compressed))
copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
result[flagOffset] = 0x5a // Zlib flag
copy(result[flagOffset+1:], compressed) // Compressed data
return result, nil
}
} else {
// Use simple encoding - just add flag
result := make([]byte, len(buffer)+1)
copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
result[flagOffset] = 0xa5 // Simple encoding flag
copy(result[flagOffset+1:], dataToCompress) // Original data
return result, nil
}
// No compression if it doesn't help
return buffer, nil
}
// DecompressPacket handles decompression with flag checking (matches C++ EQProtocolPacket::Decompress)
// Supports both zlib compression (0x5a) and simple encoding (0xa5)
func DecompressPacket(buffer []byte) ([]byte, error) {
if len(buffer) < 3 {
return buffer, nil // Too small to be compressed
}
// Determine flag offset based on opcode size
flagOffset := uint32(1)
if buffer[0] == 0x00 {
flagOffset = 2 // Two-byte opcode
}
if uint32(len(buffer)) <= flagOffset {
return buffer, nil // No room for compression flag
}
compressionFlag := buffer[flagOffset]
// Check compression type
if compressionFlag == 0x5a {
// Zlib compression
// Decompress data after flag, excluding last 2 CRC bytes
dataStart := flagOffset + 1
dataEnd := uint32(len(buffer)) - 2
if dataEnd <= dataStart {
return nil, fmt.Errorf("invalid compressed packet size")
}
decompressed, err := Decompress(buffer[dataStart:dataEnd])
if err != nil {
return nil, fmt.Errorf("zlib decompression failed: %w", err)
}
// Rebuild packet: opcode + decompressed data + CRC
result := make([]byte, flagOffset+uint32(len(decompressed))+2)
copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
copy(result[flagOffset:], decompressed) // Copy decompressed data
// Copy CRC bytes
result[len(result)-2] = buffer[len(buffer)-2]
result[len(result)-1] = buffer[len(buffer)-1]
return result, nil
} else if compressionFlag == 0xa5 {
// Simple encoding - just remove the encoding flag
result := make([]byte, len(buffer)-1)
copy(result[:flagOffset], buffer[:flagOffset]) // Copy opcode
copy(result[flagOffset:], buffer[flagOffset+1:]) // Skip flag, copy rest
return result, nil
}
// No compression
return buffer, nil
}
// ChatEncode encodes chat data using EQ's XOR-based encoding
func ChatEncode(buffer []byte, encodeKey int) {
if len(buffer) == 0 || encodeKey == 0 {
return
}
key := byte(encodeKey & 0xFF)
for i := range buffer {
buffer[i] ^= key
// Rotate key for next byte
key = ((key << 1) | (key >> 7)) & 0xFF
// Add position-based variation
if i%3 == 0 {
key ^= byte(i & 0xFF)
}
}
}
// ChatDecode decodes chat data (XOR is symmetric)
func ChatDecode(buffer []byte, decodeKey int) {
ChatEncode(buffer, decodeKey)
}
// IsChatPacket checks if opcode is a chat-related packet
func IsChatPacket(opcode uint16) bool {
chatOpcodes := map[uint16]bool{
0x0300: true, // OP_ChatMsg
0x0302: true, // OP_TellMsg
0x0307: true, // OP_ChatLeaveChannelMsg
0x0308: true, // OP_ChatTellChannelMsg
0x0309: true, // OP_ChatTellUserMsg
0x0e07: true, // OP_GuildsayMsg
}
return chatOpcodes[opcode]
}
// longToIP converts uint32 IP to string
func longToIP(ip uint32) string {
return fmt.Sprintf("%d.%d.%d.%d",
byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}
// IsProtocolPacket checks if buffer contains a valid protocol packet
func IsProtocolPacket(buffer []byte) bool {
if len(buffer) < 2 {
return false
}
opcode := binary.BigEndian.Uint16(buffer[:2])
validOpcodes := map[uint16]bool{
0x0001: true, // OP_SessionRequest
0x0002: true, // OP_SessionResponse
0x0003: true, // OP_Combined
0x0005: true, // OP_SessionDisconnect
0x0006: true, // OP_KeepAlive
0x0007: true, // OP_SessionStatRequest
0x0008: true, // OP_SessionStatResponse
0x0009: true, // OP_Packet
0x000d: true, // OP_Fragment
0x0015: true, // OP_Ack
0x0019: true, // OP_AppCombined
0x001d: true, // OP_OutOfOrderAck
0x001e: true, // OP_OutOfSession
}
return validOpcodes[opcode]
}