Emu/source/common/EQStream.cpp

2733 lines
74 KiB
C++

// EQ2Emulator: Everquest II Server Emulator
// Copyright (C) 2007 EQ2EMulator Development Team
// Licensed under GPL v3 - see <http://www.gnu.org/licenses/>
#include "debug.h"
// Standard library headers
#include <string>
#include <iomanip>
#include <iostream>
#include <vector>
#include <time.h>
#include <sys/types.h>
#include <algorithm>
#include <memory>
// Unix networking headers
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <netdb.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <unistd.h>
// Project headers
#include "EQPacket.h"
#include "EQStream.h"
#include "EQStreamFactory.h"
#include "misc.h"
#include "Mutex.h"
#include "op_codes.h"
#include "crypto/crc.h"
#include "packet_dump.h"
#include "EQ2_Common_Structs.h"
#include "Log.h"
#ifdef LOGIN
#include "../LoginServer/login_structs.h"
#endif
//#define DEBUG_EMBEDDED_PACKETS 1
// Static configuration
uint16 EQStream::MaxWindowSize = 2048;
/**
* Initialize the EQ stream with default values.
*
* @param resetSession - Whether to reset session-specific data
*/
void EQStream::init(bool resetSession)
{
if (resetSession) {
streamactive = false;
sessionAttempts = 0;
}
timeout_delays = 0;
// Initialize usage tracking
MInUse.lock();
active_users = 0;
MInUse.unlock();
// Initialize session state
Session = 0;
Key = 0;
MaxLen = 0;
NextInSeq = 0;
NextOutSeq = 0;
CombinedAppPacket = nullptr;
// Initialize acknowledgment tracking
MAcks.lock();
MaxAckReceived = -1;
NextAckToSend = -1;
LastAckSent = -1;
MAcks.unlock();
// Initialize sequence tracking
LastSeqSent = -1;
MaxSends = 5;
LastPacket = Timer::GetCurrentTime2();
// Initialize buffers
oversize_buffer = nullptr;
oversize_length = 0;
oversize_offset = 0;
Factory = nullptr;
rogue_buffer = nullptr;
roguebuf_offset = 0;
roguebuf_size = 0;
// Initialize rate limiting
MRate.lock();
RateThreshold = RATEBASE / 250;
DecayRate = DECAYBASE / 250;
MRate.unlock();
// Initialize flow control
BytesWritten = 0;
SequencedBase = 0;
AverageDelta = 500;
// Initialize encryption
crypto->setRC4Key(0);
// Initialize retransmission
retransmittimer = Timer::GetCurrentTime2();
retransmittimeout = 500 * RETRANSMIT_TIMEOUT_MULT;
// Initialize reconnection
reconnectAttempt = 0;
// Validate queue consistency
if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) {
LogWrite(PACKET__DEBUG, 9, "Packet",
"init Invalid Sequenced queue: BS %u + SQ %u != NOS %u",
SequencedBase, SequencedQueue.size(), NextOutSeq);
}
}
/**
* EQStream constructor with socket address.
* Initializes all stream components and sets up compression.
*
* @param addr - Socket address of remote endpoint
*/
EQStream::EQStream(sockaddr_in addr)
{
// Initialize crypto and timers
crypto = new Crypto();
resend_que_timer = new Timer(1000);
combine_timer = new Timer(250); // 250 milliseconds
combine_timer->Start();
resend_que_timer->Start();
// Initialize base stream
init();
// Set remote endpoint
remote_ip = addr.sin_addr.s_addr;
remote_port = addr.sin_port;
// Set initial state
State = CLOSED;
StreamType = UnknownStream;
compressed = true;
encoded = false;
app_opcode_size = 2;
// Initialize compression stream (Unix only)
bzero(&stream, sizeof(z_stream));
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;
deflateInit2(&stream, 9, Z_DEFLATED, 13, 9, Z_DEFAULT_STRATEGY);
// Initialize compression state
compressed_offset = 0;
client_version = 0;
received_packets = 0;
sent_packets = 0;
#ifdef WRITE_PACKETS
// Initialize packet logging
write_packets = nullptr;
char write_packets_filename[64];
snprintf(write_packets_filename, sizeof(write_packets_filename),
"PacketLog%i.log", Timer::GetCurrentTime2());
write_packets = fopen(write_packets_filename, "w+");
#endif
}
/**
* Process encrypted packet data and extract the embedded packet.
* Decrypts the data and extracts the opcode and payload.
*
* @param data - Encrypted data buffer
* @param size - Size of encrypted data
* @param opcode - Reference to opcode (will be modified)
* @return Newly created EQProtocolPacket with decrypted data
*/
EQProtocolPacket* EQStream::ProcessEncryptedData(uchar* data, int32 size, int16 opcode)
{
// Decrypt the data using RC4
crypto->RC4Decrypt(data, size);
int8 offset = 0;
if (data[0] == 0xFF && size > 2) {
// Extended opcode format (2-byte opcode)
offset = 3;
memcpy(&opcode, data + sizeof(int8), sizeof(int16));
} else {
// Standard opcode format (1-byte opcode)
offset = 1;
memcpy(&opcode, data, sizeof(int8));
}
return new EQProtocolPacket(opcode, data + offset, size - offset);
}
/**
* Process an encrypted protocol packet.
* Determines the correct offset and calls ProcessEncryptedData.
*
* @param p - Encrypted protocol packet to process
* @return Newly created EQProtocolPacket with decrypted data
*/
EQProtocolPacket* EQStream::ProcessEncryptedPacket(EQProtocolPacket* p)
{
EQProtocolPacket* ret = nullptr;
if (p->opcode == OP_Packet && p->size > 2) {
// Skip the first 2 bytes for OP_Packet
ret = ProcessEncryptedData(p->pBuffer + 2, p->size - 2, p->opcode);
} else {
// Process entire buffer for other opcodes
ret = ProcessEncryptedData(p->pBuffer, p->size, p->opcode);
}
return ret;
}
/**
* Process an embedded encrypted packet within another packet.
* Decrypts and queues the embedded packet for processing.
*
* @param pBuffer - Buffer containing embedded packet data
* @param length - Length of embedded packet
* @param opcode - Opcode for the embedded packet
* @return true if packet was successfully processed
*/
bool EQStream::ProcessEmbeddedPacket(uchar* pBuffer, int16 length, int8 opcode)
{
if (!pBuffer || !crypto->isEncrypted()) {
return false;
}
// Process encrypted data with thread safety
MCombineQueueLock.lock();
EQProtocolPacket* newpacket = ProcessEncryptedData(pBuffer, length, opcode);
MCombineQueueLock.unlock();
if (newpacket) {
#ifdef DEBUG_EMBEDDED_PACKETS
printf("Embedded Packet - Opcode: %u\n", newpacket->opcode);
DumpPacket(newpacket->pBuffer, newpacket->size);
#endif
// Convert to application packet and queue
EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2);
if (ap->version == 0) {
ap->version = client_version;
}
InboundQueuePush(ap);
#ifdef WRITE_PACKETS
WritePackets(ap->GetOpcodeName(), pBuffer, length, false);
#endif
safe_delete(newpacket);
return true;
}
return false;
}
/**
* Handle embedded packets within a protocol packet.
* Processes different types of embedded packet formats.
*
* @param p - Protocol packet containing embedded data
* @param offset - Offset to start of embedded data
* @param length - Length of embedded data (0 = auto-detect)
* @return true if embedded packet was successfully processed
*/
bool EQStream::HandleEmbeddedPacket(EQProtocolPacket* p, int16 offset, int16 length)
{
if (!p) {
return false;
}
#ifdef DEBUG_EMBEDDED_PACKETS
printf("HandleEmbeddedPacket - offset: %u, length: %u, size: %u\n",
offset, length, p->size);
#endif
// Ensure we have enough data for the offset
if (p->size < static_cast<uint32>(offset + 2)) {
return false;
}
// Check for OP_AppCombined embedded packet (0x00 0x19)
if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0x19) {
uint32 data_length = 0;
if (length == 0) {
// Auto-detect length
data_length = p->size - offset - 2;
} else {
// Use provided length
if (length < 2) {
return false; // Length too short
}
data_length = length - 2;
}
#ifdef DEBUG_EMBEDDED_PACKETS
printf("Processing OP_AppCombined - offset: %u, data_length: %u\n",
offset, data_length);
DumpPacket(p->pBuffer, p->size);
#endif
// Verify bounds
if (offset + 2 + data_length > p->size) {
return false; // Out of bounds
}
// Create and process sub-packet
auto subp = new EQProtocolPacket(OP_AppCombined,
p->pBuffer + offset + 2,
data_length);
subp->copyInfo(p);
ProcessPacket(subp, p);
safe_delete(subp);
return true;
}
// Check for null opcode embedded packet (0x00 0x00)
else if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0) {
if (length == 0) {
length = p->size - 1 - offset;
} else {
length--;
}
#ifdef DEBUG_EMBEDDED_PACKETS
printf("Processing null opcode embedded packet\n");
DumpPacket(p->pBuffer + 1 + offset, length);
#endif
uchar* buffer = (p->pBuffer + 1 + offset);
bool valid = ProcessEmbeddedPacket(buffer, length);
if (valid) {
return true;
}
}
// Check for general embedded packet (not 0xffffffff)
else if (offset + 4 < p->size && ntohl(*(uint32*)(p->pBuffer + offset)) != 0xffffffff) {
#ifdef DEBUG_EMBEDDED_PACKETS
uint16 seq = NextInSeq - 1;
sint8 check = 0;
if (offset == 2) {
seq = ntohs(*(uint16*)(p->pBuffer));
check = CompareSequence(NextInSeq, seq);
}
printf("Processing general embedded packet - offset: %u, length: %u, size: %u, check: %i, seq: %u\n",
offset, length, p->size, check, seq);
DumpPacket(p->pBuffer, p->size);
#endif
if (length == 0) {
length = p->size - offset;
}
uchar* buffer = (p->pBuffer + offset);
bool valid = ProcessEmbeddedPacket(buffer, length);
if (valid) {
return true;
}
}
// Check for length-prefixed embedded packet (not 0xff, then 0xff)
else if (p->pBuffer[offset] != 0xff &&
p->pBuffer[offset + 1] == 0xff &&
p->size >= offset + 3) {
// Get total length from first byte
uint16 total_length = p->pBuffer[offset];
// Verify packet size matches expected length
if (total_length + offset + 2 == p->size && total_length >= 2) {
uint32 data_length = total_length - 2;
// Create and process sub-packet
auto subp = new EQProtocolPacket(p->pBuffer + offset + 2,
data_length, OP_Packet);
subp->copyInfo(p);
ProcessPacket(subp, p);
delete subp;
return true;
}
}
return false;
}
/**
* Process a protocol packet and handle various packet types.
* This is the main packet processing dispatcher for the stream.
*
* @param p - Protocol packet to process
* @param lastp - Previous packet in sequence (for context)
*/
void EQStream::ProcessPacket(EQProtocolPacket* p, EQProtocolPacket* lastp)
{
uint32 processed = 0;
uint32 subpacket_length = 0;
if (!p) {
return;
}
// Ensure session is initialized for non-session packets
if (p->opcode != OP_SessionRequest &&
p->opcode != OP_SessionResponse &&
!Session) {
#ifdef EQN_DEBUG
LogWrite(PACKET__ERROR, 0, "Packet",
"Session not initialized, packet ignored");
#endif
return;
}
// Process packet based on opcode
switch (p->opcode) {
case OP_Combined: {
// Handle combined packets containing multiple sub-packets
processed = 0;
int8 offset = 0;
int count = 0;
#ifdef LE_DEBUG
printf("Processing OP_Combined packet\n");
DumpPacket(p);
#endif
// Process all sub-packets within the combined packet
while (processed < p->size) {
// Determine sub-packet length and offset
subpacket_length = static_cast<unsigned char>(*(p->pBuffer + processed));
if (subpacket_length == 0xff) {
// Extended length format (2-byte length)
subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1));
offset = 3;
} else {
// Standard format (1-byte length)
offset = 1;
}
count++;
#ifdef LE_DEBUG
printf("Processing sub-packet %i: length=%u, offset=%u\n",
count, subpacket_length, processed);
#endif
// Check if this is a valid protocol packet
bool isSubPacket = EQProtocolPacket::IsProtocolPacket(
p->pBuffer + processed + offset, subpacket_length, false);
if (isSubPacket) {
// Create and process sub-packet
auto subp = new EQProtocolPacket(
p->pBuffer + processed + offset, subpacket_length);
subp->copyInfo(p);
#ifdef LE_DEBUG
printf("Sub-packet opcode: %i\n", subp->opcode);
DumpPacket(subp);
#endif
ProcessPacket(subp, p);
delete subp;
}
else {
// Handle non-protocol packet data (encrypted)
offset = 1;
// Check for potential garbage packet
uint16 header_check = ntohs(*reinterpret_cast<uint16_t*>(
p->pBuffer + processed + offset));
if (header_check <= 0x1e) {
// Process as potential OP_Packet
subpacket_length = static_cast<unsigned char>(
*(p->pBuffer + processed));
LogWrite(PACKET__ERROR, 0, "Packet",
"Processing suspected garbage packet as OP_Packet");
DumpPacket(p->pBuffer + processed + offset, subpacket_length);
uchar* newbuf = p->pBuffer + processed + offset;
auto subp = new EQProtocolPacket(newbuf, subpacket_length);
subp->copyInfo(p);
ProcessPacket(subp, p);
delete subp;
} else {
// Decrypt the data and process
crypto->RC4Decrypt(p->pBuffer + processed + offset, subpacket_length);
LogWrite(PACKET__ERROR, 0, "Packet",
"Processing encrypted sub-packet: "
"processed=%u, offset=%u, count=%i, length=%u",
processed, offset, count, subpacket_length);
if (p->pBuffer[processed + offset] == 0xff) {
// Skip 0xff marker and process data
uchar* newbuf = p->pBuffer + processed + offset + 1;
DumpPacket(p->pBuffer + processed + offset, subpacket_length);
auto subp = new EQProtocolPacket(
newbuf, subpacket_length, OP_Packet);
subp->copyInfo(p);
ProcessPacket(subp, p);
delete subp;
} else {
// Invalid packet format - stop processing
break;
}
}
}
// Move to next sub-packet
processed += subpacket_length + offset;
}
break;
}
case OP_AppCombined: {
// Handle application combined packets
processed = 0;
int8 offset = 0;
int count = 0;
#ifdef DEBUG_EMBEDDED_PACKETS
printf("Processing OP_AppCombined packet\n");
DumpPacket(p);
#endif
while (processed < p->size) {
count++;
// Determine sub-packet length and offset
subpacket_length = static_cast<unsigned char>(*(p->pBuffer + processed));
if (subpacket_length == 0xff) {
// Extended length format
subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1));
offset = 3;
} else {
// Standard length format
offset = 1;
}
// Handle RSA key exchange if no encryption key is set
if (crypto->getRC4Key() == 0 && p && subpacket_length > 8 + offset) {
#ifdef DEBUG_EMBEDDED_PACKETS
DumpPacket(p->pBuffer, p->size);
#endif
p->pBuffer += offset;
processRSAKey(p, subpacket_length);
p->pBuffer -= offset;
} else if (crypto->isEncrypted()) {
// Process encrypted embedded packet
#ifdef DEBUG_EMBEDDED_PACKETS
printf("Processing encrypted sub-packet %i: length=%u, offset=%u\n",
count, subpacket_length, processed);
DumpPacket(p->pBuffer + processed + offset, subpacket_length);
#endif
if (!HandleEmbeddedPacket(p, processed + offset, subpacket_length)) {
uchar* buffer = (p->pBuffer + processed + offset);
if (!ProcessEmbeddedPacket(buffer, subpacket_length, OP_AppCombined)) {
LogWrite(PACKET__ERROR, 0, "Packet",
"ProcessEmbeddedPacket failed for OP_AppCombined");
}
}
}
// Move to next sub-packet
processed += subpacket_length + offset;
}
break;
}
case OP_Packet: {
// Handle sequenced data packets
if (!p->pBuffer || (p->Size() < 4)) {
break; // Invalid packet
}
// Extract sequence number and check ordering
uint16 seq = ntohs(*(uint16*)(p->pBuffer));
sint8 check = CompareSequence(NextInSeq, seq);
if (check == SeqFuture) {
// Future packet - store for later processing
#ifdef EQN_DEBUG
LogWrite(PACKET__DEBUG, 1, "Packet",
"Future packet: Expected=%i, Got=%i", NextInSeq, seq);
p->DumpRawHeader(seq);
#endif
OutOfOrderpackets[seq] = p->Copy();
// Note: Not sending out-of-order ACK to prevent loops
} else if (check == SeqPast) {
// Duplicate/old packet - acknowledge but don't process
#ifdef EQN_DEBUG
LogWrite(PACKET__DEBUG, 1, "Packet",
"Duplicate packet: Expected=%i, Got=%i", NextInSeq, seq);
p->DumpRawHeader(seq);
#endif
SendOutOfOrderAck(seq);
} else {
// In-order packet - process normally
EQProtocolPacket* qp = RemoveQueue(seq);
if (qp) {
LogWrite(PACKET__DEBUG, 1, "Packet",
"Removing older queued packet with sequence %i", seq);
delete qp;
}
// Update sequence tracking
SetNextAckToSend(seq);
NextInSeq++;
// Try to handle as embedded packet first
if (HandleEmbeddedPacket(p)) {
break;
}
// Handle RSA key exchange if no encryption key is set
if (crypto->getRC4Key() == 0 && p && p->size >= 69) {
#ifdef DEBUG_EMBEDDED_PACKETS
DumpPacket(p->pBuffer, p->size);
#endif
processRSAKey(p);
} else if (crypto->isEncrypted() && p) {
// Process encrypted packet
MCombineQueueLock.lock();
EQProtocolPacket* newpacket = ProcessEncryptedPacket(p);
MCombineQueueLock.unlock();
if (newpacket) {
// Convert to application packet and queue
EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2);
if (ap->version == 0) {
ap->version = client_version;
}
#ifdef WRITE_PACKETS
WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false);
#endif
InboundQueuePush(ap);
safe_delete(newpacket);
}
}
}
break;
}
case OP_Fragment: {
if (!p->pBuffer || (p->Size() < 4))
{
break;
}
uint16 seq=ntohs(*(uint16 *)(p->pBuffer));
sint8 check=CompareSequence(NextInSeq,seq);
if (check == SeqFuture) {
#ifdef EQN_DEBUG
LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq);
LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]");
//p->DumpRawHeader(seq);
LogWrite(PACKET__DEBUG, 1, "Packet", "[End]");
#endif
OutOfOrderpackets[seq] = p->Copy();
//SendOutOfOrderAck(seq);
} else if (check == SeqPast) {
#ifdef EQN_DEBUG
LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq);
LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]");
//p->DumpRawHeader(seq);
LogWrite(PACKET__DEBUG, 1, "Packet", "[End]");
#endif
//OutOfOrderpackets[seq] = p->Copy();
SendOutOfOrderAck(seq);
} else {
// In case we did queue one before as well.
EQProtocolPacket* qp = RemoveQueue(seq);
if (qp) {
LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq);
delete qp;
}
SetNextAckToSend(seq);
NextInSeq++;
if (oversize_buffer) {
memcpy(oversize_buffer+oversize_offset,p->pBuffer+2,p->size-2);
oversize_offset+=p->size-2;
//cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-2) << ") Seq=" << seq << endl;
if (oversize_offset==oversize_length) {
if (*(p->pBuffer+2)==0x00 && *(p->pBuffer+3)==0x19) {
EQProtocolPacket *subp=new EQProtocolPacket(oversize_buffer,oversize_offset);
subp->copyInfo(p);
ProcessPacket(subp, p);
delete subp;
} else {
if(crypto->isEncrypted() && p && p->size > 2){
MCombineQueueLock.lock();
EQProtocolPacket* p2 = ProcessEncryptedData(oversize_buffer, oversize_offset, p->opcode);
MCombineQueueLock.unlock();
EQApplicationPacket* ap = p2->MakeApplicationPacket(2);
ap->copyInfo(p);
if (ap->version == 0)
ap->version = client_version;
#ifdef WRITE_PACKETS
WritePackets(ap->GetOpcodeName(), oversize_buffer, oversize_offset, false);
#endif
ap->copyInfo(p);
InboundQueuePush(ap);
safe_delete(p2);
}
}
delete[] oversize_buffer;
oversize_buffer=NULL;
oversize_offset=0;
}
} else if (!oversize_buffer) {
oversize_length=ntohl(*(uint32 *)(p->pBuffer+2));
oversize_buffer=new unsigned char[oversize_length];
memcpy(oversize_buffer,p->pBuffer+6,p->size-6);
oversize_offset=p->size-6;
//cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-6) << ") Seq=" << seq << endl;
}
}
}
break;
case OP_KeepAlive: {
#ifndef COLLECTOR
NonSequencedPush(new EQProtocolPacket(p->opcode,p->pBuffer,p->size));
#endif
}
break;
case OP_Ack: {
if (!p->pBuffer || (p->Size() < 4))
{
LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_Ack that was of malformed size");
break;
}
uint16 seq = ntohs(*(uint16*)(p->pBuffer));
AckPackets(seq);
retransmittimer = Timer::GetCurrentTime2();
}
break;
case OP_SessionRequest: {
LogWrite(PACKET__INFO, 0, "Packet", "=== EQ2 PROTOCOL SESSION REQUEST ===");
struct in_addr addr;
addr.s_addr = GetRemoteIP();
LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionRequest received from %s:%d", inet_ntoa(addr), GetRemotePort());
if (p->Size() < sizeof(SessionRequest))
{
LogWrite(PACKET__ERROR, 0, "Packet", "SessionRequest packet too small: %d bytes", p->Size());
break;
}
if (GetState() == ESTABLISHED) {
//_log(NET__ERROR, _L "Received OP_SessionRequest in ESTABLISHED state (%d) streamactive (%i) attempt (%i)" __L, GetState(), streamactive, sessionAttempts);
// client seems to try a max of 4 times (initial +3 retries) then gives up, giving it a few more attempts just in case
// streamactive means we identified the opcode, we cannot re-establish this connection
if (streamactive || (sessionAttempts > 30))
{
SendDisconnect(false);
SetState(CLOSED);
break;
}
}
sessionAttempts++;
if(GetState() == WAIT_CLOSE) {
printf("WAIT_CLOSE Reconnect with streamactive %u, sessionAttempts %u\n", streamactive, sessionAttempts);
reconnectAttempt++;
}
init(GetState() != ESTABLISHED);
OutboundQueueClear();
SessionRequest *Request=(SessionRequest *)p->pBuffer;
Session=ntohl(Request->Session);
SetMaxLen(ntohl(Request->MaxLength));
LogWrite(PACKET__INFO, 0, "Packet", "SessionRequest: Session=0x%08X, MaxLength=%d", Session, MaxLen);
#ifndef COLLECTOR
NextInSeq=0;
Key=0x33624702;
LogWrite(PACKET__INFO, 0, "Packet", "Generated Session Key: 0x%08X", Key);
SendSessionResponse();
#endif
SetState(ESTABLISHED);
LogWrite(PACKET__INFO, 0, "Packet", "Session established, state set to ESTABLISHED");
}
break;
/**
* OP_SessionResponse: Handle session establishment response from server
* Contains stream parameters, encryption keys, and stream type information
*/
case OP_SessionResponse:
{
LogWrite(PACKET__INFO, 0, "Packet", "=== EQ2 PROTOCOL SESSION RESPONSE ===");
struct in_addr addr;
addr.s_addr = GetRemoteIP();
LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionResponse received from %s:%d", inet_ntoa(addr), GetRemotePort());
// Validate packet size for SessionResponse structure
if (p->Size() < sizeof(SessionResponse))
{
LogWrite(PACKET__ERROR, 0, "Packet", "SessionResponse packet too small: %d bytes", p->Size());
break;
}
// Initialize stream and clear outbound queue
init();
OutboundQueueClear();
SetActive(true);
// Extract session response data
auto Response = reinterpret_cast<SessionResponse*>(p->pBuffer);
SetMaxLen(ntohl(Response->MaxLength));
Key = ntohl(Response->Key);
NextInSeq = 0;
SetState(ESTABLISHED);
// Set session ID if not already established
if (!Session)
{
Session = ntohl(Response->Session);
}
LogWrite(PACKET__INFO, 0, "Packet", "SessionResponse: Session=0x%08X, Key=0x%08X, MaxLength=%d", Session, Key, MaxLen);
// Extract compression and encoding flags
compressed = (Response->Format & FLAG_COMPRESSED);
encoded = (Response->Format & FLAG_ENCODED);
LogWrite(PACKET__INFO, 0, "Packet", "Stream flags: Format=0x%02X, Compressed=%s, Encoded=%s",
Response->Format, compressed ? "YES" : "NO", encoded ? "YES" : "NO");
// Determine stream type based on format flags and port
if (compressed)
{
if (remote_port == 9000 || (remote_port == 0 && p->src_port == 9000))
{
SetStreamType(WorldStream);
LogWrite(PACKET__INFO, 0, "Packet", "Stream type set to: WorldStream (port=%d)", remote_port);
}
else
{
SetStreamType(ZoneStream);
LogWrite(PACKET__INFO, 0, "Packet", "Stream type set to: ZoneStream (port=%d)", remote_port);
}
}
else if (encoded)
{
SetStreamType(ChatOrMailStream);
LogWrite(PACKET__INFO, 0, "Packet", "Stream type set to: ChatOrMailStream");
}
else
{
SetStreamType(LoginStream);
LogWrite(PACKET__INFO, 0, "Packet", "Stream type set to: LoginStream");
}
LogWrite(PACKET__INFO, 0, "Packet", "=== SESSION ESTABLISHMENT COMPLETE ===");
}
break;
/**
* OP_SessionDisconnect: Handle graceful disconnect request
* Initiates proper connection termination sequence
*/
case OP_SessionDisconnect:
{
// Send disconnect acknowledgment to peer
SendDisconnect();
}
break;
/**
* OP_OutOfOrderAck: Handle out-of-order packet acknowledgment
* Marks specific packets as acknowledged and flags earlier packets for retransmission
*/
case OP_OutOfOrderAck:
{
// Validate packet structure and size
if (!p->pBuffer || (p->Size() < 4))
{
LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck that was of malformed size");
break;
}
// Extract sequence number from packet
uint16 seq = ntohs(*reinterpret_cast<uint16*>(p->pBuffer));
MOutboundQueue.lock();
// Validate sequenced queue integrity (pre-processing)
if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq)
{
LogWrite(PACKET__DEBUG, 9, "Packet", "Pre-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u",
SequencedBase, SequencedQueue.size(), NextOutSeq);
}
// Verify that acknowledged packet is within valid window
if (CompareSequence(SequencedBase, seq) != SeqPast && CompareSequence(NextOutSeq, seq) == SeqPast)
{
uint16 sqsize = SequencedQueue.size();
uint16 index = seq - SequencedBase;
LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck marking packet acked in queue (queue index = %u, queue size = %u)",
index, sqsize);
// Mark the specific packet as acknowledged
if (index < sqsize)
{
SequencedQueue[index]->acked = true;
// Flag earlier unacknowledged packets for retransmission
uint16 count = 0;
uint32 timeout = AverageDelta * 2 + 100;
for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end() && count < index; ++sitr, ++count)
{
// Check if packet needs retransmission based on timeout
if (!(*sitr)->acked && (*sitr)->sent_time > 0 &&
(((*sitr)->sent_time + timeout) < Timer::GetCurrentTime2()))
{
(*sitr)->sent_time = 0; // Mark for resend
LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck Flagging packet %u for retransmission",
SequencedBase + count);
}
}
}
// Update retransmit timer if enabled
if (RETRANSMIT_TIMEOUT_MULT)
{
retransmittimer = Timer::GetCurrentTime2();
}
}
else
{
LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck for out-of-window %u. Window (%u->%u)",
seq, SequencedBase, NextOutSeq);
}
// Validate sequenced queue integrity (post-processing)
if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq)
{
LogWrite(PACKET__DEBUG, 9, "Packet", "Post-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u",
SequencedBase, SequencedQueue.size(), NextOutSeq);
}
MOutboundQueue.unlock();
}
break;
/**
* OP_ServerKeyRequest: Handle server key exchange request
* Processes client session statistics and initiates key exchange
*/
case OP_ServerKeyRequest:
{
// Validate packet size for ClientSessionStats structure
if (p->Size() < sizeof(ClientSessionStats))
{
break;
}
// Extract client session statistics
auto Stats = reinterpret_cast<ClientSessionStats*>(p->pBuffer);
int16 request_id = Stats->RequestID;
// Adjust transmission rates based on client feedback
AdjustRates(ntohl(Stats->average_delta));
// Prepare server session statistics response
auto stats = reinterpret_cast<ServerSessionStats*>(p->pBuffer);
memset(stats, 0, sizeof(ServerSessionStats));
stats->RequestID = request_id;
stats->current_time = ntohl(Timer::GetCurrentTime2());
stats->sent_packets = ntohl(sent_packets);
stats->sent_packets2 = ntohl(sent_packets);
stats->received_packets = ntohl(received_packets);
stats->received_packets2 = ntohl(received_packets);
// Send session statistics response
NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse, p->pBuffer, p->size));
// Initiate appropriate response based on encryption state
if (!crypto->isEncrypted())
{
SendKeyRequest();
}
else
{
SendSessionResponse();
}
}
break;
/**
* OP_SessionStatResponse: Handle session statistics response
* Currently just logs the receipt for debugging purposes
*/
case OP_SessionStatResponse:
{
LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionStatResponse");
}
break;
/**
* OP_OutOfSession: Handle out-of-session packet
* Indicates client is no longer in valid session state
*/
case OP_OutOfSession:
{
LogWrite(PACKET__INFO, 0, "Packet", "OP_OutOfSession");
SendDisconnect();
SetState(CLOSED);
}
break;
/**
* Default case: Handle unknown/unprocessed packet types
* Attempts RSA key processing and packet decryption for debugging
*/
default:
{
// Debug output for original packet
std::cout << "Orig Packet: " << p->opcode << std::endl;
DumpPacket(p->pBuffer, p->size);
// Process RSA key if packet is large enough
if (p && p->size >= 69)
{
processRSAKey(p);
}
// Attempt to decrypt packet data
MCombineQueueLock.lock();
EQProtocolPacket* p2 = ProcessEncryptedData(p->pBuffer, p->size, OP_Packet);
MCombineQueueLock.unlock();
// Debug output for decrypted packet
if (p2)
{
std::cout << "Decrypted Packet: " << p2->opcode << std::endl;
DumpPacket(p2->pBuffer, p2->size);
safe_delete(p2);
}
#ifdef WRITE_PACKETS
// Write packet data to file if enabled
WritePackets("Unknown", p->pBuffer, p->size, false);
#endif
// Log unknown packet type
LogWrite(PACKET__INFO, 0, "Packet", "Received unknown packet type, not adding to inbound queue");
}
break;
}
}
/**
* Compress EQ2 packet data using zlib deflate compression
* Compresses packet data starting from the specified offset and updates packet buffer
*
* @param app - EQ2Packet to compress (modified in-place)
* @param offset - Starting offset for compression data
* @return Compression flag offset, or 0 on failure
*/
int8 EQStream::EQ2_Compress(EQ2Packet* app, int8 offset)
{
#ifdef LE_DEBUG
std::printf("Before Compress in %s, line %i:\n", __FUNCTION__, __LINE__);
DumpPacket(app);
#endif
// Set up compression parameters
uchar* pDataPtr = app->pBuffer + offset;
int xpandSize = app->size * 2; // Allocate double size for compression buffer
auto deflate_buff = std::make_unique<uchar[]>(xpandSize);
// Lock compression data and configure zlib stream
MCompressData.lock();
stream.next_in = pDataPtr;
stream.avail_in = app->size - offset;
stream.next_out = deflate_buff.get();
stream.avail_out = xpandSize;
// Perform compression
int ret = deflate(&stream, Z_SYNC_FLUSH);
// Check for compression errors
if (ret != Z_OK)
{
std::printf("ZLIB COMPRESSION RETFAIL: %i, %i (Ret: %i)\n", app->size, stream.avail_out, ret);
MCompressData.unlock();
return 0;
}
// Calculate new size and rebuild packet buffer
int32 newsize = xpandSize - stream.avail_out;
safe_delete_array(app->pBuffer);
app->size = newsize + offset;
app->pBuffer = new uchar[app->size];
// Set compression flag and copy compressed data
app->pBuffer[offset - 1] = 1; // Compression flag
std::memcpy(app->pBuffer + offset, deflate_buff.get(), newsize);
MCompressData.unlock();
#ifdef LE_DEBUG
std::printf("After Compress in %s, line %i:\n", __FUNCTION__, __LINE__);
DumpPacket(app);
#endif
return offset - 1;
}
/**
* Process RSA key from protocol packet and initialize RC4 encryption
* Extracts RSA-encrypted RC4 key from packet and configures stream encryption
*
* @param p - Protocol packet containing RSA key data
* @param subpacket_length - Length of subpacket, 0 to use full packet
* @return Always returns 0 (legacy return value)
*/
int16 EQStream::processRSAKey(EQProtocolPacket *p, uint16 subpacket_length)
{
LogWrite(PACKET__INFO, 0, "Packet", "=== RSA KEY PROCESSING ===");
LogWrite(PACKET__INFO, 0, "Packet", "Processing RSA key from packet size %d, subpacket_length %d", p->size, subpacket_length);
// Extract RSA key from appropriate location in packet
uchar* rsa_data;
if (subpacket_length)
{
// Use subpacket-relative position for key extraction
rsa_data = p->pBuffer + subpacket_length - 8;
LogWrite(PACKET__DEBUG, 0, "Packet", "Using subpacket-relative RSA data at offset %d", subpacket_length - 8);
crypto->setRC4Key(Crypto::RSADecrypt(rsa_data, 8));
}
else
{
// Use full packet size for key extraction
rsa_data = p->pBuffer + p->size - 8;
LogWrite(PACKET__DEBUG, 0, "Packet", "Using full packet RSA data at offset %d", p->size - 8);
crypto->setRC4Key(Crypto::RSADecrypt(rsa_data, 8));
}
LogWrite(PACKET__DEBUG, 0, "Packet", "RSA encrypted data (8 bytes): %02X %02X %02X %02X %02X %02X %02X %02X",
rsa_data[0], rsa_data[1], rsa_data[2], rsa_data[3], rsa_data[4], rsa_data[5], rsa_data[6], rsa_data[7]);
LogWrite(PACKET__INFO, 0, "Packet", "RC4 key set from RSA decryption, current RC4 key: 0x%08X", crypto->getRC4Key());
return 0;
}
/**
* Send encryption key request to establish secure communication
* Creates and sends a key generation packet to initiate encryption handshake
*/
void EQStream::SendKeyRequest()
{
LogWrite(PACKET__INFO, 0, "Packet", "=== SENDING KEY REQUEST ===");
LogWrite(PACKET__INFO, 0, "Packet", "Initiating encryption key exchange handshake");
// Define key generation packet parameters
constexpr int32 crypto_key_size = 60;
const int16 size = sizeof(KeyGen_Struct) + sizeof(KeyGen_End_Struct) + crypto_key_size;
LogWrite(PACKET__DEBUG, 0, "Packet", "Key request packet size: %d bytes (crypto_key_size=%d)", size, crypto_key_size);
// Create key request packet
auto outapp = new EQ2Packet(OP_WSLoginRequestMsg, nullptr, size);
// Set crypto key size in packet header
std::memcpy(&outapp->pBuffer[0], &crypto_key_size, sizeof(int32));
// Fill crypto key area with placeholder data
std::memset(&outapp->pBuffer[4], 0xFF, crypto_key_size);
// Set packet termination markers
std::memset(&outapp->pBuffer[size - 5], 1, 1);
std::memset(&outapp->pBuffer[size - 1], 1, 1);
LogWrite(PACKET__INFO, 0, "Packet", "Key request packet prepared, queuing for transmission");
// Queue packet for transmission
EQ2QueuePacket(outapp, true);
LogWrite(PACKET__INFO, 0, "Packet", "Key request sent successfully");
}
/**
* Encrypt packet data using RC4 encryption
* Applies encryption to packet payload, skipping headers and unencrypted sections
*
* @param app - EQ2Packet to encrypt (modified in-place)
* @param compress_offset - Offset for compressed data encryption
* @param offset - Additional offset for uncompressed data
*/
void EQStream::EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset)
{
// Only encrypt packets that are large enough and encryption is enabled
if (app->size > 2 && crypto->isEncrypted())
{
app->packet_encrypted = true;
uchar* crypt_buff = app->pBuffer;
// Choose encryption range based on compression status
if (app->eq2_compressed)
{
// Encrypt from compression offset to end of packet
crypto->RC4Encrypt(crypt_buff + compress_offset, app->size - compress_offset);
}
else
{
// Encrypt from header end plus offset to end of packet
crypto->RC4Encrypt(crypt_buff + 2 + offset, app->size - 2 - offset);
}
}
}
/**
* Queue EQ2 packet for transmission with optional combining
* Either adds packet to combine queue for batching or sends immediately
*
* @param app - EQ2Packet to queue for transmission
* @param attempted_combine - If true, send immediately; if false, queue for combining
*/
void EQStream::EQ2QueuePacket(EQ2Packet* app, bool attempted_combine)
{
// Only process packets if stream is active
if (CheckActive())
{
if (!attempted_combine)
{
// Add packet to combine queue for batching
MCombineQueueLock.lock();
combine_queue.push_back(app);
MCombineQueueLock.unlock();
}
else
{
// Prepare and send packet immediately
MCombineQueueLock.lock();
PreparePacket(app);
MCombineQueueLock.unlock();
#ifdef LE_DEBUG
std::printf("After B in %s, line %i:\n", __FUNCTION__, __LINE__);
DumpPacket(app);
#endif
SendPacket(app);
}
}
}
/**
* Remove packet preparation headers from EQ2 packet
* Strips protocol-specific headers that were added during packet preparation
*
* @param app - EQ2Packet to unprepare (modified in-place)
*/
void EQStream::UnPreparePacket(EQ2Packet* app)
{
// Check for specific header pattern that indicates prepared packet
if (app->pBuffer[2] == 0 && app->pBuffer[3] == 19)
{
// Create new buffer without preparation headers
auto new_buffer = std::make_unique<uchar[]>(app->size - 3);
std::memcpy(new_buffer.get() + 2, app->pBuffer + 5, app->size - 3);
// Replace packet buffer with unprepared version
delete[] app->pBuffer;
app->size -= 3;
app->pBuffer = new_buffer.release();
}
}
#ifdef WRITE_PACKETS
/**
* Convert byte to printable character for packet dumps
* Returns '.' for non-printable characters
*
* @param in - Input byte to convert
* @return Printable character representation
*/
char EQStream::GetChar(uchar in)
{
if (in < ' ' || in > '~')
{
return '.';
}
return static_cast<char>(in);
}
/**
* Write formatted output to packet dump file
* Provides printf-style formatting for packet logging
*
* @param pFormat - Printf-style format string
* @param ... - Variable arguments for formatting
*/
void EQStream::WriteToFile(char* pFormat, ...)
{
va_list args;
va_start(args, pFormat);
std::vfprintf(write_packets, pFormat, args);
va_end(args);
}
/**
* Write packet data to debug file with hex dump format
* Creates formatted hex dump with ASCII representation for debugging
*
* @param opcodeName - Name of packet opcode for header
* @param data - Raw packet data to dump
* @param size - Size of packet data in bytes
* @param outgoing - True if outgoing packet, false if incoming
*/
void EQStream::WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing)
{
MWritePackets.lock();
// Format connection information
struct in_addr ip_addr;
ip_addr.s_addr = remote_ip;
char timebuffer[80];
time_t rawtime;
struct tm* timeinfo;
std::time(&rawtime);
timeinfo = std::localtime(&rawtime);
std::strftime(timebuffer, 80, "%m/%d/%Y %H:%M:%S", timeinfo);
// Write packet header
if (outgoing)
{
WriteToFile("-- %s --\n%s\nSERVER -> %s\n", opcodeName, timebuffer, inet_ntoa(ip_addr));
}
else
{
WriteToFile("-- %s --\n%s\n%s -> SERVER\n", opcodeName, timebuffer, inet_ntoa(ip_addr));
}
// Calculate hex dump layout
int nLines = size / 16;
int nExtra = size % 16;
uchar* pPtr = data;
// Write full 16-byte lines
for (int i = 0; i < nLines; i++)
{
WriteToFile("%4.4X:\t%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n",
i * 16, pPtr[0], pPtr[1], pPtr[2], pPtr[3], pPtr[4], pPtr[5], pPtr[6], pPtr[7],
pPtr[8], pPtr[9], pPtr[10], pPtr[11], pPtr[12], pPtr[13], pPtr[14], pPtr[15],
GetChar(pPtr[0]), GetChar(pPtr[1]), GetChar(pPtr[2]), GetChar(pPtr[3]),
GetChar(pPtr[4]), GetChar(pPtr[5]), GetChar(pPtr[6]), GetChar(pPtr[7]),
GetChar(pPtr[8]), GetChar(pPtr[9]), GetChar(pPtr[10]), GetChar(pPtr[11]),
GetChar(pPtr[12]), GetChar(pPtr[13]), GetChar(pPtr[14]), GetChar(pPtr[15]));
pPtr += 16;
}
// Write partial line if remaining bytes
if (nExtra)
{
WriteToFile("%4.4X\t", nLines * 16);
// Write hex bytes
for (int i = 0; i < nExtra; i++)
{
WriteToFile("%2.2X ", pPtr[i]);
}
// Pad with spaces
for (int i = nExtra; i < 16; i++)
{
WriteToFile(" ");
}
// Write ASCII representation
for (int i = 0; i < nExtra; i++)
{
WriteToFile("%c", GetChar(pPtr[i]));
}
WriteToFile("\n");
}
WriteToFile("\n\n");
std::fflush(write_packets);
MWritePackets.unlock();
}
/**
* Write EQ2 packet to debug file with automatic version handling
* Convenience wrapper for WritePackets that handles version setup
*
* @param app - EQ2Packet to write to debug file
* @param outgoing - True if outgoing packet, false if incoming
*/
void EQStream::WritePackets(EQ2Packet* app, bool outgoing)
{
if (app->version == 0)
{
app->version = client_version;
}
WritePackets(app->GetOpcodeName(), app->pBuffer, app->size, outgoing);
}
#endif
/**
* Prepare EQ2 packet for transmission
* Handles packet preparation, compression, and encryption in sequence
*
* @param app - EQ2Packet to prepare (modified in-place)
* @param offset - Additional offset for encryption
*/
void EQStream::PreparePacket(EQ2Packet* app, int8 offset)
{
// Set client version and reset compression offset
app->setVersion(client_version);
compressed_offset = 0;
#ifdef LE_DEBUG
std::printf("Before A in %s, line %i:\n", __FUNCTION__, __LINE__);
DumpPacket(app);
#endif
// Prepare packet headers and structure if not already done
if (!app->packet_prepared)
{
if (app->PreparePacket(MaxLen) == 255) // Invalid version
{
return;
}
}
#ifdef LE_DEBUG
std::printf("After Prepare in %s, line %i:\n", __FUNCTION__, __LINE__);
DumpPacket(app);
#endif
#ifdef WRITE_PACKETS
// Write uncompressed/unencrypted packet to file if enabled
if (!app->eq2_compressed && !app->packet_encrypted)
{
WritePackets(app, true);
}
#endif
// Compress packet if it's large enough and not already compressed
if (!app->eq2_compressed && app->size > 128)
{
compressed_offset = EQ2_Compress(app);
if (compressed_offset)
{
app->eq2_compressed = true;
}
}
// Encrypt packet if not already encrypted
if (!app->packet_encrypted)
{
EncryptPacket(app, compressed_offset, offset);
// Handle special case for zero byte at offset 2
if (app->size > 2 && app->pBuffer[2] == 0)
{
auto new_buffer = std::make_unique<uchar[]>(app->size + 1);
new_buffer[2] = 0;
std::memcpy(new_buffer.get() + 3, app->pBuffer + 2, app->size - 2);
delete[] app->pBuffer;
app->pBuffer = new_buffer.release();
app->size++;
}
}
#ifdef LE_DEBUG
std::printf("After A in %s, line %i:\n", __FUNCTION__, __LINE__);
DumpPacket(app);
#endif
}
/**
* Send EQProtocolPacket with automatic fragmentation for large packets
* Fragments packets that exceed maximum transmission unit into smaller chunks
*
* @param p - EQProtocolPacket to send (ownership transferred, will be deleted)
*/
void EQStream::SendPacket(EQProtocolPacket *p)
{
uint32 chunksize, used;
uint32 length;
// Check if packet needs fragmentation
if (p->size > (MaxLen - 8)) // proto-op(2), seq(2), app-op(2) ... data ... crc(2)
{
uchar* tmpbuff = p->pBuffer;
length = p->size - 2;
// Create first fragment with length header
auto out = new EQProtocolPacket(OP_Fragment, nullptr, MaxLen - 4);
*reinterpret_cast<uint32*>(out->pBuffer + 2) = htonl(length);
used = MaxLen - 10;
std::memcpy(out->pBuffer + 6, tmpbuff + 2, used);
#ifdef LE_DEBUG
std::printf("(%s, %i) New Fragment:\n ", __FUNCTION__, __LINE__);
DumpPacket(out);
#endif
SequencedPush(out);
// Create additional fragments for remaining data
while (used < length)
{
chunksize = std::min(length - used, MaxLen - 6);
out = new EQProtocolPacket(OP_Fragment, nullptr, chunksize + 2);
std::memcpy(out->pBuffer + 2, tmpbuff + used + 2, chunksize);
#ifdef LE_DEBUG
std::printf("Chunk: \n");
DumpPacket(out);
#endif
SequencedPush(out);
used += chunksize;
}
#ifdef LE_DEBUG
std::printf("ChunkDelete: \n");
DumpPacket(out);
#endif
delete p;
}
else
{
// Packet fits within MTU, send directly
SequencedPush(p);
}
}
/**
* Send EQApplicationPacket with automatic fragmentation for large packets
* Serializes application packet and fragments if necessary for transmission
*
* @param p - EQApplicationPacket to send (ownership transferred, will be deleted)
*/
void EQStream::SendPacket(EQApplicationPacket *p)
{
uint32 chunksize, used;
uint32 length;
// Check if packet needs fragmentation
if (p->size > (MaxLen - 8)) // proto-op(2), seq(2), app-op(2) ... data ... crc(2)
{
// Serialize application packet to buffer
auto tmpbuff = std::make_unique<unsigned char[]>(p->size + 2);
length = p->serialize(tmpbuff.get());
// Create first fragment with size header
auto out = new EQProtocolPacket(OP_Fragment, nullptr, MaxLen - 4);
*reinterpret_cast<uint32*>(out->pBuffer + 2) = htonl(p->Size());
std::memcpy(out->pBuffer + 6, tmpbuff.get(), MaxLen - 10);
used = MaxLen - 10;
SequencedPush(out);
// Create additional fragments for remaining data
while (used < length)
{
out = new EQProtocolPacket(OP_Fragment, nullptr, MaxLen - 4);
chunksize = std::min(length - used, MaxLen - 6);
std::memcpy(out->pBuffer + 2, tmpbuff.get() + used, chunksize);
out->size = chunksize + 2;
SequencedPush(out);
used += chunksize;
}
delete p;
}
else
{
// Packet fits within MTU, serialize directly to protocol packet
auto out = new EQProtocolPacket(OP_Packet, nullptr, p->Size() + 2);
p->serialize(out->pBuffer + 2);
SequencedPush(out);
delete p;
}
}
/**
* Add packet to sequenced transmission queue
* Assigns sequence number and queues packet for reliable delivery
*
* @param p - EQProtocolPacket to queue (ownership transferred)
*/
void EQStream::SequencedPush(EQProtocolPacket *p)
{
// Set client version for compatibility
p->setVersion(client_version);
MOutboundQueue.lock();
// Assign sequence number to packet header
*reinterpret_cast<uint16*>(p->pBuffer) = htons(NextOutSeq);
SequencedQueue.push_back(p);
p->sequence = NextOutSeq;
NextOutSeq++;
MOutboundQueue.unlock();
}
/**
* Add packet to non-sequenced transmission queue
* Queues packet for immediate transmission without sequence numbering
*
* @param p - EQProtocolPacket to queue (ownership transferred)
*/
void EQStream::NonSequencedPush(EQProtocolPacket *p)
{
// Set client version for compatibility
p->setVersion(client_version);
MOutboundQueue.lock();
NonSequencedQueue.push(p);
MOutboundQueue.unlock();
}
/**
* Send acknowledgment packet for received sequence number
* Confirms successful receipt of sequenced packet to peer
*
* @param seq - Sequence number to acknowledge
*/
void EQStream::SendAck(uint16 seq)
{
uint16 Seq = htons(seq);
SetLastAckSent(seq);
NonSequencedPush(new EQProtocolPacket(OP_Ack, reinterpret_cast<unsigned char*>(&Seq), sizeof(uint16)));
}
/**
* Send out-of-order acknowledgment packet
* Notifies peer that a specific packet was received out of sequence
*
* @param seq - Sequence number that was received out of order
*/
void EQStream::SendOutOfOrderAck(uint16 seq)
{
uint16 Seq = htons(seq);
NonSequencedPush(new EQProtocolPacket(OP_OutOfOrderAck, reinterpret_cast<unsigned char*>(&Seq), sizeof(uint16)));
}
/**
* Process packet combination queue to optimize transmission
* Combines multiple small packets into larger ones for efficiency
*
* @return true if all packets processed, false if processing was limited
*/
bool EQStream::CheckCombineQueue()
{
bool ret = true; // Processed all packets flag
MCombineQueueLock.lock();
if (combine_queue.size() > 0)
{
// Get first packet from queue
EQ2Packet* first = combine_queue.front();
combine_queue.pop_front();
if (combine_queue.size() == 0)
{
// Nothing to combine with, send immediately
EQ2QueuePacket(first, true);
}
else
{
// Prepare first packet for combining
PreparePacket(first);
EQ2Packet* second = nullptr;
bool combine_worked = false;
int16 count = 0;
// Attempt to combine with additional packets
while (combine_queue.size())
{
count++;
second = combine_queue.front();
combine_queue.pop_front();
PreparePacket(second);
// Try to combine second packet with first
if (!first->AppCombine(second))
{
// Combination failed, send first packet
first->SetProtocolOpcode(OP_Packet);
if (combine_worked)
{
SequencedPush(first);
}
else
{
EQ2QueuePacket(first, true);
}
// Make second packet the new first
first = second;
combine_worked = false;
}
else
{
// Combination succeeded
combine_worked = true;
}
// Limit processing to prevent blocking other clients
if (count >= 60 || first->size > 4000)
{
ret = false;
break;
}
}
// Send final packet
if (first)
{
first->SetProtocolOpcode(OP_Packet);
if (combine_worked)
{
SequencedPush(first);
}
else
{
EQ2QueuePacket(first, true);
}
}
}
}
MCombineQueueLock.unlock();
return ret;
}
/**
* Check and handle packet retransmission for unacknowledged packets
* Retries failed packet deliveries up to maximum attempt limit
*
* @param eq_fd - Socket file descriptor for transmission
*/
void EQStream::CheckResend(int eq_fd)
{
int32 curr = Timer::GetCurrentTime2();
EQProtocolPacket* packet = nullptr;
MResendQue.lock();
for (auto itr = resend_que.begin(); itr != resend_que.end();)
{
packet = *itr;
// Check if packet has exceeded maximum retry attempts
if (packet->attempt_count >= 5)
{
// Give up on this packet - client likely has it but didn't ack
safe_delete(packet);
itr = resend_que.erase(itr);
if (itr == resend_que.end())
{
break;
}
}
else
{
// Check if enough time has passed for retry
if ((curr - packet->sent_time) < 1000)
{
++itr;
continue;
}
// Retry packet transmission
packet->sent_time -= 1000;
packet->attempt_count++;
WritePacket(eq_fd, packet);
++itr;
}
}
MResendQue.unlock();
}
/**
* Compare two sequence numbers considering wraparound behavior
* Determines temporal relationship between sequence numbers within sliding window
*
* @param expected_seq - Expected sequence number
* @param seq - Actual sequence number to compare
* @return SeqOrder indicating temporal relationship (past, in-order, future)
*/
EQStream::SeqOrder EQStream::CompareSequence(uint16 expected_seq, uint16 seq)
{
if (expected_seq == seq)
{
// Sequence numbers match exactly
return SeqInOrder;
}
else if ((seq > expected_seq && static_cast<uint32>(seq) < (static_cast<uint32>(expected_seq) + EQStream::MaxWindowSize)) ||
seq < (expected_seq - EQStream::MaxWindowSize))
{
// Sequence is in future window or wrapped around
return SeqFuture;
}
else
{
// Sequence is in past window
return SeqPast;
}
}
/**
* Process acknowledgment packet and remove acknowledged packets from queue
* Handles sliding window advancement and packet cleanup for reliable delivery
*
* @param seq - Highest sequence number being acknowledged
*/
void EQStream::AckPackets(uint16 seq)
{
MOutboundQueue.lock();
SeqOrder ord = CompareSequence(SequencedBase, seq);
if (ord == SeqInOrder)
{
// No new acknowledgments - duplicate ack
LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with no window advancement (seq %u)", seq);
}
else if (ord == SeqPast)
{
// Backward acknowledgment - protocol error
LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with backward window advancement (they gave %u, our window starts at %u). This is bad", seq, SequencedBase);
}
else
{
// Valid acknowledgment - advance window
LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack up through sequence %u. Our base is %u", seq, SequencedBase);
// Process all packets up to acknowledgment point
seq++; // Stop at block right after their ack
while (SequencedBase != seq)
{
if (SequencedQueue.empty())
{
LogWrite(PACKET__DEBUG, 9, "Packet", "OUT OF PACKETS acked packet with sequence %u. Next send is %u before this",
static_cast<unsigned long>(SequencedBase), SequencedQueue.size());
SequencedBase = NextOutSeq;
break;
}
LogWrite(PACKET__DEBUG, 9, "Packet", "Removing acked packet with sequence %u",
static_cast<unsigned long>(SequencedBase));
// Remove acknowledged packet from queue
delete SequencedQueue.front();
SequencedQueue.pop_front();
// Advance base sequence number
SequencedBase++;
}
// Validate queue consistency
if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq)
{
LogWrite(PACKET__DEBUG, 9, "Packet", "Post-Ack on %u Invalid Sequenced queue: BS %u + SQ %u != NOS %u",
seq, SequencedBase, SequencedQueue.size(), NextOutSeq);
}
}
MOutboundQueue.unlock();
}
/**
* Main packet transmission loop - processes outbound queues and sends packets
* Handles rate limiting, packet combining, acknowledgments, and transmission scheduling
*
* @param eq_fd - Socket file descriptor for transmission
*/
void EQStream::Write(int eq_fd)
{
queue<EQProtocolPacket *> ReadyToSend;
long maxack;
// Check our rate to make sure we can send more
MRate.lock();
sint32 threshold=RateThreshold;
MRate.unlock();
if (BytesWritten > threshold) {
//cout << "Over threshold: " << BytesWritten << " > " << threshold << endl;
return;
}
MCombinedAppPacket.lock();
EQApplicationPacket *CombPack=CombinedAppPacket;
CombinedAppPacket=NULL;
MCombinedAppPacket.unlock();
if (CombPack) {
SendPacket(CombPack);
}
// If we got more packets to we need to ack, send an ack on the highest one
MAcks.lock();
maxack=MaxAckReceived;
// Added from peaks findings
if (NextAckToSend>LastAckSent || LastAckSent == 0x0000ffff)
SendAck(NextAckToSend);
MAcks.unlock();
// Lock the outbound queues while we process
MOutboundQueue.lock();
// Adjust where we start sending in case we get a late ack
//if (maxack>LastSeqSent)
// LastSeqSent=maxack;
// Place to hold the base packet t combine into
EQProtocolPacket *p=NULL;
std::deque<EQProtocolPacket*>::iterator sitr;
// Find the next sequenced packet to send from the "queue"
sitr = SequencedQueue.begin();
uint16 count = 0;
// get to start of packets
while (sitr != SequencedQueue.end() && (*sitr)->sent_time > 0) {
++sitr;
++count;
}
bool SeqEmpty = false, NonSeqEmpty = false;
// Loop until both are empty or MaxSends is reached
while (!SeqEmpty || !NonSeqEmpty) {
// See if there are more non-sequenced packets left
if (!NonSequencedQueue.empty()) {
if (!p) {
// If we don't have a packet to try to combine into, use this one as the base
// And remove it form the queue
p = NonSequencedQueue.front();
LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with non-seq packet of len %u",p->size);
NonSequencedQueue.pop();
}
else if (!p->combine(NonSequencedQueue.front())) {
// Trying to combine this packet with the base didn't work (too big maybe)
// So just send the base packet (we'll try this packet again later)
LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next non-seq packet is len %u", p->size, (NonSequencedQueue.front())->size);
ReadyToSend.push(p);
BytesWritten += p->size;
p = nullptr;
if (BytesWritten > threshold) {
// Sent enough this round, lets stop to be fair
LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in nonseq (%u > %u)", BytesWritten, threshold);
break;
}
}
else {
// Combine worked, so just remove this packet and it's spot in the queue
LogWrite(PACKET__DEBUG, 9, "Packet", "Combined non-seq packet of len %u, yeilding %u combined", (NonSequencedQueue.front())->size, p->size);
delete NonSequencedQueue.front();
NonSequencedQueue.pop();
}
}
else {
// No more non-sequenced packets
NonSeqEmpty = true;
}
if (sitr != SequencedQueue.end()) {
uint16 seq_send = SequencedBase + count; //just for logging...
if (SequencedQueue.empty()) {
LogWrite(PACKET__DEBUG, 9, "Packet", "Tried to write a packet with an empty queue (%u is past next out %u)", seq_send, NextOutSeq);
SeqEmpty = true;
continue;
}
if ((*sitr)->acked || (*sitr)->sent_time != 0) {
++sitr;
++count;
if (p) {
LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size);
ReadyToSend.push(p);
BytesWritten += p->size;
p = nullptr;
}
LogWrite(PACKET__DEBUG, 9, "Packet", "Not retransmitting seq packet %u because already marked as acked", seq_send);
}
else if (!p) {
// If we don't have a packet to try to combine into, use this one as the base
// Copy it first as it will still live until it is acked
p = (*sitr)->Copy();
LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with seq packet %u of len %u", seq_send, p->size);
(*sitr)->sent_time = Timer::GetCurrentTime2();
++sitr;
++count;
}
else if (!p->combine(*sitr)) {
// Trying to combine this packet with the base didn't work (too big maybe)
// So just send the base packet (we'll try this packet again later)
LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next seq packet %u is len %u", p->size, seq_send + 1, (*sitr)->size);
ReadyToSend.push(p);
BytesWritten += p->size;
p = nullptr;
if ((*sitr)->opcode != OP_Fragment && BytesWritten > threshold) {
// Sent enough this round, lets stop to be fair
LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in seq (%u > %u)", BytesWritten, threshold);
break;
}
}
else {
// Combine worked
LogWrite(PACKET__DEBUG, 9, "Packet", "Combined seq packet %u of len %u, yeilding %u combined", seq_send, (*sitr)->size, p->size);
(*sitr)->sent_time = Timer::GetCurrentTime2();
++sitr;
++count;
}
if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) {
LogWrite(PACKET__DEBUG, 9, "Packet", "Post send Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq);
}
}
else {
// No more sequenced packets
SeqEmpty = true;
}
}
MOutboundQueue.unlock(); // Unlock the queue
// We have a packet still, must have run out of both seq and non-seq, so send it
if (p) {
LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size);
ReadyToSend.push(p);
BytesWritten += p->size;
}
// Send all the packets we "made"
while (!ReadyToSend.empty()) {
p = ReadyToSend.front();
WritePacket(eq_fd, p);
delete p;
ReadyToSend.pop();
}
//see if we need to send our disconnect and finish our close
if (SeqEmpty && NonSeqEmpty) {
//no more data to send
if (GetState() == CLOSING) {
MOutboundQueue.lock();
if (SequencedQueue.size() > 0 ) {
// retransmission attempts
}
else
{
LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client.");
//we are waiting for the queues to empty, now we can do our disconnect.
//this packet will not actually go out until the next call to Write().
SendDisconnect();
//SetState(CLOSED);
}
MOutboundQueue.unlock();
}
}
}
/**
* Write individual protocol packet to network socket
* Handles packet serialization, compression, encoding, and UDP transmission
*
* @param eq_fd - Socket file descriptor for transmission
* @param p - EQProtocolPacket to transmit
*/
void EQStream::WritePacket(int eq_fd, EQProtocolPacket *p)
{
uint32 length = 0;
sockaddr_in address;
// Set up destination address
address.sin_family = AF_INET;
address.sin_addr.s_addr = remote_ip;
address.sin_port = remote_port;
#ifdef NOWAY
uint32 ip=address.sin_addr.s_addr;
cout << "Sending to: "
<< (int)*(unsigned char *)&ip
<< "." << (int)*((unsigned char *)&ip+1)
<< "." << (int)*((unsigned char *)&ip+2)
<< "." << (int)*((unsigned char *)&ip+3)
<< "," << (int)ntohs(address.sin_port) << "(" << p->size << ")" << endl;
p->DumpRaw();
cout << "-------------" << endl;
#endif
length=p->serialize(buffer);
if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse) {
if (compressed) {
BytesWritten -= p->size;
uint32 newlen=EQProtocolPacket::Compress(buffer,length,write_buffer,2048);
memcpy(buffer,write_buffer,newlen);
length=newlen;
BytesWritten += newlen;
}
if (encoded) {
EQProtocolPacket::ChatEncode(buffer,length,Key);
}
*(uint16 *)(buffer+length)=htons(CRC16(buffer,length,Key));
length+=2;
}
sent_packets++;
//dump_message_column(buffer,length,"Writer: ");
//cout << "Raw Data:\n";
//DumpPacket(buffer, length);
sendto(eq_fd,(char *)buffer,length,0,(sockaddr *)&address,sizeof(address));
}
EQProtocolPacket *EQStream::Read(int eq_fd, sockaddr_in *from)
{
int socklen;
int length=0;
unsigned char buffer[2048];
EQProtocolPacket *p=NULL;
char temp[15];
socklen=sizeof(sockaddr);
#ifdef WIN32
length=recvfrom(eq_fd, (char *)buffer, 2048, 0, (struct sockaddr*)from, (int *)&socklen);
#else
length=recvfrom(eq_fd, buffer, 2048, 0, (struct sockaddr*)from, (socklen_t *)&socklen);
#endif
if (length>=2) {
DumpPacket(buffer, length);
p=new EQProtocolPacket(buffer[1],&buffer[2],length-2);
//printf("Read packet: opcode %i length %u, expected-length: %u\n",buffer[1], length, p->size);
uint32 ip=from->sin_addr.s_addr;
sprintf(temp,"%d.%d.%d.%d:%d",
*(unsigned char *)&ip,
*((unsigned char *)&ip+1),
*((unsigned char *)&ip+2),
*((unsigned char *)&ip+3),
ntohs(from->sin_port));
//cout << timestamp() << "Data from: " << temp << " OpCode 0x" << hex << setw(2) << setfill('0') << (int)p->opcode << dec << endl;
//dump_message(p->pBuffer,p->size,timestamp());
}
return p;
}
/**
* Send session establishment response to client
* Provides session parameters including encryption keys and stream format flags
*/
void EQStream::SendSessionResponse()
{
LogWrite(PACKET__INFO, 0, "Packet", "=== SENDING SESSION RESPONSE ===");
struct in_addr addr;
addr.s_addr = GetRemoteIP();
LogWrite(PACKET__INFO, 0, "Packet", "Preparing SessionResponse to %s:%d", inet_ntoa(addr), GetRemotePort());
auto out = new EQProtocolPacket(OP_SessionResponse, nullptr, sizeof(SessionResponse));
auto Response = reinterpret_cast<SessionResponse*>(out->pBuffer);
// Set session parameters
Response->Session = htonl(Session);
Response->MaxLength = htonl(MaxLen);
Response->UnknownA = 2;
Response->Format = 0;
// Set format flags based on stream configuration
if (compressed)
{
Response->Format |= FLAG_COMPRESSED;
}
if (encoded)
{
Response->Format |= FLAG_ENCODED;
}
Response->Key = htonl(Key);
out->size = sizeof(SessionResponse);
LogWrite(PACKET__INFO, 0, "Packet", "SessionResponse: Session=0x%08X, Key=0x%08X, MaxLength=%d, Format=0x%02X",
Session, Key, MaxLen, Response->Format);
LogWrite(PACKET__DEBUG, 0, "Packet", "SessionResponse packet size: %d bytes", out->size);
NonSequencedPush(out);
LogWrite(PACKET__INFO, 0, "Packet", "SessionResponse sent successfully");
}
/**
* Send session establishment request to server
* Initiates connection handshake with session parameters
*/
void EQStream::SendSessionRequest()
{
auto out = new EQProtocolPacket(OP_SessionRequest, nullptr, sizeof(SessionRequest));
auto Request = reinterpret_cast<SessionRequest*>(out->pBuffer);
// Initialize request structure
std::memset(Request, 0, sizeof(SessionRequest));
Request->Session = htonl(std::time(nullptr));
Request->MaxLength = htonl(512);
NonSequencedPush(out);
}
/**
* Send session disconnect packet to peer
* Initiates graceful connection termination sequence
*
* @param setstate - If true, set stream state to CLOSING
*/
void EQStream::SendDisconnect(bool setstate)
{
try
{
// Only send disconnect if in valid state
if (GetState() != ESTABLISHED && GetState() != WAIT_CLOSE)
{
return;
}
// Create disconnect packet
auto out = new EQProtocolPacket(OP_SessionDisconnect, nullptr, sizeof(uint32) + sizeof(int16));
*reinterpret_cast<uint32*>(out->pBuffer) = htonl(Session);
out->pBuffer[4] = 0;
out->pBuffer[5] = 6;
NonSequencedPush(out);
// Update state if requested
if (setstate)
{
SetState(CLOSING);
}
}
catch (...)
{
// Ignore exceptions during disconnect
}
}
/**
* Add application packet to inbound queue for processing
* Thread-safe method to queue received packets for application layer
*
* @param p - EQApplicationPacket to add to inbound queue
*/
void EQStream::InboundQueuePush(EQApplicationPacket *p)
{
MInboundQueue.lock();
InboundQueue.push_back(p);
MInboundQueue.unlock();
}
/**
* Retrieve next application packet from inbound queue
* Thread-safe method to get received packets for application processing
*
* @return Next EQApplicationPacket from queue, or nullptr if queue is empty
*/
EQApplicationPacket *EQStream::PopPacket()
{
EQApplicationPacket *p = nullptr;
MInboundQueue.lock();
if (InboundQueue.size())
{
p = InboundQueue.front();
InboundQueue.pop_front();
}
MInboundQueue.unlock();
// Set client version for compatibility
if (p)
{
p->setVersion(client_version);
}
return p;
}
/**
* Clear all packets from inbound queue
* Thread-safe method to empty and delete all queued packets
*/
void EQStream::InboundQueueClear()
{
MInboundQueue.lock();
while (InboundQueue.size())
{
delete InboundQueue.front();
InboundQueue.pop_front();
}
MInboundQueue.unlock();
}
/**
* Legacy packet encryption function (currently unused)
* Placeholder for packet-level encryption functionality
*
* @param data - Packet data buffer
* @param size - Size of packet data
*/
void EQStream::EncryptPacket(uchar* data, int16 size)
{
if (size > 6)
{
// Implementation placeholder
}
}
/**
* Check if stream has pending outbound data
* Determines if there are packets waiting to be transmitted
*
* @return true if outbound data is available, false otherwise
*/
bool EQStream::HasOutgoingData()
{
bool flag;
// Once closed, we have nothing more to say
if (CheckClosed())
{
return false;
}
// Check for queued packets
MOutboundQueue.lock();
flag = (!NonSequencedQueue.empty());
if (!flag)
{
flag = (!SequencedQueue.empty());
}
MOutboundQueue.unlock();
// Check for pending acknowledgments
if (!flag)
{
MAcks.lock();
flag = (NextAckToSend > LastAckSent);
MAcks.unlock();
}
// Check for combined application packets
if (!flag)
{
MCombinedAppPacket.lock();
flag = (CombinedAppPacket != nullptr);
MCombinedAppPacket.unlock();
}
return flag;
}
/**
* Clear all packets from outbound queues
* Thread-safe method to empty and delete all queued outbound packets
*/
void EQStream::OutboundQueueClear()
{
MOutboundQueue.lock();
// Clear non-sequenced queue
while (NonSequencedQueue.size())
{
delete NonSequencedQueue.front();
NonSequencedQueue.pop();
}
// Clear sequenced queue
while (SequencedQueue.size())
{
delete SequencedQueue.front();
SequencedQueue.pop_front();
}
MOutboundQueue.unlock();
}
/**
* Process incoming raw network data into protocol packets
* Handles decompression, decoding, validation, and packet creation
*
* @param buffer - Raw network data buffer
* @param length - Length of network data
*/
void EQStream::Process(const unsigned char *buffer, const uint32 length)
{
received_packets++;
static unsigned char newbuffer[2048];
uint32 newlength = 0;
#ifdef LE_DEBUG
printf("ProcessBuffer:\n");
DumpPacket(buffer, length);
#endif
if (EQProtocolPacket::ValidateCRC(buffer,length,Key)) {
if (compressed) {
newlength=EQProtocolPacket::Decompress(buffer,length,newbuffer,2048);
#ifdef LE_DEBUG
printf("ProcessBufferDecompress:\n");
DumpPacket(buffer, newlength);
#endif
} else {
memcpy(newbuffer,buffer,length);
newlength=length;
if (encoded)
EQProtocolPacket::ChatDecode(newbuffer,newlength-2,Key);
}
#ifdef LE_DEBUG
printf("ResultProcessBuffer:\n");
DumpPacket(buffer, newlength);
#endif
uint16 opcode=ntohs(*(const uint16 *)newbuffer);
//printf("Read packet: opcode %i newlength %u, newbuffer2len: %u, newbuffer3len: %u\n",opcode, newlength, newbuffer[2], newbuffer[3]);
if(opcode > 0 && opcode <= OP_OutOfSession)
{
if (buffer[1]!=0x01 && buffer[1]!=0x02 && buffer[1]!=0x1d)
newlength-=2;
EQProtocolPacket p(newbuffer,newlength);
ProcessPacket(&p);
}
else
{
cout << "2Orig Packet: " << opcode << endl;
DumpPacket(newbuffer, newlength);
ProcessEmbeddedPacket(newbuffer, newlength, OP_Fragment);
}
ProcessQueue();
} else {
cout << "Incoming packet failed checksum:" <<endl;
dump_message_column(const_cast<unsigned char *>(buffer),length,"CRC failed: ");
}
}
/**
* Get the highest sequence number that has been acknowledged
* Thread-safe accessor for maximum acknowledged sequence number
*
* @return Highest acknowledged sequence number
*/
long EQStream::GetMaxAckReceived()
{
MAcks.lock();
long l = MaxAckReceived;
MAcks.unlock();
return l;
}
/**
* Get the next sequence number that should be acknowledged
* Thread-safe accessor for next acknowledgment to send
*
* @return Next sequence number to acknowledge
*/
long EQStream::GetNextAckToSend()
{
MAcks.lock();
long l = NextAckToSend;
MAcks.unlock();
return l;
}
/**
* Get the last sequence number that was acknowledged
* Thread-safe accessor for last sent acknowledgment
*
* @return Last acknowledged sequence number
*/
long EQStream::GetLastAckSent()
{
MAcks.lock();
long l = LastAckSent;
MAcks.unlock();
return l;
}
/**
* Set the maximum acknowledged sequence number
* Updates acknowledgment tracking and cleans up acknowledged packets from resend queue
*
* @param seq - New maximum acknowledged sequence number
*/
void EQStream::SetMaxAckReceived(uint32 seq)
{
MAcks.lock();
MaxAckReceived = seq;
MAcks.unlock();
MOutboundQueue.lock();
if (static_cast<long>(seq) > LastSeqSent)
{
LastSeqSent = seq;
}
// Clean up acknowledged packets from resend queue
MResendQue.lock();
for (auto itr = resend_que.begin(); itr != resend_que.end();)
{
EQProtocolPacket* packet = *itr;
if (packet && packet->sequence <= seq)
{
safe_delete(packet);
itr = resend_que.erase(itr);
if (itr == resend_que.end())
{
break;
}
}
else
{
++itr;
}
}
MResendQue.unlock();
MOutboundQueue.unlock();
}
/**
* Set the next sequence number to acknowledge
* Thread-safe setter for next acknowledgment to send
*
* @param seq - Next sequence number to acknowledge
*/
void EQStream::SetNextAckToSend(uint32 seq)
{
MAcks.lock();
NextAckToSend = seq;
MAcks.unlock();
}
/**
* Set the last sequence number that was acknowledged
* Thread-safe setter for last sent acknowledgment
*
* @param seq - Last acknowledged sequence number
*/
void EQStream::SetLastAckSent(uint32 seq)
{
MAcks.lock();
LastAckSent = seq;
MAcks.unlock();
}
/**
* Set the last sequence number that was sent
* Thread-safe setter for last transmitted sequence number
*
* @param seq - Last sent sequence number
*/
void EQStream::SetLastSeqSent(uint32 seq)
{
MOutboundQueue.lock();
LastSeqSent = seq;
MOutboundQueue.unlock();
}
/**
* Configure stream parameters based on stream type
* Sets opcode size, compression, and encoding flags for different stream types
*
* @param type - EQStreamType to configure stream for
*/
void EQStream::SetStreamType(EQStreamType type)
{
StreamType = type;
switch (StreamType)
{
case LoginStream:
app_opcode_size = 1;
compressed = false;
encoded = false;
break;
case EQ2Stream:
app_opcode_size = 2;
compressed = false;
encoded = false;
break;
case ChatOrMailStream:
case ChatStream:
case MailStream:
app_opcode_size = 1;
compressed = false;
encoded = true;
break;
case ZoneStream:
case WorldStream:
default:
app_opcode_size = 2;
compressed = true;
encoded = false;
break;
}
}
/**
* Process queued out-of-order packets in sequence
* Handles packets that arrived before their predecessors
*/
void EQStream::ProcessQueue()
{
if (OutOfOrderpackets.empty())
{
return;
}
// Process packets in sequence order
EQProtocolPacket* qp = nullptr;
while ((qp = RemoveQueue(NextInSeq)) != nullptr)
{
ProcessPacket(qp);
delete qp;
}
}
/**
* Remove and return packet with specific sequence number from queue
* Searches out-of-order packet queue for specified sequence
*
* @param seq - Sequence number of packet to remove
* @return EQProtocolPacket if found, nullptr if not found
*/
EQProtocolPacket* EQStream::RemoveQueue(uint16 seq)
{
EQProtocolPacket* qp = nullptr;
auto itr = OutOfOrderpackets.find(seq);
if (itr != OutOfOrderpackets.end())
{
qp = itr->second;
OutOfOrderpackets.erase(itr);
}
return qp;
}
/**
* Apply bandwidth decay and check for packet timeouts
* Reduces byte counter over time and flags timed-out packets for retransmission
*/
void EQStream::Decay()
{
// Apply bandwidth decay
MRate.lock();
uint32 rate = DecayRate;
MRate.unlock();
if (BytesWritten > 0)
{
BytesWritten -= rate;
if (BytesWritten < 0)
{
BytesWritten = 0;
}
}
// Check for packet timeouts and flag for retransmission
int count = 0;
MOutboundQueue.lock();
for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end(); ++sitr, count++)
{
if (!(*sitr)->acked && (*sitr)->sent_time > 0 &&
((*sitr)->sent_time + retransmittimeout) < Timer::GetCurrentTime2())
{
(*sitr)->sent_time = 0; // Flag for retransmission
LogWrite(PACKET__DEBUG, 9, "Packet", "Timeout exceeded for seq %u. Flagging packet for retransmission",
SequencedBase + count);
}
}
MOutboundQueue.unlock();
}
/**
* Adjust transmission rates based on network conditions
* Calculates rate thresholds and decay rates from average delta timing
*
* @param average_delta - Average packet transmission time in milliseconds
*/
void EQStream::AdjustRates(uint32 average_delta)
{
if (average_delta && (average_delta <= AVERAGE_DELTA_MAX))
{
MRate.lock();
// Calculate transmission parameters based on network timing
AverageDelta = average_delta;
RateThreshold = RATEBASE / average_delta;
DecayRate = DECAYBASE / average_delta;
// Adjust current byte counter if needed
if (BytesWritten > RateThreshold)
{
BytesWritten = RateThreshold + DecayRate;
}
MRate.unlock();
}
else
{
// Use maximum delta if invalid value provided
AverageDelta = AVERAGE_DELTA_MAX;
}
}