EQ2 Protocol
This library is meant to replicate EverQuest 2's UDP protocol. The work here is based on the immense work done by contributors to the EQ2Emu project. I am porting it to Go with the idea of a simpler, faster development pipeline - and as a personal experiment.
Opcodes
The opcodes package manages the mapping between internal emulator opcodes and client-specific opcodes across different client versions.
OpcodeManager
The OpcodeManager
handles opcode translation for specific client versions:
import "git.sharkk.net/EQ2/Protocol/opcodes"
// Create a manager for client version 60085
om := opcodes.NewOpcodeManager(60085)
// Load opcode mappings from database
err := om.LoadFromDatabase(db, "opcodes_table")
// Convert between emulator and client opcodes
clientOpcode := om.EmuToEQ(opcodes.OP_LoginRequestMsg)
emuOpcode := om.EQToEmu(0x0123)
// Get opcode names
name := om.EmuToName(opcodes.OP_LoginRequestMsg)
Opcode Types
- EmuOpcode: Internal emulator opcodes (constants like
OP_LoginRequestMsg
) - Protocol opcodes: Low-level protocol opcodes (e.g.,
OP_SessionRequest
,OP_Packet
) - Client opcodes: Version-specific opcodes sent to/from the game client
Global Manager Cache
// Get or create a manager for a specific version
mgr := opcodes.GetManager(version, db, tableName)
// Get all loaded versions
versions := opcodes.GetLoadedVersions()
Packet Definitions
The defs package provides structured packet definitions with automatic version selection based on client version.
PacketManager
Manages packet structure lookups with version-aware selection:
import "git.sharkk.net/EQ2/Protocol/defs"
// Create a manager for client version 60085
pm := defs.NewPacketManager(60085)
// Get the appropriate packet struct for this version
ps := pm.GetStruct("CreateCharacter", 60085)
// Returns CreateCharacterV60085 struct
// Get by exact version
ps := pm.GetStructByVersion("CreateCharacter", 373)
// Get by opcode
ps := pm.GetStructByOpcode(opcodes.OP_CreateCharacterRequestMsg)
// Create a new packet instance
packet := pm.CreateInstance("CreateCharacter")
Version Selection
The manager automatically selects the highest packet version that doesn't exceed the client version:
- Client 1000 requesting "CreateCharacter" gets version 869
- Client 60085 requesting "CreateCharacter" gets version 60085
- Client 70000 requesting "CreateCharacter" gets version 65534
Code Generation
Packet structures and registrations are generated from XML definitions:
# Regenerate packet structures from XML
cd defs && go generate
# XML definitions are in defs/xml/
# Generated code goes to defs/generated/
Packets
The packets package provides base packet types and interfaces for all protocol packets.
Packet Interface
All packets implement the Packet
interface:
type Packet interface {
GetOpcode() uint16
SetOpcode(uint16)
GetVersion() uint16
SetVersion(uint16)
GetSequence() uint16
SetSequence(uint16)
GetData() []byte
GetSize() uint32
}
Base Packet Types
// RawPacket - Basic packet with opcode and data
packet := &packets.RawPacket{
Opcode: 0x0123,
Data: []byte{...},
}
// EncodedPacket - Packet with protocol encoding
encoded := &packets.EncodedPacket{
Sequence: seq,
Opcode: opcode,
Data: data,
Compressed: false,
}
Packet Processing
// Check if packet needs processing
if packet.ShouldCombine() {
// Handle combined packets
}
if packet.IsProtocolPacket() {
// Handle protocol-level packets
}
// Encode for transmission
encoded := packet.Encode()
// Decode received data
packet, err := packets.Decode(data)
Stream
The stream package handles bidirectional packet streaming with buffering, fragmentation, and reassembly.
PacketStream
Manages packet flow between client and server:
import "git.sharkk.net/EQ2/Protocol/stream"
// Create a new packet stream
stream := stream.NewPacketStream(conn, bufferSize)
// Set packet handlers
stream.SetIncomingHandler(func(packet packets.Packet) {
// Process incoming packets
})
stream.SetOutgoingHandler(func(packet packets.Packet) {
// Process outgoing packets
})
// Start processing
stream.Start()
// Send a packet
err := stream.Send(packet)
// Receive packets (handled by callback)
// Packets are automatically decoded and reassembled
Features
- Automatic fragmentation: Large packets are split into fragments
- Reassembly: Fragments are automatically reassembled
- Buffering: Configurable send/receive buffers
- Sequencing: Automatic sequence number management
- Compression: Optional packet compression support
Stream Configuration
config := &stream.Config{
MaxPacketSize: 512,
BufferSize: 8192,
EnableCompression: true,
Timeout: 30 * time.Second,
}
stream := stream.NewPacketStreamWithConfig(conn, config)
Factory
The factory package provides centralized packet creation and registration.
PacketFactory
Creates packets based on opcodes and manages packet type registration:
import "git.sharkk.net/EQ2/Protocol/factory"
// Get the global factory instance
factory := factory.GetInstance()
// Register a packet type
factory.RegisterPacket(opcodes.OP_LoginRequestMsg, func() packets.Packet {
return &LoginRequestPacket{}
})
// Create a packet by opcode
packet := factory.CreatePacket(opcodes.OP_LoginRequestMsg)
// Create with data
packet := factory.CreatePacketWithData(opcode, data)
// Create from raw bytes (auto-detects type)
packet, err := factory.CreateFromBytes(rawData)
Packet Registration
Packets can be registered globally or per-version:
// Register for all versions
factory.RegisterPacket(opcode, constructor)
// Register version-specific
factory.RegisterVersionedPacket(opcode, version, constructor)
// Bulk registration
factory.RegisterPackets(map[uint16]PacketConstructor{
opcodes.OP_LoginRequestMsg: NewLoginRequest,
opcodes.OP_CharacterCreate: NewCharacterCreate,
})
Integration with PacketManager
The factory can work with the defs package for automatic struct-based packets:
// Set packet manager for automatic struct registration
factory.SetPacketManager(defs.GetManager(version))
// Now factory can create packets from struct definitions
packet := factory.CreatePacket(opcodes.OP_CreateCharacterRequestMsg)
// Returns properly versioned CreateCharacter struct
Usage Example
Complete example showing protocol usage:
package main
import (
"git.sharkk.net/EQ2/Protocol/opcodes"
"git.sharkk.net/EQ2/Protocol/defs"
"git.sharkk.net/EQ2/Protocol/stream"
"git.sharkk.net/EQ2/Protocol/factory"
)
func main() {
version := uint16(60085)
// Initialize managers
opcodeManager := opcodes.GetManager(version, db, "opcodes")
packetManager := defs.GetManager(version)
// Setup factory
factory := factory.GetInstance()
factory.SetPacketManager(packetManager)
// Create stream for connection
stream := stream.NewPacketStream(conn, 8192)
// Handle incoming packets
stream.SetIncomingHandler(func(packet packets.Packet) {
// Translate opcode
emuOpcode := opcodeManager.EQToEmu(packet.GetOpcode())
// Process based on opcode
switch emuOpcode {
case opcodes.OP_LoginRequestMsg:
handleLogin(packet)
case opcodes.OP_CreateCharacterRequestMsg:
handleCharacterCreate(packet)
}
})
// Send a packet
packet := packetManager.CreateInstance("LSWorldList")
populateWorldList(packet)
// Convert to client opcode
clientOpcode := opcodeManager.EmuToEQ(opcodes.OP_WorldListMsg)
packet.SetOpcode(clientOpcode)
// Send to client
stream.Send(packet)
// Start processing
stream.Start()
}
Protocol Architecture
The protocol library is organized in layers:
- Opcodes: Manages opcode mappings and translations
- Defs: Defines packet structures with version management
- Packets: Base packet types and interfaces
- Factory: Creates packet instances from opcodes/data
- Stream: Handles network I/O, fragmentation, and reassembly
Each layer builds on the previous, providing a complete protocol implementation for EQ2 server emulation.