packets
This commit is contained in:
parent
aac862aebe
commit
fd7126878f
209
compression.go
Normal file
209
compression.go
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
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
|
||||||
|
}
|
93
crc16.go
Normal file
93
crc16.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
// CRC16 table for CCITT polynomial (0x1021)
|
||||||
|
var crc16Table [256]uint16
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Initialize CRC16 lookup table
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
crc := uint16(i << 8)
|
||||||
|
for j := 0; j < 8; j++ {
|
||||||
|
if (crc & 0x8000) != 0 {
|
||||||
|
crc = (crc << 1) ^ 0x1021
|
||||||
|
} else {
|
||||||
|
crc = crc << 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crc16Table[i] = crc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRC16 calculates the CRC16-CCITT checksum with a key
|
||||||
|
func CRC16(data []byte, length int, key uint32) uint16 {
|
||||||
|
if length <= 0 || len(data) < length {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mix the key into initial CRC value
|
||||||
|
crc := uint16(0xFFFF)
|
||||||
|
keyBytes := []byte{
|
||||||
|
byte(key),
|
||||||
|
byte(key >> 8),
|
||||||
|
byte(key >> 16),
|
||||||
|
byte(key >> 24),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process key bytes first
|
||||||
|
for _, b := range keyBytes {
|
||||||
|
tableIndex := (uint8(crc>>8) ^ b) & 0xFF
|
||||||
|
crc = (crc << 8) ^ crc16Table[tableIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process data
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
tableIndex := (uint8(crc>>8) ^ data[i]) & 0xFF
|
||||||
|
crc = (crc << 8) ^ crc16Table[tableIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc ^ 0xFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCRC checks if a packet has a valid CRC
|
||||||
|
func ValidateCRC(buffer []byte, key uint32) bool {
|
||||||
|
if len(buffer) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for CRC-exempt packets
|
||||||
|
if len(buffer) >= 2 && buffer[0] == 0x00 {
|
||||||
|
switch buffer[1] {
|
||||||
|
case byte(OPSessionRequest), byte(OPSessionResponse), byte(OPOutOfSession):
|
||||||
|
return true // Session packets don't require CRC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for combined application packets (also CRC-exempt)
|
||||||
|
if len(buffer) >= 4 && buffer[2] == 0x00 && buffer[3] == 0x19 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate CRC for the packet (excluding last 2 CRC bytes)
|
||||||
|
dataLen := len(buffer) - 2
|
||||||
|
calculatedCRC := CRC16(buffer, dataLen, key)
|
||||||
|
|
||||||
|
// Extract packet CRC (big-endian in last 2 bytes)
|
||||||
|
packetCRC := uint16(buffer[dataLen])<<8 | uint16(buffer[dataLen+1])
|
||||||
|
|
||||||
|
// Valid if no CRC present (0) or CRCs match
|
||||||
|
return packetCRC == 0 || calculatedCRC == packetCRC
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendCRC adds CRC to the end of a packet
|
||||||
|
func AppendCRC(buffer []byte, key uint32) []byte {
|
||||||
|
// Calculate CRC for current buffer
|
||||||
|
crc := CRC16(buffer, len(buffer), key)
|
||||||
|
|
||||||
|
// Append CRC in big-endian format
|
||||||
|
result := make([]byte, len(buffer)+2)
|
||||||
|
copy(result, buffer)
|
||||||
|
result[len(buffer)] = byte(crc >> 8)
|
||||||
|
result[len(buffer)+1] = byte(crc)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -1,3 +1,3 @@
|
|||||||
module git.sharkk.net/EQ2/Protocol
|
module git.sharkk.net/EQ2/Protocol
|
||||||
|
|
||||||
go 1.25.0
|
go 1.21
|
||||||
|
299
packet.go
Normal file
299
packet.go
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// Package eq2net implements the EverQuest 2 network protocol
|
||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protocol opcodes for low-level packet control
|
||||||
|
const (
|
||||||
|
OPSessionRequest = 0x0001
|
||||||
|
OPSessionResponse = 0x0002
|
||||||
|
OPCombined = 0x0003
|
||||||
|
OPSessionDisconnect = 0x0005
|
||||||
|
OPKeepAlive = 0x0006
|
||||||
|
OPSessionStatRequest = 0x0007
|
||||||
|
OPSessionStatResponse = 0x0008
|
||||||
|
OPPacket = 0x0009
|
||||||
|
OPFragment = 0x000D
|
||||||
|
OPOutOfOrderAck = 0x0011
|
||||||
|
OPAck = 0x0015
|
||||||
|
OPAppCombined = 0x0019
|
||||||
|
OPOutOfSession = 0x001D
|
||||||
|
)
|
||||||
|
|
||||||
|
// EQPacket is the base packet type for all EverQuest packets
|
||||||
|
type EQPacket struct {
|
||||||
|
// Core packet data
|
||||||
|
Buffer []byte
|
||||||
|
Size uint32
|
||||||
|
Opcode uint16
|
||||||
|
|
||||||
|
// Network information
|
||||||
|
SrcIP net.IP
|
||||||
|
DstIP net.IP
|
||||||
|
SrcPort uint16
|
||||||
|
DstPort uint16
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
Priority uint32
|
||||||
|
Timestamp time.Time
|
||||||
|
Version int16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEQPacket creates a new packet with the specified opcode and data
|
||||||
|
func NewEQPacket(opcode uint16, data []byte) *EQPacket {
|
||||||
|
p := &EQPacket{
|
||||||
|
Opcode: opcode,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) > 0 {
|
||||||
|
p.Buffer = make([]byte, len(data))
|
||||||
|
copy(p.Buffer, data)
|
||||||
|
p.Size = uint32(len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalSize returns the total packet size including opcode
|
||||||
|
func (p *EQPacket) TotalSize() uint32 {
|
||||||
|
return p.Size + 2 // +2 for opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetworkInfo sets the source and destination network information
|
||||||
|
func (p *EQPacket) SetNetworkInfo(srcIP net.IP, srcPort uint16, dstIP net.IP, dstPort uint16) {
|
||||||
|
p.SrcIP = srcIP
|
||||||
|
p.SrcPort = srcPort
|
||||||
|
p.DstIP = dstIP
|
||||||
|
p.DstPort = dstPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyInfo copies network and timing information from another packet
|
||||||
|
func (p *EQPacket) CopyInfo(other *EQPacket) {
|
||||||
|
p.SrcIP = other.SrcIP
|
||||||
|
p.SrcPort = other.SrcPort
|
||||||
|
p.DstIP = other.DstIP
|
||||||
|
p.DstPort = other.DstPort
|
||||||
|
p.Timestamp = other.Timestamp
|
||||||
|
p.Version = other.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
// EQProtocolPacket handles low-level protocol operations
|
||||||
|
type EQProtocolPacket struct {
|
||||||
|
*EQPacket
|
||||||
|
|
||||||
|
// Protocol state flags
|
||||||
|
Compressed bool
|
||||||
|
Prepared bool
|
||||||
|
Encrypted bool
|
||||||
|
Acked bool
|
||||||
|
|
||||||
|
// Reliability tracking
|
||||||
|
SentTime time.Time
|
||||||
|
AttemptCount uint8
|
||||||
|
Sequence uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEQProtocolPacket creates a new protocol packet
|
||||||
|
func NewEQProtocolPacket(opcode uint16, data []byte) *EQProtocolPacket {
|
||||||
|
return &EQProtocolPacket{
|
||||||
|
EQPacket: NewEQPacket(opcode, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEQProtocolPacketFromBuffer creates a protocol packet from raw buffer
|
||||||
|
func NewEQProtocolPacketFromBuffer(buffer []byte, opcodeOverride int) (*EQProtocolPacket, error) {
|
||||||
|
if len(buffer) < 2 {
|
||||||
|
return nil, fmt.Errorf("buffer too small for opcode")
|
||||||
|
}
|
||||||
|
|
||||||
|
var opcode uint16
|
||||||
|
var dataOffset int
|
||||||
|
|
||||||
|
if opcodeOverride >= 0 {
|
||||||
|
opcode = uint16(opcodeOverride)
|
||||||
|
dataOffset = 0
|
||||||
|
} else {
|
||||||
|
opcode = binary.BigEndian.Uint16(buffer[:2])
|
||||||
|
dataOffset = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
if len(buffer) > dataOffset {
|
||||||
|
data = buffer[dataOffset:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewEQProtocolPacket(opcode, data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize writes the protocol packet to a byte buffer
|
||||||
|
func (p *EQProtocolPacket) Serialize(offset int) []byte {
|
||||||
|
// Allocate buffer for opcode + data
|
||||||
|
result := make([]byte, 2+len(p.Buffer)-offset)
|
||||||
|
|
||||||
|
// Write opcode (big-endian)
|
||||||
|
if p.Opcode > 0xFF {
|
||||||
|
binary.BigEndian.PutUint16(result[0:2], p.Opcode)
|
||||||
|
} else {
|
||||||
|
result[0] = 0
|
||||||
|
result[1] = byte(p.Opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy packet data
|
||||||
|
if len(p.Buffer) > offset {
|
||||||
|
copy(result[2:], p.Buffer[offset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsProtocolPacket checks if the opcode is a valid protocol packet
|
||||||
|
func IsProtocolPacket(opcode uint16) bool {
|
||||||
|
switch opcode {
|
||||||
|
case OPSessionRequest, OPSessionDisconnect, OPKeepAlive,
|
||||||
|
OPSessionStatResponse, OPPacket, OPCombined, OPFragment,
|
||||||
|
OPAck, OPOutOfOrderAck, OPOutOfSession:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a deep copy of the protocol packet
|
||||||
|
func (p *EQProtocolPacket) Copy() *EQProtocolPacket {
|
||||||
|
newPacket := &EQProtocolPacket{
|
||||||
|
EQPacket: NewEQPacket(p.Opcode, p.Buffer),
|
||||||
|
Compressed: p.Compressed,
|
||||||
|
Prepared: p.Prepared,
|
||||||
|
Encrypted: p.Encrypted,
|
||||||
|
Acked: p.Acked,
|
||||||
|
SentTime: p.SentTime,
|
||||||
|
AttemptCount: p.AttemptCount,
|
||||||
|
Sequence: p.Sequence,
|
||||||
|
}
|
||||||
|
newPacket.CopyInfo(p.EQPacket)
|
||||||
|
return newPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// EQApplicationPacket represents high-level application packets
|
||||||
|
type EQApplicationPacket struct {
|
||||||
|
*EQPacket
|
||||||
|
|
||||||
|
// Cached emulator opcode
|
||||||
|
EmuOpcode uint16
|
||||||
|
|
||||||
|
// Opcode size (1 or 2 bytes)
|
||||||
|
OpcodeSize uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOpcodeSize is the default size for application opcodes
|
||||||
|
var DefaultOpcodeSize uint8 = 2
|
||||||
|
|
||||||
|
// NewEQApplicationPacket creates a new application packet
|
||||||
|
func NewEQApplicationPacket(opcode uint16, data []byte) *EQApplicationPacket {
|
||||||
|
return &EQApplicationPacket{
|
||||||
|
EQPacket: NewEQPacket(opcode, data),
|
||||||
|
EmuOpcode: opcode,
|
||||||
|
OpcodeSize: DefaultOpcodeSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize writes the application packet to a byte buffer
|
||||||
|
func (p *EQApplicationPacket) Serialize() []byte {
|
||||||
|
opcodeBytes := p.OpcodeSize
|
||||||
|
|
||||||
|
// Special handling for opcodes with low byte = 0x00
|
||||||
|
if p.OpcodeSize == 2 && (p.Opcode&0x00FF) == 0 {
|
||||||
|
opcodeBytes = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]byte, uint32(opcodeBytes)+p.Size)
|
||||||
|
|
||||||
|
if p.OpcodeSize == 1 {
|
||||||
|
result[0] = byte(p.Opcode)
|
||||||
|
} else {
|
||||||
|
if (p.Opcode & 0x00FF) == 0 {
|
||||||
|
result[0] = 0
|
||||||
|
binary.BigEndian.PutUint16(result[1:3], p.Opcode)
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint16(result[0:2], p.Opcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data after opcode
|
||||||
|
if p.Size > 0 {
|
||||||
|
copy(result[opcodeBytes:], p.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a deep copy of the application packet
|
||||||
|
func (p *EQApplicationPacket) Copy() *EQApplicationPacket {
|
||||||
|
newPacket := &EQApplicationPacket{
|
||||||
|
EQPacket: NewEQPacket(p.Opcode, p.Buffer),
|
||||||
|
EmuOpcode: p.EmuOpcode,
|
||||||
|
OpcodeSize: p.OpcodeSize,
|
||||||
|
}
|
||||||
|
newPacket.CopyInfo(p.EQPacket)
|
||||||
|
return newPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// PacketCombiner handles combining multiple packets for efficient transmission
|
||||||
|
type PacketCombiner struct {
|
||||||
|
maxSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPacketCombiner creates a new packet combiner with max size limit
|
||||||
|
func NewPacketCombiner(maxSize int) *PacketCombiner {
|
||||||
|
return &PacketCombiner{maxSize: maxSize}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombineProtocolPackets combines multiple protocol packets into one
|
||||||
|
func (c *PacketCombiner) CombineProtocolPackets(packets []*EQProtocolPacket) *EQProtocolPacket {
|
||||||
|
if len(packets) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(packets) == 1 {
|
||||||
|
return packets[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total size needed
|
||||||
|
totalSize := 0
|
||||||
|
for _, p := range packets {
|
||||||
|
totalSize += 1 + int(p.TotalSize()) // 1 byte for size prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalSize > c.maxSize {
|
||||||
|
return nil // Too large to combine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build combined packet buffer
|
||||||
|
buffer := make([]byte, totalSize)
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for _, p := range packets {
|
||||||
|
// Write size prefix
|
||||||
|
buffer[offset] = byte(p.TotalSize())
|
||||||
|
offset++
|
||||||
|
|
||||||
|
// Serialize packet
|
||||||
|
serialized := p.Serialize(0)
|
||||||
|
copy(buffer[offset:], serialized)
|
||||||
|
offset += len(serialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create combined packet
|
||||||
|
combined := NewEQProtocolPacket(OPCombined, buffer)
|
||||||
|
if len(packets) > 0 {
|
||||||
|
combined.CopyInfo(packets[0].EQPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined
|
||||||
|
}
|
207
packet_test.go
Normal file
207
packet_test.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEQPacket(t *testing.T) {
|
||||||
|
data := []byte("Hello, World!")
|
||||||
|
packet := NewEQPacket(OPPacket, data)
|
||||||
|
|
||||||
|
if packet.Opcode != OPPacket {
|
||||||
|
t.Errorf("Expected opcode %04x, got %04x", OPPacket, packet.Opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if packet.Size != uint32(len(data)) {
|
||||||
|
t.Errorf("Expected size %d, got %d", len(data), packet.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(packet.Buffer, data) {
|
||||||
|
t.Errorf("Buffer mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtocolPacketSerialization(t *testing.T) {
|
||||||
|
data := []byte("Test Data")
|
||||||
|
packet := NewEQProtocolPacket(OPPacket, data)
|
||||||
|
|
||||||
|
serialized := packet.Serialize(0)
|
||||||
|
|
||||||
|
// Check opcode (first 2 bytes)
|
||||||
|
if len(serialized) < 2 {
|
||||||
|
t.Fatal("Serialized packet too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPPacket = 0x0009, should be [0x00, 0x09] in big-endian
|
||||||
|
if serialized[0] != 0x00 || serialized[1] != 0x09 {
|
||||||
|
t.Errorf("Opcode not serialized correctly: %02x %02x", serialized[0], serialized[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check data
|
||||||
|
if !bytes.Equal(serialized[2:], data) {
|
||||||
|
t.Error("Data not serialized correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCRC16(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
data []byte
|
||||||
|
key uint32
|
||||||
|
}{
|
||||||
|
{[]byte{0x00, 0x09, 0x00, 0x00, 0x00}, 0x12345678},
|
||||||
|
{[]byte{0x00, 0x01}, 0},
|
||||||
|
{[]byte("Hello"), 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
// Just test that CRC16 produces consistent results
|
||||||
|
got1 := CRC16(tt.data, len(tt.data), tt.key)
|
||||||
|
got2 := CRC16(tt.data, len(tt.data), tt.key)
|
||||||
|
if got1 != got2 {
|
||||||
|
t.Errorf("CRC16 not consistent: %04x != %04x", got1, got2)
|
||||||
|
}
|
||||||
|
// Test that different keys produce different CRCs
|
||||||
|
if tt.key != 0 {
|
||||||
|
got3 := CRC16(tt.data, len(tt.data), 0)
|
||||||
|
if got1 == got3 {
|
||||||
|
t.Errorf("Different keys should produce different CRCs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateCRC(t *testing.T) {
|
||||||
|
// Test session packet (CRC exempt)
|
||||||
|
sessionPacket := []byte{0x00, byte(OPSessionRequest), 0x00, 0x00}
|
||||||
|
if !ValidateCRC(sessionPacket, 0) {
|
||||||
|
t.Error("Session packet should be CRC exempt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test packet with valid CRC
|
||||||
|
data := []byte{0x00, 0x09, 0x48, 0x65, 0x6C, 0x6C, 0x6F} // "Hello"
|
||||||
|
crc := CRC16(data, len(data), 0x1234)
|
||||||
|
dataWithCRC := append(data, byte(crc>>8), byte(crc))
|
||||||
|
|
||||||
|
if !ValidateCRC(dataWithCRC, 0x1234) {
|
||||||
|
t.Error("Packet with valid CRC should validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test packet with invalid CRC
|
||||||
|
dataWithCRC[len(dataWithCRC)-1] ^= 0xFF // Corrupt CRC
|
||||||
|
if ValidateCRC(dataWithCRC, 0x1234) {
|
||||||
|
t.Error("Packet with invalid CRC should not validate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompression(t *testing.T) {
|
||||||
|
// Test simple encoding (small packet)
|
||||||
|
smallData := []byte{0x00, 0x09, 0x01, 0x02, 0x03}
|
||||||
|
compressed, err := CompressPacket(smallData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if compressed[2] != CompressionFlagSimple {
|
||||||
|
t.Errorf("Small packet should use simple encoding, got flag %02x", compressed[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed, err := DecompressPacket(compressed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(decompressed, smallData) {
|
||||||
|
t.Error("Decompressed data doesn't match original")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test zlib compression (large packet)
|
||||||
|
largeData := make([]byte, 100)
|
||||||
|
largeData[0] = 0x00
|
||||||
|
largeData[1] = 0x09
|
||||||
|
for i := 2; i < len(largeData); i++ {
|
||||||
|
largeData[i] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed, err = CompressPacket(largeData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if compressed[2] != CompressionFlagZlib {
|
||||||
|
t.Errorf("Large packet should use zlib compression, got flag %02x", compressed[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed, err = DecompressPacket(compressed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(decompressed, largeData) {
|
||||||
|
t.Error("Decompressed large data doesn't match original")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatEncryption(t *testing.T) {
|
||||||
|
// Test chat encoding/decoding
|
||||||
|
original := []byte{0x00, 0x09, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21} // "Hello!"
|
||||||
|
key := uint32(0x12345678)
|
||||||
|
|
||||||
|
encoded := ChatEncode(original, key)
|
||||||
|
if bytes.Equal(encoded[2:], original[2:]) {
|
||||||
|
t.Error("Encoded data should differ from original")
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded := ChatDecode(encoded, key)
|
||||||
|
if !bytes.Equal(decoded, original) {
|
||||||
|
t.Errorf("Decoded data doesn't match original\nOriginal: %v\nDecoded: %v", original, decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test exempt packet types
|
||||||
|
exemptPacket := []byte{0x00, 0x01, 0x12, 0x34}
|
||||||
|
encoded = ChatEncode(exemptPacket, key)
|
||||||
|
if !bytes.Equal(encoded, exemptPacket) {
|
||||||
|
t.Error("Exempt packet should not be encoded")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPacketCombiner(t *testing.T) {
|
||||||
|
combiner := NewPacketCombiner(256)
|
||||||
|
|
||||||
|
p1 := NewEQProtocolPacket(OPPacket, []byte{0x01, 0x02})
|
||||||
|
p2 := NewEQProtocolPacket(OPAck, []byte{0x03, 0x04})
|
||||||
|
p3 := NewEQProtocolPacket(OPKeepAlive, []byte{0x05})
|
||||||
|
|
||||||
|
combined := combiner.CombineProtocolPackets([]*EQProtocolPacket{p1, p2, p3})
|
||||||
|
|
||||||
|
if combined == nil {
|
||||||
|
t.Fatal("Failed to combine packets")
|
||||||
|
}
|
||||||
|
|
||||||
|
if combined.Opcode != OPCombined {
|
||||||
|
t.Errorf("Combined packet should have opcode %04x, got %04x", OPCombined, combined.Opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify combined packet structure
|
||||||
|
buffer := combined.Buffer
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
// First packet
|
||||||
|
if buffer[offset] != byte(p1.TotalSize()) {
|
||||||
|
t.Errorf("First packet size incorrect: %d", buffer[offset])
|
||||||
|
}
|
||||||
|
offset++
|
||||||
|
offset += int(p1.TotalSize())
|
||||||
|
|
||||||
|
// Second packet
|
||||||
|
if buffer[offset] != byte(p2.TotalSize()) {
|
||||||
|
t.Errorf("Second packet size incorrect: %d", buffer[offset])
|
||||||
|
}
|
||||||
|
offset++
|
||||||
|
offset += int(p2.TotalSize())
|
||||||
|
|
||||||
|
// Third packet
|
||||||
|
if buffer[offset] != byte(p3.TotalSize()) {
|
||||||
|
t.Errorf("Third packet size incorrect: %d", buffer[offset])
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user