9.6 KiB
EQ2EMu Protocol Structure Report
Overview
The EQ2EMu protocol is a custom UDP-based networking protocol designed for EverQuest II server emulation. It implements reliability, compression, encryption, and packet fragmentation on top of UDP.
Core Architecture
- Protocol Layers
Application Layer
- EQApplicationPacket: High-level game packets containing game logic
- PacketStruct: Dynamic packet structure system using XML definitions
- DataBuffer: Serialization/deserialization buffer management
Protocol Layer
- EQProtocolPacket: Low-level protocol packets with sequencing and reliability
- EQStream: Stream management with connection state, retransmission, and flow control
- EQPacket: Base packet class with common functionality
- Packet Types
Protocol Control Packets (opcodes.h)
OP_SessionRequest = 0x01 // Client initiates connection OP_SessionResponse = 0x02 // Server accepts connection OP_Combined = 0x03 // Multiple packets combined OP_SessionDisconnect = 0x05 // Connection termination OP_KeepAlive = 0x06 // Keep connection alive OP_ServerKeyRequest = 0x07 // Request encryption key OP_SessionStatResponse = 0x08 // Connection statistics OP_Packet = 0x09 // Regular data packet OP_Fragment = 0x0d // Fragmented packet piece OP_OutOfOrderAck = 0x11 // Acknowledge out-of-order packet OP_Ack = 0x15 // Acknowledge packet receipt OP_AppCombined = 0x19 // Combined application packets OP_OutOfSession = 0x1d // Out of session notification
-
Connection Flow
-
Session Establishment
- Client sends OP_SessionRequest with session parameters
- Server responds with OP_SessionResponse containing session ID and encryption key
- RC4 encryption initialized using the provided key
- Data Transfer
- Packets are sequenced (16-bit sequence numbers)
- Reliable packets require acknowledgment
- Retransmission on timeout (default 500ms * 3.0 multiplier)
- Packet combining for efficiency (max 256 bytes combined)
- Session Termination
- Either side sends OP_SessionDisconnect
- Graceful shutdown with cleanup of pending packets
- Key Features
Compression
- Uses zlib compression (indicated by 0x5a flag byte)
- Applied after protocol header
- Decompression required before processing
Encryption
- RC4 stream cipher
- Separate keys for client/server directions
- Client uses ~key (bitwise NOT), server uses key directly
- 20 bytes of dummy data prime both ciphers
CRC Validation
- Custom CRC16 implementation
- Applied to entire packet except last 2 bytes
- Session packets exempt from CRC
Fragmentation
- Large packets split into fragments
- Each fragment marked with OP_Fragment
- Reassembly at receiver before processing
- Packet Structure
Basic Protocol Packet
[1-2 bytes] Opcode (1 byte if < 0xFF, otherwise 2 bytes) [variable] Payload [2 bytes] CRC16 (if applicable)
Combined Packet Format
[1 byte] Size of packet 1 [variable] Packet 1 data [1 byte] Size of packet 2 [variable] Packet 2 data ...
- Dynamic Packet System
The PacketStruct system allows runtime-defined packet structures:
- XML Configuration: Packet structures defined in XML files
- Version Support: Multiple versions per packet type
- Data Types: int8/16/32/64, float, double, string, array, color, equipment
- Conditional Fields: Fields can be conditional based on other field values
- Oversized Support: Dynamic sizing for variable-length fields
Example structure:
- Opcode Management
- Dynamic Mapping: Runtime opcode mapping via configuration files
- Version Support: Different opcode sets per client version
- Name Resolution: String names mapped to numeric opcodes
- Shared Memory Option: Multi-process opcode sharing support
-
Implementation Considerations for Porting
-
Memory Management
- Extensive use of dynamic allocation
- Custom safe_delete macros for cleanup
- Reference counting for shared packets
- Threading
- Mutex protection for all shared resources
- Separate threads for packet processing
- Condition variables for synchronization
- Platform Dependencies
- POSIX sockets and threading
- Network byte order conversions
- Platform-specific timing functions
- Performance Optimizations
- Packet combining to reduce overhead
- Preallocated buffers for common sizes
- Fast CRC table lookup
- Error Handling
- Graceful degradation on errors
- Extensive logging for debugging
- Automatic retransmission on failure
-
Critical Classes to Port
-
EQStream: Core connection management
-
EQPacket/EQProtocolPacket: Packet representation
-
PacketStruct: Dynamic packet structure system
-
DataBuffer: Serialization utilities
-
OpcodeManager: Opcode mapping system
-
Crypto: RC4 encryption wrapper
-
CRC16: Custom CRC implementation
-
Protocol Quirks
- Opcode byte order depends on value (0x00 prefix for opcodes < 0xFF)
- Special handling for session control packets (no CRC)
- 20-byte RC4 priming sequence required
- Custom CRC polynomial (0xEDB88320 reversed)
- Compression flag at variable offset based on opcode size
This protocol is designed for reliability over UDP while maintaining low latency for game traffic. The dynamic packet structure system allows flexibility in supporting multiple client versions without recompilation.
UDP Network Layer Architecture
- EQStreamFactory - The UDP Socket Manager
- Opens and manages the main UDP socket (line 148: socket(AF_INET, SOCK_DGRAM, 0))
- Binds to a specific port (line 153: bind())
- Sets socket to non-blocking mode (line 159: fcntl(sock, F_SETFL, O_NONBLOCK))
- Runs three main threads:
- ReaderLoop(): Uses recvfrom() to read incoming UDP packets (line 228)
- WriterLoop(): Manages outgoing packet transmission
- CombinePacketLoop(): Combines small packets for efficiency
- EQStream - Per-Connection Protocol Handler
- Manages individual client connections over the shared UDP socket
- Handles packet serialization/deserialization
- Implements reliability layer (sequencing, acknowledgments, retransmission)
- WritePacket() method uses sendto() for actual UDP transmission (line 1614)
- Key UDP Operations:
Receiving (EQStreamFactory::ReaderLoop):
recvfrom(sock, buffer, 2048, 0, (struct sockaddr *)&from, (socklen_t *)&socklen)
- Uses select() for non-blocking I/O
- Creates new EQStream instances for new connections (OP_SessionRequest)
- Routes packets to existing streams by IP:port mapping
Sending (EQStream::WritePacket):
sendto(eq_fd,(char *)buffer,length,0,(sockaddr *)&address,sizeof(address))
- Serializes protocol packets to byte arrays
- Applies compression and encryption if enabled
- Adds CRC checksums
- Sends via UDP to specific client address
- Connection Multiplexing:
- Single UDP socket serves multiple clients
- Stream identification by IP:port combination (sprintf(temp, "%u.%d", ntohl(from.sin_addr.s_addr), ntohs(from.sin_port)))
- Thread-safe stream management with mutex protection
So yes, EQStream and EQStreamFactory together provide a complete UDP networking layer that implements a reliable, connection-oriented protocol on top of unreliable UDP datagrams. The factory manages the socket and dispatches packets to individual stream instances that handle the protocol logic for each client connection.
TCP Usage in EQ2EMu
The TCP implementation serves a completely separate purpose from the main game client communication:
- HTTP Web Server (web_server.hpp)
- Purpose: Administrative/monitoring interface
- Features:
- REST API endpoints (/version, / root)
- SSL/TLS support with certificate-based encryption
- HTTP Basic Authentication with session management
- Database-driven user authentication and authorization levels
- JSON response formatting for API calls
- Use Cases:
- Server status monitoring
- Administrative controls
- Third-party tool integration
- Web-based dashboards
- Server-to-Server Communication (tcp_connection.hpp)
- Purpose: Inter-server communication within EQ2EMu cluster
- Features:
- Custom packet framing protocol
- Optional zlib compression for bandwidth efficiency
- Message relay capabilities between servers
- Connection state management
- Use Cases:
- Login server to world server communication
- Zone server coordination
- Cross-server messaging
- Load balancing and failover
Key Differences: UDP vs TCP
| Aspect | UDP (Game Clients) | TCP (Admin/Server)
---- | ||
Protocol | Custom EQ2 protocol with reliability layer | Standard HTTP/Custom framing |
Encryption | RC4 stream cipher | SSL/TLS or none |
Clients | Game clients (players) | Web browsers/Admin tools/Other |
servers | ||
Port | 9001 (World), 9100 (Login) | Configurable web port |
Threading | 3 worker threads per factory | Thread-per-connection |
Architecture Summary
Game Clients ←→ [UDP EQStream] ←→ World/Login Servers ↕ Admin Tools ←→ [TCP WebServer] ←→ World/Login Servers ↕ Other Servers ←→ [TCP Connection] ←→ World/Login Servers
The TCP implementation provides out-of-band management and inter-server communication, while the UDP implementation handles all real-time game traffic. This separation allows for robust administration without interfering with game performance.