362 lines
8.2 KiB
Go
362 lines
8.2 KiB
Go
package eq2net
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"git.sharkk.net/EQ2/Protocol/crypto"
|
|
)
|
|
|
|
// ProtocolPacket handles low-level protocol operations including
|
|
// compression, encryption, sequencing, and packet combining
|
|
type ProtocolPacket struct {
|
|
EQPacket
|
|
|
|
Compressed bool
|
|
Encrypted bool
|
|
PacketPrepared bool
|
|
|
|
Sequence uint16
|
|
Acknowledged bool
|
|
SentTime int32
|
|
AttemptCount int8
|
|
|
|
SubPackets []*ProtocolPacket
|
|
}
|
|
|
|
// NewProtocolPacket creates a new protocol packet
|
|
func NewProtocolPacket(opcode uint16, data []byte) *ProtocolPacket {
|
|
p := &ProtocolPacket{
|
|
EQPacket: *NewEQPacket(opcode, data),
|
|
}
|
|
return p
|
|
}
|
|
|
|
// ParseProtocolPacket creates a protocol packet from raw network data
|
|
func ParseProtocolPacket(data []byte) (*ProtocolPacket, error) {
|
|
base, err := ParsePacket(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p := &ProtocolPacket{
|
|
EQPacket: *base,
|
|
}
|
|
|
|
// Check for compression flag
|
|
if len(p.Buffer) > 0 {
|
|
switch p.Buffer[0] {
|
|
case 0x5a: // Zlib compressed
|
|
p.Compressed = true
|
|
case 0xa5: // Simple encoding
|
|
p.Compressed = true
|
|
}
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// Serialize writes the protocol packet to a byte buffer with optional offset
|
|
func (p *ProtocolPacket) SerializeWithOffset(offset int8) []byte {
|
|
opcodeSize := 2 // Protocol packets always use 2-byte opcodes
|
|
buf := make([]byte, opcodeSize+len(p.Buffer)-int(offset))
|
|
|
|
if p.Opcode > 0xff {
|
|
binary.BigEndian.PutUint16(buf[0:2], p.Opcode)
|
|
} else {
|
|
buf[0] = 0x00
|
|
buf[1] = byte(p.Opcode)
|
|
}
|
|
|
|
if len(p.Buffer) > int(offset) {
|
|
copy(buf[opcodeSize:], p.Buffer[offset:])
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
// ValidateCRC checks if the packet has a valid CRC
|
|
// Returns true if CRC is valid or packet is exempt
|
|
func (p *ProtocolPacket) ValidateCRC(key uint32) bool {
|
|
// Session control packets are exempt from CRC
|
|
if p.Opcode == OP_SessionRequest ||
|
|
p.Opcode == OP_SessionResponse ||
|
|
p.Opcode == OP_OutOfSession {
|
|
return true
|
|
}
|
|
|
|
// Need full packet data including opcode for CRC check
|
|
fullData := p.Serialize()
|
|
if len(fullData) < 2 {
|
|
return false
|
|
}
|
|
|
|
// CRC is in last 2 bytes
|
|
if len(fullData) < 4 { // Minimum: 2 byte opcode + 2 byte CRC
|
|
return false
|
|
}
|
|
|
|
dataLen := len(fullData) - 2
|
|
calculatedCRC := crypto.CalculateCRC(fullData[:dataLen], key)
|
|
packetCRC := binary.BigEndian.Uint16(fullData[dataLen:])
|
|
|
|
// CRC of 0 means no CRC check required
|
|
return packetCRC == 0 || calculatedCRC == packetCRC
|
|
}
|
|
|
|
// Compress compresses the packet data using zlib or simple encoding
|
|
func (p *ProtocolPacket) Compress() error {
|
|
if p.Compressed {
|
|
return fmt.Errorf("packet already compressed")
|
|
}
|
|
|
|
// Only compress packets larger than 30 bytes
|
|
if len(p.Buffer) > 30 {
|
|
compressed, err := p.zlibCompress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.Buffer = compressed
|
|
p.Compressed = true
|
|
} else if len(p.Buffer) > 0 {
|
|
// Simple encoding for small packets
|
|
p.simpleEncode()
|
|
p.Compressed = true
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Decompress decompresses the packet data
|
|
func (p *ProtocolPacket) Decompress() error {
|
|
if !p.Compressed {
|
|
return fmt.Errorf("packet not compressed")
|
|
}
|
|
|
|
if len(p.Buffer) == 0 {
|
|
return fmt.Errorf("no data to decompress")
|
|
}
|
|
|
|
var decompressed []byte
|
|
var err error
|
|
|
|
switch p.Buffer[0] {
|
|
case 0x5a: // Zlib compressed
|
|
decompressed, err = p.zlibDecompress()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case 0xa5: // Simple encoding
|
|
decompressed = p.simpleDecoded()
|
|
default:
|
|
return fmt.Errorf("unknown compression flag: 0x%02x", p.Buffer[0])
|
|
}
|
|
|
|
p.Buffer = decompressed
|
|
p.Compressed = false
|
|
return nil
|
|
}
|
|
|
|
// zlibCompress performs zlib compression
|
|
func (p *ProtocolPacket) zlibCompress() ([]byte, error) {
|
|
var compressed bytes.Buffer
|
|
|
|
// Write compression flag
|
|
compressed.WriteByte(0x5a)
|
|
|
|
// Compress the data
|
|
w := zlib.NewWriter(&compressed)
|
|
_, err := w.Write(p.Buffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = w.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return compressed.Bytes(), nil
|
|
}
|
|
|
|
// zlibDecompress performs zlib decompression
|
|
func (p *ProtocolPacket) zlibDecompress() ([]byte, error) {
|
|
if len(p.Buffer) < 2 {
|
|
return nil, fmt.Errorf("compressed data too small")
|
|
}
|
|
|
|
// Skip compression flag
|
|
r, err := zlib.NewReader(bytes.NewReader(p.Buffer[1:]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer r.Close()
|
|
|
|
var decompressed bytes.Buffer
|
|
_, err = decompressed.ReadFrom(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return decompressed.Bytes(), nil
|
|
}
|
|
|
|
// simpleEncode adds simple encoding flag
|
|
func (p *ProtocolPacket) simpleEncode() {
|
|
// Add encoding flag at the beginning
|
|
newBuffer := make([]byte, len(p.Buffer)+1)
|
|
newBuffer[0] = 0xa5
|
|
copy(newBuffer[1:], p.Buffer)
|
|
p.Buffer = newBuffer
|
|
}
|
|
|
|
// simpleDecoded removes simple encoding flag
|
|
func (p *ProtocolPacket) simpleDecoded() []byte {
|
|
if len(p.Buffer) > 1 {
|
|
return p.Buffer[1:]
|
|
}
|
|
return []byte{}
|
|
}
|
|
|
|
// Combine bundles multiple protocol packets into a single combined packet
|
|
func (p *ProtocolPacket) Combine(other *ProtocolPacket) bool {
|
|
// Check if this is already a combined packet
|
|
if p.Opcode != OP_Combined {
|
|
// Convert to combined packet
|
|
firstPacket := p.Clone()
|
|
p.Opcode = OP_Combined
|
|
p.SubPackets = []*ProtocolPacket{
|
|
{EQPacket: *firstPacket},
|
|
}
|
|
p.Buffer = p.buildCombinedBuffer()
|
|
}
|
|
|
|
// Check size limit (max 255 bytes for combined packets)
|
|
totalSize := len(p.Buffer) + int(other.TotalSize()) + 1 // +1 for size byte
|
|
if totalSize > 255 {
|
|
return false
|
|
}
|
|
|
|
// Add the new packet
|
|
p.SubPackets = append(p.SubPackets, other)
|
|
p.Buffer = p.buildCombinedBuffer()
|
|
return true
|
|
}
|
|
|
|
// buildCombinedBuffer creates the buffer for a combined packet
|
|
func (p *ProtocolPacket) buildCombinedBuffer() []byte {
|
|
var buf bytes.Buffer
|
|
|
|
for _, subPacket := range p.SubPackets {
|
|
serialized := subPacket.Serialize()
|
|
buf.WriteByte(byte(len(serialized))) // Size byte
|
|
buf.Write(serialized)
|
|
}
|
|
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// ExtractSubPackets extracts individual packets from a combined packet
|
|
func (p *ProtocolPacket) ExtractSubPackets() ([]*ProtocolPacket, error) {
|
|
if p.Opcode != OP_Combined {
|
|
return nil, fmt.Errorf("not a combined packet")
|
|
}
|
|
|
|
var packets []*ProtocolPacket
|
|
offset := 0
|
|
data := p.Buffer
|
|
|
|
for offset < len(data) {
|
|
// Read size byte
|
|
if offset >= len(data) {
|
|
break
|
|
}
|
|
size := int(data[offset])
|
|
offset++
|
|
|
|
// Extract sub-packet data
|
|
if offset+size > len(data) {
|
|
return nil, fmt.Errorf("invalid combined packet: size exceeds buffer")
|
|
}
|
|
|
|
subData := data[offset : offset+size]
|
|
offset += size
|
|
|
|
// Parse sub-packet
|
|
subPacket, err := ParseProtocolPacket(subData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse sub-packet: %w", err)
|
|
}
|
|
|
|
packets = append(packets, subPacket)
|
|
}
|
|
|
|
return packets, nil
|
|
}
|
|
|
|
// MakeApplicationPacket converts this protocol packet to an application packet
|
|
// This is used when a protocol packet contains application-level data
|
|
func (p *ProtocolPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket {
|
|
if len(p.Buffer) < int(opcodeSize) {
|
|
return nil
|
|
}
|
|
|
|
// Extract the application opcode from the buffer
|
|
var appOpcode uint16
|
|
if opcodeSize == 1 {
|
|
appOpcode = uint16(p.Buffer[0])
|
|
} else {
|
|
appOpcode = binary.BigEndian.Uint16(p.Buffer[0:2])
|
|
}
|
|
|
|
// Create application packet with remaining data
|
|
app := &AppPacket{
|
|
EQPacket: *NewEQPacket(appOpcode, p.Buffer[opcodeSize:]),
|
|
OpcodeSize: opcodeSize,
|
|
}
|
|
|
|
// Copy network info
|
|
app.SrcIP = p.SrcIP
|
|
app.SrcPort = p.SrcPort
|
|
app.DstIP = p.DstIP
|
|
app.DstPort = p.DstPort
|
|
app.Timestamp = p.Timestamp
|
|
app.Version = p.Version
|
|
|
|
return app
|
|
}
|
|
|
|
// ChatEncode applies XOR-based chat encryption
|
|
func (p *ProtocolPacket) ChatEncode(key uint32) {
|
|
if len(p.Buffer) <= 2 {
|
|
return // Skip opcode bytes
|
|
}
|
|
|
|
data := p.Buffer[2:] // Skip first 2 bytes (opcode)
|
|
keyBytes := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(keyBytes, key)
|
|
|
|
// Process 4-byte blocks with rolling key
|
|
i := 0
|
|
for ; i+4 <= len(data); i += 4 {
|
|
// XOR with current key
|
|
block := binary.LittleEndian.Uint32(data[i : i+4])
|
|
encrypted := block ^ key
|
|
binary.LittleEndian.PutUint32(data[i:i+4], encrypted)
|
|
// Update key with encrypted data
|
|
key = encrypted
|
|
}
|
|
|
|
// Handle remaining bytes with last key byte
|
|
keyByte := byte(key & 0xFF)
|
|
for ; i < len(data); i++ {
|
|
data[i] ^= keyByte
|
|
}
|
|
}
|
|
|
|
// ChatDecode applies XOR-based chat decryption (same as encode due to XOR properties)
|
|
func (p *ProtocolPacket) ChatDecode(key uint32) {
|
|
p.ChatEncode(key) // XOR encryption is symmetric
|
|
}
|