// Copyright (C) 2007 EQ2EMulator Development Team - GPL v3+ License #pragma once #include #include #include #include #include #include #include #include #include #include #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 mapEQOpcodeManager; 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(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(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); }