970 lines
26 KiB
C++
970 lines
26 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"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <time.h>
|
|
|
|
#include "EQPacket.h"
|
|
#include "misc.h"
|
|
#include "op_codes.h"
|
|
#include "CRC16.h"
|
|
#include "opcodemgr.h"
|
|
#include "packet_dump.h"
|
|
#include "Log.h"
|
|
|
|
using namespace std;
|
|
|
|
// Global opcode manager map
|
|
extern map<int16, OpcodeManager*> EQOpcodeManager;
|
|
|
|
// Default opcode size for application packets
|
|
uint8 EQApplicationPacket::default_opcode_size = 2;
|
|
|
|
/**
|
|
* EQPacket constructor - creates a packet with specified opcode and data.
|
|
*
|
|
* @param op - The packet opcode
|
|
* @param buf - Source buffer to copy data from (can be nullptr)
|
|
* @param len - Length of data to allocate/copy
|
|
*/
|
|
EQPacket::EQPacket(const uint16 op, const unsigned char* buf, uint32 len)
|
|
{
|
|
this->opcode = op;
|
|
this->pBuffer = nullptr;
|
|
this->size = 0;
|
|
version = 0;
|
|
setTimeInfo(0, 0);
|
|
|
|
if (len > 0) {
|
|
this->size = len;
|
|
pBuffer = new unsigned char[this->size];
|
|
if (buf) {
|
|
memcpy(this->pBuffer, buf, this->size);
|
|
} else {
|
|
memset(this->pBuffer, 0, this->size);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the human-readable name of this EQ2 packet's opcode.
|
|
*
|
|
* @return Opcode name string, or nullptr if not found
|
|
*/
|
|
const char* EQ2Packet::GetOpcodeName()
|
|
{
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
if (EQOpcodeManager.count(OpcodeVersion) > 0) {
|
|
return EQOpcodeManager[OpcodeVersion]->EmuToName(login_op);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Prepare an EQ2 packet for transmission by adding protocol headers.
|
|
* Converts the emulator opcode to network opcode and adds necessary headers.
|
|
*
|
|
* @param MaxLen - Maximum allowed packet length
|
|
* @return Offset value for the prepared packet, or -1 on error
|
|
*/
|
|
int8 EQ2Packet::PreparePacket(int16 MaxLen)
|
|
{
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
|
|
// Validate that we have an opcode manager for this version
|
|
if (EQOpcodeManager.count(OpcodeVersion) == 0) {
|
|
LogWrite(PACKET__ERROR, 0, "Packet",
|
|
"Version %i is not listed in the opcodes table.", version);
|
|
return -1;
|
|
}
|
|
|
|
packet_prepared = true;
|
|
|
|
// Convert emulator opcode to network opcode
|
|
int16 login_opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(login_op);
|
|
if (login_opcode == 0xcdcd) {
|
|
LogWrite(PACKET__ERROR, 0, "Packet",
|
|
"Version %i is not listed in the opcodes table for opcode %s",
|
|
version, EQOpcodeManager[OpcodeVersion]->EmuToName(login_op));
|
|
return -1;
|
|
}
|
|
|
|
int8 offset = 0;
|
|
// Calculate new size: sequence (int16) + compressed flag (int8) + opcode + data
|
|
int32 new_size = size + sizeof(int16) + sizeof(int8);
|
|
bool oversized = false;
|
|
|
|
// Handle different opcode sizes and formats
|
|
if (login_opcode != 2) {
|
|
new_size += sizeof(int8); // for opcode type
|
|
if (login_opcode >= 255) {
|
|
new_size += sizeof(int16); // oversized opcode needs extra bytes
|
|
oversized = true;
|
|
} else {
|
|
login_opcode = ntohs(login_opcode);
|
|
}
|
|
}
|
|
|
|
// Allocate new buffer and build the packet
|
|
uchar* new_buffer = new uchar[new_size];
|
|
memset(new_buffer, 0, new_size);
|
|
uchar* ptr = new_buffer + sizeof(int16); // skip sequence field
|
|
|
|
if (login_opcode != 2) {
|
|
if (oversized) {
|
|
ptr += sizeof(int8); // compressed flag position
|
|
int8 addon = 0xff; // oversized marker
|
|
memcpy(ptr, &addon, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
}
|
|
memcpy(ptr, &login_opcode, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
memcpy(ptr, &login_opcode, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
}
|
|
|
|
// Copy original packet data
|
|
memcpy(ptr, pBuffer, size);
|
|
|
|
// Replace old buffer with new prepared buffer
|
|
safe_delete_array(pBuffer);
|
|
pBuffer = new_buffer;
|
|
offset = new_size - size - 1;
|
|
size = new_size;
|
|
|
|
return offset;
|
|
}
|
|
|
|
/**
|
|
* Serialize this protocol packet into a destination buffer.
|
|
*
|
|
* @param dest - Destination buffer to write to
|
|
* @param offset - Offset into source buffer to start copying from
|
|
* @return Total bytes written to destination
|
|
*/
|
|
uint32 EQProtocolPacket::serialize(unsigned char* dest, int8 offset) const
|
|
{
|
|
// Write opcode (2 bytes, handling both 8-bit and 16-bit opcodes)
|
|
if (opcode > 0xff) {
|
|
*(uint16*)dest = opcode;
|
|
} else {
|
|
*(dest) = 0;
|
|
*(dest + 1) = opcode;
|
|
}
|
|
|
|
// Copy packet data after the opcode
|
|
memcpy(dest + 2, pBuffer + offset, size - offset);
|
|
|
|
return size + 2;
|
|
}
|
|
|
|
/**
|
|
* Serialize this application packet into a destination buffer.
|
|
* Handles special opcode encoding rules for application-level packets.
|
|
*
|
|
* @param dest - Destination buffer to write to
|
|
* @return Total bytes written to destination
|
|
*/
|
|
uint32 EQApplicationPacket::serialize(unsigned char* dest) const
|
|
{
|
|
uint8 OpCodeBytes = app_opcode_size;
|
|
|
|
if (app_opcode_size == 1) {
|
|
// Single-byte opcode
|
|
*(unsigned char*)dest = opcode;
|
|
} else {
|
|
// Two-byte opcode with special encoding rules
|
|
// Application opcodes with low byte = 0x00 need extra 0x00 prefix
|
|
if ((opcode & 0x00ff) == 0) {
|
|
*(uint8*)dest = 0;
|
|
*(uint16*)(dest + 1) = opcode;
|
|
++OpCodeBytes;
|
|
} else {
|
|
*(uint16*)dest = opcode;
|
|
}
|
|
}
|
|
|
|
// Copy packet data after opcode
|
|
memcpy(dest + app_opcode_size, pBuffer, size);
|
|
|
|
return size + OpCodeBytes;
|
|
}
|
|
|
|
/**
|
|
* EQPacket destructor - cleans up allocated buffer memory.
|
|
*/
|
|
EQPacket::~EQPacket()
|
|
{
|
|
safe_delete_array(pBuffer);
|
|
pBuffer = nullptr;
|
|
}
|
|
|
|
/**
|
|
* Dump packet header with timestamp information to file.
|
|
*
|
|
* @param seq - Sequence number to display
|
|
* @param to - File pointer to write to (default: stdout)
|
|
*/
|
|
void EQPacket::DumpRawHeader(uint16 seq, FILE* to) const
|
|
{
|
|
// Note: Timestamp formatting code is commented out but preserved
|
|
// for potential future use in debugging
|
|
/*
|
|
if (timestamp.tv_sec) {
|
|
char temp[20];
|
|
tm t;
|
|
const time_t sec = timestamp.tv_sec;
|
|
localtime_s(&t, &sec);
|
|
strftime(temp, 20, "%F %T", &t);
|
|
fprintf(to, "%s.%06lu ", temp, timestamp.tv_usec);
|
|
}
|
|
*/
|
|
|
|
DumpRawHeaderNoTime(seq, to);
|
|
}
|
|
|
|
/**
|
|
* Get the human-readable name of this packet's opcode.
|
|
*
|
|
* @return Opcode name string, or nullptr if not found
|
|
*/
|
|
const char* EQPacket::GetOpcodeName()
|
|
{
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
if (EQOpcodeManager.count(OpcodeVersion) > 0) {
|
|
return EQOpcodeManager[OpcodeVersion]->EQToName(opcode);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Dump packet header without timestamp to file.
|
|
* Shows network addresses, sequence number, opcode, and size.
|
|
*
|
|
* @param seq - Sequence number to display (0xffff = don't show)
|
|
* @param to - File pointer to write to
|
|
*/
|
|
void EQPacket::DumpRawHeaderNoTime(uint16 seq, FILE* to) const
|
|
{
|
|
// Show network addressing if available
|
|
if (src_ip) {
|
|
string sIP = long2ip(src_ip);
|
|
string dIP = long2ip(dst_ip);
|
|
fprintf(to, "[%s:%d->%s:%d] ", sIP.c_str(), src_port, dIP.c_str(), dst_port);
|
|
}
|
|
|
|
// Show sequence number if valid
|
|
if (seq != 0xffff) {
|
|
fprintf(to, "[Seq=%u] ", seq);
|
|
}
|
|
|
|
// Get opcode name for display
|
|
string name;
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
if (EQOpcodeManager.count(OpcodeVersion) > 0) {
|
|
name = EQOpcodeManager[OpcodeVersion]->EQToName(opcode);
|
|
}
|
|
|
|
fprintf(to, "[OpCode 0x%04x (%s) Size=%u]\n", opcode, name.c_str(), size);
|
|
}
|
|
|
|
/**
|
|
* Dump complete packet (header + data) to file in formatted columns.
|
|
*
|
|
* @param to - File pointer to write to (default: stdout)
|
|
*/
|
|
void EQPacket::DumpRaw(FILE* to) const {
|
|
DumpRawHeader();
|
|
if (pBuffer && size) {
|
|
dump_message_column(pBuffer, size, " ", to);
|
|
}
|
|
fprintf(to, "\n");
|
|
}
|
|
|
|
/**
|
|
* EQProtocolPacket constructor from raw buffer.
|
|
* Parses opcode from buffer or uses provided opcode override.
|
|
*
|
|
* @param buf - Raw packet buffer
|
|
* @param len - Length of buffer
|
|
* @param in_opcode - Optional opcode override (-1 = parse from buffer)
|
|
*/
|
|
EQProtocolPacket::EQProtocolPacket(const unsigned char* buf, uint32 len, int in_opcode)
|
|
{
|
|
uint32 offset = 0;
|
|
|
|
if (in_opcode >= 0) {
|
|
// Use provided opcode override
|
|
opcode = in_opcode;
|
|
} else {
|
|
// Parse opcode from buffer (first 2 bytes)
|
|
if (len < 2 || buf == nullptr) {
|
|
// Insufficient data - set safe defaults
|
|
opcode = 0;
|
|
offset = len; // consume entire buffer
|
|
} else {
|
|
offset = 2;
|
|
opcode = ntohs(*(const uint16*)buf);
|
|
}
|
|
}
|
|
|
|
// Allocate and copy payload data after opcode
|
|
if (len > offset) {
|
|
size = len - offset;
|
|
pBuffer = new unsigned char[size];
|
|
if (buf) {
|
|
memcpy(pBuffer, buf + offset, size);
|
|
} else {
|
|
memset(pBuffer, 0, size);
|
|
}
|
|
} else {
|
|
pBuffer = nullptr;
|
|
size = 0;
|
|
}
|
|
|
|
// Initialize protocol packet state
|
|
version = 0;
|
|
eq2_compressed = false;
|
|
packet_prepared = false;
|
|
packet_encrypted = false;
|
|
sent_time = 0;
|
|
attempt_count = 0;
|
|
sequence = 0;
|
|
}
|
|
|
|
/**
|
|
* Combine this EQ2 packet with another EQ2 packet for efficient transmission.
|
|
* Implements EQ2's application-level packet combining protocol.
|
|
*
|
|
* @param rhs - Right-hand side packet to combine with this one
|
|
* @return true if packets were successfully combined, false otherwise
|
|
*/
|
|
bool EQ2Packet::AppCombine(EQ2Packet* rhs)
|
|
{
|
|
bool result = false;
|
|
uchar* tmpbuffer = nullptr;
|
|
bool over_sized_packet = false;
|
|
int32 new_size = 0;
|
|
|
|
// Case 1: This packet is already a combined packet
|
|
if (opcode == OP_AppCombined && ((size + rhs->size + 3) < 255)) {
|
|
int16 tmp_size = rhs->size - 2; // Subtract opcode bytes
|
|
|
|
// Check if we need oversized packet encoding
|
|
if (tmp_size >= 255) {
|
|
new_size = size + tmp_size + 3; // Extra bytes for oversized encoding
|
|
over_sized_packet = true;
|
|
} else {
|
|
new_size = size + tmp_size + 1; // One byte for size prefix
|
|
}
|
|
|
|
tmpbuffer = new uchar[new_size];
|
|
uchar* ptr = tmpbuffer;
|
|
|
|
// Copy existing combined packet data
|
|
memcpy(ptr, pBuffer, size);
|
|
ptr += size;
|
|
|
|
// Add size information for the new packet
|
|
if (over_sized_packet) {
|
|
*ptr++ = 255; // Oversized marker
|
|
tmp_size = htons(tmp_size);
|
|
memcpy(ptr, &tmp_size, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
*ptr++ = static_cast<uint8>(tmp_size);
|
|
}
|
|
|
|
// Copy packet data (skip opcode)
|
|
memcpy(ptr, rhs->pBuffer + 2, rhs->size - 2);
|
|
|
|
// Replace buffer and clean up
|
|
delete[] pBuffer;
|
|
size = new_size;
|
|
pBuffer = tmpbuffer;
|
|
safe_delete(rhs);
|
|
result = true;
|
|
}
|
|
// Case 2: Neither packet is combined - create new combined packet
|
|
else if (rhs->size > 2 && size > 2 && (size + rhs->size + 6) < 255) {
|
|
int32 tmp_size = size - 2;
|
|
int32 tmp_size2 = rhs->size - 2;
|
|
bool over_sized_packet2 = false;
|
|
|
|
// Calculate new size with headers
|
|
new_size = 4; // Base combined packet header
|
|
|
|
// First packet size encoding
|
|
if (tmp_size >= 255) {
|
|
new_size += tmp_size + 3; // Oversized encoding
|
|
over_sized_packet = true;
|
|
} else {
|
|
new_size += tmp_size + 1; // Normal encoding
|
|
}
|
|
|
|
// Second packet size encoding
|
|
if (tmp_size2 >= 255) {
|
|
new_size += tmp_size2 + 3; // Oversized encoding
|
|
over_sized_packet2 = true;
|
|
} else {
|
|
new_size += tmp_size2 + 1; // Normal encoding
|
|
}
|
|
|
|
tmpbuffer = new uchar[new_size];
|
|
|
|
// Set combined packet header
|
|
tmpbuffer[2] = 0;
|
|
tmpbuffer[3] = 0x19; // Combined packet marker
|
|
uchar* ptr = tmpbuffer + 4;
|
|
|
|
// Add first packet
|
|
if (over_sized_packet) {
|
|
*ptr++ = 255; // Oversized marker
|
|
tmp_size = htons(tmp_size);
|
|
memcpy(ptr, &tmp_size, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
*ptr++ = static_cast<uint8>(tmp_size);
|
|
}
|
|
memcpy(ptr, pBuffer + 2, size - 2);
|
|
ptr += (size - 2);
|
|
|
|
// Add second packet
|
|
if (over_sized_packet2) {
|
|
*ptr++ = 255; // Oversized marker
|
|
tmp_size2 = htons(tmp_size2);
|
|
memcpy(ptr, &tmp_size2, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
*ptr++ = static_cast<uint8>(tmp_size2);
|
|
}
|
|
memcpy(ptr, rhs->pBuffer + 2, rhs->size - 2);
|
|
|
|
// Replace buffer and update opcode
|
|
delete[] pBuffer;
|
|
size = new_size;
|
|
pBuffer = tmpbuffer;
|
|
opcode = OP_AppCombined;
|
|
safe_delete(rhs);
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Combine this protocol packet with another protocol packet.
|
|
* Used for efficient transmission of multiple small packets together.
|
|
*
|
|
* @param rhs - Right-hand side packet to combine with this one
|
|
* @return true if packets were successfully combined, false otherwise
|
|
*/
|
|
bool EQProtocolPacket::combine(const EQProtocolPacket* rhs)
|
|
{
|
|
bool result = false;
|
|
|
|
// Case 1: This packet is already combined - append to it
|
|
if (opcode == OP_Combined && size + rhs->size + 5 < 256) {
|
|
auto tmpbuffer = new unsigned char[size + rhs->size + 3];
|
|
|
|
// Copy existing combined data
|
|
memcpy(tmpbuffer, pBuffer, size);
|
|
uint32 offset = size;
|
|
|
|
// Add size prefix for new packet
|
|
tmpbuffer[offset++] = rhs->Size();
|
|
|
|
// Serialize and append new packet
|
|
offset += rhs->serialize(tmpbuffer + offset);
|
|
|
|
// Update buffer
|
|
size = offset;
|
|
delete[] pBuffer;
|
|
pBuffer = tmpbuffer;
|
|
result = true;
|
|
}
|
|
// Case 2: Neither packet is combined - create new combined packet
|
|
else if (size + rhs->size + 7 < 256) {
|
|
auto tmpbuffer = new unsigned char[size + rhs->size + 6];
|
|
uint32 offset = 0;
|
|
|
|
// Add first packet with size prefix
|
|
tmpbuffer[offset++] = Size();
|
|
offset += serialize(tmpbuffer + offset);
|
|
|
|
// Add second packet with size prefix
|
|
tmpbuffer[offset++] = rhs->Size();
|
|
offset += rhs->serialize(tmpbuffer + offset);
|
|
|
|
// Update buffer and mark as combined
|
|
size = offset;
|
|
delete[] pBuffer;
|
|
pBuffer = tmpbuffer;
|
|
opcode = OP_Combined;
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* EQApplicationPacket constructor from raw buffer.
|
|
* Used by EQProtocolPacket to create application packets from network data.
|
|
*
|
|
* @param buf - Raw packet buffer (starts with opcode)
|
|
* @param len - Length of buffer
|
|
* @param opcode_size - Size of opcode in bytes (0 = use default)
|
|
*/
|
|
EQApplicationPacket::EQApplicationPacket(const unsigned char* buf, uint32 len, uint8 opcode_size)
|
|
{
|
|
uint32 offset = 0;
|
|
app_opcode_size = (opcode_size == 0) ? EQApplicationPacket::default_opcode_size : opcode_size;
|
|
|
|
// Extract opcode based on size
|
|
if (app_opcode_size == 1) {
|
|
opcode = *(const unsigned char*)buf;
|
|
offset++;
|
|
} else {
|
|
opcode = *(const uint16*)buf;
|
|
offset += 2;
|
|
}
|
|
|
|
// Copy remaining data as payload
|
|
if ((len - offset) > 0) {
|
|
pBuffer = new unsigned char[len - offset];
|
|
memcpy(pBuffer, buf + offset, len - offset);
|
|
size = len - offset;
|
|
} else {
|
|
pBuffer = nullptr;
|
|
size = 0;
|
|
}
|
|
|
|
emu_opcode = OP_Unknown;
|
|
}
|
|
|
|
/**
|
|
* Combine this application packet with another application packet.
|
|
* Currently not implemented for application-level packets.
|
|
*
|
|
* @param rhs - Right-hand side packet to combine
|
|
* @return false (combining not supported at application level)
|
|
*/
|
|
bool EQApplicationPacket::combine(const EQApplicationPacket* rhs)
|
|
{
|
|
// Application packet combining is not implemented
|
|
// Use protocol-level combining instead
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Set the opcode for this application packet.
|
|
* Converts emulator opcode to network protocol opcode.
|
|
*
|
|
* @param emu_op - Emulator opcode to set
|
|
*/
|
|
void EQApplicationPacket::SetOpcode(EmuOpcode emu_op)
|
|
{
|
|
if (emu_op == OP_Unknown) {
|
|
opcode = 0;
|
|
emu_opcode = OP_Unknown;
|
|
return;
|
|
}
|
|
|
|
// Convert emulator opcode to network opcode
|
|
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(emu_op);
|
|
|
|
if (opcode == OP_Unknown) {
|
|
LogWrite(PACKET__DEBUG, 0, "Packet",
|
|
"Unable to convert Emu opcode %s (%d) into an EQ opcode.",
|
|
OpcodeNames[emu_op], emu_op);
|
|
}
|
|
|
|
// Cache the emulator opcode for future lookups
|
|
emu_opcode = emu_op;
|
|
}
|
|
|
|
/**
|
|
* Get the emulator opcode for this application packet.
|
|
* Converts network opcode to emulator opcode if not cached.
|
|
*
|
|
* @return Emulator opcode constant
|
|
*/
|
|
const EmuOpcode EQApplicationPacket::GetOpcodeConst() const
|
|
{
|
|
// Return cached opcode if available
|
|
if (emu_opcode != OP_Unknown) {
|
|
return emu_opcode;
|
|
}
|
|
|
|
// Handle special invalid opcode case
|
|
if (opcode == 10000) {
|
|
return OP_Unknown;
|
|
}
|
|
|
|
// Convert network opcode to emulator opcode
|
|
EmuOpcode emu_op = EQOpcodeManager[GetOpcodeVersion(version)]->EQToEmu(opcode);
|
|
if (emu_op == OP_Unknown) {
|
|
LogWrite(PACKET__DEBUG, 1, "Packet",
|
|
"Unable to convert EQ opcode 0x%.4X (%i) to an emu opcode (%s)",
|
|
opcode, opcode, __FUNCTION__);
|
|
}
|
|
|
|
return emu_op;
|
|
}
|
|
|
|
/**
|
|
* Convert this protocol packet to an application packet.
|
|
* Handles opcode format conversion between protocol and application layers.
|
|
*
|
|
* @param opcode_size - Size of application opcode (0 = use default)
|
|
* @return New application packet, or nullptr on failure
|
|
*/
|
|
EQApplicationPacket* EQProtocolPacket::MakeApplicationPacket(uint8 opcode_size) const
|
|
{
|
|
auto res = new EQApplicationPacket;
|
|
res->app_opcode_size = (opcode_size == 0) ? EQApplicationPacket::default_opcode_size : opcode_size;
|
|
|
|
if (res->app_opcode_size == 1) {
|
|
// Single-byte opcode format
|
|
res->pBuffer = new unsigned char[size + 1];
|
|
memcpy(res->pBuffer + 1, pBuffer, size);
|
|
*(res->pBuffer) = htons(opcode) & 0xff;
|
|
res->opcode = opcode & 0xff;
|
|
res->size = size + 1;
|
|
} else {
|
|
// Two-byte opcode format
|
|
res->pBuffer = new unsigned char[size];
|
|
memcpy(res->pBuffer, pBuffer, size);
|
|
res->opcode = opcode;
|
|
res->size = size;
|
|
}
|
|
|
|
// Copy network and timing information
|
|
res->copyInfo(this);
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Validate the CRC checksum of a network packet.
|
|
* Some packet types (session packets) are exempt from CRC validation.
|
|
*
|
|
* @param buffer - Packet buffer to validate
|
|
* @param length - Length of packet buffer
|
|
* @param Key - CRC key for validation
|
|
* @return true if CRC is valid or packet is exempt, false otherwise
|
|
*/
|
|
bool EQProtocolPacket::ValidateCRC(const unsigned char* buffer, int length, uint32 Key)
|
|
{
|
|
bool valid = false;
|
|
|
|
// Session packets are not CRC protected
|
|
if (buffer[0] == 0x00 &&
|
|
(buffer[1] == OP_SessionRequest ||
|
|
buffer[1] == OP_SessionResponse ||
|
|
buffer[1] == OP_OutOfSession)) {
|
|
valid = true;
|
|
}
|
|
// Combined application packets are also exempt
|
|
else if (buffer[2] == 0x00 && buffer[3] == 0x19) {
|
|
valid = true;
|
|
}
|
|
// All other packets must have valid CRC
|
|
else {
|
|
uint16 comp_crc = CRC16(buffer, length - 2, Key);
|
|
uint16 packet_crc = ntohs(*(const uint16*)(buffer + length - 2));
|
|
|
|
#ifdef EQN_DEBUG
|
|
if (packet_crc && comp_crc != packet_crc) {
|
|
cout << "CRC mismatch: comp=" << hex << comp_crc
|
|
<< ", packet=" << packet_crc << dec << endl;
|
|
}
|
|
#endif
|
|
// Valid if no CRC present (packet_crc == 0) or CRCs match
|
|
valid = (!packet_crc || comp_crc == packet_crc);
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
/**
|
|
* Decompress a network packet using zlib or simple encoding.
|
|
* Supports both zlib compression (0x5a) and simple encoding (0xa5).
|
|
*
|
|
* @param buffer - Compressed packet buffer
|
|
* @param length - Length of compressed buffer
|
|
* @param newbuf - Destination buffer for decompressed data
|
|
* @param newbufsize - Size of destination buffer
|
|
* @return Length of decompressed data
|
|
*/
|
|
uint32 EQProtocolPacket::Decompress(const unsigned char* buffer, uint32 length, unsigned char* newbuf, uint32 newbufsize)
|
|
{
|
|
uint32 newlen = 0;
|
|
uint32 flag_offset = 0;
|
|
|
|
// Copy opcode header
|
|
newbuf[0] = buffer[0];
|
|
if (buffer[0] == 0x00) {
|
|
flag_offset = 2; // Two-byte opcode
|
|
newbuf[1] = buffer[1];
|
|
} else {
|
|
flag_offset = 1; // One-byte opcode
|
|
}
|
|
|
|
// Check compression type
|
|
if (length > 2 && buffer[flag_offset] == 0x5a) {
|
|
// Zlib compression
|
|
LogWrite(PACKET__DEBUG, 0, "Packet", "Decompressing zlib packet");
|
|
newlen = Inflate(const_cast<unsigned char*>(buffer + flag_offset + 1),
|
|
length - (flag_offset + 1) - 2, // Subtract CRC bytes
|
|
newbuf + flag_offset,
|
|
newbufsize - flag_offset) + flag_offset;
|
|
|
|
// Handle decompression failure
|
|
if (newlen == static_cast<uint32>(-1)) {
|
|
LogWrite(PACKET__ERROR, 0, "Packet", "Zlib decompression failed!");
|
|
DumpPacket(buffer, length);
|
|
// Fallback: copy original buffer
|
|
memcpy(newbuf, buffer, length);
|
|
return length;
|
|
}
|
|
|
|
// Copy CRC bytes to end
|
|
newbuf[newlen++] = buffer[length - 2];
|
|
newbuf[newlen++] = buffer[length - 1];
|
|
} else if (length > 2 && buffer[flag_offset] == 0xa5) {
|
|
// Simple encoding - just remove the encoding flag
|
|
LogWrite(PACKET__DEBUG, 0, "Packet", "Decompressing simple encoded packet");
|
|
memcpy(newbuf + flag_offset, buffer + flag_offset + 1, length - (flag_offset + 1));
|
|
newlen = length - 1;
|
|
} else {
|
|
// No compression - direct copy
|
|
memcpy(newbuf, buffer, length);
|
|
newlen = length;
|
|
}
|
|
|
|
return newlen;
|
|
}
|
|
|
|
/**
|
|
* Compress a network packet using zlib or simple encoding.
|
|
* Uses zlib for packets > 30 bytes, simple encoding for smaller packets.
|
|
*
|
|
* @param buffer - Source packet buffer
|
|
* @param length - Length of source buffer
|
|
* @param newbuf - Destination buffer for compressed data
|
|
* @param newbufsize - Size of destination buffer
|
|
* @return Length of compressed data
|
|
*/
|
|
uint32 EQProtocolPacket::Compress(const unsigned char* buffer, uint32 length, unsigned char* newbuf, uint32 newbufsize)
|
|
{
|
|
uint32 flag_offset = 1;
|
|
uint32 newlength;
|
|
|
|
// Copy opcode header
|
|
newbuf[0] = buffer[0];
|
|
if (buffer[0] == 0) {
|
|
flag_offset = 2; // Two-byte opcode
|
|
newbuf[1] = buffer[1];
|
|
}
|
|
|
|
// Choose compression method based on packet size
|
|
if (length > 30) {
|
|
// Use zlib compression for larger packets
|
|
newlength = Deflate(const_cast<unsigned char*>(buffer + flag_offset),
|
|
length - flag_offset,
|
|
newbuf + flag_offset + 1,
|
|
newbufsize);
|
|
*(newbuf + flag_offset) = 0x5a; // Zlib compression flag
|
|
newlength += flag_offset + 1;
|
|
} else {
|
|
// Use simple encoding for smaller packets
|
|
memmove(newbuf + flag_offset + 1, buffer + flag_offset, length - flag_offset);
|
|
*(newbuf + flag_offset) = 0xa5; // Simple encoding flag
|
|
newlength = length + 1;
|
|
}
|
|
|
|
return newlength;
|
|
}
|
|
|
|
/**
|
|
* Decode chat packet data using XOR encryption.
|
|
* Uses a rolling XOR key that updates with each 4-byte block.
|
|
*
|
|
* @param buffer - Buffer containing chat data to decode
|
|
* @param size - Size of buffer
|
|
* @param DecodeKey - Initial decoding key
|
|
*/
|
|
void EQProtocolPacket::ChatDecode(unsigned char* buffer, int size, int DecodeKey)
|
|
{
|
|
// Skip decoding for certain packet types
|
|
if (buffer[1] != 0x01 && buffer[0] != 0x02 && buffer[0] != 0x1d) {
|
|
int Key = DecodeKey;
|
|
auto test = static_cast<unsigned char*>(malloc(size));
|
|
|
|
// Skip the first 2 bytes (opcode)
|
|
buffer += 2;
|
|
size -= 2;
|
|
|
|
// Decode 4-byte blocks with rolling key
|
|
int i;
|
|
for (i = 0; i + 4 <= size; i += 4) {
|
|
int pt = (*(int*)&buffer[i]) ^ Key;
|
|
Key = (*(int*)&buffer[i]); // Update key with encrypted data
|
|
*(int*)&test[i] = pt;
|
|
}
|
|
|
|
// Decode remaining bytes with last key byte
|
|
unsigned char KC = Key & 0xFF;
|
|
for (; i < size; i++) {
|
|
test[i] = buffer[i] ^ KC;
|
|
}
|
|
|
|
// Copy decoded data back
|
|
memcpy(buffer, test, size);
|
|
free(test);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encode chat packet data using XOR encryption.
|
|
* Uses a rolling XOR key that updates with each encrypted 4-byte block.
|
|
*
|
|
* @param buffer - Buffer containing chat data to encode
|
|
* @param size - Size of buffer
|
|
* @param EncodeKey - Initial encoding key
|
|
*/
|
|
void EQProtocolPacket::ChatEncode(unsigned char* buffer, int size, int EncodeKey)
|
|
{
|
|
// Skip encoding for certain packet types
|
|
if (buffer[1] != 0x01 && buffer[0] != 0x02 && buffer[0] != 0x1d) {
|
|
int Key = EncodeKey;
|
|
auto test = static_cast<char*>(malloc(size));
|
|
|
|
// Skip the first 2 bytes (opcode)
|
|
buffer += 2;
|
|
size -= 2;
|
|
|
|
// Encode 4-byte blocks with rolling key
|
|
int i;
|
|
for (i = 0; i + 4 <= size; i += 4) {
|
|
int pt = (*(int*)&buffer[i]) ^ Key;
|
|
Key = pt; // Update key with encrypted data
|
|
*(int*)&test[i] = pt;
|
|
}
|
|
|
|
// Encode remaining bytes with last key byte
|
|
unsigned char KC = Key & 0xFF;
|
|
for (; i < size; i++) {
|
|
test[i] = buffer[i] ^ KC;
|
|
}
|
|
|
|
// Copy encoded data back
|
|
memcpy(buffer, test, size);
|
|
free(test);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a buffer contains a valid EverQuest protocol packet.
|
|
* Validates the opcode against known protocol opcodes.
|
|
*
|
|
* @param in_buff - Input buffer to check
|
|
* @param len - Length of input buffer
|
|
* @param bTrimCRC - Whether CRC should be trimmed (unused)
|
|
* @return true if buffer contains a valid protocol packet
|
|
*/
|
|
bool EQProtocolPacket::IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC)
|
|
{
|
|
bool ret = false;
|
|
uint16_t opcode = ntohs(*(uint16_t*)in_buff);
|
|
|
|
// Check against known protocol opcodes
|
|
switch (opcode) {
|
|
case OP_SessionRequest:
|
|
case OP_SessionDisconnect:
|
|
case OP_KeepAlive:
|
|
case OP_SessionStatResponse:
|
|
case OP_Packet:
|
|
case OP_Combined:
|
|
case OP_Fragment:
|
|
case OP_Ack:
|
|
case OP_OutOfOrderAck:
|
|
case OP_OutOfSession:
|
|
ret = true;
|
|
break;
|
|
default:
|
|
// Unknown opcode - not a protocol packet
|
|
ret = false;
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Dump application packet data in hexadecimal format.
|
|
*
|
|
* @param app - Application packet to dump
|
|
*/
|
|
void DumpPacketHex(const EQApplicationPacket* app)
|
|
{
|
|
DumpPacketHex(app->pBuffer, app->size);
|
|
}
|
|
|
|
/**
|
|
* Dump application packet data in ASCII format.
|
|
*
|
|
* @param app - Application packet to dump
|
|
*/
|
|
void DumpPacketAscii(const EQApplicationPacket* app)
|
|
{
|
|
DumpPacketAscii(app->pBuffer, app->size);
|
|
}
|
|
|
|
/**
|
|
* Dump protocol packet data in hexadecimal format.
|
|
*
|
|
* @param app - Protocol packet to dump
|
|
*/
|
|
void DumpPacket(const EQProtocolPacket* app)
|
|
{
|
|
DumpPacketHex(app->pBuffer, app->size);
|
|
}
|
|
|
|
/**
|
|
* Dump application packet with optional header information.
|
|
*
|
|
* @param app - Application packet to dump
|
|
* @param iShowInfo - Whether to show packet information header
|
|
*/
|
|
void DumpPacket(const EQApplicationPacket* app, bool iShowInfo)
|
|
{
|
|
if (iShowInfo) {
|
|
cout << "Dumping Applayer: 0x" << hex << setfill('0') << setw(4)
|
|
<< app->GetOpcode() << dec;
|
|
cout << " size:" << app->size << endl;
|
|
}
|
|
DumpPacketHex(app->pBuffer, app->size);
|
|
// ASCII dump is commented out but available:
|
|
// DumpPacketAscii(app->pBuffer, app->size);
|
|
}
|
|
|
|
/**
|
|
* Dump application packet data in binary format.
|
|
*
|
|
* @param app - Application packet to dump
|
|
*/
|
|
void DumpPacketBin(const EQApplicationPacket* app)
|
|
{
|
|
DumpPacketBin(app->pBuffer, app->size);
|
|
}
|
|
|
|
|