299 lines
6.8 KiB
Go
299 lines
6.8 KiB
Go
// 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
|
|
} |