Emu/source/common/EQPacket.cpp

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 "crypto/crc.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);
}