// 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);
}


