1
0
This commit is contained in:
Sky Johnson 2025-09-01 13:45:40 -05:00
parent aac862aebe
commit fd7126878f
5 changed files with 809 additions and 1 deletions

209
compression.go Normal file
View 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
View 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
View File

@ -1,3 +1,3 @@
module git.sharkk.net/EQ2/Protocol
go 1.25.0
go 1.21

299
packet.go Normal file
View 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
View 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])
}
}