243 lines
6.0 KiB
Go
243 lines
6.0 KiB
Go
package packets
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"io"
|
|
|
|
"git.sharkk.net/EQ2/Protocol/crypto"
|
|
)
|
|
|
|
// ValidateCRC validates packet CRC using EQ's CRC32 implementation
|
|
func ValidateCRC(buffer []byte, key uint32) bool {
|
|
if len(buffer) < 4 {
|
|
return false
|
|
}
|
|
|
|
// Extract CRC from last 4 bytes
|
|
packetCRC := binary.BigEndian.Uint32(buffer[len(buffer)-4:])
|
|
|
|
// Calculate CRC on data portion (excluding CRC bytes)
|
|
data := buffer[:len(buffer)-4]
|
|
calculatedCRC := CalculateCRC(data, key)
|
|
|
|
return packetCRC == calculatedCRC
|
|
}
|
|
|
|
// CalculateCRC calculates CRC32 for packet data
|
|
func CalculateCRC(data []byte, key uint32) uint32 {
|
|
// EQ uses standard CRC32 with XOR key
|
|
crc := crc32.ChecksumIEEE(data)
|
|
return crc ^ key
|
|
}
|
|
|
|
// Compress compresses packet data using zlib (matches EQ compression)
|
|
func Compress(src []byte) ([]byte, error) {
|
|
if len(src) == 0 {
|
|
return src, nil
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
// EQ uses default compression level
|
|
w := zlib.NewWriter(&buf)
|
|
|
|
// Write uncompressed length first (4 bytes) - EQ protocol requirement
|
|
uncompressedLen := uint32(len(src))
|
|
if err := binary.Write(&buf, binary.BigEndian, uncompressedLen); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compress the data
|
|
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
|
|
}
|
|
|
|
// Decompress decompresses packet data using zlib
|
|
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 - prevent decompression bombs
|
|
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
|
|
}
|
|
|
|
// ChatEncode encodes chat data using EQ's XOR-based encoding
|
|
// EQ uses a simple rotating XOR with the encode key
|
|
func ChatEncode(buffer []byte, encodeKey int) {
|
|
if len(buffer) == 0 || encodeKey == 0 {
|
|
return
|
|
}
|
|
|
|
// EQ chat encoding algorithm
|
|
key := byte(encodeKey & 0xFF)
|
|
for i := range buffer {
|
|
// XOR with rotating key based on position
|
|
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 using EQ's XOR-based encoding
|
|
// Decoding is the same as encoding for XOR
|
|
func ChatDecode(buffer []byte, decodeKey int) {
|
|
// XOR encoding is symmetric - encode and decode are the same operation
|
|
ChatEncode(buffer, decodeKey)
|
|
}
|
|
|
|
// IsProtocolPacket checks if buffer contains a valid protocol packet
|
|
func IsProtocolPacket(buffer []byte, trimCRC bool) bool {
|
|
if len(buffer) < 2 {
|
|
return false
|
|
}
|
|
|
|
// Check for valid protocol opcodes
|
|
opcode := binary.BigEndian.Uint16(buffer[:2])
|
|
|
|
// Protocol opcodes from protocol.go
|
|
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
|
|
}
|
|
|
|
if !validOpcodes[opcode] {
|
|
return false
|
|
}
|
|
|
|
// If checking CRC, validate it
|
|
if trimCRC && len(buffer) >= 6 {
|
|
// Protocol packets have 2-byte opcode + data + 4-byte CRC
|
|
return ValidateCRC(buffer, 0)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// EncodePacket applies encoding/compression based on flags
|
|
func EncodePacket(packet *ProtoPacket, compressThreshold int, encodeKey int) error {
|
|
// Apply compression if packet is large enough
|
|
if len(packet.Buffer) > compressThreshold && !packet.IsCompressed() {
|
|
compressed, err := Compress(packet.Buffer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
packet.Buffer = compressed
|
|
packet.SetCompressed(true)
|
|
}
|
|
|
|
// Apply chat encoding if this is a chat packet
|
|
if IsChatPacket(packet.Opcode) && encodeKey != 0 {
|
|
ChatEncode(packet.Buffer, encodeKey)
|
|
packet.SetEncrypted(true)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DecodePacket reverses encoding/compression
|
|
func DecodePacket(packet *ProtoPacket, decodeKey int) error {
|
|
// Decrypt if encrypted
|
|
if packet.IsEncrypted() && decodeKey != 0 {
|
|
ChatDecode(packet.Buffer, decodeKey)
|
|
packet.SetEncrypted(false)
|
|
}
|
|
|
|
// Decompress if compressed
|
|
if packet.IsCompressed() {
|
|
decompressed, err := Decompress(packet.Buffer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
packet.Buffer = decompressed
|
|
packet.SetCompressed(false)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IsChatPacket checks if opcode is a chat-related packet
|
|
func IsChatPacket(opcode uint16) bool {
|
|
// Chat-related opcodes that need encoding
|
|
// These would map to OP_ChatMsg, OP_TellMsg, etc in the opcodes package
|
|
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]
|
|
}
|
|
|
|
// Helper function to convert 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))
|
|
}
|
|
|
|
// 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]
|
|
}
|