891 lines
24 KiB
C++
891 lines
24 KiB
C++
// Copyright (C) 2007 EQ2EMulator Development Team - GPL v3+ License
|
|
|
|
#pragma once
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <sys/time.h>
|
|
#include <netinet/in.h>
|
|
#include <memory>
|
|
|
|
#include "packet_dump.hpp"
|
|
#include "../log.hpp"
|
|
#include "../misc.hpp"
|
|
#include "../debug.hpp"
|
|
#include "../types.hpp"
|
|
#include "../crc16.hpp"
|
|
#include "../opcodes/opcodes.hpp"
|
|
#include "../opcodes/emu_opcodes.hpp"
|
|
#include "../opcodes/opcode_manager.hpp"
|
|
|
|
using namespace std;
|
|
extern map<int16,OpcodeManager*>EQOpcodeManager;
|
|
|
|
class OpcodeManager;
|
|
class EQStream;
|
|
|
|
// Base packet class for all EverQuest protocol packets
|
|
class EQPacket
|
|
{
|
|
friend class EQStream;
|
|
|
|
public:
|
|
unsigned char *pBuffer; // Raw packet data buffer
|
|
uint32 size; // Size of packet data
|
|
uint32 src_ip, dst_ip; // Source and destination IP addresses
|
|
uint16 src_port, dst_port; // Source and destination ports
|
|
uint32 priority; // Packet priority for queuing
|
|
timeval timestamp; // Timestamp when packet was created/received
|
|
int16 version; // Protocol version
|
|
|
|
// Destructor - cleans up allocated buffer
|
|
~EQPacket()
|
|
{
|
|
safe_delete_array(pBuffer);
|
|
pBuffer = nullptr;
|
|
}
|
|
|
|
// Dumps packet header with timestamp information to file
|
|
void DumpRawHeader(uint16 seq = 0xffff, FILE* to = stdout) const
|
|
{
|
|
DumpRawHeaderNoTime(seq, to);
|
|
}
|
|
|
|
// Dumps packet header without timestamp information to file
|
|
void DumpRawHeaderNoTime(uint16 seq = 0xffff, FILE* to = stdout) const
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (seq != 0xffff)
|
|
fprintf(to, "[Seq=%u] ", seq);
|
|
|
|
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);
|
|
}
|
|
|
|
// Dumps complete packet with header and data in hex format
|
|
void DumpRaw(FILE* to = stdout) const
|
|
{
|
|
DumpRawHeader();
|
|
if (pBuffer && size)
|
|
dump_message_column(pBuffer, size, " ", to);
|
|
fprintf(to, "\n");
|
|
}
|
|
|
|
// Gets human-readable name for packet opcode
|
|
const char* GetOpcodeName()
|
|
{
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
if (EQOpcodeManager.count(OpcodeVersion) > 0)
|
|
return EQOpcodeManager[OpcodeVersion]->EQToName(opcode);
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
// Sets protocol version for this packet
|
|
void setVersion(int16 new_version) { version = new_version; }
|
|
|
|
// Sets source IP and port information
|
|
void setSrcInfo(uint32 sip, uint16 sport) { src_ip = sip; src_port = sport; }
|
|
|
|
// Sets destination IP and port information
|
|
void setDstInfo(uint32 dip, uint16 dport) { dst_ip = dip; dst_port = dport; }
|
|
|
|
// Sets timestamp information for packet timing
|
|
void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { timestamp.tv_sec = ts_sec; timestamp.tv_usec = ts_usec; }
|
|
|
|
// Copies connection and timing info from another packet
|
|
void copyInfo(const EQPacket* p)
|
|
{
|
|
src_ip = p->src_ip;
|
|
src_port = p->src_port;
|
|
dst_ip = p->dst_ip;
|
|
dst_port = p->dst_port;
|
|
timestamp.tv_sec = p->timestamp.tv_sec;
|
|
timestamp.tv_usec = p->timestamp.tv_usec;
|
|
}
|
|
|
|
// Returns total packet size including opcode header
|
|
uint32 Size() const { return size + 2; }
|
|
|
|
// Gets raw opcode value without translation
|
|
uint16 GetRawOpcode() const { return opcode; }
|
|
|
|
// Comparison operator for timestamp-based sorting
|
|
inline bool operator<(const EQPacket& rhs)
|
|
{
|
|
return (timestamp.tv_sec < rhs.timestamp.tv_sec ||
|
|
(timestamp.tv_sec == rhs.timestamp.tv_sec && timestamp.tv_usec < rhs.timestamp.tv_usec));
|
|
}
|
|
|
|
// Sets the protocol-level opcode
|
|
void SetProtocolOpcode(int16 new_opcode) { opcode = new_opcode; }
|
|
|
|
protected:
|
|
uint16 opcode; // Packet opcode identifier
|
|
|
|
// Constructor for creating packet with opcode and data buffer
|
|
EQPacket(const uint16 op, const unsigned char* buf, uint32 len)
|
|
{
|
|
opcode = op;
|
|
pBuffer = nullptr;
|
|
size = 0;
|
|
version = 0;
|
|
setTimeInfo(0, 0);
|
|
|
|
if (len > 0) {
|
|
size = len;
|
|
pBuffer = new unsigned char[size];
|
|
if (buf) {
|
|
memcpy(pBuffer, buf, size);
|
|
} else {
|
|
memset(pBuffer, 0, size);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy constructor - disabled to prevent accidental copies
|
|
EQPacket(const EQPacket& p) { version = 0; }
|
|
|
|
// Default constructor
|
|
EQPacket()
|
|
{
|
|
opcode = 0;
|
|
pBuffer = nullptr;
|
|
size = 0;
|
|
version = 0;
|
|
setTimeInfo(0, 0);
|
|
}
|
|
};
|
|
|
|
class EQApplicationPacket;
|
|
|
|
// Protocol-level packet handling EverQuest network protocol specifics
|
|
class EQProtocolPacket : public EQPacket
|
|
{
|
|
public:
|
|
bool eq2_compressed; // Flag indicating if packet is compressed
|
|
bool packet_prepared; // Flag indicating if packet has been prepared for sending
|
|
bool packet_encrypted; // Flag indicating if packet is encrypted
|
|
bool acked; // Flag indicating if packet has been acknowledged
|
|
int32 sent_time; // Timestamp when packet was sent
|
|
int8 attempt_count; // Number of send attempts for this packet
|
|
int32 sequence; // Sequence number for ordering
|
|
|
|
// Constructor with opcode and data buffer
|
|
EQProtocolPacket(uint16 op, const unsigned char* buf, uint32 len) : EQPacket(op, buf, len)
|
|
{
|
|
eq2_compressed = false;
|
|
packet_prepared = false;
|
|
packet_encrypted = false;
|
|
sequence = 0;
|
|
sent_time = 0;
|
|
attempt_count = 0;
|
|
acked = false;
|
|
}
|
|
|
|
// Constructor from raw buffer with optional opcode override
|
|
EQProtocolPacket(const unsigned char* buf, uint32 len, int in_opcode = -1)
|
|
{
|
|
uint32 offset = 0;
|
|
if (in_opcode >= 0) {
|
|
opcode = in_opcode;
|
|
} else {
|
|
// Ensure there are at least 2 bytes for the opcode
|
|
if (len < 2 || buf == nullptr) {
|
|
opcode = 0;
|
|
offset = len;
|
|
} else {
|
|
offset = 2;
|
|
opcode = ntohs(*(const uint16*)buf);
|
|
}
|
|
}
|
|
|
|
// Check that there is payload data after the header
|
|
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;
|
|
}
|
|
|
|
version = 0;
|
|
eq2_compressed = false;
|
|
packet_prepared = false;
|
|
packet_encrypted = false;
|
|
sent_time = 0;
|
|
attempt_count = 0;
|
|
sequence = 0;
|
|
}
|
|
|
|
// Attempts to combine this packet with another protocol packet for efficiency
|
|
bool combine(const EQProtocolPacket* rhs)
|
|
{
|
|
bool result = false;
|
|
|
|
// Check if this is already a combined packet and we can add more
|
|
if (opcode == OP_Combined && size + rhs->size + 5 < 256) {
|
|
auto tmpbuffer = new unsigned char[size + rhs->size + 3];
|
|
memcpy(tmpbuffer, pBuffer, size);
|
|
uint32 offset = size;
|
|
tmpbuffer[offset++] = rhs->Size();
|
|
offset += rhs->serialize(tmpbuffer + offset);
|
|
size = offset;
|
|
delete[] pBuffer;
|
|
pBuffer = tmpbuffer;
|
|
result = true;
|
|
}
|
|
// Check if we can create a new combined packet from two individual packets
|
|
else if (size + rhs->size + 7 < 256) {
|
|
auto tmpbuffer = new unsigned char[size + rhs->size + 6];
|
|
uint32 offset = 0;
|
|
tmpbuffer[offset++] = Size();
|
|
offset += serialize(tmpbuffer + offset);
|
|
tmpbuffer[offset++] = rhs->Size();
|
|
offset += rhs->serialize(tmpbuffer + offset);
|
|
size = offset;
|
|
delete[] pBuffer;
|
|
pBuffer = tmpbuffer;
|
|
opcode = OP_Combined;
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Serializes packet data to destination buffer with optional offset
|
|
uint32 serialize(unsigned char* dest, int8 offset = 0) const
|
|
{
|
|
if (opcode > 0xff) {
|
|
*(uint16*)dest = opcode;
|
|
} else {
|
|
*(dest) = 0;
|
|
*(dest + 1) = opcode;
|
|
}
|
|
memcpy(dest + 2, pBuffer + offset, size - offset);
|
|
|
|
return size + 2;
|
|
}
|
|
|
|
// Creates a deep copy of this protocol packet
|
|
EQProtocolPacket* Copy()
|
|
{
|
|
EQProtocolPacket* new_packet = new EQProtocolPacket(opcode, pBuffer, size);
|
|
new_packet->eq2_compressed = this->eq2_compressed;
|
|
new_packet->packet_prepared = this->packet_prepared;
|
|
new_packet->packet_encrypted = this->packet_encrypted;
|
|
return new_packet;
|
|
}
|
|
|
|
// Converts protocol packet to application packet for higher-level processing
|
|
EQApplicationPacket* MakeApplicationPacket(uint8 opcode_size = 0) const;
|
|
|
|
// Validates CRC checksum on received packet data
|
|
static bool ValidateCRC(const unsigned char* buffer, int length, uint32 Key)
|
|
{
|
|
bool valid = false;
|
|
|
|
// OP_SessionRequest, OP_SessionResponse, OP_OutOfSession are not CRC'd
|
|
if (buffer[0] == 0x00 && (buffer[1] == OP_SessionRequest || buffer[1] == OP_SessionResponse || buffer[1] == OP_OutOfSession)) {
|
|
valid = true;
|
|
} else if (buffer[2] == 0x00 && buffer[3] == 0x19) {
|
|
valid = true;
|
|
} else {
|
|
uint16 comp_crc = CRC16(buffer, length - 2, Key);
|
|
uint16 packet_crc = ntohs(*(const uint16*)(buffer + length - 2));
|
|
valid = (!packet_crc || comp_crc == packet_crc);
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
// Decompresses packet data using zlib inflation
|
|
static uint32 Decompress(const unsigned char* buffer, const uint32 length, unsigned char* newbuf, uint32 newbufsize)
|
|
{
|
|
uint32 newlen = 0;
|
|
uint32 flag_offset = 0;
|
|
newbuf[0] = buffer[0];
|
|
|
|
if (buffer[0] == 0x00) {
|
|
flag_offset = 2;
|
|
newbuf[1] = buffer[1];
|
|
} else {
|
|
flag_offset = 1;
|
|
}
|
|
|
|
if (length > 2 && buffer[flag_offset] == 0x5a) {
|
|
LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 1");
|
|
newlen = Inflate(const_cast<unsigned char*>(buffer + flag_offset + 1), length - (flag_offset + 1) - 2, newbuf + flag_offset, newbufsize - flag_offset) + 2;
|
|
|
|
// something went bad with zlib
|
|
if (newlen == -1) {
|
|
LogWrite(PACKET__ERROR, 0, "Packet", "Debug Bad Inflate!");
|
|
DumpPacket(buffer, length);
|
|
memcpy(newbuf, buffer, length);
|
|
return length;
|
|
}
|
|
|
|
newbuf[newlen++] = buffer[length - 2];
|
|
newbuf[newlen++] = buffer[length - 1];
|
|
} else if (length > 2 && buffer[flag_offset] == 0xa5) {
|
|
LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 2");
|
|
memcpy(newbuf + flag_offset, buffer + flag_offset + 1, length - (flag_offset + 1));
|
|
newlen = length - 1;
|
|
} else {
|
|
memcpy(newbuf, buffer, length);
|
|
newlen = length;
|
|
}
|
|
|
|
return newlen;
|
|
}
|
|
|
|
// Compresses packet data using zlib deflation
|
|
static uint32 Compress(const unsigned char* buffer, const uint32 length, unsigned char* newbuf, uint32 newbufsize)
|
|
{
|
|
uint32 flag_offset = 1, newlength;
|
|
newbuf[0] = buffer[0];
|
|
|
|
if (buffer[0] == 0) {
|
|
flag_offset = 2;
|
|
newbuf[1] = buffer[1];
|
|
}
|
|
|
|
if (length > 30) {
|
|
newlength = Deflate(const_cast<unsigned char*>(buffer + flag_offset), length - flag_offset, newbuf + flag_offset + 1, newbufsize);
|
|
*(newbuf + flag_offset) = 0x5a;
|
|
newlength += flag_offset + 1;
|
|
} else {
|
|
memmove(newbuf + flag_offset + 1, buffer + flag_offset, length - flag_offset);
|
|
*(newbuf + flag_offset) = 0xa5;
|
|
newlength = length + 1;
|
|
}
|
|
|
|
return newlength;
|
|
}
|
|
|
|
// Decodes chat message encryption
|
|
static void ChatDecode(unsigned char* buffer, int size, int DecodeKey)
|
|
{
|
|
if (buffer[1] != 0x01 && buffer[0] != 0x02 && buffer[0] != 0x1d) {
|
|
int Key = DecodeKey;
|
|
unsigned char* test = (unsigned char*)malloc(size);
|
|
buffer += 2;
|
|
size -= 2;
|
|
|
|
int i;
|
|
for (i = 0; i + 4 <= size; i += 4) {
|
|
int pt = (*(int*)&buffer[i]) ^ (Key);
|
|
Key = (*(int*)&buffer[i]);
|
|
*(int*)&test[i] = pt;
|
|
}
|
|
unsigned char KC = Key & 0xFF;
|
|
for (; i < size; i++) {
|
|
test[i] = buffer[i] ^ KC;
|
|
}
|
|
memcpy(buffer, test, size);
|
|
free(test);
|
|
}
|
|
}
|
|
|
|
// Encodes chat message with encryption
|
|
static void ChatEncode(unsigned char* buffer, int size, int EncodeKey)
|
|
{
|
|
if (buffer[1] != 0x01 && buffer[0] != 0x02 && buffer[0] != 0x1d) {
|
|
int Key = EncodeKey;
|
|
char* test = (char*)malloc(size);
|
|
int i;
|
|
buffer += 2;
|
|
size -= 2;
|
|
for (i = 0; i + 4 <= size; i += 4) {
|
|
int pt = (*(int*)&buffer[i]) ^ (Key);
|
|
Key = pt;
|
|
*(int*)&test[i] = pt;
|
|
}
|
|
unsigned char KC = Key & 0xFF;
|
|
for (; i < size; i++) {
|
|
test[i] = buffer[i] ^ KC;
|
|
}
|
|
memcpy(buffer, test, size);
|
|
free(test);
|
|
}
|
|
}
|
|
|
|
// Determines if buffer contains a valid protocol packet
|
|
static bool IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC)
|
|
{
|
|
bool ret = false;
|
|
uint16_t opcode = ntohs(*(uint16_t*)in_buff);
|
|
|
|
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;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
private:
|
|
// Copy constructor disabled to prevent accidental copies
|
|
EQProtocolPacket(const EQProtocolPacket& p) {}
|
|
};
|
|
|
|
// EverQuest 2 specific packet handling with login opcodes
|
|
class EQ2Packet : public EQProtocolPacket
|
|
{
|
|
public:
|
|
EmuOpcode login_op; // EmuOpcode for this packet
|
|
|
|
// Constructor with login opcode and data buffer
|
|
EQ2Packet(const EmuOpcode in_login_op, const unsigned char* buf, uint32 len) : EQProtocolPacket(OP_Packet, buf, len)
|
|
{
|
|
login_op = in_login_op;
|
|
eq2_compressed = false;
|
|
packet_prepared = false;
|
|
packet_encrypted = false;
|
|
}
|
|
|
|
// Attempts to combine this EQ2 packet with another for transmission efficiency
|
|
bool AppCombine(EQ2Packet* rhs)
|
|
{
|
|
bool result = false;
|
|
uchar* tmpbuffer = nullptr;
|
|
bool over_sized_packet = false;
|
|
int32 new_size = 0;
|
|
|
|
// If this is already a combined packet and we can add more
|
|
if (opcode == OP_AppCombined && ((size + rhs->size + 3) < 255)) {
|
|
int16 tmp_size = rhs->size - 2;
|
|
if (tmp_size >= 255) {
|
|
new_size = size + tmp_size + 3;
|
|
over_sized_packet = true;
|
|
} else {
|
|
new_size = size + tmp_size + 1;
|
|
}
|
|
|
|
tmpbuffer = new uchar[new_size];
|
|
uchar* ptr = tmpbuffer;
|
|
memcpy(ptr, pBuffer, size);
|
|
ptr += size;
|
|
|
|
if (over_sized_packet) {
|
|
memset(ptr, 255, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
tmp_size = htons(tmp_size);
|
|
memcpy(ptr, &tmp_size, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
memcpy(ptr, &tmp_size, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
}
|
|
|
|
memcpy(ptr, rhs->pBuffer + 2, rhs->size - 2);
|
|
delete[] pBuffer;
|
|
size = new_size;
|
|
pBuffer = tmpbuffer;
|
|
safe_delete(rhs);
|
|
result = true;
|
|
}
|
|
// Create a new combined packet from two individual packets
|
|
else if (rhs->size > 2 && size > 2 && (size + rhs->size + 6) < 255) {
|
|
int32 tmp_size = size - 2;
|
|
int32 tmp_size2 = rhs->size - 2;
|
|
opcode = OP_AppCombined;
|
|
bool over_sized_packet2 = false;
|
|
new_size = size;
|
|
|
|
if (tmp_size >= 255) {
|
|
new_size += 5;
|
|
over_sized_packet = true;
|
|
} else {
|
|
new_size += 3;
|
|
}
|
|
|
|
if (tmp_size2 >= 255) {
|
|
new_size += tmp_size2 + 3;
|
|
over_sized_packet2 = true;
|
|
} else {
|
|
new_size += tmp_size2 + 1;
|
|
}
|
|
|
|
tmpbuffer = new uchar[new_size];
|
|
tmpbuffer[2] = 0;
|
|
tmpbuffer[3] = 0x19;
|
|
uchar* ptr = tmpbuffer + 4;
|
|
|
|
if (over_sized_packet) {
|
|
memset(ptr, 255, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
tmp_size = htons(tmp_size);
|
|
memcpy(ptr, &tmp_size, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
memcpy(ptr, &tmp_size, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
}
|
|
|
|
memcpy(ptr, pBuffer + 2, size - 2);
|
|
ptr += (size - 2);
|
|
|
|
if (over_sized_packet2) {
|
|
memset(ptr, 255, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
tmp_size2 = htons(tmp_size2);
|
|
memcpy(ptr, &tmp_size2, sizeof(int16));
|
|
ptr += sizeof(int16);
|
|
} else {
|
|
memcpy(ptr, &tmp_size2, sizeof(int8));
|
|
ptr += sizeof(int8);
|
|
}
|
|
|
|
memcpy(ptr, rhs->pBuffer + 2, rhs->size - 2);
|
|
size = new_size;
|
|
delete[] pBuffer;
|
|
pBuffer = tmpbuffer;
|
|
safe_delete(rhs);
|
|
result = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Creates a deep copy of this EQ2 packet
|
|
EQ2Packet* Copy()
|
|
{
|
|
EQ2Packet* new_packet = new EQ2Packet(login_op, pBuffer, size);
|
|
new_packet->eq2_compressed = this->eq2_compressed;
|
|
new_packet->packet_prepared = this->packet_prepared;
|
|
new_packet->packet_encrypted = this->packet_encrypted;
|
|
return new_packet;
|
|
}
|
|
|
|
// Prepares packet for transmission by adding headers and opcodes
|
|
int8 PreparePacket(int16 MaxLen)
|
|
{
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
|
|
// stops a crash for incorrect 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;
|
|
|
|
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;
|
|
}
|
|
|
|
int16 orig_opcode = login_opcode;
|
|
int8 offset = 0;
|
|
// one of the int16s is for the seq, other is for the EQ2 opcode and compressed flag (OP_Packet is the header, not the opcode)
|
|
int32 new_size = size + sizeof(int16) + sizeof(int8);
|
|
bool oversized = false;
|
|
|
|
if (login_opcode != 2) {
|
|
new_size += sizeof(int8); // for opcode
|
|
if (login_opcode >= 255) {
|
|
new_size += sizeof(int16);
|
|
oversized = true;
|
|
} else {
|
|
login_opcode = ntohs(login_opcode);
|
|
}
|
|
}
|
|
|
|
uchar* new_buffer = new uchar[new_size];
|
|
memset(new_buffer, 0, new_size);
|
|
uchar* ptr = new_buffer + sizeof(int16); // sequence is first
|
|
|
|
if (login_opcode != 2) {
|
|
if (oversized) {
|
|
ptr += sizeof(int8); // compressed flag
|
|
int8 addon = 0xff;
|
|
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);
|
|
}
|
|
|
|
memcpy(ptr, pBuffer, size);
|
|
|
|
safe_delete_array(pBuffer);
|
|
pBuffer = new_buffer;
|
|
offset = new_size - size - 1;
|
|
size = new_size;
|
|
|
|
return offset;
|
|
}
|
|
|
|
// Gets human-readable name for the login opcode
|
|
const char* GetOpcodeName()
|
|
{
|
|
int16 OpcodeVersion = GetOpcodeVersion(version);
|
|
if (EQOpcodeManager.count(OpcodeVersion) > 0)
|
|
return EQOpcodeManager[OpcodeVersion]->EmuToName(login_op);
|
|
else
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
// Application-level packet handling for game logic
|
|
class EQApplicationPacket : public EQPacket
|
|
{
|
|
friend class EQProtocolPacket;
|
|
friend class EQStream;
|
|
|
|
public:
|
|
static uint8 default_opcode_size; // Default size for opcodes in bytes
|
|
|
|
// Default constructor
|
|
EQApplicationPacket() : EQPacket(0, nullptr, 0)
|
|
{
|
|
emu_opcode = OP_Unknown;
|
|
app_opcode_size = default_opcode_size;
|
|
}
|
|
|
|
// Constructor with opcode only
|
|
EQApplicationPacket(const EmuOpcode op) : EQPacket(0, nullptr, 0)
|
|
{
|
|
SetOpcode(op);
|
|
app_opcode_size = default_opcode_size;
|
|
}
|
|
|
|
// Constructor with opcode and length
|
|
EQApplicationPacket(const EmuOpcode op, const uint32 len) : EQPacket(0, nullptr, len)
|
|
{
|
|
SetOpcode(op);
|
|
app_opcode_size = default_opcode_size;
|
|
}
|
|
|
|
// Constructor with opcode, buffer and length
|
|
EQApplicationPacket(const EmuOpcode op, const unsigned char* buf, const uint32 len) : EQPacket(0, buf, len)
|
|
{
|
|
SetOpcode(op);
|
|
app_opcode_size = default_opcode_size;
|
|
}
|
|
|
|
// Attempts to combine this application packet with another (currently not implemented)
|
|
bool combine(const EQApplicationPacket* rhs)
|
|
{
|
|
cout << "CALLED AP COMBINE!!!!\n";
|
|
return false;
|
|
}
|
|
|
|
// Serializes application packet to destination buffer
|
|
uint32 serialize(unsigned char* dest) const
|
|
{
|
|
uint8 OpCodeBytes = app_opcode_size;
|
|
|
|
if (app_opcode_size == 1) {
|
|
*(unsigned char*)dest = opcode;
|
|
} else {
|
|
// Application opcodes with a low order byte of 0x00 require an extra 0x00 byte inserting prior to the opcode.
|
|
if ((opcode & 0x00ff) == 0) {
|
|
*(uint8*)dest = 0;
|
|
*(uint16*)(dest + 1) = opcode;
|
|
++OpCodeBytes;
|
|
} else {
|
|
*(uint16*)dest = opcode;
|
|
}
|
|
}
|
|
|
|
memcpy(dest + app_opcode_size, pBuffer, size);
|
|
|
|
return size + OpCodeBytes;
|
|
}
|
|
|
|
// Returns total packet size including opcode
|
|
uint32 Size() const { return size + app_opcode_size; }
|
|
|
|
// Creates a deep copy of this application packet
|
|
EQApplicationPacket* Copy() const
|
|
{
|
|
EQApplicationPacket* it = new EQApplicationPacket;
|
|
try {
|
|
it->pBuffer = new unsigned char[size];
|
|
memcpy(it->pBuffer, pBuffer, size);
|
|
it->size = size;
|
|
it->opcode = opcode;
|
|
it->emu_opcode = emu_opcode;
|
|
it->version = version;
|
|
return it;
|
|
}
|
|
catch (bad_alloc& ba) {
|
|
cout << ba.what() << endl;
|
|
if (it != nullptr)
|
|
delete it;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Sets the opcode size for this packet
|
|
void SetOpcodeSize(uint8 s) { app_opcode_size = s; }
|
|
|
|
// Sets the opcode for this packet using EmuOpcode
|
|
void SetOpcode(EmuOpcode emu_op)
|
|
{
|
|
if (emu_op == OP_Unknown) {
|
|
opcode = 0;
|
|
emu_opcode = OP_Unknown;
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// save the emu opcode we just set.
|
|
emu_opcode = emu_op;
|
|
}
|
|
|
|
// Gets the constant opcode value for this packet
|
|
const EmuOpcode GetOpcodeConst() const
|
|
{
|
|
if (emu_opcode != OP_Unknown) {
|
|
return emu_opcode;
|
|
}
|
|
if (opcode == 10000) {
|
|
return OP_Unknown;
|
|
}
|
|
|
|
EmuOpcode emu_op;
|
|
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;
|
|
}
|
|
|
|
// Gets the opcode for this packet (const version)
|
|
inline const EmuOpcode GetOpcode() const { return GetOpcodeConst(); }
|
|
|
|
// Gets the opcode for this packet (caching version)
|
|
inline const EmuOpcode GetOpcode() { EmuOpcode r = GetOpcodeConst(); emu_opcode = r; return r; }
|
|
|
|
protected:
|
|
EmuOpcode emu_opcode; // Cached emu opcode to avoid repeated lookups
|
|
|
|
private:
|
|
uint8 app_opcode_size; // Size of opcode in bytes
|
|
|
|
// Constructor from raw buffer - used internally by EQProtocolPacket
|
|
EQApplicationPacket(const unsigned char* buf, uint32 len, uint8 opcode_size = 0)
|
|
{
|
|
uint32 offset = 0;
|
|
app_opcode_size = (opcode_size == 0) ? EQApplicationPacket::default_opcode_size : opcode_size;
|
|
|
|
if (app_opcode_size == 1) {
|
|
opcode = *(const unsigned char*)buf;
|
|
offset++;
|
|
} else {
|
|
opcode = *(const uint16*)buf;
|
|
offset += 2;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Copy constructor disabled to prevent accidental copies
|
|
EQApplicationPacket(const EQApplicationPacket& p) { emu_opcode = OP_Unknown; app_opcode_size = default_opcode_size; }
|
|
};
|
|
|
|
// Implementation of MakeApplicationPacket that was forward declared
|
|
inline EQApplicationPacket* EQProtocolPacket::MakeApplicationPacket(uint8 opcode_size) const
|
|
{
|
|
EQApplicationPacket* res = new EQApplicationPacket;
|
|
res->app_opcode_size = (opcode_size == 0) ? EQApplicationPacket::default_opcode_size : opcode_size;
|
|
|
|
if (res->app_opcode_size == 1) {
|
|
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 {
|
|
res->pBuffer = new unsigned char[size];
|
|
memcpy(res->pBuffer, pBuffer, size);
|
|
res->opcode = opcode;
|
|
res->size = size;
|
|
}
|
|
res->copyInfo(this);
|
|
return res;
|
|
}
|
|
|
|
// Global packet dumping functions for debugging
|
|
|
|
// Dumps application packet data in hexadecimal format
|
|
void DumpPacketHex(const EQApplicationPacket* app)
|
|
{
|
|
DumpPacketHex(app->pBuffer, app->size);
|
|
}
|
|
|
|
// Dumps application packet data in ASCII format
|
|
void DumpPacketAscii(const EQApplicationPacket* app)
|
|
{
|
|
DumpPacketAscii(app->pBuffer, app->size);
|
|
}
|
|
|
|
// Dumps protocol packet data in hexadecimal format
|
|
void DumpPacket(const EQProtocolPacket* app)
|
|
{
|
|
DumpPacketHex(app->pBuffer, app->size);
|
|
}
|
|
|
|
// Dumps application packet with optional info display
|
|
void DumpPacket(const EQApplicationPacket* app, bool iShowInfo = false)
|
|
{
|
|
if (iShowInfo) {
|
|
cout << "Dumping Applayer: 0x" << hex << setfill('0') << setw(4) << app->GetOpcode() << dec;
|
|
cout << " size:" << app->size << endl;
|
|
}
|
|
DumpPacketHex(app->pBuffer, app->size);
|
|
}
|
|
|
|
// Dumps application packet data in binary format
|
|
void DumpPacketBin(const EQApplicationPacket* app)
|
|
{
|
|
DumpPacketBin(app->pBuffer, app->size);
|
|
} |