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