From 76fa77d590dfcc44a5ba4a747f798bafcdf35438 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Mon, 1 Sep 2025 12:31:14 -0500 Subject: [PATCH] upload old login --- crc16.go | 88 + old/EQPacket.cpp | 661 ------ old/EQPacket.h | 209 -- old/EQStream.cpp | 1921 ------------------ old/EQStream.h | 375 ---- old/EQStreamFactory.cpp | 444 ---- old/EQStreamFactory.h | 86 - old/{ => common}/CRC16.cpp | 0 old/{ => common}/CRC16.h | 0 old/{ => common}/Common_Defines.h | 0 old/{ => common}/Condition.cpp | 0 old/{ => common}/Condition.h | 0 old/{ => common}/ConfigReader.cpp | 0 old/{ => common}/ConfigReader.h | 0 old/{ => common}/Crypto.cpp | 0 old/{ => common}/Crypto.h | 0 old/{ => common}/DataBuffer.h | 0 old/{ => common}/DatabaseNew.cpp | 0 old/{ => common}/DatabaseNew.h | 0 old/{ => common}/DatabaseResult.cpp | 0 old/{ => common}/DatabaseResult.h | 0 old/{ => common}/EQ2_Common_Structs.h | 0 old/{ => common}/EQEMuError.cpp | 0 old/{ => common}/EQEMuError.h | 0 old/common/EQPacket.cpp | 969 +++++++++ old/common/EQPacket.h | 310 +++ old/common/EQStream.cpp | 2680 +++++++++++++++++++++++++ old/common/EQStream.h | 523 +++++ old/common/EQStreamFactory.cpp | 546 +++++ old/common/EQStreamFactory.h | 125 ++ old/{ => common}/GlobalHeaders.h | 0 old/{ => common}/JsonParser.cpp | 0 old/{ => common}/JsonParser.h | 0 old/{ => common}/Log.cpp | 0 old/{ => common}/Log.h | 0 old/{ => common}/LogTypes.h | 0 old/{ => common}/MiscFunctions.cpp | 0 old/{ => common}/MiscFunctions.h | 0 old/{ => common}/Mutex.cpp | 0 old/{ => common}/Mutex.h | 0 old/{ => common}/PacketStruct.cpp | 0 old/{ => common}/PacketStruct.h | 0 old/{ => common}/RC4.cpp | 0 old/{ => common}/RC4.h | 0 old/{ => common}/TCPConnection.cpp | 0 old/{ => common}/TCPConnection.h | 0 old/{ => common}/Web/WebServer.cpp | 0 old/{ => common}/Web/WebServer.h | 0 old/{ => common}/database.cpp | 0 old/{ => common}/database.h | 0 old/{ => common}/dbcore.cpp | 0 old/{ => common}/dbcore.h | 0 old/{ => common}/debug.cpp | 0 old/{ => common}/debug.h | 0 old/{ => common}/emu_opcodes.cpp | 0 old/{ => common}/emu_opcodes.h | 0 old/{ => common}/emu_oplist.h | 0 old/{ => common}/linked_list.h | 0 old/{ => common}/login_oplist.h | 0 old/{ => common}/md5.cpp | 0 old/{ => common}/md5.h | 0 old/{ => common}/misc.cpp | 0 old/{ => common}/misc.h | 0 old/{ => common}/op_codes.h | 0 old/{ => common}/opcodemgr.cpp | 0 old/{ => common}/opcodemgr.h | 0 old/{ => common}/packet_dump.cpp | 0 old/{ => common}/packet_dump.h | 0 old/{ => common}/packet_functions.cpp | 0 old/{ => common}/packet_functions.h | 0 old/{ => common}/queue.h | 0 old/{ => common}/seperator.h | 0 old/{ => common}/servertalk.h | 0 old/{ => common}/sha512.cpp | 0 old/{ => common}/sha512.h | 0 old/{ => common}/string_util.cpp | 0 old/{ => common}/string_util.h | 0 old/{ => common}/timer.cpp | 0 old/{ => common}/timer.h | 0 old/{ => common}/types.h | 0 old/{ => common}/unix.cpp | 0 old/{ => common}/unix.h | 0 old/{ => common}/version.h | 0 old/{ => common}/xmlParser.cpp | 0 old/{ => common}/xmlParser.h | 0 old/login/Character.cpp | 20 + old/login/Character.h | 25 + old/login/LWorld.cpp | 1561 ++++++++++++++ old/login/LWorld.h | 253 +++ old/login/LoginAccount.cpp | 58 + old/login/LoginAccount.h | 54 + old/login/LoginDatabase.cpp | 1083 ++++++++++ old/login/LoginDatabase.h | 96 + old/login/PacketHeaders.cpp | 88 + old/login/PacketHeaders.h | 61 + old/login/Web/LoginWeb.cpp | 67 + old/login/client.cpp | 813 ++++++++ old/login/client.h | 131 ++ old/login/login_opcodes.h | 52 + old/login/login_structs.h | 61 + old/login/net.cpp | 363 ++++ old/login/net.h | 147 ++ opcodes.go | 56 + packet.go | 356 ++++ stream.go | 675 +++++++ stream_factory.go | 471 +++++ 106 files changed, 11732 insertions(+), 3696 deletions(-) create mode 100644 crc16.go delete mode 100644 old/EQPacket.cpp delete mode 100644 old/EQPacket.h delete mode 100644 old/EQStream.cpp delete mode 100644 old/EQStream.h delete mode 100644 old/EQStreamFactory.cpp delete mode 100644 old/EQStreamFactory.h rename old/{ => common}/CRC16.cpp (100%) rename old/{ => common}/CRC16.h (100%) rename old/{ => common}/Common_Defines.h (100%) rename old/{ => common}/Condition.cpp (100%) rename old/{ => common}/Condition.h (100%) rename old/{ => common}/ConfigReader.cpp (100%) rename old/{ => common}/ConfigReader.h (100%) rename old/{ => common}/Crypto.cpp (100%) rename old/{ => common}/Crypto.h (100%) rename old/{ => common}/DataBuffer.h (100%) rename old/{ => common}/DatabaseNew.cpp (100%) rename old/{ => common}/DatabaseNew.h (100%) rename old/{ => common}/DatabaseResult.cpp (100%) rename old/{ => common}/DatabaseResult.h (100%) rename old/{ => common}/EQ2_Common_Structs.h (100%) rename old/{ => common}/EQEMuError.cpp (100%) rename old/{ => common}/EQEMuError.h (100%) create mode 100644 old/common/EQPacket.cpp create mode 100644 old/common/EQPacket.h create mode 100644 old/common/EQStream.cpp create mode 100644 old/common/EQStream.h create mode 100644 old/common/EQStreamFactory.cpp create mode 100644 old/common/EQStreamFactory.h rename old/{ => common}/GlobalHeaders.h (100%) rename old/{ => common}/JsonParser.cpp (100%) rename old/{ => common}/JsonParser.h (100%) rename old/{ => common}/Log.cpp (100%) rename old/{ => common}/Log.h (100%) rename old/{ => common}/LogTypes.h (100%) rename old/{ => common}/MiscFunctions.cpp (100%) rename old/{ => common}/MiscFunctions.h (100%) rename old/{ => common}/Mutex.cpp (100%) rename old/{ => common}/Mutex.h (100%) rename old/{ => common}/PacketStruct.cpp (100%) rename old/{ => common}/PacketStruct.h (100%) rename old/{ => common}/RC4.cpp (100%) rename old/{ => common}/RC4.h (100%) rename old/{ => common}/TCPConnection.cpp (100%) rename old/{ => common}/TCPConnection.h (100%) rename old/{ => common}/Web/WebServer.cpp (100%) rename old/{ => common}/Web/WebServer.h (100%) rename old/{ => common}/database.cpp (100%) rename old/{ => common}/database.h (100%) rename old/{ => common}/dbcore.cpp (100%) rename old/{ => common}/dbcore.h (100%) rename old/{ => common}/debug.cpp (100%) rename old/{ => common}/debug.h (100%) rename old/{ => common}/emu_opcodes.cpp (100%) rename old/{ => common}/emu_opcodes.h (100%) rename old/{ => common}/emu_oplist.h (100%) rename old/{ => common}/linked_list.h (100%) rename old/{ => common}/login_oplist.h (100%) rename old/{ => common}/md5.cpp (100%) rename old/{ => common}/md5.h (100%) rename old/{ => common}/misc.cpp (100%) rename old/{ => common}/misc.h (100%) rename old/{ => common}/op_codes.h (100%) rename old/{ => common}/opcodemgr.cpp (100%) rename old/{ => common}/opcodemgr.h (100%) rename old/{ => common}/packet_dump.cpp (100%) rename old/{ => common}/packet_dump.h (100%) rename old/{ => common}/packet_functions.cpp (100%) rename old/{ => common}/packet_functions.h (100%) rename old/{ => common}/queue.h (100%) rename old/{ => common}/seperator.h (100%) rename old/{ => common}/servertalk.h (100%) rename old/{ => common}/sha512.cpp (100%) rename old/{ => common}/sha512.h (100%) rename old/{ => common}/string_util.cpp (100%) rename old/{ => common}/string_util.h (100%) rename old/{ => common}/timer.cpp (100%) rename old/{ => common}/timer.h (100%) rename old/{ => common}/types.h (100%) rename old/{ => common}/unix.cpp (100%) rename old/{ => common}/unix.h (100%) rename old/{ => common}/version.h (100%) rename old/{ => common}/xmlParser.cpp (100%) rename old/{ => common}/xmlParser.h (100%) create mode 100644 old/login/Character.cpp create mode 100644 old/login/Character.h create mode 100644 old/login/LWorld.cpp create mode 100644 old/login/LWorld.h create mode 100644 old/login/LoginAccount.cpp create mode 100644 old/login/LoginAccount.h create mode 100644 old/login/LoginDatabase.cpp create mode 100644 old/login/LoginDatabase.h create mode 100644 old/login/PacketHeaders.cpp create mode 100644 old/login/PacketHeaders.h create mode 100644 old/login/Web/LoginWeb.cpp create mode 100644 old/login/client.cpp create mode 100644 old/login/client.h create mode 100644 old/login/login_opcodes.h create mode 100644 old/login/login_structs.h create mode 100644 old/login/net.cpp create mode 100644 old/login/net.h create mode 100644 opcodes.go create mode 100644 packet.go create mode 100644 stream.go create mode 100644 stream_factory.go diff --git a/crc16.go b/crc16.go new file mode 100644 index 0000000..f7384a7 --- /dev/null +++ b/crc16.go @@ -0,0 +1,88 @@ +package eq2net + +// CRC16 calculates the CRC-16 checksum for EQ2 packets +// This matches the exact implementation from the original EQ2Emu +func CRC16(buf []byte, size int, key uint32) uint32 { + // CRC32 table used for EQ2's CRC16 calculation + intArray := [256]uint32{ + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, + } + + ecx := key + eax := ecx + + eax = ^eax + eax &= 0xFF + eax = intArray[eax] + eax ^= 0x00FFFFFF + edx := ecx + edx = edx >> 8 + edx = edx ^ eax + eax = eax >> 8 + edx &= 0xFF + eax &= 0x00FFFFFF + eax ^= intArray[edx] + edx = ecx + edx = edx >> 0x10 + edx ^= eax + eax = eax >> 8 + edx &= 0xFF + esi := intArray[edx] + eax &= 0x00FFFFFF + eax ^= esi + ecx = ecx >> 0x18 + ecx ^= eax + ecx &= 0xFF + esi = intArray[ecx] + eax = eax >> 8 + eax &= 0x00FFFFFF + eax ^= esi + + for x := range size { + edx := uint32(0) + edx = uint32(buf[x]) & 0x00FF + edx ^= eax + eax = eax >> 8 + edx &= 0xFF + edi := intArray[edx] + eax &= 0x00FFFFFF + eax ^= edi + } + return ^eax +} + +// CalculateCRC16 is a convenience wrapper +func CalculateCRC16(data []byte, key uint32) uint16 { + return uint16(CRC16(data, len(data), key)) +} diff --git a/old/EQPacket.cpp b/old/EQPacket.cpp deleted file mode 100644 index c2f9e1f..0000000 --- a/old/EQPacket.cpp +++ /dev/null @@ -1,661 +0,0 @@ -/* - EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - - This file is part of EQ2Emulator. - - EQ2Emulator is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EQ2Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with EQ2Emulator. If not, see . -*/ -#include "debug.h" -#include -#include -#include -#include -#include "EQPacket.h" -#include "misc.h" -#include "op_codes.h" -#include "CRC16.h" -#include "opcodemgr.h" -#include "packet_dump.h" -#include -#include "Log.h" -#include - -using namespace std; -extern mapEQOpcodeManager; - -uint8 EQApplicationPacket::default_opcode_size=2; - -EQPacket::EQPacket(const uint16 op, const unsigned char *buf, uint32 len) -{ - this->opcode=op; - this->pBuffer=NULL; - 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); - } - } -} - -const char* EQ2Packet::GetOpcodeName() { - int16 OpcodeVersion = GetOpcodeVersion(version); - if (EQOpcodeManager.count(OpcodeVersion) > 0) - return EQOpcodeManager[OpcodeVersion]->EmuToName(login_op); - else - return NULL; -} - -int8 EQ2Packet::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; -} - -uint32 EQProtocolPacket::serialize(unsigned char *dest, int8 offset) const -{ - if (opcode>0xff) { - *(uint16 *)dest=opcode; - } else { - *(dest)=0; - *(dest+1)=opcode; - } - memcpy(dest+2,pBuffer+offset,size-offset); - - return size+2; -} - -uint32 EQApplicationPacket::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; -} - -EQPacket::~EQPacket() -{ - safe_delete_array(pBuffer); - pBuffer=NULL; -} - - -void EQPacket::DumpRawHeader(uint16 seq, FILE* to) const -{ - /*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); -} - -const char* EQPacket::GetOpcodeName(){ - int16 OpcodeVersion = GetOpcodeVersion(version); - if(EQOpcodeManager.count(OpcodeVersion) > 0) - return EQOpcodeManager[OpcodeVersion]->EQToName(opcode); - else - return NULL; -} -void EQPacket::DumpRawHeaderNoTime(uint16 seq, FILE *to) const -{ - if (src_ip) { - string sIP,dIP;; - sIP=long2ip(src_ip); - 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); -} - -void EQPacket::DumpRaw(FILE *to) const -{ - DumpRawHeader(); - if (pBuffer && size) - dump_message_column(pBuffer, size, " ", to); - fprintf(to, "\n"); -} - -EQProtocolPacket::EQProtocolPacket(const unsigned char *buf, uint32 len, int in_opcode) -{ - 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) { - // Not enough data to read opcode; set defaults or handle error appropriately - opcode = 0; // or set to a designated invalid opcode - offset = len; // no payload available - } 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; -} - -bool EQ2Packet::AppCombine(EQ2Packet* rhs){ - bool result = false; - uchar* tmpbuffer = 0; - bool over_sized_packet = false; - int32 new_size = 0; - //bool whee = false; -// DumpPacket(this); -// DumpPacket(rhs); - /*if(rhs->size >= 255){ - DumpPacket(this); - DumpPacket(rhs); - whee = true; - }*/ - 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; - } - 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; - } - /*if(whee){ - DumpPacket(this); - cout << "fsdfsdf"; - }*/ - //DumpPacket(this); - return result; -} - -bool EQProtocolPacket::combine(const EQProtocolPacket *rhs) -{ - bool result=false; - //if(dont_combine) - // return false; - //if (opcode==OP_Combined && size+rhs->size+5<256) { - 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; - } - 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; -} - -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; - - 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=NULL; - size=0; - } - - emu_opcode = OP_Unknown; -} - -bool EQApplicationPacket::combine(const EQApplicationPacket *rhs) -{ -cout << "CALLED AP COMBINE!!!!\n"; - return false; -} - -void EQApplicationPacket::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; -} - -const EmuOpcode EQApplicationPacket::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); -} - -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); -} -bool EQProtocolPacket::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)); -#ifdef EQN_DEBUG - if (packet_crc && comp_crc != packet_crc) { - cout << "CRC mismatch: comp=" << hex << comp_crc << ", packet=" << packet_crc << dec << endl; - } -#endif - valid = (!packet_crc || comp_crc == packet_crc); - } - return valid; -} - -uint32 EQProtocolPacket::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; -} - -uint32 EQProtocolPacket::Compress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize) { -uint32 flag_offset=1,newlength; - //dump_message_column(buffer,length,"Before: "); - 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; - } - //dump_message_column(newbuf,length,"After: "); - - return newlength; -} - -void EQProtocolPacket::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); - } -} - -void EQProtocolPacket::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); - } -} - -bool EQProtocolPacket::IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC) { - bool ret = false; - uint16_t opcode = ntohs(*(uint16_t*)in_buff); - uint32_t offset = 2; - - 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; -} - - - -void DumpPacketHex(const EQApplicationPacket* app) -{ - DumpPacketHex(app->pBuffer, app->size); -} - -void DumpPacketAscii(const EQApplicationPacket* app) -{ - DumpPacketAscii(app->pBuffer, app->size); -} -void DumpPacket(const EQProtocolPacket* app) { - DumpPacketHex(app->pBuffer, app->size); -} -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); -// DumpPacketAscii(app->pBuffer, app->size); -} - -void DumpPacketBin(const EQApplicationPacket* app) { - DumpPacketBin(app->pBuffer, app->size); -} - - diff --git a/old/EQPacket.h b/old/EQPacket.h deleted file mode 100644 index 455a72c..0000000 --- a/old/EQPacket.h +++ /dev/null @@ -1,209 +0,0 @@ -/* - EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - - This file is part of EQ2Emulator. - - EQ2Emulator is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EQ2Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with EQ2Emulator. If not, see . -*/ -#ifndef _EQPACKET_H -#define _EQPACKET_H - -#include "types.h" -#include -#include - -#ifdef WIN32 - #include - #include -#else - #include - #include -#endif - -#include "emu_opcodes.h" -#include "op_codes.h" -#include "packet_dump.h" - -class OpcodeManager; - -class EQStream; - -class EQPacket { - friend class EQStream; -public: - unsigned char *pBuffer; - uint32 size; - uint32 src_ip,dst_ip; - uint16 src_port,dst_port; - uint32 priority; - timeval timestamp; - int16 version; - ~EQPacket(); - void DumpRawHeader(uint16 seq=0xffff, FILE *to = stdout) const; - void DumpRawHeaderNoTime(uint16 seq=0xffff, FILE *to = stdout) const; - void DumpRaw(FILE *to = stdout) const; - const char* GetOpcodeName(); - - void setVersion(int16 new_version){ version = new_version; } - void setSrcInfo(uint32 sip, uint16 sport) { src_ip=sip; src_port=sport; } - void setDstInfo(uint32 dip, uint16 dport) { dst_ip=dip; dst_port=dport; } - void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { timestamp.tv_sec=ts_sec; timestamp.tv_usec=ts_usec; } - 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; } - uint32 Size() const { return size+2; } - -//no reason to have this method in zone or world - - uint16 GetRawOpcode() const { return(opcode); } - - - 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)); - } - void SetProtocolOpcode(int16 new_opcode){ - opcode = new_opcode; - } - -protected: - uint16 opcode; - - EQPacket(const uint16 op, const unsigned char *buf, const uint32 len); - EQPacket(const EQPacket &p) { version = 0; } - EQPacket() { opcode=0; pBuffer=NULL; size=0; version = 0; setTimeInfo(0, 0); } - -}; - -class EQApplicationPacket; - -class EQProtocolPacket : public EQPacket { -public: - 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; - } - EQProtocolPacket(const unsigned char *buf, uint32 len, int in_opcode = -1); - bool combine(const EQProtocolPacket *rhs); - uint32 serialize (unsigned char *dest, int8 offset = 0) const; - static bool ValidateCRC(const unsigned char *buffer, int length, uint32 Key); - static uint32 Decompress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize); - static uint32 Compress(const unsigned char *buffer, const uint32 length, unsigned char *newbuf, uint32 newbufsize); - static void ChatDecode(unsigned char *buffer, int size, int DecodeKey); - static void ChatEncode(unsigned char *buffer, int size, int EncodeKey); - static bool IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC); - - 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; - } - EQApplicationPacket *MakeApplicationPacket(uint8 opcode_size=0) const; - bool eq2_compressed; - bool packet_prepared; - bool packet_encrypted; - bool acked; - int32 sent_time; - int8 attempt_count; - int32 sequence; - -private: - EQProtocolPacket(const EQProtocolPacket &p) { } - //bool dont_combine; -}; -class EQ2Packet : public EQProtocolPacket { -public: - 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; - } - bool AppCombine(EQ2Packet* rhs); - 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; - } - int8 PreparePacket(int16 MaxLen); - const char* GetOpcodeName(); - EmuOpcode login_op; -}; -class EQApplicationPacket : public EQPacket { - friend class EQProtocolPacket; - friend class EQStream; -public: - EQApplicationPacket() : EQPacket(0,NULL,0) { emu_opcode = OP_Unknown; app_opcode_size=default_opcode_size; } - EQApplicationPacket(const EmuOpcode op) : EQPacket(0,NULL,0) { SetOpcode(op); app_opcode_size=default_opcode_size; } - EQApplicationPacket(const EmuOpcode op, const uint32 len) : EQPacket(0,NULL,len) { SetOpcode(op); app_opcode_size=default_opcode_size; } - EQApplicationPacket(const EmuOpcode op, const unsigned char *buf, const uint32 len) : EQPacket(0,buf,len) { SetOpcode(op); app_opcode_size=default_opcode_size; } - bool combine(const EQApplicationPacket *rhs); - uint32 serialize (unsigned char *dest) const; - uint32 Size() const { return size+app_opcode_size; } - 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( NULL != it ) - delete it; - } - return NULL; - } - - void SetOpcodeSize(uint8 s) { app_opcode_size=s; } - void SetOpcode(EmuOpcode op); - const EmuOpcode GetOpcodeConst() const; - inline const EmuOpcode GetOpcode() const { return(GetOpcodeConst()); } - //caching version of get - inline const EmuOpcode GetOpcode() { EmuOpcode r = GetOpcodeConst(); emu_opcode = r; return(r); } - - static uint8 default_opcode_size; - -protected: - //this is just a cache so we dont look it up several times on Get() - EmuOpcode emu_opcode; - -private: - //this constructor should only be used by EQProtocolPacket, as it - //assumes the first two bytes of buf are the opcode. - EQApplicationPacket(const unsigned char *buf, uint32 len, uint8 opcode_size=0); - EQApplicationPacket(const EQApplicationPacket &p) { emu_opcode = OP_Unknown; app_opcode_size=default_opcode_size; } - - uint8 app_opcode_size; -}; - -void DumpPacketHex(const EQApplicationPacket* app); -void DumpPacket(const EQProtocolPacket* app); -void DumpPacketAscii(const EQApplicationPacket* app); -void DumpPacket(const EQApplicationPacket* app, bool iShowInfo = false); -void DumpPacketBin(const EQApplicationPacket* app); - -#endif diff --git a/old/EQStream.cpp b/old/EQStream.cpp deleted file mode 100644 index fef52ad..0000000 --- a/old/EQStream.cpp +++ /dev/null @@ -1,1921 +0,0 @@ -/* - EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - - This file is part of EQ2Emulator. - - EQ2Emulator is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EQ2Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with EQ2Emulator. If not, see . -*/ -#ifdef WIN32 -#include - #include -#endif -#include "debug.h" -#include -#include -#include -#include -#include -#include -#ifdef WIN32 - #include -#else - #include - #include - #include - #include - #include - #include - #include -#endif -#include "EQPacket.h" -#include "EQStream.h" -#include "EQStreamFactory.h" -#include "misc.h" -#include "Mutex.h" -#include "op_codes.h" -#include "CRC16.h" -#include "packet_dump.h" -#ifdef LOGIN - #include "../LoginServer/login_structs.h" -#endif -#include "EQ2_Common_Structs.h" -#include "Log.h" - - -//#define DEBUG_EMBEDDED_PACKETS 1 -uint16 EQStream::MaxWindowSize=2048; - -void EQStream::init(bool resetSession) { - if (resetSession) - { - streamactive = false; - sessionAttempts = 0; - } - - timeout_delays = 0; - - MInUse.lock(); - active_users = 0; - MInUse.unlock(); - - Session=0; - Key=0; - MaxLen=0; - NextInSeq=0; - NextOutSeq=0; - CombinedAppPacket=NULL; - - MAcks.lock(); - MaxAckReceived = -1; - NextAckToSend = -1; - LastAckSent = -1; - MAcks.unlock(); - - LastSeqSent=-1; - MaxSends=5; - LastPacket=Timer::GetCurrentTime2(); - oversize_buffer=NULL; - oversize_length=0; - oversize_offset=0; - Factory = NULL; - - rogue_buffer=NULL; - roguebuf_offset=0; - roguebuf_size=0; - - MRate.lock(); - RateThreshold=RATEBASE/250; - DecayRate=DECAYBASE/250; - MRate.unlock(); - - BytesWritten=0; - SequencedBase = 0; - AverageDelta = 500; - - crypto->setRC4Key(0); - - retransmittimer = Timer::GetCurrentTime2(); - retransmittimeout = 500 * RETRANSMIT_TIMEOUT_MULT; - - reconnectAttempt = 0; - if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { - LogWrite(PACKET__DEBUG, 9, "Packet", "init Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); - } -} - -EQStream::EQStream(sockaddr_in addr){ - crypto = new Crypto(); - resend_que_timer = new Timer(1000); - combine_timer = new Timer(250); //250 milliseconds - combine_timer->Start(); - resend_que_timer->Start(); - init(); - remote_ip=addr.sin_addr.s_addr; - remote_port=addr.sin_port; - State=CLOSED; - StreamType=UnknownStream; - compressed=true; - encoded=false; - app_opcode_size=2; - #ifdef WIN32 - ZeroMemory(&stream, sizeof(z_stream)); - #else - bzero(&stream, sizeof(z_stream)); - #endif - stream.zalloc = (alloc_func)0; - stream.zfree = (free_func)0; - stream.opaque = (voidpf)0; - deflateInit2(&stream, 9, Z_DEFLATED, 13, 9, Z_DEFAULT_STRATEGY); - //deflateInit(&stream, 5); - compressed_offset = 0; - client_version = 0; - received_packets = 0; - sent_packets = 0; - -#ifdef WRITE_PACKETS - write_packets = 0; - char write_packets_filename[64]; - snprintf(write_packets_filename, sizeof(write_packets_filename), "PacketLog%i.log", Timer::GetCurrentTime2()); - write_packets = fopen(write_packets_filename, "w+"); -#endif -} - -EQProtocolPacket* EQStream::ProcessEncryptedData(uchar* data, int32 size, int16 opcode){ - //cout << "B4:\n"; - //DumpPacket(data, size); - /*if(size >= 2 && data[0] == 0 && data[1] == 0){ - cout << "Attempting to fix packet!\n"; - //Have to fix bad packet from client or it will screw up encryption :P - size--; - data++; - }*/ - crypto->RC4Decrypt(data,size); - int8 offset = 0; - if(data[0] == 0xFF && size > 2){ - offset = 3; - memcpy(&opcode, data+sizeof(int8), sizeof(int16)); - } - else{ - offset = 1; - memcpy(&opcode, data, sizeof(int8)); - } - //cout << "After:\n"; - //DumpPacket(data, size); - return new EQProtocolPacket(opcode, data+offset, size - offset); -} - -EQProtocolPacket* EQStream::ProcessEncryptedPacket(EQProtocolPacket *p){ - EQProtocolPacket* ret = NULL; - if(p->opcode == OP_Packet && p->size > 2) - ret = ProcessEncryptedData(p->pBuffer+2, p->size-2, p->opcode); - else - ret = ProcessEncryptedData(p->pBuffer, p->size, p->opcode); - return ret; -} - -bool EQStream::ProcessEmbeddedPacket(uchar* pBuffer, int16 length,int8 opcode) { - if(!pBuffer || !crypto->isEncrypted()) - return false; - - MCombineQueueLock.lock(); - EQProtocolPacket* newpacket = ProcessEncryptedData(pBuffer, length, opcode); - MCombineQueueLock.unlock(); - - if (newpacket) { -#ifdef DEBUG_EMBEDDED_PACKETS - printf("Opcode: %u\n", newpacket->opcode); - DumpPacket(newpacket->pBuffer, newpacket->size); -#endif - - EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); - if (ap->version == 0) - ap->version = client_version; - InboundQueuePush(ap); -#ifdef WRITE_PACKETS - WritePackets(ap->GetOpcodeName(), pBuffer, length, false); -#endif - safe_delete(newpacket); - return true; - } - - return false; -} - -bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 length){ - if(!p) - return false; - -#ifdef DEBUG_EMBEDDED_PACKETS - // printf works better with DumpPacket - printf( "Start Packet with offset %u, length %u, p->size %u\n", offset, length, p->size); -#endif - - if(p->size >= ((uint32)(offset+2))){ - if(p->pBuffer[offset] == 0 && p->pBuffer[offset+1] == 0x19){ - uint32 data_length = 0; - if(length == 0) { - // Ensure there are at least 2 bytes after offset. - if(p->size < offset + 2) { - return false; // Not enough data. - } - data_length = p->size - offset - 2; - } else { - // Ensure provided length is at least 2. - if(length < 2) { - return false; // Provided length too short. - } - data_length = length - 2; - } -#ifdef DEBUG_EMBEDDED_PACKETS - printf( "Creating OP_AppCombined Packet with offset %u, length %u, p->size %u\n", offset, length, p->size); - DumpPacket(p->pBuffer, p->size); -#endif - // Verify that offset + 2 + data_length does not exceed p->size. - if(offset + 2 + data_length > p->size) { - return false; // Out-of-bounds. - } - EQProtocolPacket *subp = new EQProtocolPacket(OP_AppCombined, p->pBuffer + offset + 2, data_length); - subp->copyInfo(p); - ProcessPacket(subp, p); - safe_delete(subp); - return true; - } - else if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0) { - if (length == 0) - length = p->size - 1 - offset; - else - length--; - -#ifdef DEBUG_EMBEDDED_PACKETS - printf( "Creating Opcode 0 Packet!"); - DumpPacket(p->pBuffer + 1 + offset, length); -#endif - uchar* buffer = (p->pBuffer + 1 + offset); - bool valid = ProcessEmbeddedPacket(buffer, length); - - if(valid) - return true; - } - else if(offset+4 < p->size && ntohl(*(uint32 *)(p->pBuffer+offset)) != 0xffffffff) { -#ifdef DEBUG_EMBEDDED_PACKETS - uint16 seq = NextInSeq-1; - sint8 check = 0; - - if(offset == 2) { - seq=ntohs(*(uint16 *)(p->pBuffer)); - check=CompareSequence(NextInSeq,seq); - } - printf( "Unhandled Packet with offset %u, length %u, p->size %u, check: %i, nextinseq: %u, seq: %u\n", offset, length, p->size, check, NextInSeq, seq); - DumpPacket(p->pBuffer, p->size); -#endif - - if(length == 0) - length = p->size - offset; - - - uchar* buffer = (p->pBuffer + offset); - - bool valid = ProcessEmbeddedPacket(buffer, length); - - if(valid) - return true; - } - else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff && p->size >= offset + 3) { - // Read the first byte into a wider type to avoid underflow. - uint16 total_length = p->pBuffer[offset]; // promote to uint16 - // Check that there is enough data: we expect offset+2+total_length == p->size. - if(total_length + offset + 2 == p->size && total_length >= 2) { - uint32 data_length = total_length - 2; - // No additional bounds check needed because equality condition ensures it. - EQProtocolPacket *subp = new EQProtocolPacket(p->pBuffer + offset + 2, data_length, OP_Packet); - subp->copyInfo(p); - ProcessPacket(subp, p); - delete subp; - return true; - } - } - } - return false; -} - -void EQStream::ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp) -{ - uint32 processed=0,subpacket_length=0; - - if (p) { - - if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse && !Session) { -#ifdef EQN_DEBUG - LogWrite(PACKET__ERROR, 0, "Packet", "*** Session not initialized, packet ignored "); - //p->DumpRaw(); -#endif - return; - } - - //cout << "Received " << (int)p->opcode << ":\n"; - //DumpPacket(p->pBuffer, p->size); - switch (p->opcode) { - case OP_Combined: { - processed=0; - int8 offset = 0; - int count = 0; -#ifdef LE_DEBUG - printf( "OP_Combined:\n"); - DumpPacket(p); -#endif - while(processedsize) { - if ((subpacket_length=(unsigned char)*(p->pBuffer+processed))==0xff) { - subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1)); - //printf("OP_Combined subpacket_length %u\n",subpacket_length); - offset = 3; - } - else { - offset = 1; - } - - //printf("OP_Combined processed %u p->size %u subpacket length %u count %i\n",processed, p->size, subpacket_length, count); - count++; -#ifdef LE_DEBUG - printf( "OP_Combined Packet %i (%u) (%u):\n", count, subpacket_length, processed); -#endif - bool isSubPacket = EQProtocolPacket::IsProtocolPacket(p->pBuffer + processed + offset, subpacket_length, false); - if (isSubPacket) { - EQProtocolPacket* subp = new EQProtocolPacket(p->pBuffer + processed + offset, subpacket_length); - subp->copyInfo(p); -#ifdef LE_DEBUG - printf( "Opcode %i:\n", subp->opcode); - DumpPacket(subp); -#endif - ProcessPacket(subp, p); -#ifdef LE_DEBUG - DumpPacket(subp); -#endif - delete subp; - } - else { - offset = 1; // 0xFF in this case means it is actually 255 bytes of encrypted data after a 00 09 packet - //Garbage packet? - if(ntohs(*reinterpret_cast(p->pBuffer + processed + offset)) <= 0x1e) { - subpacket_length=(unsigned char)*(p->pBuffer+processed); - LogWrite(PACKET__ERROR, 0, "Packet", "!!!!!!!!!Garbage Packet Unknown Process as OP_Packet!!!!!!!!!!!!!\n"); - DumpPacket(p->pBuffer + processed + offset, subpacket_length); - uchar* newbuf = p->pBuffer; - newbuf += processed + offset; - EQProtocolPacket *subp=new EQProtocolPacket(newbuf,subpacket_length); - subp->copyInfo(p); - ProcessPacket(subp, p); - delete subp; - } - else { - crypto->RC4Decrypt(p->pBuffer + processed + offset, subpacket_length); - LogWrite(PACKET__ERROR, 0, "Packet", "!!!!!!!!!Garbage Packet!!!!!!!!!!!!! processed: %u, offset: %u, count: %i, subpacket_length: %u, offset_pos_1: %u, oversized_buffer_present: %u, offset size: %u, offset length: %u\n", - processed, offset, count, subpacket_length, p->pBuffer[processed + offset], oversize_buffer ? 1 : 0, oversize_offset, oversize_length); - if(p->pBuffer[processed + offset] == 0xff) - { - uchar* newbuf = p->pBuffer; - newbuf += processed + offset + 1; - - DumpPacket(p->pBuffer + processed + offset, subpacket_length); - EQProtocolPacket *subp=new EQProtocolPacket(newbuf, subpacket_length, OP_Packet); - subp->copyInfo(p); - ProcessPacket(subp, p); - delete subp; - } - else - break; // bad packet - } - } - processed+=subpacket_length+offset; - } - break; - } - case OP_AppCombined: { - processed=0; - EQProtocolPacket* newpacket = 0; - int8 offset = 0; -#ifdef DEBUG_EMBEDDED_PACKETS - printf( "OP_AppCombined: \n"); - DumpPacket(p); -#endif - int count = 0; - while(processedsize) { - count++; - if ((subpacket_length=(unsigned char)*(p->pBuffer+processed))==0xff) { - subpacket_length=ntohs(*(uint16 *)(p->pBuffer+processed+1)); - offset = 3; - } else - offset = 1; - - if(crypto->getRC4Key()==0 && p && subpacket_length > 8+offset){ - #ifdef DEBUG_EMBEDDED_PACKETS - DumpPacket(p->pBuffer, p->size); - #endif - p->pBuffer += offset; - processRSAKey(p, subpacket_length); - p->pBuffer -= offset; - } - else if(crypto->isEncrypted()){ -#ifdef DEBUG_EMBEDDED_PACKETS - printf( "OP_AppCombined Packet %i (%u) (%u): \n", count, subpacket_length, processed); - DumpPacket(p->pBuffer+processed+offset, subpacket_length); -#endif - if(!HandleEmbeddedPacket(p, processed + offset, subpacket_length)){ - uchar* buffer = (p->pBuffer + processed + offset); - if(!ProcessEmbeddedPacket(buffer, subpacket_length, OP_AppCombined)) { - LogWrite(PACKET__ERROR, 0, "Packet", "*** This is bad, ProcessEmbeddedPacket failed, report to Image!"); - } - } - } - processed+=subpacket_length+offset; - } - } - break; - case OP_Packet: { - if (!p->pBuffer || (p->Size() < 4)) - { - break; - } - - uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); - sint8 check=CompareSequence(NextInSeq,seq); - if (check == SeqFuture) { -#ifdef EQN_DEBUG - LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); - p->DumpRawHeader(seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); -#endif - OutOfOrderpackets[seq] = p->Copy(); - - // Image (2020): Removed as this is bad contributes to infinite loop - //SendOutOfOrderAck(seq); - } else if (check == SeqPast) { -#ifdef EQN_DEBUG - LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); - p->DumpRawHeader(seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); -#endif - // Image (2020): Removed as this is bad contributes to infinite loop - //OutOfOrderpackets[seq] = p->Copy(); - SendOutOfOrderAck(seq); - } else { - EQProtocolPacket* qp = RemoveQueue(seq); - if (qp) { - LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); - delete qp; - } - - SetNextAckToSend(seq); - NextInSeq++; - - if(HandleEmbeddedPacket(p)) - break; - if(crypto->getRC4Key()==0 && p && p->size >= 69){ - #ifdef DEBUG_EMBEDDED_PACKETS - DumpPacket(p->pBuffer, p->size); - #endif - processRSAKey(p); - } - else if(crypto->isEncrypted() && p){ - MCombineQueueLock.lock(); - EQProtocolPacket* newpacket = ProcessEncryptedPacket(p); - MCombineQueueLock.unlock(); - if(newpacket){ - EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); - if (ap->version == 0) - ap->version = client_version; -#ifdef WRITE_PACKETS - WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); -#endif - InboundQueuePush(ap); - safe_delete(newpacket); - } - } - } - } - break; - case OP_Fragment: { - if (!p->pBuffer || (p->Size() < 4)) - { - break; - } - - uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); - sint8 check=CompareSequence(NextInSeq,seq); - if (check == SeqFuture) { -#ifdef EQN_DEBUG - LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); - //p->DumpRawHeader(seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); -#endif - OutOfOrderpackets[seq] = p->Copy(); - //SendOutOfOrderAck(seq); - } else if (check == SeqPast) { -#ifdef EQN_DEBUG - LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); - //p->DumpRawHeader(seq); - LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); -#endif - //OutOfOrderpackets[seq] = p->Copy(); - SendOutOfOrderAck(seq); - } else { - // In case we did queue one before as well. - EQProtocolPacket* qp = RemoveQueue(seq); - if (qp) { - LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); - delete qp; - } - - SetNextAckToSend(seq); - NextInSeq++; - if (oversize_buffer) { - memcpy(oversize_buffer+oversize_offset,p->pBuffer+2,p->size-2); - oversize_offset+=p->size-2; - //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-2) << ") Seq=" << seq << endl; - if (oversize_offset==oversize_length) { - if (*(p->pBuffer+2)==0x00 && *(p->pBuffer+3)==0x19) { - EQProtocolPacket *subp=new EQProtocolPacket(oversize_buffer,oversize_offset); - subp->copyInfo(p); - ProcessPacket(subp, p); - delete subp; - } else { - - if(crypto->isEncrypted() && p && p->size > 2){ - MCombineQueueLock.lock(); - EQProtocolPacket* p2 = ProcessEncryptedData(oversize_buffer, oversize_offset, p->opcode); - MCombineQueueLock.unlock(); - EQApplicationPacket* ap = p2->MakeApplicationPacket(2); - ap->copyInfo(p); - if (ap->version == 0) - ap->version = client_version; -#ifdef WRITE_PACKETS - WritePackets(ap->GetOpcodeName(), oversize_buffer, oversize_offset, false); -#endif - ap->copyInfo(p); - InboundQueuePush(ap); - safe_delete(p2); - } - } - delete[] oversize_buffer; - oversize_buffer=NULL; - oversize_offset=0; - } - } else if (!oversize_buffer) { - oversize_length=ntohl(*(uint32 *)(p->pBuffer+2)); - oversize_buffer=new unsigned char[oversize_length]; - memcpy(oversize_buffer,p->pBuffer+6,p->size-6); - oversize_offset=p->size-6; - //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-6) << ") Seq=" << seq << endl; - } - } - } - break; - case OP_KeepAlive: { -#ifndef COLLECTOR - NonSequencedPush(new EQProtocolPacket(p->opcode,p->pBuffer,p->size)); -#endif - } - break; - case OP_Ack: { - if (!p->pBuffer || (p->Size() < 4)) - { - LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_Ack that was of malformed size"); - break; - } - uint16 seq = ntohs(*(uint16*)(p->pBuffer)); - AckPackets(seq); - retransmittimer = Timer::GetCurrentTime2(); - } - break; - case OP_SessionRequest: { - if (p->Size() < sizeof(SessionRequest)) - { - break; - } - - if (GetState() == ESTABLISHED) { - //_log(NET__ERROR, _L "Received OP_SessionRequest in ESTABLISHED state (%d) streamactive (%i) attempt (%i)" __L, GetState(), streamactive, sessionAttempts); - - // client seems to try a max of 4 times (initial +3 retries) then gives up, giving it a few more attempts just in case - // streamactive means we identified the opcode, we cannot re-establish this connection - if (streamactive || (sessionAttempts > 30)) - { - SendDisconnect(false); - SetState(CLOSED); - break; - } - } - - sessionAttempts++; - if(GetState() == WAIT_CLOSE) { - printf("WAIT_CLOSE Reconnect with streamactive %u, sessionAttempts %u\n", streamactive, sessionAttempts); - reconnectAttempt++; - } - init(GetState() != ESTABLISHED); - OutboundQueueClear(); - SessionRequest *Request=(SessionRequest *)p->pBuffer; - Session=ntohl(Request->Session); - SetMaxLen(ntohl(Request->MaxLength)); -#ifndef COLLECTOR - NextInSeq=0; - Key=0x33624702; - SendSessionResponse(); -#endif - SetState(ESTABLISHED); - } - break; - case OP_SessionResponse: { - if (p->Size() < sizeof(SessionResponse)) - { - break; - } - init(); - OutboundQueueClear(); - SetActive(true); - SessionResponse *Response=(SessionResponse *)p->pBuffer; - SetMaxLen(ntohl(Response->MaxLength)); - Key=ntohl(Response->Key); - NextInSeq=0; - SetState(ESTABLISHED); - if (!Session) - Session=ntohl(Response->Session); - compressed=(Response->Format&FLAG_COMPRESSED); - encoded=(Response->Format&FLAG_ENCODED); - - // Kinda kludgy, but trie for now - if (compressed) { - if (remote_port==9000 || (remote_port==0 && p->src_port==9000)) - SetStreamType(WorldStream); - else - SetStreamType(ZoneStream); - } else if (encoded) - SetStreamType(ChatOrMailStream); - else - SetStreamType(LoginStream); - } - break; - case OP_SessionDisconnect: { - //NextInSeq=0; - SendDisconnect(); - //SetState(CLOSED); - } - break; - case OP_OutOfOrderAck: { - if (!p->pBuffer || (p->Size() < 4)) - { - LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck that was of malformed size"); - break; - } - uint16 seq = ntohs(*(uint16*)(p->pBuffer)); - MOutboundQueue.lock(); - - if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Pre-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); - } - - //if the packet they got out of order is between our last acked packet and the last sent packet, then its valid. - if (CompareSequence(SequencedBase, seq) != SeqPast && CompareSequence(NextOutSeq, seq) == SeqPast) { - uint16 sqsize = SequencedQueue.size(); - uint16 index = seq - SequencedBase; - LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck marking packet acked in queue (queue index = %u, queue size = %u)", index, sqsize); - if (index < sqsize) { - SequencedQueue[index]->acked = true; - // flag packets for a resend - uint16 count = 0; - uint32 timeout = AverageDelta * 2 + 100; - for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end() && count < index; ++sitr, ++count) { - if (!(*sitr)->acked && (*sitr)->sent_time > 0 && (((*sitr)->sent_time + timeout) < Timer::GetCurrentTime2())) { - (*sitr)->sent_time = 0; - LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck Flagging packet %u for retransmission", SequencedBase + count); - } - } - } - - if (RETRANSMIT_TIMEOUT_MULT) { - retransmittimer = Timer::GetCurrentTime2(); - } - } - else { - LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck for out-of-window %u. Window (%u->%u)", seq, SequencedBase, NextOutSeq); - } - - if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Post-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); - } - - MOutboundQueue.unlock(); - } - break; - case OP_ServerKeyRequest:{ - if (p->Size() < sizeof(ClientSessionStats)) - { - //_log(NET__ERROR, _L "Received OP_SessionStatRequest that was of malformed size" __L); - break; - } - - ClientSessionStats* Stats = (ClientSessionStats*)p->pBuffer; - int16 request_id = Stats->RequestID; - AdjustRates(ntohl(Stats->average_delta)); - ServerSessionStats* stats=(ServerSessionStats*)p->pBuffer; - memset(stats, 0, sizeof(ServerSessionStats)); - stats->RequestID = request_id; - stats->current_time = ntohl(Timer::GetCurrentTime2()); - stats->sent_packets = ntohl(sent_packets); - stats->sent_packets2 = ntohl(sent_packets); - stats->received_packets = ntohl(received_packets); - stats->received_packets2 = ntohl(received_packets); - NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse,p->pBuffer,p->size)); - if(!crypto->isEncrypted()) - SendKeyRequest(); - else - SendSessionResponse(); - } - break; - case OP_SessionStatResponse: { - LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionStatResponse"); - } - break; - case OP_OutOfSession: { - LogWrite(PACKET__INFO, 0, "Packet", "OP_OutOfSession"); - SendDisconnect(); - SetState(CLOSED); - } - break; - default: - //EQApplicationPacket *ap = p->MakeApplicationPacket(app_opcode_size); - //InboundQueuePush(ap); - - cout << "Orig Packet: " << p->opcode << endl; - DumpPacket(p->pBuffer, p->size); - if(p && p->size >= 69){ - processRSAKey(p); - } - MCombineQueueLock.lock(); - EQProtocolPacket* p2 = ProcessEncryptedData(p->pBuffer, p->size, OP_Packet); - MCombineQueueLock.unlock(); - cout << "Decrypted Packet: " << p2->opcode << endl; - DumpPacket(p2->pBuffer, p2->size); - - safe_delete(p2); - /* if(p2) - { - EQApplicationPacket* ap = p2->MakeApplicationPacket(2); - if (ap->version == 0) - ap->version = client_version; - InboundQueuePush(ap); - safe_delete(p2); - }*/ - - //EQProtocolPacket* puse = p2; - /* if (!rogue_buffer) { - roguebuf_size=puse->size; - rogue_buffer=new unsigned char[roguebuf_size]; - memcpy(rogue_buffer,puse->pBuffer,puse->size); - roguebuf_offset=puse->size; - cout << "RogueBuf is " << roguebuf_offset << "/" << roguebuf_size << " (" << (p->size-6) << ") NextInSeq=" << NextInSeq << endl; - } - else { - int32 new_size = roguebuf_size + puse->size; - uchar* tmp_buffer = new unsigned char[new_size]; - uchar* ptr = tmp_buffer; - - memcpy(ptr,rogue_buffer,roguebuf_size); - ptr += roguebuf_size; - memcpy(ptr,puse->pBuffer,puse->size); - roguebuf_offset=puse->size; - - safe_delete_array(rogue_buffer); - - rogue_buffer = tmp_buffer; - roguebuf_size = new_size; - roguebuf_offset = new_size; - cout << "RogueBuf is " << roguebuf_offset << "/" << roguebuf_size << " (" << (p->size-6) << ") NextInSeq=" << NextInSeq << endl; - }*/ -#ifdef WRITE_PACKETS - WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); -#endif - //InboundQueuePush(ap); - LogWrite(PACKET__INFO, 0, "Packet", "Received unknown packet type, not adding to inbound queue"); - //safe_delete(p2); - //SendDisconnect(); - break; - } - } -} - -int8 EQStream::EQ2_Compress(EQ2Packet* app, int8 offset){ - -#ifdef LE_DEBUG - printf( "Before Compress in %s, line %i:\n", __FUNCTION__, __LINE__); - DumpPacket(app); -#endif - - - uchar* pDataPtr = app->pBuffer + offset; - int xpandSize = app->size * 2; - uchar* deflate_buff = new uchar[xpandSize]; - MCompressData.lock(); - stream.next_in = pDataPtr; - stream.avail_in = app->size - offset; - stream.next_out = deflate_buff; - stream.avail_out = xpandSize; - - int ret = deflate(&stream, Z_SYNC_FLUSH); - - if (ret != Z_OK) - { - printf("ZLIB COMPRESSION RETFAIL: %i, %i (Ret: %i)\n", app->size, stream.avail_out, ret); - MCompressData.unlock(); - safe_delete_array(deflate_buff); - return 0; - } - - int32 newsize = xpandSize - stream.avail_out; - safe_delete_array(app->pBuffer); - app->size = newsize + offset; - app->pBuffer = new uchar[app->size]; - app->pBuffer[(offset - 1)] = 1; - memcpy(app->pBuffer + offset, deflate_buff, newsize); - MCompressData.unlock(); - safe_delete_array(deflate_buff); - -#ifdef LE_DEBUG - printf( "After Compress in %s, line %i:\n", __FUNCTION__, __LINE__); - DumpPacket(app); -#endif - - return offset - 1; -} - -int16 EQStream::processRSAKey(EQProtocolPacket *p, uint16 subpacket_length){ - /*int16 limit = 0; - int8 offset = 13; - int8 offset2 = 0; - if(p->pBuffer[2] == 0) - limit = p->pBuffer[9]; - else{ - limit = p->pBuffer[5]; - offset2 = 5; - offset-=1; - } - crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + offset + (limit-8), 8)); - return (limit + offset +1) - offset2;*/ - if(subpacket_length) - crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + subpacket_length - 8, 8)); - else - crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + p->size - 8, 8)); - - return 0; -} - -void EQStream::SendKeyRequest(){ - int32 crypto_key_size = 60; - int16 size = sizeof(KeyGen_Struct) + sizeof(KeyGen_End_Struct) + crypto_key_size; - EQ2Packet *outapp=new EQ2Packet(OP_WSLoginRequestMsg,NULL,size); - memcpy(&outapp->pBuffer[0], &crypto_key_size, sizeof(int32)); - memset(&outapp->pBuffer[4], 0xFF, crypto_key_size); - memset(&outapp->pBuffer[size-5], 1, 1); - memset(&outapp->pBuffer[size-1], 1, 1); - EQ2QueuePacket(outapp, true); -} - -void EQStream::EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset){ - if(app->size>2 && crypto->isEncrypted()){ - app->packet_encrypted = true; - uchar* crypt_buff = app->pBuffer; - if(app->eq2_compressed) - crypto->RC4Encrypt(crypt_buff + compress_offset, app->size - compress_offset); - else - crypto->RC4Encrypt(crypt_buff + 2 + offset, app->size - 2 - offset); - } -} - -void EQStream::EQ2QueuePacket(EQ2Packet* app, bool attempted_combine){ - if(CheckActive()){ - if(!attempted_combine){ - MCombineQueueLock.lock(); - combine_queue.push_back(app); - MCombineQueueLock.unlock(); - } - else{ - MCombineQueueLock.lock(); - PreparePacket(app); - MCombineQueueLock.unlock(); -#ifdef LE_DEBUG - printf( "After B in %s, line %i:\n", __FUNCTION__, __LINE__); - DumpPacket(app); -#endif - SendPacket(app); - } - } -} - -void EQStream::UnPreparePacket(EQ2Packet* app){ - if(app->pBuffer[2] == 0 && app->pBuffer[3] == 19){ - uchar* new_buffer = new uchar[app->size-3]; - memcpy(new_buffer+2, app->pBuffer+5, app->size-3); - delete[] app->pBuffer; - app->size-=3; - app->pBuffer = new_buffer; - } -} - -#ifdef WRITE_PACKETS -char EQStream::GetChar(uchar in) -{ - if (in < ' ' || in > '~') - return '.'; - return (char)in; -} -void EQStream::WriteToFile(char* pFormat, ...) { - va_list args; - va_start(args, pFormat); - vfprintf(write_packets, pFormat, args); - va_end(args); -} - -void EQStream::WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing) { - MWritePackets.lock(); - struct in_addr ip_addr; - ip_addr.s_addr = remote_ip; - char timebuffer[80]; - time_t rawtime; - struct tm* timeinfo; - time(&rawtime); - timeinfo = localtime(&rawtime); - strftime(timebuffer, 80, "%m/%d/%Y %H:%M:%S", timeinfo); - if (outgoing) - WriteToFile("-- %s --\n%s\nSERVER -> %s\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); - else - WriteToFile("-- %s --\n%s\n%s -> SERVER\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); - int i; - int nLines = size / 16; - int nExtra = size % 16; - uchar* pPtr = data; - for (i = 0; i < nLines; i++) - { - WriteToFile("%4.4X:\t%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n", i * 16, pPtr[0], pPtr[1], pPtr[2], pPtr[3], pPtr[4], pPtr[5], pPtr[6], pPtr[7], pPtr[8], pPtr[9], pPtr[10], pPtr[11], pPtr[12], pPtr[13], pPtr[14], pPtr[15], GetChar(pPtr[0]), GetChar(pPtr[1]), GetChar(pPtr[2]), GetChar(pPtr[3]), GetChar(pPtr[4]), GetChar(pPtr[5]), GetChar(pPtr[6]), GetChar(pPtr[7]), GetChar(pPtr[8]), GetChar(pPtr[9]), GetChar(pPtr[10]), GetChar(pPtr[11]), GetChar(pPtr[12]), GetChar(pPtr[13]), GetChar(pPtr[14]), GetChar(pPtr[15])); - pPtr += 16; - } - if (nExtra) - { - WriteToFile("%4.4X\t", nLines * 16); - for (i = 0; i < nExtra; i++) - { - WriteToFile("%2.2X ", pPtr[i]); - } - for (i; i < 16; i++) - WriteToFile(" "); - for (i = 0; i < nExtra; i++) - { - WriteToFile("%c", GetChar(pPtr[i])); - } - WriteToFile("\n"); - } - WriteToFile("\n\n"); - fflush(write_packets); - MWritePackets.unlock(); -} - -void EQStream::WritePackets(EQ2Packet* app, bool outgoing) { - if (app->version == 0) - app->version = client_version; - WritePackets(app->GetOpcodeName(), app->pBuffer, app->size, outgoing); -} -#endif - -void EQStream::PreparePacket(EQ2Packet* app, int8 offset){ - app->setVersion(client_version); - compressed_offset = 0; - -#ifdef LE_DEBUG - printf( "Before A in %s, line %i:\n", __FUNCTION__, __LINE__); - DumpPacket(app); -#endif - if(!app->packet_prepared){ - if(app->PreparePacket(MaxLen) == 255) //invalid version - return; - } - -#ifdef LE_DEBUG - printf( "After Prepare in %s, line %i:\n", __FUNCTION__, __LINE__); - DumpPacket(app); -#endif -#ifdef WRITE_PACKETS - if (!app->eq2_compressed && !app->packet_encrypted) - WritePackets(app, true); -#endif - - if(!app->eq2_compressed && app->size>128){ - compressed_offset = EQ2_Compress(app); - if (compressed_offset) - app->eq2_compressed = true; - } - if(!app->packet_encrypted){ - EncryptPacket(app, compressed_offset, offset); - if(app->size > 2 && app->pBuffer[2] == 0){ - uchar* new_buffer = new uchar[app->size+1]; - new_buffer[2] = 0; - memcpy(new_buffer+3, app->pBuffer+2, app->size-2); - delete[] app->pBuffer; - app->pBuffer = new_buffer; - app->size++; - } - } - -#ifdef LE_DEBUG - printf( "After A in %s, line %i:\n", __FUNCTION__, __LINE__); - DumpPacket(app); -#endif - -} - -void EQStream::SendPacket(EQProtocolPacket *p) -{ - uint32 chunksize,used; - uint32 length; - - // Convert the EQApplicationPacket to 1 or more EQProtocolPackets - if (p->size>( MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) - uchar* tmpbuff=p->pBuffer; - length=p->size - 2; - - EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,NULL,MaxLen-4); - *(uint32 *)(out->pBuffer+2)=htonl(length); - used=MaxLen-10; - memcpy(out->pBuffer+6,tmpbuff+2,used); - -#ifdef LE_DEBUG - printf("(%s, %i) New Fragment:\n ", __FUNCTION__, __LINE__); - DumpPacket(out); -#endif - - SequencedPush(out); - - while (usedpBuffer+2,tmpbuff,1); - memcpy(out->pBuffer+2,tmpbuff+used+2,chunksize); -#ifdef LE_DEBUG - printf("Chunk: \n"); - DumpPacket(out); -#endif - SequencedPush(out); - used+=chunksize; - - } - -#ifdef LE_DEBUG - printf( "ChunkDelete: \n"); - DumpPacket(out); - //cerr << "1: Deleting 0x" << hex << (uint32)(p) << dec << endl; -#endif - - delete p; - } else { - SequencedPush(p); - } -} -void EQStream::SendPacket(EQApplicationPacket *p) -{ -uint32 chunksize,used; -uint32 length; - - // Convert the EQApplicationPacket to 1 or more EQProtocolPackets - if (p->size>(MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) - //cout << "Making oversized packet for: " << endl; - //cout << p->size << endl; - //p->DumpRawHeader(); - //dump_message(p->pBuffer,p->size,timestamp()); - //cout << p->size << endl; - unsigned char *tmpbuff=new unsigned char[p->size+2]; - //cout << hex << (int)tmpbuff << dec << endl; - length=p->serialize(tmpbuff); - - EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,NULL,MaxLen-4); - *(uint32 *)(out->pBuffer+2)=htonl(p->Size()); - memcpy(out->pBuffer+6,tmpbuff,MaxLen-10); - used=MaxLen-10; - SequencedPush(out); - //cout << "Chunk #" << ++i << " size=" << used << ", length-used=" << (length-used) << endl; - while (usedpBuffer+2,tmpbuff+used,chunksize); - out->size=chunksize+2; - SequencedPush(out); - used+=chunksize; - //cout << "Chunk #"<< ++i << " size=" << chunksize << ", length-used=" << (length-used) << endl; - } - //cerr << "1: Deleting 0x" << hex << (uint32)(p) << dec << endl; - delete p; - delete[] tmpbuff; - } else { - EQProtocolPacket *out=new EQProtocolPacket(OP_Packet,NULL,p->Size()+2); - p->serialize(out->pBuffer+2); - SequencedPush(out); - //cerr << "2: Deleting 0x" << hex << (uint32)(p) << dec << endl; - delete p; - } -} - -void EQStream::SequencedPush(EQProtocolPacket *p) -{ - p->setVersion(client_version); - MOutboundQueue.lock(); - *(uint16 *)(p->pBuffer)=htons(NextOutSeq); - SequencedQueue.push_back(p); - p->sequence = NextOutSeq; - NextOutSeq++; - MOutboundQueue.unlock(); -} - -void EQStream::NonSequencedPush(EQProtocolPacket *p) -{ - p->setVersion(client_version); - MOutboundQueue.lock(); - NonSequencedQueue.push(p); - MOutboundQueue.unlock(); -} - -void EQStream::SendAck(uint16 seq) -{ - uint16 Seq=htons(seq); - SetLastAckSent(seq); - NonSequencedPush(new EQProtocolPacket(OP_Ack,(unsigned char *)&Seq,sizeof(uint16))); -} - -void EQStream::SendOutOfOrderAck(uint16 seq) -{ - uint16 Seq=htons(seq); - NonSequencedPush(new EQProtocolPacket(OP_OutOfOrderAck,(unsigned char *)&Seq,sizeof(uint16))); -} - -bool EQStream::CheckCombineQueue(){ - bool ret = true; //processed all packets - MCombineQueueLock.lock(); - if(combine_queue.size() > 0){ - EQ2Packet* first = combine_queue.front(); - combine_queue.pop_front(); - if(combine_queue.size() == 0){ //nothing to combine this with - EQ2QueuePacket(first, true); - } - else{ - PreparePacket(first); - EQ2Packet* second = 0; - bool combine_worked = false; - int16 count = 0; - while(combine_queue.size()){ - count++; - second = combine_queue.front(); - combine_queue.pop_front(); - PreparePacket(second); - /*if(first->GetRawOpcode() != OP_AppCombined && first->pBuffer[2] == 0){ - EQ2Packet* tmp = second; - second = first; - first = tmp; - }*/ - if(!first->AppCombine(second)){ - first->SetProtocolOpcode(OP_Packet); - if(combine_worked){ - SequencedPush(first); - } - else{ - EQ2QueuePacket(first, true); - } - first = second; - combine_worked = false; - } - else{ - combine_worked = true; - //DumpPacket(first); - } - if(count >= 60 || first->size > 4000){ //other clients need packets too - ret = false; - break; - } - } - if(first){ - first->SetProtocolOpcode(OP_Packet); - if(combine_worked){ - SequencedPush(first); - } - else{ - EQ2QueuePacket(first, true); - } - } - } - } - MCombineQueueLock.unlock(); - return ret; -} - -void EQStream::CheckResend(int eq_fd){ - int32 curr = Timer::GetCurrentTime2(); - EQProtocolPacket* packet = 0; - deque::iterator itr; - MResendQue.lock(); - for(itr=resend_que.begin();itr!=resend_que.end();itr++){ - packet = *itr; - if(packet->attempt_count >= 5){//tried to resend this packet 5 times, client must already have it but didnt ack it - safe_delete(packet); - itr = resend_que.erase(itr); - if(itr == resend_que.end()) - break; - } - else{ - if((curr - packet->sent_time) < 1000) - continue; - packet->sent_time -=1000; - packet->attempt_count++; - WritePacket(eq_fd, packet); - } - } - MResendQue.unlock(); -} - - - -//returns SeqFuture if `seq` is later than `expected_seq` -EQStream::SeqOrder EQStream::CompareSequence(uint16 expected_seq, uint16 seq) -{ - if (expected_seq == seq) { - // Curent - return SeqInOrder; - } - else if ((seq > expected_seq && (uint32)seq < ((uint32)expected_seq + EQStream::MaxWindowSize)) || seq < (expected_seq - EQStream::MaxWindowSize)) { - // Future - return SeqFuture; - } - else { - // Past - return SeqPast; - } -} - -void EQStream::AckPackets(uint16 seq) -{ - std::deque::iterator itr, tmp; - - MOutboundQueue.lock(); - - SeqOrder ord = CompareSequence(SequencedBase, seq); - if (ord == SeqInOrder) { - //they are not acking anything new... - LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with no window advancement (seq %u)", seq); - } - else if (ord == SeqPast) { - //they are nacking blocks going back before our buffer, wtf? - LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with backward window advancement (they gave %u, our window starts at %u). This is bad" , seq, SequencedBase); - } - else { - LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack up through sequence %u. Our base is %u", seq, SequencedBase); - - - //this is a good ack, we get to ack some blocks. - seq++; //we stop at the block right after their ack, counting on the wrap of both numbers. - while (SequencedBase != seq) { - if (SequencedQueue.empty()) { - LogWrite(PACKET__DEBUG, 9, "Packet", "OUT OF PACKETS acked packet with sequence %u. Next send is %u before this", (unsigned long)SequencedBase, SequencedQueue.size()); - SequencedBase = NextOutSeq; - break; - } - LogWrite(PACKET__DEBUG, 9, "Packet", "Removing acked packet with sequence %u", (unsigned long)SequencedBase); - //clean out the acked packet - delete SequencedQueue.front(); - SequencedQueue.pop_front(); - //advance the base sequence number to the seq of the block after the one we just got rid of. - SequencedBase++; - } - if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Post-Ack on %u Invalid Sequenced queue: BS %u + SQ %u != NOS %u", seq, SequencedBase, SequencedQueue.size(), NextOutSeq); - } - } - - MOutboundQueue.unlock(); -} - -void EQStream::Write(int eq_fd) -{ - queue ReadyToSend; - long maxack; - - // Check our rate to make sure we can send more - MRate.lock(); - sint32 threshold=RateThreshold; - MRate.unlock(); - if (BytesWritten > threshold) { - //cout << "Over threshold: " << BytesWritten << " > " << threshold << endl; - return; - } - - MCombinedAppPacket.lock(); - EQApplicationPacket *CombPack=CombinedAppPacket; - CombinedAppPacket=NULL; - MCombinedAppPacket.unlock(); - - if (CombPack) { - SendPacket(CombPack); - } - - // If we got more packets to we need to ack, send an ack on the highest one - MAcks.lock(); - maxack=MaxAckReceived; - // Added from peaks findings - if (NextAckToSend>LastAckSent || LastAckSent == 0x0000ffff) - SendAck(NextAckToSend); - MAcks.unlock(); - - // Lock the outbound queues while we process - MOutboundQueue.lock(); - - // Adjust where we start sending in case we get a late ack - //if (maxack>LastSeqSent) - // LastSeqSent=maxack; - - // Place to hold the base packet t combine into - EQProtocolPacket *p=NULL; - std::deque::iterator sitr; - - // Find the next sequenced packet to send from the "queue" - sitr = SequencedQueue.begin(); - - uint16 count = 0; - // get to start of packets - while (sitr != SequencedQueue.end() && (*sitr)->sent_time > 0) { - ++sitr; - ++count; - } - - bool SeqEmpty = false, NonSeqEmpty = false; - // Loop until both are empty or MaxSends is reached - while (!SeqEmpty || !NonSeqEmpty) { - - // See if there are more non-sequenced packets left - if (!NonSequencedQueue.empty()) { - if (!p) { - // If we don't have a packet to try to combine into, use this one as the base - // And remove it form the queue - p = NonSequencedQueue.front(); - LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with non-seq packet of len %u",p->size); - NonSequencedQueue.pop(); - } - else if (!p->combine(NonSequencedQueue.front())) { - // Trying to combine this packet with the base didn't work (too big maybe) - // So just send the base packet (we'll try this packet again later) - LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next non-seq packet is len %u", p->size, (NonSequencedQueue.front())->size); - ReadyToSend.push(p); - BytesWritten += p->size; - p = nullptr; - - if (BytesWritten > threshold) { - // Sent enough this round, lets stop to be fair - LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in nonseq (%u > %u)", BytesWritten, threshold); - break; - } - } - else { - // Combine worked, so just remove this packet and it's spot in the queue - LogWrite(PACKET__DEBUG, 9, "Packet", "Combined non-seq packet of len %u, yeilding %u combined", (NonSequencedQueue.front())->size, p->size); - delete NonSequencedQueue.front(); - NonSequencedQueue.pop(); - } - } - else { - // No more non-sequenced packets - NonSeqEmpty = true; - } - - if (sitr != SequencedQueue.end()) { - uint16 seq_send = SequencedBase + count; //just for logging... - - if (SequencedQueue.empty()) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Tried to write a packet with an empty queue (%u is past next out %u)", seq_send, NextOutSeq); - SeqEmpty = true; - continue; - } - - if ((*sitr)->acked || (*sitr)->sent_time != 0) { - ++sitr; - ++count; - if (p) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); - ReadyToSend.push(p); - BytesWritten += p->size; - p = nullptr; - } - LogWrite(PACKET__DEBUG, 9, "Packet", "Not retransmitting seq packet %u because already marked as acked", seq_send); - } - else if (!p) { - // If we don't have a packet to try to combine into, use this one as the base - // Copy it first as it will still live until it is acked - p = (*sitr)->Copy(); - LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with seq packet %u of len %u", seq_send, p->size); - (*sitr)->sent_time = Timer::GetCurrentTime2(); - ++sitr; - ++count; - } - else if (!p->combine(*sitr)) { - // Trying to combine this packet with the base didn't work (too big maybe) - // So just send the base packet (we'll try this packet again later) - LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next seq packet %u is len %u", p->size, seq_send + 1, (*sitr)->size); - ReadyToSend.push(p); - BytesWritten += p->size; - p = nullptr; - if ((*sitr)->opcode != OP_Fragment && BytesWritten > threshold) { - // Sent enough this round, lets stop to be fair - LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in seq (%u > %u)", BytesWritten, threshold); - break; - } - } - else { - // Combine worked - LogWrite(PACKET__DEBUG, 9, "Packet", "Combined seq packet %u of len %u, yeilding %u combined", seq_send, (*sitr)->size, p->size); - (*sitr)->sent_time = Timer::GetCurrentTime2(); - ++sitr; - ++count; - } - - if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Post send Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); - } - } - else { - // No more sequenced packets - SeqEmpty = true; - } - } - MOutboundQueue.unlock(); // Unlock the queue - - // We have a packet still, must have run out of both seq and non-seq, so send it - if (p) { - LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); - ReadyToSend.push(p); - BytesWritten += p->size; - } - - // Send all the packets we "made" - while (!ReadyToSend.empty()) { - p = ReadyToSend.front(); - WritePacket(eq_fd, p); - delete p; - ReadyToSend.pop(); - } - - //see if we need to send our disconnect and finish our close - if (SeqEmpty && NonSeqEmpty) { - //no more data to send - if (GetState() == CLOSING) { - MOutboundQueue.lock(); - if (SequencedQueue.size() > 0 ) { - // retransmission attempts - } - else - { - LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client."); - //we are waiting for the queues to empty, now we can do our disconnect. - //this packet will not actually go out until the next call to Write(). - SendDisconnect(); - //SetState(CLOSED); - } - MOutboundQueue.unlock(); - } - } -} - -void EQStream::WritePacket(int eq_fd, EQProtocolPacket *p) -{ -uint32 length = 0; -sockaddr_in address; - address.sin_family = AF_INET; - address.sin_addr.s_addr=remote_ip; - address.sin_port=remote_port; -#ifdef NOWAY - uint32 ip=address.sin_addr.s_addr; - cout << "Sending to: " - << (int)*(unsigned char *)&ip - << "." << (int)*((unsigned char *)&ip+1) - << "." << (int)*((unsigned char *)&ip+2) - << "." << (int)*((unsigned char *)&ip+3) - << "," << (int)ntohs(address.sin_port) << "(" << p->size << ")" << endl; - - p->DumpRaw(); - cout << "-------------" << endl; -#endif - length=p->serialize(buffer); - if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse) { - if (compressed) { - BytesWritten -= p->size; - uint32 newlen=EQProtocolPacket::Compress(buffer,length,write_buffer,2048); - memcpy(buffer,write_buffer,newlen); - length=newlen; - BytesWritten += newlen; - } - if (encoded) { - EQProtocolPacket::ChatEncode(buffer,length,Key); - } - *(uint16 *)(buffer+length)=htons(CRC16(buffer,length,Key)); - length+=2; - } - sent_packets++; - //dump_message_column(buffer,length,"Writer: "); - //cout << "Raw Data:\n"; - //DumpPacket(buffer, length); - sendto(eq_fd,(char *)buffer,length,0,(sockaddr *)&address,sizeof(address)); -} - -EQProtocolPacket *EQStream::Read(int eq_fd, sockaddr_in *from) -{ -int socklen; -int length=0; -unsigned char buffer[2048]; -EQProtocolPacket *p=NULL; -char temp[15]; - - socklen=sizeof(sockaddr); -#ifdef WIN32 - length=recvfrom(eq_fd, (char *)buffer, 2048, 0, (struct sockaddr*)from, (int *)&socklen); -#else - length=recvfrom(eq_fd, buffer, 2048, 0, (struct sockaddr*)from, (socklen_t *)&socklen); -#endif - if (length>=2) { - DumpPacket(buffer, length); - p=new EQProtocolPacket(buffer[1],&buffer[2],length-2); - //printf("Read packet: opcode %i length %u, expected-length: %u\n",buffer[1], length, p->size); - uint32 ip=from->sin_addr.s_addr; - sprintf(temp,"%d.%d.%d.%d:%d", - *(unsigned char *)&ip, - *((unsigned char *)&ip+1), - *((unsigned char *)&ip+2), - *((unsigned char *)&ip+3), - ntohs(from->sin_port)); - //cout << timestamp() << "Data from: " << temp << " OpCode 0x" << hex << setw(2) << setfill('0') << (int)p->opcode << dec << endl; - //dump_message(p->pBuffer,p->size,timestamp()); - - } - return p; -} - -void EQStream::SendSessionResponse() -{ -EQProtocolPacket *out=new EQProtocolPacket(OP_SessionResponse,NULL,sizeof(SessionResponse)); - SessionResponse *Response=(SessionResponse *)out->pBuffer; - Response->Session=htonl(Session); - Response->MaxLength=htonl(MaxLen); - Response->UnknownA=2; - Response->Format=0; - if (compressed) - Response->Format|=FLAG_COMPRESSED; - if (encoded) - Response->Format|=FLAG_ENCODED; - Response->Key=htonl(Key); - - out->size=sizeof(SessionResponse); - - NonSequencedPush(out); -} - -void EQStream::SendSessionRequest() -{ - EQProtocolPacket *out=new EQProtocolPacket(OP_SessionRequest,NULL,sizeof(SessionRequest)); - SessionRequest *Request=(SessionRequest *)out->pBuffer; - memset(Request,0,sizeof(SessionRequest)); - Request->Session=htonl(time(NULL)); - Request->MaxLength=htonl(512); - - NonSequencedPush(out); -} - -void EQStream::SendDisconnect(bool setstate) -{ - try{ - if(GetState() != ESTABLISHED && GetState() != WAIT_CLOSE) - return; - - EQProtocolPacket *out=new EQProtocolPacket(OP_SessionDisconnect,NULL,sizeof(uint32)+sizeof(int16)); - *(uint32 *)out->pBuffer=htonl(Session); - out->pBuffer[4] = 0; - out->pBuffer[5] = 6; - NonSequencedPush(out); - if(setstate) - SetState(CLOSING); - } - catch(...){} -} - -void EQStream::InboundQueuePush(EQApplicationPacket *p) -{ - MInboundQueue.lock(); - InboundQueue.push_back(p); - MInboundQueue.unlock(); -} - -EQApplicationPacket *EQStream::PopPacket() -{ -EQApplicationPacket *p=NULL; - - MInboundQueue.lock(); - if (InboundQueue.size()) { - p=InboundQueue.front(); - InboundQueue.pop_front(); - } - MInboundQueue.unlock(); - if(p) - p->setVersion(client_version); - return p; -} - -void EQStream::InboundQueueClear() -{ - MInboundQueue.lock(); - while(InboundQueue.size()){ - delete InboundQueue.front(); - InboundQueue.pop_front(); - } - MInboundQueue.unlock(); -} -void EQStream::EncryptPacket(uchar* data, int16 size){ - if(size>6){ - - } -} -bool EQStream::HasOutgoingData() -{ -bool flag; - - //once closed, we have nothing more to say - if(CheckClosed()) - return(false); - - MOutboundQueue.lock(); - flag=(!NonSequencedQueue.empty()); - if (!flag) { - flag = (!SequencedQueue.empty()); - } - MOutboundQueue.unlock(); - - if (!flag) { - MAcks.lock(); - flag= (NextAckToSend>LastAckSent); - MAcks.unlock(); - } - - if (!flag) { - MCombinedAppPacket.lock(); - flag=(CombinedAppPacket!=NULL); - MCombinedAppPacket.unlock(); - } - - return flag; -} - -void EQStream::OutboundQueueClear() -{ - MOutboundQueue.lock(); - while(NonSequencedQueue.size()) { - delete NonSequencedQueue.front(); - NonSequencedQueue.pop(); - } - while(SequencedQueue.size()) { - delete SequencedQueue.front(); - SequencedQueue.pop_front(); - } - MOutboundQueue.unlock(); -} - -void EQStream::Process(const unsigned char *buffer, const uint32 length) -{ - received_packets++; -static unsigned char newbuffer[2048]; -uint32 newlength=0; - -#ifdef LE_DEBUG -printf("ProcessBuffer:\n"); -DumpPacket(buffer, length); -#endif - - if (EQProtocolPacket::ValidateCRC(buffer,length,Key)) { - if (compressed) { - newlength=EQProtocolPacket::Decompress(buffer,length,newbuffer,2048); -#ifdef LE_DEBUG - printf("ProcessBufferDecompress:\n"); - DumpPacket(buffer, newlength); -#endif - } else { - memcpy(newbuffer,buffer,length); - newlength=length; - if (encoded) - EQProtocolPacket::ChatDecode(newbuffer,newlength-2,Key); - } - -#ifdef LE_DEBUG - printf("ResultProcessBuffer:\n"); - DumpPacket(buffer, newlength); -#endif - uint16 opcode=ntohs(*(const uint16 *)newbuffer); - //printf("Read packet: opcode %i newlength %u, newbuffer2len: %u, newbuffer3len: %u\n",opcode, newlength, newbuffer[2], newbuffer[3]); - if(opcode > 0 && opcode <= OP_OutOfSession) - { - if (buffer[1]!=0x01 && buffer[1]!=0x02 && buffer[1]!=0x1d) - newlength-=2; - - EQProtocolPacket p(newbuffer,newlength); - ProcessPacket(&p); - } - else - { - cout << "2Orig Packet: " << opcode << endl; - DumpPacket(newbuffer, newlength); - ProcessEmbeddedPacket(newbuffer, newlength, OP_Fragment); - } - ProcessQueue(); - } else { - cout << "Incoming packet failed checksum:" <(buffer),length,"CRC failed: "); - } -} - -long EQStream::GetMaxAckReceived() -{ - MAcks.lock(); - long l=MaxAckReceived; - MAcks.unlock(); - - return l; -} - -long EQStream::GetNextAckToSend() -{ - MAcks.lock(); - long l=NextAckToSend; - MAcks.unlock(); - - return l; -} - -long EQStream::GetLastAckSent() -{ - MAcks.lock(); - long l=LastAckSent; - MAcks.unlock(); - - return l; -} - -void EQStream::SetMaxAckReceived(uint32 seq) -{ - deque::iterator itr; - - MAcks.lock(); - MaxAckReceived=seq; - MAcks.unlock(); - MOutboundQueue.lock(); - if (long(seq) > LastSeqSent) - LastSeqSent=seq; - MResendQue.lock(); - EQProtocolPacket* packet = 0; - for(itr=resend_que.begin();itr!=resend_que.end();itr++){ - packet = *itr; - if(packet && packet->sequence <= seq){ - safe_delete(packet); - itr = resend_que.erase(itr); - if(itr == resend_que.end()) - break; - } - } - MResendQue.unlock(); - MOutboundQueue.unlock(); -} - -void EQStream::SetNextAckToSend(uint32 seq) -{ - MAcks.lock(); - NextAckToSend=seq; - MAcks.unlock(); -} - -void EQStream::SetLastAckSent(uint32 seq) -{ - MAcks.lock(); - LastAckSent=seq; - MAcks.unlock(); -} - -void EQStream::SetLastSeqSent(uint32 seq) -{ - MOutboundQueue.lock(); - LastSeqSent=seq; - MOutboundQueue.unlock(); -} - -void EQStream::SetStreamType(EQStreamType type) -{ - StreamType=type; - switch (StreamType) { - case LoginStream: - app_opcode_size=1; - compressed=false; - encoded=false; - break; - case EQ2Stream: - app_opcode_size=2; - compressed=false; - encoded=false; - break; - case ChatOrMailStream: - case ChatStream: - case MailStream: - app_opcode_size=1; - compressed=false; - encoded=true; - break; - case ZoneStream: - case WorldStream: - default: - app_opcode_size=2; - compressed=true; - encoded=false; - break; - } -} - -void EQStream::ProcessQueue() -{ - if (OutOfOrderpackets.empty()) { - return; - } - - EQProtocolPacket* qp = NULL; - while ((qp = RemoveQueue(NextInSeq)) != NULL) { - //_log(NET__DEBUG, _L "Processing Queued Packet: Seq=%d" __L, NextInSeq); - ProcessPacket(qp); - delete qp; - //_log(NET__APP_TRACE, _L "OP_Packet Queue size=%d" __L, PacketQueue.size()); - } -} - -EQProtocolPacket* EQStream::RemoveQueue(uint16 seq) -{ - map::iterator itr; - EQProtocolPacket* qp = NULL; - if ((itr = OutOfOrderpackets.find(seq)) != OutOfOrderpackets.end()) { - qp = itr->second; - OutOfOrderpackets.erase(itr); - //_log(NET__APP_TRACE, _L "OP_Packet Queue size=%d" __L, PacketQueue.size()); - } - return qp; -} - -void EQStream::Decay() -{ - MRate.lock(); - uint32 rate=DecayRate; - MRate.unlock(); - if (BytesWritten>0) { - BytesWritten-=rate; - if (BytesWritten<0) - BytesWritten=0; - } - - int count = 0; - MOutboundQueue.lock(); - for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end(); ++sitr, count++) { - if (!(*sitr)->acked && (*sitr)->sent_time > 0 && ((*sitr)->sent_time + retransmittimeout) < Timer::GetCurrentTime2()) { - (*sitr)->sent_time = 0; - LogWrite(PACKET__DEBUG, 9, "Packet", "Timeout exceeded for seq %u. Flagging packet for retransmission", SequencedBase + count); - } - } - MOutboundQueue.unlock(); -} - -void EQStream::AdjustRates(uint32 average_delta) -{ - if (average_delta && (average_delta <= AVERAGE_DELTA_MAX)) { - MRate.lock(); - AverageDelta = average_delta; - RateThreshold = RATEBASE / average_delta; - DecayRate = DECAYBASE / average_delta; - if (BytesWritten > RateThreshold) - BytesWritten = RateThreshold + DecayRate; - MRate.unlock(); - } - else { - AverageDelta = AVERAGE_DELTA_MAX; - } -} diff --git a/old/EQStream.h b/old/EQStream.h deleted file mode 100644 index 3ce45a8..0000000 --- a/old/EQStream.h +++ /dev/null @@ -1,375 +0,0 @@ -/* - EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - - This file is part of EQ2Emulator. - - EQ2Emulator is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EQ2Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with EQ2Emulator. If not, see . -*/ -#ifndef _EQPROTOCOL_H -#define _EQPROTOCOL_H - -#include -#include -#include -#include - -#include -#include -#ifndef WIN32 -#include -#endif -#include "EQPacket.h" -#include "Mutex.h" -#include "opcodemgr.h" -#include "misc.h" -#include "Condition.h" -#include "Crypto.h" -#include "zlib.h" -#include "timer.h" -#ifdef WRITE_PACKETS -#include -#endif - -using namespace std; - -typedef enum { - ESTABLISHED, - WAIT_CLOSE, - CLOSING, - DISCONNECTING, - CLOSED -} EQStreamState; - -#define FLAG_COMPRESSED 0x01 -#define FLAG_ENCODED 0x04 - -#define RATEBASE 1048576 // 1 MB -#define DECAYBASE 78642 // RATEBASE/10 - -#ifndef RETRANSMIT_TIMEOUT_MULT -#define RETRANSMIT_TIMEOUT_MULT 3.0 -#endif - -#ifndef RETRANSMIT_TIMEOUT_MAX -#define RETRANSMIT_TIMEOUT_MAX 5000 -#endif - -#ifndef AVERAGE_DELTA_MAX -#define AVERAGE_DELTA_MAX 2500 -#endif - -#pragma pack(1) -struct SessionRequest { - uint32 UnknownA; - uint32 Session; - uint32 MaxLength; -}; - -struct SessionResponse { - uint32 Session; - uint32 Key; - uint8 UnknownA; - uint8 Format; - uint8 UnknownB; - uint32 MaxLength; - uint32 UnknownD; -}; - -//Deltas are in ms, representing round trip times -struct ClientSessionStats { -/*000*/ uint16 RequestID; -/*002*/ uint32 last_local_delta; -/*006*/ uint32 average_delta; -/*010*/ uint32 low_delta; -/*014*/ uint32 high_delta; -/*018*/ uint32 last_remote_delta; -/*022*/ uint64 packets_sent; -/*030*/ uint64 packets_recieved; -/*038*/ -}; - -struct ServerSessionStats { - uint16 RequestID; - uint32 current_time; - uint32 unknown1; - uint32 received_packets; - uint32 unknown2; - uint32 sent_packets; - uint32 unknown3; - uint32 sent_packets2; - uint32 unknown4; - uint32 received_packets2; -}; - -#pragma pack() - -class OpcodeManager; -extern OpcodeManager *EQNetworkOpcodeManager; - -class EQStreamFactory; - -typedef enum { - UnknownStream=0, - LoginStream, - WorldStream, - ZoneStream, - ChatOrMailStream, - ChatStream, - MailStream, - EQ2Stream, -} EQStreamType; - -class EQStream { - protected: - typedef enum { - SeqPast, - SeqInOrder, - SeqFuture - } SeqOrder; - - uint32 received_packets; - uint32 sent_packets; - uint32 remote_ip; - uint16 remote_port; - uint8 buffer[8192]; - unsigned char *oversize_buffer; - uint32 oversize_offset,oversize_length; - unsigned char *rogue_buffer; - uint32 roguebuf_offset,roguebuf_size; - uint8 app_opcode_size; - EQStreamType StreamType; - bool compressed,encoded; - - unsigned char write_buffer[2048]; - - uint32 retransmittimer; - uint32 retransmittimeout; - //uint32 buffer_len; - - uint16 sessionAttempts; - uint16 reconnectAttempt; - bool streamactive; - - uint32 Session, Key; - uint16 NextInSeq; - uint16 NextOutSeq; - uint16 SequencedBase; //the sequence number of SequencedQueue[0] - uint32 MaxLen; - uint16 MaxSends; - int8 timeout_delays; - - uint8 active_users; //how many things are actively using this - Mutex MInUse; - -#ifdef WRITE_PACKETS - FILE* write_packets = NULL; - char GetChar(uchar in); - void WriteToFile(char* pFormat, ...); - void WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing); - void WritePackets(EQ2Packet* app, bool outgoing); - Mutex MWritePackets; -#endif - - EQStreamState State; - Mutex MState; - - uint32 LastPacket; - Mutex MVarlock; - - EQApplicationPacket* CombinedAppPacket; - Mutex MCombinedAppPacket; - - long LastSeqSent; - Mutex MLastSeqSent; - void SetLastSeqSent(uint32); - - // Ack sequence tracking. - long MaxAckReceived,NextAckToSend,LastAckSent; - long GetMaxAckReceived(); - long GetNextAckToSend(); - long GetLastAckSent(); - void SetMaxAckReceived(uint32 seq); - void SetNextAckToSend(uint32); - void SetLastAckSent(uint32); - - Mutex MAcks; - - // Packets waiting to be sent - queue NonSequencedQueue; - deque SequencedQueue; - map OutOfOrderpackets; - Mutex MOutboundQueue; - - // Packes waiting to be processed - deque InboundQueue; - Mutex MInboundQueue; - - static uint16 MaxWindowSize; - - sint32 BytesWritten; - - Mutex MRate; - sint32 RateThreshold; - sint32 DecayRate; - uint32 AverageDelta; - - EQStreamFactory *Factory; - - public: - Mutex MCombineQueueLock; - bool CheckCombineQueue(); - deque combine_queue; - Timer* combine_timer; - - Crypto* crypto; - int8 EQ2_Compress(EQ2Packet* app, int8 offset = 3); - z_stream stream; - uchar* stream_buffer; - int32 stream_buffer_size; - bool eq2_compressed; - int8 compressed_offset; - int16 client_version; - int16 GetClientVersion(){ return client_version; } - void SetClientVersion(int16 version){ client_version = version; } - void ResetSessionAttempts() { reconnectAttempt = 0; } - bool HasSessionAttempts() { return reconnectAttempt>0; } - EQStream() { init(); remote_ip = 0; remote_port = 0; State = CLOSED; StreamType = UnknownStream; compressed = true; - encoded = false; app_opcode_size = 2;} - EQStream(sockaddr_in addr); - virtual ~EQStream() { - MOutboundQueue.lock(); - SetState(CLOSED); - MOutboundQueue.unlock(); - RemoveData(); - safe_delete(crypto); - safe_delete(combine_timer); - safe_delete(resend_que_timer); - safe_delete_array(oversize_buffer); - safe_delete_array(rogue_buffer); - deque::iterator cmb; - MCombineQueueLock.lock(); - for (cmb = combine_queue.begin(); cmb != combine_queue.end(); cmb++){ - safe_delete(*cmb); - } - MCombineQueueLock.unlock(); - deflateEnd(&stream); - map::iterator oop; - for (oop = OutOfOrderpackets.begin(); oop != OutOfOrderpackets.end(); oop++){ - safe_delete(oop->second); - } -#ifdef WRITE_PACKETS - if (write_packets) - fclose(write_packets); -#endif - } - inline void SetFactory(EQStreamFactory *f) { Factory=f; } - void init(bool resetSession = true); - void SetMaxLen(uint32 length) { MaxLen=length; } - int8 getTimeoutDelays(){ return timeout_delays; } - void addTimeoutDelay(){ timeout_delays++; } - void EQ2QueuePacket(EQ2Packet* app, bool attempted_combine = false); - void PreparePacket(EQ2Packet* app, int8 offset = 0); - void UnPreparePacket(EQ2Packet* app); - void EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset); - void FlushCombinedPacket(); - void SendPacket(EQApplicationPacket *p); - void QueuePacket(EQProtocolPacket *p); - void SendPacket(EQProtocolPacket *p); - vector convert(EQApplicationPacket *p); - void NonSequencedPush(EQProtocolPacket *p); - void SequencedPush(EQProtocolPacket *p); - - Mutex MResendQue; - Mutex MCompressData; - dequeresend_que; - void CheckResend(int eq_fd); - - void AckPackets(uint16 seq); - void Write(int eq_fd); - - void SetActive(bool val) { streamactive = val; } - - void WritePacket(int fd,EQProtocolPacket *p); - - void EncryptPacket(uchar* data, int16 size); - uint32 GetKey() { return Key; } - void SetKey(uint32 k) { Key=k; } - void SetSession(uint32 s) { Session=s; } - void SetLastPacketTime(uint32 t) {LastPacket=t;} - - void Process(const unsigned char *data, const uint32 length); - void ProcessPacket(EQProtocolPacket *p, EQProtocolPacket* lastp=NULL); - - bool ProcessEmbeddedPacket(uchar* pBuffer, uint16 length, int8 opcode = OP_Packet); - bool HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset = 2, int16 length = 0); - - EQProtocolPacket * ProcessEncryptedPacket(EQProtocolPacket *p); - EQProtocolPacket * ProcessEncryptedData(uchar* data, int32 size, int16 opcode); - - virtual void DispatchPacket(EQApplicationPacket *p) { p->DumpRaw(); } - - void SendSessionResponse(); - void SendSessionRequest(); - void SendDisconnect(bool setstate = true); - void SendAck(uint16 seq); - void SendOutOfOrderAck(uint16 seq); - - bool CheckTimeout(uint32 now, uint32 timeout=30) { return (LastPacket && (now-LastPacket) > timeout); } - bool Stale(uint32 now, uint32 timeout=30) { return (LastPacket && (now-LastPacket) > timeout); } - - void InboundQueuePush(EQApplicationPacket *p); - EQApplicationPacket *PopPacket(); // InboundQueuePop - void InboundQueueClear(); - - void OutboundQueueClear(); - bool HasOutgoingData(); - void SendKeyRequest(); - int16 processRSAKey(EQProtocolPacket *p, uint16 subpacket_length = 0); - void RemoveData() { InboundQueueClear(); OutboundQueueClear(); if (CombinedAppPacket) delete CombinedAppPacket; } - - // - inline bool IsInUse() { bool flag; MInUse.lock(); flag=(active_users>0); MInUse.unlock(); return flag; } - inline void PutInUse() { MInUse.lock(); active_users++; MInUse.unlock(); } - inline void ReleaseFromUse() { MInUse.lock(); if(active_users > 0) active_users--; MInUse.unlock(); } - - static SeqOrder CompareSequence(uint16 expected_seq, uint16 seq); - - inline EQStreamState GetState() { return State; } - inline void SetState(EQStreamState state) { MState.lock(); State = state; MState.unlock(); } - - inline uint32 GetRemoteIP() { return remote_ip; } - inline uint32 GetrIP() { return remote_ip; } - inline uint16 GetRemotePort() { return remote_port; } - inline uint16 GetrPort() { return remote_port; } - - - static EQProtocolPacket *Read(int eq_fd, sockaddr_in *from); - - void Close() { SendDisconnect(); } - bool CheckActive() { return (GetState()==ESTABLISHED); } - bool CheckClosed() { return GetState()==CLOSED; } - void SetOpcodeSize(uint8 s) { app_opcode_size = s; } - void SetStreamType(EQStreamType t); - inline const EQStreamType GetStreamType() const { return StreamType; } - - void ProcessQueue(); - EQProtocolPacket* RemoveQueue(uint16 seq); - - void Decay(); - void AdjustRates(uint32 average_delta); - Timer* resend_que_timer; -}; - -#endif diff --git a/old/EQStreamFactory.cpp b/old/EQStreamFactory.cpp deleted file mode 100644 index 965865e..0000000 --- a/old/EQStreamFactory.cpp +++ /dev/null @@ -1,444 +0,0 @@ -/* - EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - - This file is part of EQ2Emulator. - - EQ2Emulator is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EQ2Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with EQ2Emulator. If not, see . -*/ - -#include "EQStreamFactory.h" -#include "Log.h" - -#ifdef WIN32 - #include - #include - #include - #include - #include -#else - #include - #include - #include - #include - #include - #include -#endif -#include -#include -#include -#include "op_codes.h" -#include "EQStream.h" -#include "packet_dump.h" -#ifdef WORLD - #include "../WorldServer/client.h" -#endif -using namespace std; - -#ifdef WORLD - extern ClientList client_list; -#endif -ThreadReturnType EQStreamFactoryReaderLoop(void *eqfs) -{ - if(eqfs){ - EQStreamFactory *fs=(EQStreamFactory *)eqfs; - fs->ReaderLoop(); - } - THREAD_RETURN(NULL); -} - -ThreadReturnType EQStreamFactoryWriterLoop(void *eqfs) -{ - if(eqfs){ - EQStreamFactory *fs=(EQStreamFactory *)eqfs; - fs->WriterLoop(); - } - THREAD_RETURN(NULL); -} - -ThreadReturnType EQStreamFactoryCombinePacketLoop(void *eqfs) -{ - if(eqfs){ - EQStreamFactory *fs=(EQStreamFactory *)eqfs; - fs->CombinePacketLoop(); - } - THREAD_RETURN(NULL); -} - -EQStreamFactory::EQStreamFactory(EQStreamType type, int port) -{ - StreamType=type; - Port=port; - listen_ip_address = 0; -} - -void EQStreamFactory::Close() -{ - CheckTimeout(true); - Stop(); - if (sock != -1) { -#ifdef WIN32 - closesocket(sock); -#else - close(sock); -#endif - sock = -1; - } -} -bool EQStreamFactory::Open() -{ -struct sockaddr_in address; -#ifndef WIN32 - pthread_t t1, t2, t3; -#endif - /* Setup internet address information. - This is used with the bind() call */ - memset((char *) &address, 0, sizeof(address)); - address.sin_family = AF_INET; - address.sin_port = htons(Port); -#if defined(LOGIN) || defined(MINILOGIN) - if(listen_ip_address) - address.sin_addr.s_addr = inet_addr(listen_ip_address); - else - address.sin_addr.s_addr = htonl(INADDR_ANY); -#else - address.sin_addr.s_addr = htonl(INADDR_ANY); -#endif - /* Setting up UDP port for new clients */ - sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock < 0) { - return false; - } - - if (::bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { - //close(sock); - sock=-1; - return false; - } - #ifdef WIN32 - unsigned long nonblock = 1; - ioctlsocket(sock, FIONBIO, &nonblock); - #else - fcntl(sock, F_SETFL, O_NONBLOCK); - #endif - //moved these because on windows the output was delayed and causing the console window to look bad -#ifdef LOGIN - LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Reader"); - LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Writer"); -#elif WORLD - LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Reader"); - LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Writer"); -#endif - #ifdef WIN32 - _beginthread(EQStreamFactoryReaderLoop,0, this); - _beginthread(EQStreamFactoryWriterLoop,0, this); - _beginthread(EQStreamFactoryCombinePacketLoop,0, this); - #else - pthread_create(&t1,NULL,EQStreamFactoryReaderLoop,this); - pthread_create(&t2,NULL,EQStreamFactoryWriterLoop,this); - pthread_create(&t3,NULL,EQStreamFactoryCombinePacketLoop,this); - pthread_detach(t1); - pthread_detach(t2); - pthread_detach(t3); - #endif - return true; -} - -EQStream *EQStreamFactory::Pop() -{ - if (!NewStreams.size()) - return NULL; - -EQStream *s=NULL; - //cout << "Pop():Locking MNewStreams" << endl; - MNewStreams.lock(); - if (NewStreams.size()) { - s=NewStreams.front(); - NewStreams.pop(); - s->PutInUse(); - } - MNewStreams.unlock(); - //cout << "Pop(): Unlocking MNewStreams" << endl; - - return s; -} - -void EQStreamFactory::Push(EQStream *s) -{ - //cout << "Push():Locking MNewStreams" << endl; - MNewStreams.lock(); - NewStreams.push(s); - MNewStreams.unlock(); - //cout << "Push(): Unlocking MNewStreams" << endl; -} - -void EQStreamFactory::ReaderLoop() -{ -fd_set readset; -map::iterator stream_itr; -int num; -int length; -unsigned char buffer[2048]; -sockaddr_in from; -int socklen=sizeof(sockaddr_in); -timeval sleep_time; - ReaderRunning=true; - while(sock!=-1) { - MReaderRunning.lock(); - if (!ReaderRunning) - break; - MReaderRunning.unlock(); - - FD_ZERO(&readset); - FD_SET(sock,&readset); - - sleep_time.tv_sec=30; - sleep_time.tv_usec=0; - if ((num=select(sock+1,&readset,NULL,NULL,&sleep_time))<0) { - // What do we wanna do? - } else if (num==0) - continue; - - if (FD_ISSET(sock,&readset)) { -#ifdef WIN32 - if ((length=recvfrom(sock,(char*)buffer,sizeof(buffer),0,(struct sockaddr*)&from,(int *)&socklen))<2) -#else - if ((length=recvfrom(sock,buffer,2048,0,(struct sockaddr *)&from,(socklen_t *)&socklen))<2) -#endif - { - // What do we wanna do? - } else { - char temp[25]; - sprintf(temp,"%u.%d",ntohl(from.sin_addr.s_addr),ntohs(from.sin_port)); - MStreams.lock(); - if ((stream_itr=Streams.find(temp))==Streams.end() || buffer[1]==OP_SessionRequest) { - MStreams.unlock(); - if (buffer[1]==OP_SessionRequest) { - if(stream_itr != Streams.end() && stream_itr->second) - stream_itr->second->SetState(CLOSED); - EQStream *s=new EQStream(from); - s->SetFactory(this); - s->SetStreamType(StreamType); - Streams[temp]=s; - WriterWork.Signal(); - Push(s); - s->Process(buffer,length); - s->SetLastPacketTime(Timer::GetCurrentTime2()); - } - } else { - EQStream *curstream = stream_itr->second; - //dont bother processing incoming packets for closed connections - if(curstream->CheckClosed()) - curstream = NULL; - else - curstream->PutInUse(); - MStreams.unlock(); - - if(curstream) { - curstream->Process(buffer,length); - curstream->SetLastPacketTime(Timer::GetCurrentTime2()); - curstream->ReleaseFromUse(); - } - } - } - } - } -} - -void EQStreamFactory::CheckTimeout(bool remove_all) -{ - //lock streams the entire time were checking timeouts, it should be fast. - MStreams.lock(); - - unsigned long now=Timer::GetCurrentTime2(); - map::iterator stream_itr; - - for(stream_itr=Streams.begin();stream_itr!=Streams.end();) { - EQStream *s = stream_itr->second; - EQStreamState state = s->GetState(); - - if (state==CLOSING && !s->HasOutgoingData()) { - stream_itr->second->SetState(CLOSED); - state = CLOSED; - } else if (s->CheckTimeout(now, STREAM_TIMEOUT)) { - const char* stateString; - switch (state){ - case ESTABLISHED: - stateString = "Established"; - break; - case CLOSING: - stateString = "Closing"; - break; - case CLOSED: - stateString = "Closed"; - break; - case WAIT_CLOSE: - stateString = "Wait-Close"; - break; - default: - stateString = "Unknown"; - break; - } - LogWrite(WORLD__DEBUG, 0, "World", "Timeout up!, state=%s (%u)", stateString, state); - if (state==ESTABLISHED) { - s->Close(); - } - else if (state == WAIT_CLOSE) { - s->SetState(CLOSING); - state = CLOSING; - } - else if (state == CLOSING) { - //if we time out in the closing state, just give up - s->SetState(CLOSED); - state = CLOSED; - } - } - //not part of the else so we check it right away on state change - if (remove_all || state==CLOSED) { - if (!remove_all && s->getTimeoutDelays()<2) { - s->addTimeoutDelay(); - //give it a little time for everybody to finish with it - } else { - //everybody is done, we can delete it now - -#ifdef LOGIN - LogWrite(LOGIN__DEBUG, 0, "Login", "Removing connection..."); -#else - LogWrite(WORLD__DEBUG, 0, "World", "Removing connection..."); -#endif - map::iterator temp=stream_itr; - stream_itr++; - //let whoever has the stream outside delete it - #ifdef WORLD - client_list.RemoveConnection(temp->second); - #endif - EQStream* stream = temp->second; - Streams.erase(temp); - delete stream; - continue; - } - } - - stream_itr++; - } - MStreams.unlock(); -} - -void EQStreamFactory::CombinePacketLoop(){ - deque combine_que; - CombinePacketRunning = true; - bool packets_waiting = false; - while(sock!=-1) { - if (!CombinePacketRunning) - break; - MStreams.lock(); - map::iterator stream_itr; - for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { - if(!stream_itr->second){ - continue; - } - if(stream_itr->second->combine_timer && stream_itr->second->combine_timer->Check()) - combine_que.push_back(stream_itr->second); - } - EQStream* stream = 0; - packets_waiting = false; - while(combine_que.size()){ - stream = combine_que.front(); - if(stream->CheckActive()){ - if(!stream->CheckCombineQueue()) - packets_waiting = true; - } - combine_que.pop_front(); - } - MStreams.unlock(); - if(!packets_waiting) - Sleep(25); - - Sleep(1); - } -} - -void EQStreamFactory::WriterLoop() -{ -map::iterator stream_itr; -vector wants_write; -vector::iterator cur,end; -deque resend_que; -bool decay=false; -uint32 stream_count; - -Timer DecayTimer(20); - - WriterRunning=true; - DecayTimer.Enable(); - while(sock!=-1) { - Timer::SetCurrentTime(); - //if (!havework) { - //WriterWork.Wait(); - //} - MWriterRunning.lock(); - if (!WriterRunning) - break; - MWriterRunning.unlock(); - - wants_write.clear(); - - decay=DecayTimer.Check(); - - //copy streams into a seperate list so we dont have to keep - //MStreams locked while we are writting - MStreams.lock(); - for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { - // If it's time to decay the bytes sent, then let's do it before we try to write - if(!stream_itr->second){ - Streams.erase(stream_itr); - break; - } - if (decay) - stream_itr->second->Decay(); - - if (stream_itr->second->HasOutgoingData()) { - stream_itr->second->PutInUse(); - wants_write.push_back(stream_itr->second); - } - if(stream_itr->second->resend_que_timer->Check()) - resend_que.push_back(stream_itr->second); - } - MStreams.unlock(); - - //do the actual writes - cur = wants_write.begin(); - end = wants_write.end(); - for(; cur != end; cur++) { - (*cur)->Write(sock); - (*cur)->ReleaseFromUse(); - } - while(resend_que.size()){ - resend_que.front()->CheckResend(sock); - resend_que.pop_front(); - } - Sleep(10); - - MStreams.lock(); - stream_count=Streams.size(); - MStreams.unlock(); - if (!stream_count) { - //cout << "No streams, waiting on condition" << endl; - WriterWork.Wait(); - //cout << "Awake from condition, must have a stream now" << endl; - } - } -} - - diff --git a/old/EQStreamFactory.h b/old/EQStreamFactory.h deleted file mode 100644 index 9dce3c7..0000000 --- a/old/EQStreamFactory.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - - This file is part of EQ2Emulator. - - EQ2Emulator is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - EQ2Emulator is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with EQ2Emulator. If not, see . -*/ -#ifndef _EQSTREAMFACTORY_H - -#define _EQSTREAMFACTORY_H - -#include -#include -#include "../common/EQStream.h" -#include "../common/Condition.h" -#include "../common/opcodemgr.h" -#include "../common/timer.h" - -#define STREAM_TIMEOUT 45000 //in ms - -class EQStreamFactory { - private: - int sock; - int Port; - - bool ReaderRunning; - Mutex MReaderRunning; - bool WriterRunning; - Mutex MWriterRunning; - bool CombinePacketRunning; - Mutex MCombinePacketRunning; - - Condition WriterWork; - - EQStreamType StreamType; - - queue NewStreams; - Mutex MNewStreams; - - map Streams; - Mutex MStreams; - - - - Timer *DecayTimer; - - public: - char* listen_ip_address; - void CheckTimeout(bool remove_all = false); - EQStreamFactory(EQStreamType type) { ReaderRunning=false; WriterRunning=false; StreamType=type; } - EQStreamFactory(EQStreamType type, int port); - ~EQStreamFactory(){ - safe_delete_array(listen_ip_address); - } - - EQStream *Pop(); - void Push(EQStream *s); - - bool loadPublicKey(); - bool Open(); - bool Open(unsigned long port) { Port=port; return Open(); } - void Close(); - void ReaderLoop(); - void WriterLoop(); - void CombinePacketLoop(); - void Stop() { StopReader(); StopWriter(); StopCombinePacket(); } - void StopReader() { MReaderRunning.lock(); ReaderRunning=false; MReaderRunning.unlock(); } - void StopWriter() { MWriterRunning.lock(); WriterRunning=false; MWriterRunning.unlock(); WriterWork.Signal(); } - void StopCombinePacket() { MCombinePacketRunning.lock(); CombinePacketRunning=false; MCombinePacketRunning.unlock(); } - void SignalWriter() { WriterWork.Signal(); } - -}; - -#endif diff --git a/old/CRC16.cpp b/old/common/CRC16.cpp similarity index 100% rename from old/CRC16.cpp rename to old/common/CRC16.cpp diff --git a/old/CRC16.h b/old/common/CRC16.h similarity index 100% rename from old/CRC16.h rename to old/common/CRC16.h diff --git a/old/Common_Defines.h b/old/common/Common_Defines.h similarity index 100% rename from old/Common_Defines.h rename to old/common/Common_Defines.h diff --git a/old/Condition.cpp b/old/common/Condition.cpp similarity index 100% rename from old/Condition.cpp rename to old/common/Condition.cpp diff --git a/old/Condition.h b/old/common/Condition.h similarity index 100% rename from old/Condition.h rename to old/common/Condition.h diff --git a/old/ConfigReader.cpp b/old/common/ConfigReader.cpp similarity index 100% rename from old/ConfigReader.cpp rename to old/common/ConfigReader.cpp diff --git a/old/ConfigReader.h b/old/common/ConfigReader.h similarity index 100% rename from old/ConfigReader.h rename to old/common/ConfigReader.h diff --git a/old/Crypto.cpp b/old/common/Crypto.cpp similarity index 100% rename from old/Crypto.cpp rename to old/common/Crypto.cpp diff --git a/old/Crypto.h b/old/common/Crypto.h similarity index 100% rename from old/Crypto.h rename to old/common/Crypto.h diff --git a/old/DataBuffer.h b/old/common/DataBuffer.h similarity index 100% rename from old/DataBuffer.h rename to old/common/DataBuffer.h diff --git a/old/DatabaseNew.cpp b/old/common/DatabaseNew.cpp similarity index 100% rename from old/DatabaseNew.cpp rename to old/common/DatabaseNew.cpp diff --git a/old/DatabaseNew.h b/old/common/DatabaseNew.h similarity index 100% rename from old/DatabaseNew.h rename to old/common/DatabaseNew.h diff --git a/old/DatabaseResult.cpp b/old/common/DatabaseResult.cpp similarity index 100% rename from old/DatabaseResult.cpp rename to old/common/DatabaseResult.cpp diff --git a/old/DatabaseResult.h b/old/common/DatabaseResult.h similarity index 100% rename from old/DatabaseResult.h rename to old/common/DatabaseResult.h diff --git a/old/EQ2_Common_Structs.h b/old/common/EQ2_Common_Structs.h similarity index 100% rename from old/EQ2_Common_Structs.h rename to old/common/EQ2_Common_Structs.h diff --git a/old/EQEMuError.cpp b/old/common/EQEMuError.cpp similarity index 100% rename from old/EQEMuError.cpp rename to old/common/EQEMuError.cpp diff --git a/old/EQEMuError.h b/old/common/EQEMuError.h similarity index 100% rename from old/EQEMuError.h rename to old/common/EQEMuError.h diff --git a/old/common/EQPacket.cpp b/old/common/EQPacket.cpp new file mode 100644 index 0000000..4fb0359 --- /dev/null +++ b/old/common/EQPacket.cpp @@ -0,0 +1,969 @@ +// EQ2Emulator: Everquest II Server Emulator +// Copyright (C) 2007 EQ2EMulator Development Team +// Licensed under GPL v3 - see +#include "debug.h" +#include +#include +#include +#include +#include +#include +#include + +#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 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(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(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(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(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(-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(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(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(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); +} + + diff --git a/old/common/EQPacket.h b/old/common/EQPacket.h new file mode 100644 index 0000000..b1b308c --- /dev/null +++ b/old/common/EQPacket.h @@ -0,0 +1,310 @@ +// EQ2Emulator: Everquest II Server Emulator +// Copyright (C) 2007 EQ2EMulator Development Team +// Licensed under GPL v3 - see +#ifndef _EQPACKET_H +#define _EQPACKET_H + +#include "types.h" +#include +#include +#include + +#ifdef WIN32 + #include + #include +#else + #include + #include +#endif + +#include "emu_opcodes.h" +#include "op_codes.h" +#include "packet_dump.h" + +// Forward declarations +class OpcodeManager; +class EQStream; + +/** + * Base class for all EverQuest packet types. + * Provides common functionality for packet management, opcode handling, + * and network information tracking. + */ +class EQPacket { + friend class EQStream; + +public: + // Packet data + unsigned char* pBuffer; // Raw packet data buffer + uint32 size; // Size of packet data + + // Network information + uint32 src_ip, dst_ip; // Source and destination IP addresses + uint16 src_port, dst_port; // Source and destination ports + uint32 priority; // Packet priority for processing + timeval timestamp; // Packet timestamp + int16 version; // Protocol version + + ~EQPacket(); + + // Debug and display methods + void DumpRawHeader(uint16 seq = 0xffff, FILE* to = stdout) const; + void DumpRawHeaderNoTime(uint16 seq = 0xffff, FILE* to = stdout) const; + void DumpRaw(FILE* to = stdout) const; + const char* GetOpcodeName(); + + // Packet information setters + void setVersion(int16 new_version) { version = new_version; } + void setSrcInfo(uint32 sip, uint16 sport) { src_ip = sip; src_port = sport; } + void setDstInfo(uint32 dip, uint16 dport) { dst_ip = dip; dst_port = dport; } + void setTimeInfo(uint32 ts_sec, uint32 ts_usec) { + timestamp.tv_sec = ts_sec; + timestamp.tv_usec = ts_usec; + } + + // Copy network 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; + } + + // Get total packet size including opcode + uint32 Size() const { return size + 2; } + + // Get raw opcode value + uint16 GetRawOpcode() const { return opcode; } + + // Comparison operator for timestamp-based sorting + bool operator<(const EQPacket& rhs) const { + return (timestamp.tv_sec < rhs.timestamp.tv_sec || + (timestamp.tv_sec == rhs.timestamp.tv_sec && + timestamp.tv_usec < rhs.timestamp.tv_usec)); + } + + // Set the protocol opcode + void SetProtocolOpcode(int16 new_opcode) { + opcode = new_opcode; + } + +protected: + uint16 opcode; // Packet opcode + + // Constructors (protected to enforce proper inheritance) + EQPacket(uint16 op, const unsigned char* buf, uint32 len); + EQPacket(const EQPacket& p) { version = 0; } + EQPacket() { + opcode = 0; + pBuffer = nullptr; + size = 0; + version = 0; + setTimeInfo(0, 0); + } +}; + +// Forward declaration +class EQApplicationPacket; + +/** + * Protocol-level packet class for EverQuest network communication. + * Handles low-level protocol features like compression, encryption, + * sequencing, and packet combining. + */ +class EQProtocolPacket : public EQPacket { +public: + // Constructor with opcode and 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); + + // Packet manipulation methods + bool combine(const EQProtocolPacket* rhs); + uint32 serialize(unsigned char* dest, int8 offset = 0) const; + + // Static utility methods for packet processing + static bool ValidateCRC(const unsigned char* buffer, int length, uint32 Key); + static uint32 Decompress(const unsigned char* buffer, uint32 length, + unsigned char* newbuf, uint32 newbufsize); + static uint32 Compress(const unsigned char* buffer, uint32 length, + unsigned char* newbuf, uint32 newbufsize); + static void ChatDecode(unsigned char* buffer, int size, int DecodeKey); + static void ChatEncode(unsigned char* buffer, int size, int EncodeKey); + static bool IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC); + + // Create a copy of this packet + EQProtocolPacket* Copy() { + auto 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; + } + + // Convert to application-level packet + EQApplicationPacket* MakeApplicationPacket(uint8 opcode_size = 0) const; + + // Protocol packet state flags + bool eq2_compressed; // Packet is compressed + bool packet_prepared; // Packet has been prepared for sending + bool packet_encrypted; // Packet is encrypted + bool acked; // Packet has been acknowledged + + // Reliability and sequencing + int32 sent_time; // Timestamp when packet was sent + int8 attempt_count; // Number of send attempts + int32 sequence; // Sequence number for ordering + +private: + // Prevent copy construction + EQProtocolPacket(const EQProtocolPacket& p) = delete; + EQProtocolPacket& operator=(const EQProtocolPacket& p) = delete; +}; + +/** + * EverQuest 2 specific packet class. + * Handles EQ2-specific packet operations like application combining + * and login opcode management. + */ +class EQ2Packet : public EQProtocolPacket { +public: + // Constructor with EQ2 login opcode + EQ2Packet(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; + } + + // EQ2-specific packet operations + bool AppCombine(EQ2Packet* rhs); + int8 PreparePacket(int16 MaxLen); + const char* GetOpcodeName(); + + // Create a copy of this EQ2 packet + EQ2Packet* Copy() { + auto 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; + } + + EmuOpcode login_op; // EQ2 login/application opcode +}; + +/** + * Application-level packet class for EverQuest. + * Handles high-level game opcodes and application data. + * This is the main packet type used by game logic. + */ +class EQApplicationPacket : public EQPacket { + friend class EQProtocolPacket; + friend class EQStream; + +public: + // Default constructor + EQApplicationPacket() : EQPacket(0, nullptr, 0) { + emu_opcode = OP_Unknown; + app_opcode_size = default_opcode_size; + } + + // Constructor with opcode only + EQApplicationPacket(EmuOpcode op) : EQPacket(0, nullptr, 0) { + SetOpcode(op); + app_opcode_size = default_opcode_size; + } + + // Constructor with opcode and size + EQApplicationPacket(EmuOpcode op, uint32 len) : EQPacket(0, nullptr, len) { + SetOpcode(op); + app_opcode_size = default_opcode_size; + } + + // Constructor with opcode, buffer, and size + EQApplicationPacket(EmuOpcode op, const unsigned char* buf, uint32 len) + : EQPacket(0, buf, len) { + SetOpcode(op); + app_opcode_size = default_opcode_size; + } + + // Packet operations + bool combine(const EQApplicationPacket* rhs); + uint32 serialize(unsigned char* dest) const; + uint32 Size() const { return size + app_opcode_size; } + + // Create a deep copy of this packet + EQApplicationPacket* Copy() const { + auto it = std::make_unique(); + try { + if (size > 0) { + 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.release(); + } + catch (const std::bad_alloc& ba) { + // Memory allocation failed - return nullptr + return nullptr; + } + } + + // Opcode management + void SetOpcodeSize(uint8 s) { app_opcode_size = s; } + void SetOpcode(EmuOpcode op); + const EmuOpcode GetOpcodeConst() const; + + // Get opcode (const version) + const EmuOpcode GetOpcode() const { return GetOpcodeConst(); } + + // Get opcode (caching version for performance) + EmuOpcode GetOpcode() { + EmuOpcode r = GetOpcodeConst(); + emu_opcode = r; + return r; + } + + // Default opcode size for all application packets + static uint8 default_opcode_size; + +protected: + // Cached emulator opcode to avoid repeated lookups + EmuOpcode emu_opcode; + +private: + // Constructor used by EQProtocolPacket - assumes first bytes are opcode + EQApplicationPacket(const unsigned char* buf, uint32 len, uint8 opcode_size = 0); + + // Prevent copy construction + EQApplicationPacket(const EQApplicationPacket& p) = delete; + EQApplicationPacket& operator=(const EQApplicationPacket& p) = delete; + + // Size of the opcode in bytes (1 or 2) + uint8 app_opcode_size; +}; + +// Packet debugging and display functions +void DumpPacketHex(const EQApplicationPacket* app); +void DumpPacket(const EQProtocolPacket* app); +void DumpPacketAscii(const EQApplicationPacket* app); +void DumpPacket(const EQApplicationPacket* app, bool iShowInfo = false); +void DumpPacketBin(const EQApplicationPacket* app); + +#endif diff --git a/old/common/EQStream.cpp b/old/common/EQStream.cpp new file mode 100644 index 0000000..f4cda97 --- /dev/null +++ b/old/common/EQStream.cpp @@ -0,0 +1,2680 @@ +// EQ2Emulator: Everquest II Server Emulator +// Copyright (C) 2007 EQ2EMulator Development Team +// Licensed under GPL v3 - see +#include "debug.h" + +// Standard library headers +#include +#include +#include +#include +#include +#include +#include +#include + +// Unix networking headers +#include +#include +#include +#include +#include +#include +#include + +// Project headers +#include "EQPacket.h" +#include "EQStream.h" +#include "EQStreamFactory.h" +#include "misc.h" +#include "Mutex.h" +#include "op_codes.h" +#include "CRC16.h" +#include "packet_dump.h" +#include "EQ2_Common_Structs.h" +#include "Log.h" + +#ifdef LOGIN + #include "../LoginServer/login_structs.h" +#endif + + +//#define DEBUG_EMBEDDED_PACKETS 1 + +// Static configuration +uint16 EQStream::MaxWindowSize = 2048; + +/** + * Initialize the EQ stream with default values. + * + * @param resetSession - Whether to reset session-specific data + */ +void EQStream::init(bool resetSession) +{ + if (resetSession) { + streamactive = false; + sessionAttempts = 0; + } + + timeout_delays = 0; + + // Initialize usage tracking + MInUse.lock(); + active_users = 0; + MInUse.unlock(); + + // Initialize session state + Session = 0; + Key = 0; + MaxLen = 0; + NextInSeq = 0; + NextOutSeq = 0; + CombinedAppPacket = nullptr; + + // Initialize acknowledgment tracking + MAcks.lock(); + MaxAckReceived = -1; + NextAckToSend = -1; + LastAckSent = -1; + MAcks.unlock(); + + // Initialize sequence tracking + LastSeqSent = -1; + MaxSends = 5; + LastPacket = Timer::GetCurrentTime2(); + + // Initialize buffers + oversize_buffer = nullptr; + oversize_length = 0; + oversize_offset = 0; + Factory = nullptr; + + rogue_buffer = nullptr; + roguebuf_offset = 0; + roguebuf_size = 0; + + // Initialize rate limiting + MRate.lock(); + RateThreshold = RATEBASE / 250; + DecayRate = DECAYBASE / 250; + MRate.unlock(); + + // Initialize flow control + BytesWritten = 0; + SequencedBase = 0; + AverageDelta = 500; + + // Initialize encryption + crypto->setRC4Key(0); + + // Initialize retransmission + retransmittimer = Timer::GetCurrentTime2(); + retransmittimeout = 500 * RETRANSMIT_TIMEOUT_MULT; + + // Initialize reconnection + reconnectAttempt = 0; + + // Validate queue consistency + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", + "init Invalid Sequenced queue: BS %u + SQ %u != NOS %u", + SequencedBase, SequencedQueue.size(), NextOutSeq); + } +} + +/** + * EQStream constructor with socket address. + * Initializes all stream components and sets up compression. + * + * @param addr - Socket address of remote endpoint + */ +EQStream::EQStream(sockaddr_in addr) +{ + // Initialize crypto and timers + crypto = new Crypto(); + resend_que_timer = new Timer(1000); + combine_timer = new Timer(250); // 250 milliseconds + combine_timer->Start(); + resend_que_timer->Start(); + + // Initialize base stream + init(); + + // Set remote endpoint + remote_ip = addr.sin_addr.s_addr; + remote_port = addr.sin_port; + + // Set initial state + State = CLOSED; + StreamType = UnknownStream; + compressed = true; + encoded = false; + app_opcode_size = 2; + + // Initialize compression stream (Unix only) + bzero(&stream, sizeof(z_stream)); + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + deflateInit2(&stream, 9, Z_DEFLATED, 13, 9, Z_DEFAULT_STRATEGY); + + // Initialize compression state + compressed_offset = 0; + client_version = 0; + received_packets = 0; + sent_packets = 0; + +#ifdef WRITE_PACKETS + // Initialize packet logging + write_packets = nullptr; + char write_packets_filename[64]; + snprintf(write_packets_filename, sizeof(write_packets_filename), + "PacketLog%i.log", Timer::GetCurrentTime2()); + write_packets = fopen(write_packets_filename, "w+"); +#endif +} + +/** + * Process encrypted packet data and extract the embedded packet. + * Decrypts the data and extracts the opcode and payload. + * + * @param data - Encrypted data buffer + * @param size - Size of encrypted data + * @param opcode - Reference to opcode (will be modified) + * @return Newly created EQProtocolPacket with decrypted data + */ +EQProtocolPacket* EQStream::ProcessEncryptedData(uchar* data, int32 size, int16 opcode) +{ + // Decrypt the data using RC4 + crypto->RC4Decrypt(data, size); + + int8 offset = 0; + if (data[0] == 0xFF && size > 2) { + // Extended opcode format (2-byte opcode) + offset = 3; + memcpy(&opcode, data + sizeof(int8), sizeof(int16)); + } else { + // Standard opcode format (1-byte opcode) + offset = 1; + memcpy(&opcode, data, sizeof(int8)); + } + + return new EQProtocolPacket(opcode, data + offset, size - offset); +} + +/** + * Process an encrypted protocol packet. + * Determines the correct offset and calls ProcessEncryptedData. + * + * @param p - Encrypted protocol packet to process + * @return Newly created EQProtocolPacket with decrypted data + */ +EQProtocolPacket* EQStream::ProcessEncryptedPacket(EQProtocolPacket* p) +{ + EQProtocolPacket* ret = nullptr; + + if (p->opcode == OP_Packet && p->size > 2) { + // Skip the first 2 bytes for OP_Packet + ret = ProcessEncryptedData(p->pBuffer + 2, p->size - 2, p->opcode); + } else { + // Process entire buffer for other opcodes + ret = ProcessEncryptedData(p->pBuffer, p->size, p->opcode); + } + + return ret; +} + +/** + * Process an embedded encrypted packet within another packet. + * Decrypts and queues the embedded packet for processing. + * + * @param pBuffer - Buffer containing embedded packet data + * @param length - Length of embedded packet + * @param opcode - Opcode for the embedded packet + * @return true if packet was successfully processed + */ +bool EQStream::ProcessEmbeddedPacket(uchar* pBuffer, int16 length, int8 opcode) +{ + if (!pBuffer || !crypto->isEncrypted()) { + return false; + } + + // Process encrypted data with thread safety + MCombineQueueLock.lock(); + EQProtocolPacket* newpacket = ProcessEncryptedData(pBuffer, length, opcode); + MCombineQueueLock.unlock(); + + if (newpacket) { +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Embedded Packet - Opcode: %u\n", newpacket->opcode); + DumpPacket(newpacket->pBuffer, newpacket->size); +#endif + + // Convert to application packet and queue + EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); + if (ap->version == 0) { + ap->version = client_version; + } + InboundQueuePush(ap); + +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), pBuffer, length, false); +#endif + + safe_delete(newpacket); + return true; + } + + return false; +} + +/** + * Handle embedded packets within a protocol packet. + * Processes different types of embedded packet formats. + * + * @param p - Protocol packet containing embedded data + * @param offset - Offset to start of embedded data + * @param length - Length of embedded data (0 = auto-detect) + * @return true if embedded packet was successfully processed + */ +bool EQStream::HandleEmbeddedPacket(EQProtocolPacket* p, int16 offset, int16 length) +{ + if (!p) { + return false; + } + +#ifdef DEBUG_EMBEDDED_PACKETS + printf("HandleEmbeddedPacket - offset: %u, length: %u, size: %u\n", + offset, length, p->size); +#endif + + // Ensure we have enough data for the offset + if (p->size < static_cast(offset + 2)) { + return false; + } + + // Check for OP_AppCombined embedded packet (0x00 0x19) + if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0x19) { + uint32 data_length = 0; + + if (length == 0) { + // Auto-detect length + data_length = p->size - offset - 2; + } else { + // Use provided length + if (length < 2) { + return false; // Length too short + } + data_length = length - 2; + } + +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Processing OP_AppCombined - offset: %u, data_length: %u\n", + offset, data_length); + DumpPacket(p->pBuffer, p->size); +#endif + + // Verify bounds + if (offset + 2 + data_length > p->size) { + return false; // Out of bounds + } + + // Create and process sub-packet + auto subp = new EQProtocolPacket(OP_AppCombined, + p->pBuffer + offset + 2, + data_length); + subp->copyInfo(p); + ProcessPacket(subp, p); + safe_delete(subp); + return true; + } + // Check for null opcode embedded packet (0x00 0x00) + else if (p->pBuffer[offset] == 0 && p->pBuffer[offset + 1] == 0) { + if (length == 0) { + length = p->size - 1 - offset; + } else { + length--; + } + +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Processing null opcode embedded packet\n"); + DumpPacket(p->pBuffer + 1 + offset, length); +#endif + + uchar* buffer = (p->pBuffer + 1 + offset); + bool valid = ProcessEmbeddedPacket(buffer, length); + + if (valid) { + return true; + } + } + // Check for general embedded packet (not 0xffffffff) + else if (offset + 4 < p->size && ntohl(*(uint32*)(p->pBuffer + offset)) != 0xffffffff) { +#ifdef DEBUG_EMBEDDED_PACKETS + uint16 seq = NextInSeq - 1; + sint8 check = 0; + + if (offset == 2) { + seq = ntohs(*(uint16*)(p->pBuffer)); + check = CompareSequence(NextInSeq, seq); + } + printf("Processing general embedded packet - offset: %u, length: %u, size: %u, check: %i, seq: %u\n", + offset, length, p->size, check, seq); + DumpPacket(p->pBuffer, p->size); +#endif + + if (length == 0) { + length = p->size - offset; + } + + uchar* buffer = (p->pBuffer + offset); + bool valid = ProcessEmbeddedPacket(buffer, length); + + if (valid) { + return true; + } + } + // Check for length-prefixed embedded packet (not 0xff, then 0xff) + else if (p->pBuffer[offset] != 0xff && + p->pBuffer[offset + 1] == 0xff && + p->size >= offset + 3) { + + // Get total length from first byte + uint16 total_length = p->pBuffer[offset]; + + // Verify packet size matches expected length + if (total_length + offset + 2 == p->size && total_length >= 2) { + uint32 data_length = total_length - 2; + + // Create and process sub-packet + auto subp = new EQProtocolPacket(p->pBuffer + offset + 2, + data_length, OP_Packet); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + return true; + } + } + + return false; +} + +/** + * Process a protocol packet and handle various packet types. + * This is the main packet processing dispatcher for the stream. + * + * @param p - Protocol packet to process + * @param lastp - Previous packet in sequence (for context) + */ +void EQStream::ProcessPacket(EQProtocolPacket* p, EQProtocolPacket* lastp) +{ + uint32 processed = 0; + uint32 subpacket_length = 0; + + if (!p) { + return; + } + + // Ensure session is initialized for non-session packets + if (p->opcode != OP_SessionRequest && + p->opcode != OP_SessionResponse && + !Session) { +#ifdef EQN_DEBUG + LogWrite(PACKET__ERROR, 0, "Packet", + "Session not initialized, packet ignored"); +#endif + return; + } + + + // Process packet based on opcode + switch (p->opcode) { + case OP_Combined: { + // Handle combined packets containing multiple sub-packets + processed = 0; + int8 offset = 0; + int count = 0; + +#ifdef LE_DEBUG + printf("Processing OP_Combined packet\n"); + DumpPacket(p); +#endif + + // Process all sub-packets within the combined packet + while (processed < p->size) { + // Determine sub-packet length and offset + subpacket_length = static_cast(*(p->pBuffer + processed)); + + if (subpacket_length == 0xff) { + // Extended length format (2-byte length) + subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1)); + offset = 3; + } else { + // Standard format (1-byte length) + offset = 1; + } + + count++; + +#ifdef LE_DEBUG + printf("Processing sub-packet %i: length=%u, offset=%u\n", + count, subpacket_length, processed); +#endif + + // Check if this is a valid protocol packet + bool isSubPacket = EQProtocolPacket::IsProtocolPacket( + p->pBuffer + processed + offset, subpacket_length, false); + + if (isSubPacket) { + // Create and process sub-packet + auto subp = new EQProtocolPacket( + p->pBuffer + processed + offset, subpacket_length); + subp->copyInfo(p); + +#ifdef LE_DEBUG + printf("Sub-packet opcode: %i\n", subp->opcode); + DumpPacket(subp); +#endif + + ProcessPacket(subp, p); + delete subp; + } + else { + // Handle non-protocol packet data (encrypted) + offset = 1; + + // Check for potential garbage packet + uint16 header_check = ntohs(*reinterpret_cast( + p->pBuffer + processed + offset)); + + if (header_check <= 0x1e) { + // Process as potential OP_Packet + subpacket_length = static_cast( + *(p->pBuffer + processed)); + + LogWrite(PACKET__ERROR, 0, "Packet", + "Processing suspected garbage packet as OP_Packet"); + DumpPacket(p->pBuffer + processed + offset, subpacket_length); + + uchar* newbuf = p->pBuffer + processed + offset; + auto subp = new EQProtocolPacket(newbuf, subpacket_length); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } else { + // Decrypt the data and process + crypto->RC4Decrypt(p->pBuffer + processed + offset, subpacket_length); + + LogWrite(PACKET__ERROR, 0, "Packet", + "Processing encrypted sub-packet: " + "processed=%u, offset=%u, count=%i, length=%u", + processed, offset, count, subpacket_length); + + if (p->pBuffer[processed + offset] == 0xff) { + // Skip 0xff marker and process data + uchar* newbuf = p->pBuffer + processed + offset + 1; + + DumpPacket(p->pBuffer + processed + offset, subpacket_length); + + auto subp = new EQProtocolPacket( + newbuf, subpacket_length, OP_Packet); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } else { + // Invalid packet format - stop processing + break; + } + } + } + + // Move to next sub-packet + processed += subpacket_length + offset; + } + break; + } + case OP_AppCombined: { + // Handle application combined packets + processed = 0; + int8 offset = 0; + int count = 0; + +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Processing OP_AppCombined packet\n"); + DumpPacket(p); +#endif + + while (processed < p->size) { + count++; + + // Determine sub-packet length and offset + subpacket_length = static_cast(*(p->pBuffer + processed)); + + if (subpacket_length == 0xff) { + // Extended length format + subpacket_length = ntohs(*(uint16*)(p->pBuffer + processed + 1)); + offset = 3; + } else { + // Standard length format + offset = 1; + } + + // Handle RSA key exchange if no encryption key is set + if (crypto->getRC4Key() == 0 && p && subpacket_length > 8 + offset) { +#ifdef DEBUG_EMBEDDED_PACKETS + DumpPacket(p->pBuffer, p->size); +#endif + p->pBuffer += offset; + processRSAKey(p, subpacket_length); + p->pBuffer -= offset; + } else if (crypto->isEncrypted()) { + // Process encrypted embedded packet +#ifdef DEBUG_EMBEDDED_PACKETS + printf("Processing encrypted sub-packet %i: length=%u, offset=%u\n", + count, subpacket_length, processed); + DumpPacket(p->pBuffer + processed + offset, subpacket_length); +#endif + + if (!HandleEmbeddedPacket(p, processed + offset, subpacket_length)) { + uchar* buffer = (p->pBuffer + processed + offset); + if (!ProcessEmbeddedPacket(buffer, subpacket_length, OP_AppCombined)) { + LogWrite(PACKET__ERROR, 0, "Packet", + "ProcessEmbeddedPacket failed for OP_AppCombined"); + } + } + } + + // Move to next sub-packet + processed += subpacket_length + offset; + } + break; + } + case OP_Packet: { + // Handle sequenced data packets + if (!p->pBuffer || (p->Size() < 4)) { + break; // Invalid packet + } + + // Extract sequence number and check ordering + uint16 seq = ntohs(*(uint16*)(p->pBuffer)); + sint8 check = CompareSequence(NextInSeq, seq); + + if (check == SeqFuture) { + // Future packet - store for later processing +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", + "Future packet: Expected=%i, Got=%i", NextInSeq, seq); + p->DumpRawHeader(seq); +#endif + + OutOfOrderpackets[seq] = p->Copy(); + // Note: Not sending out-of-order ACK to prevent loops + + } else if (check == SeqPast) { + // Duplicate/old packet - acknowledge but don't process +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", + "Duplicate packet: Expected=%i, Got=%i", NextInSeq, seq); + p->DumpRawHeader(seq); +#endif + + SendOutOfOrderAck(seq); + + } else { + // In-order packet - process normally + EQProtocolPacket* qp = RemoveQueue(seq); + if (qp) { + LogWrite(PACKET__DEBUG, 1, "Packet", + "Removing older queued packet with sequence %i", seq); + delete qp; + } + + // Update sequence tracking + SetNextAckToSend(seq); + NextInSeq++; + + // Try to handle as embedded packet first + if (HandleEmbeddedPacket(p)) { + break; + } + + // Handle RSA key exchange if no encryption key is set + if (crypto->getRC4Key() == 0 && p && p->size >= 69) { +#ifdef DEBUG_EMBEDDED_PACKETS + DumpPacket(p->pBuffer, p->size); +#endif + processRSAKey(p); + } else if (crypto->isEncrypted() && p) { + // Process encrypted packet + MCombineQueueLock.lock(); + EQProtocolPacket* newpacket = ProcessEncryptedPacket(p); + MCombineQueueLock.unlock(); + + if (newpacket) { + // Convert to application packet and queue + EQApplicationPacket* ap = newpacket->MakeApplicationPacket(2); + if (ap->version == 0) { + ap->version = client_version; + } + +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), p->pBuffer, p->size, false); +#endif + + InboundQueuePush(ap); + safe_delete(newpacket); + } + } + } + break; + } + case OP_Fragment: { + if (!p->pBuffer || (p->Size() < 4)) + { + break; + } + + uint16 seq=ntohs(*(uint16 *)(p->pBuffer)); + sint8 check=CompareSequence(NextInSeq,seq); + if (check == SeqFuture) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Future packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + //p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + OutOfOrderpackets[seq] = p->Copy(); + //SendOutOfOrderAck(seq); + } else if (check == SeqPast) { +#ifdef EQN_DEBUG + LogWrite(PACKET__DEBUG, 1, "Packet", "*** Duplicate packet2: Expecting Seq=%i, but got Seq=%i", NextInSeq, seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[Start]"); + //p->DumpRawHeader(seq); + LogWrite(PACKET__DEBUG, 1, "Packet", "[End]"); +#endif + //OutOfOrderpackets[seq] = p->Copy(); + SendOutOfOrderAck(seq); + } else { + // In case we did queue one before as well. + EQProtocolPacket* qp = RemoveQueue(seq); + if (qp) { + LogWrite(PACKET__DEBUG, 1, "Packet", "OP_Fragment: Removing older queued packet with sequence %i", seq); + delete qp; + } + + SetNextAckToSend(seq); + NextInSeq++; + if (oversize_buffer) { + memcpy(oversize_buffer+oversize_offset,p->pBuffer+2,p->size-2); + oversize_offset+=p->size-2; + //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-2) << ") Seq=" << seq << endl; + if (oversize_offset==oversize_length) { + if (*(p->pBuffer+2)==0x00 && *(p->pBuffer+3)==0x19) { + EQProtocolPacket *subp=new EQProtocolPacket(oversize_buffer,oversize_offset); + subp->copyInfo(p); + ProcessPacket(subp, p); + delete subp; + } else { + + if(crypto->isEncrypted() && p && p->size > 2){ + MCombineQueueLock.lock(); + EQProtocolPacket* p2 = ProcessEncryptedData(oversize_buffer, oversize_offset, p->opcode); + MCombineQueueLock.unlock(); + EQApplicationPacket* ap = p2->MakeApplicationPacket(2); + ap->copyInfo(p); + if (ap->version == 0) + ap->version = client_version; +#ifdef WRITE_PACKETS + WritePackets(ap->GetOpcodeName(), oversize_buffer, oversize_offset, false); +#endif + ap->copyInfo(p); + InboundQueuePush(ap); + safe_delete(p2); + } + } + delete[] oversize_buffer; + oversize_buffer=NULL; + oversize_offset=0; + } + } else if (!oversize_buffer) { + oversize_length=ntohl(*(uint32 *)(p->pBuffer+2)); + oversize_buffer=new unsigned char[oversize_length]; + memcpy(oversize_buffer,p->pBuffer+6,p->size-6); + oversize_offset=p->size-6; + //cout << "Oversized is " << oversize_offset << "/" << oversize_length << " (" << (p->size-6) << ") Seq=" << seq << endl; + } + } + } + break; + case OP_KeepAlive: { +#ifndef COLLECTOR + NonSequencedPush(new EQProtocolPacket(p->opcode,p->pBuffer,p->size)); +#endif + } + break; + case OP_Ack: { + if (!p->pBuffer || (p->Size() < 4)) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_Ack that was of malformed size"); + break; + } + uint16 seq = ntohs(*(uint16*)(p->pBuffer)); + AckPackets(seq); + retransmittimer = Timer::GetCurrentTime2(); + } + break; + case OP_SessionRequest: { + if (p->Size() < sizeof(SessionRequest)) + { + break; + } + + if (GetState() == ESTABLISHED) { + //_log(NET__ERROR, _L "Received OP_SessionRequest in ESTABLISHED state (%d) streamactive (%i) attempt (%i)" __L, GetState(), streamactive, sessionAttempts); + + // client seems to try a max of 4 times (initial +3 retries) then gives up, giving it a few more attempts just in case + // streamactive means we identified the opcode, we cannot re-establish this connection + if (streamactive || (sessionAttempts > 30)) + { + SendDisconnect(false); + SetState(CLOSED); + break; + } + } + + sessionAttempts++; + if(GetState() == WAIT_CLOSE) { + printf("WAIT_CLOSE Reconnect with streamactive %u, sessionAttempts %u\n", streamactive, sessionAttempts); + reconnectAttempt++; + } + init(GetState() != ESTABLISHED); + OutboundQueueClear(); + SessionRequest *Request=(SessionRequest *)p->pBuffer; + Session=ntohl(Request->Session); + SetMaxLen(ntohl(Request->MaxLength)); +#ifndef COLLECTOR + NextInSeq=0; + Key=0x33624702; + SendSessionResponse(); +#endif + SetState(ESTABLISHED); + } + break; + /** + * OP_SessionResponse: Handle session establishment response from server + * Contains stream parameters, encryption keys, and stream type information + */ + case OP_SessionResponse: + { + // Validate packet size for SessionResponse structure + if (p->Size() < sizeof(SessionResponse)) + { + break; + } + + // Initialize stream and clear outbound queue + init(); + OutboundQueueClear(); + SetActive(true); + + // Extract session response data + auto Response = reinterpret_cast(p->pBuffer); + SetMaxLen(ntohl(Response->MaxLength)); + Key = ntohl(Response->Key); + NextInSeq = 0; + SetState(ESTABLISHED); + + // Set session ID if not already established + if (!Session) + { + Session = ntohl(Response->Session); + } + + // Extract compression and encoding flags + compressed = (Response->Format & FLAG_COMPRESSED); + encoded = (Response->Format & FLAG_ENCODED); + + // Determine stream type based on format flags and port + if (compressed) + { + if (remote_port == 9000 || (remote_port == 0 && p->src_port == 9000)) + { + SetStreamType(WorldStream); + } + else + { + SetStreamType(ZoneStream); + } + } + else if (encoded) + { + SetStreamType(ChatOrMailStream); + } + else + { + SetStreamType(LoginStream); + } + } + break; + /** + * OP_SessionDisconnect: Handle graceful disconnect request + * Initiates proper connection termination sequence + */ + case OP_SessionDisconnect: + { + // Send disconnect acknowledgment to peer + SendDisconnect(); + } + break; + /** + * OP_OutOfOrderAck: Handle out-of-order packet acknowledgment + * Marks specific packets as acknowledged and flags earlier packets for retransmission + */ + case OP_OutOfOrderAck: + { + // Validate packet structure and size + if (!p->pBuffer || (p->Size() < 4)) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck that was of malformed size"); + break; + } + + // Extract sequence number from packet + uint16 seq = ntohs(*reinterpret_cast(p->pBuffer)); + MOutboundQueue.lock(); + + // Validate sequenced queue integrity (pre-processing) + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Pre-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", + SequencedBase, SequencedQueue.size(), NextOutSeq); + } + + // Verify that acknowledged packet is within valid window + if (CompareSequence(SequencedBase, seq) != SeqPast && CompareSequence(NextOutSeq, seq) == SeqPast) + { + uint16 sqsize = SequencedQueue.size(); + uint16 index = seq - SequencedBase; + + LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck marking packet acked in queue (queue index = %u, queue size = %u)", + index, sqsize); + + // Mark the specific packet as acknowledged + if (index < sqsize) + { + SequencedQueue[index]->acked = true; + + // Flag earlier unacknowledged packets for retransmission + uint16 count = 0; + uint32 timeout = AverageDelta * 2 + 100; + + for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end() && count < index; ++sitr, ++count) + { + // Check if packet needs retransmission based on timeout + if (!(*sitr)->acked && (*sitr)->sent_time > 0 && + (((*sitr)->sent_time + timeout) < Timer::GetCurrentTime2())) + { + (*sitr)->sent_time = 0; // Mark for resend + LogWrite(PACKET__DEBUG, 9, "Packet", "OP_OutOfOrderAck Flagging packet %u for retransmission", + SequencedBase + count); + } + } + } + + // Update retransmit timer if enabled + if (RETRANSMIT_TIMEOUT_MULT) + { + retransmittimer = Timer::GetCurrentTime2(); + } + } + else + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Received OP_OutOfOrderAck for out-of-window %u. Window (%u->%u)", + seq, SequencedBase, NextOutSeq); + } + + // Validate sequenced queue integrity (post-processing) + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post-OOA Invalid Sequenced queue: BS %u + SQ %u != NOS %u", + SequencedBase, SequencedQueue.size(), NextOutSeq); + } + + MOutboundQueue.unlock(); + } + break; + /** + * OP_ServerKeyRequest: Handle server key exchange request + * Processes client session statistics and initiates key exchange + */ + case OP_ServerKeyRequest: + { + // Validate packet size for ClientSessionStats structure + if (p->Size() < sizeof(ClientSessionStats)) + { + break; + } + + // Extract client session statistics + auto Stats = reinterpret_cast(p->pBuffer); + int16 request_id = Stats->RequestID; + + // Adjust transmission rates based on client feedback + AdjustRates(ntohl(Stats->average_delta)); + + // Prepare server session statistics response + auto stats = reinterpret_cast(p->pBuffer); + memset(stats, 0, sizeof(ServerSessionStats)); + stats->RequestID = request_id; + stats->current_time = ntohl(Timer::GetCurrentTime2()); + stats->sent_packets = ntohl(sent_packets); + stats->sent_packets2 = ntohl(sent_packets); + stats->received_packets = ntohl(received_packets); + stats->received_packets2 = ntohl(received_packets); + + // Send session statistics response + NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse, p->pBuffer, p->size)); + + // Initiate appropriate response based on encryption state + if (!crypto->isEncrypted()) + { + SendKeyRequest(); + } + else + { + SendSessionResponse(); + } + } + break; + /** + * OP_SessionStatResponse: Handle session statistics response + * Currently just logs the receipt for debugging purposes + */ + case OP_SessionStatResponse: + { + LogWrite(PACKET__INFO, 0, "Packet", "OP_SessionStatResponse"); + } + break; + /** + * OP_OutOfSession: Handle out-of-session packet + * Indicates client is no longer in valid session state + */ + case OP_OutOfSession: + { + LogWrite(PACKET__INFO, 0, "Packet", "OP_OutOfSession"); + SendDisconnect(); + SetState(CLOSED); + } + break; + /** + * Default case: Handle unknown/unprocessed packet types + * Attempts RSA key processing and packet decryption for debugging + */ + default: + { + // Debug output for original packet + std::cout << "Orig Packet: " << p->opcode << std::endl; + DumpPacket(p->pBuffer, p->size); + + // Process RSA key if packet is large enough + if (p && p->size >= 69) + { + processRSAKey(p); + } + + // Attempt to decrypt packet data + MCombineQueueLock.lock(); + EQProtocolPacket* p2 = ProcessEncryptedData(p->pBuffer, p->size, OP_Packet); + MCombineQueueLock.unlock(); + + // Debug output for decrypted packet + if (p2) + { + std::cout << "Decrypted Packet: " << p2->opcode << std::endl; + DumpPacket(p2->pBuffer, p2->size); + safe_delete(p2); + } + +#ifdef WRITE_PACKETS + // Write packet data to file if enabled + WritePackets("Unknown", p->pBuffer, p->size, false); +#endif + + // Log unknown packet type + LogWrite(PACKET__INFO, 0, "Packet", "Received unknown packet type, not adding to inbound queue"); + } + break; + } + } +} + +/** + * Compress EQ2 packet data using zlib deflate compression + * Compresses packet data starting from the specified offset and updates packet buffer + * + * @param app - EQ2Packet to compress (modified in-place) + * @param offset - Starting offset for compression data + * @return Compression flag offset, or 0 on failure + */ +int8 EQStream::EQ2_Compress(EQ2Packet* app, int8 offset) +{ +#ifdef LE_DEBUG + std::printf("Before Compress in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + // Set up compression parameters + uchar* pDataPtr = app->pBuffer + offset; + int xpandSize = app->size * 2; // Allocate double size for compression buffer + auto deflate_buff = std::make_unique(xpandSize); + + // Lock compression data and configure zlib stream + MCompressData.lock(); + stream.next_in = pDataPtr; + stream.avail_in = app->size - offset; + stream.next_out = deflate_buff.get(); + stream.avail_out = xpandSize; + + // Perform compression + int ret = deflate(&stream, Z_SYNC_FLUSH); + + // Check for compression errors + if (ret != Z_OK) + { + std::printf("ZLIB COMPRESSION RETFAIL: %i, %i (Ret: %i)\n", app->size, stream.avail_out, ret); + MCompressData.unlock(); + return 0; + } + + // Calculate new size and rebuild packet buffer + int32 newsize = xpandSize - stream.avail_out; + safe_delete_array(app->pBuffer); + app->size = newsize + offset; + app->pBuffer = new uchar[app->size]; + + // Set compression flag and copy compressed data + app->pBuffer[offset - 1] = 1; // Compression flag + std::memcpy(app->pBuffer + offset, deflate_buff.get(), newsize); + + MCompressData.unlock(); + +#ifdef LE_DEBUG + std::printf("After Compress in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + return offset - 1; +} + +/** + * Process RSA key from protocol packet and initialize RC4 encryption + * Extracts RSA-encrypted RC4 key from packet and configures stream encryption + * + * @param p - Protocol packet containing RSA key data + * @param subpacket_length - Length of subpacket, 0 to use full packet + * @return Always returns 0 (legacy return value) + */ +int16 EQStream::processRSAKey(EQProtocolPacket *p, uint16 subpacket_length) +{ + // Extract RSA key from appropriate location in packet + if (subpacket_length) + { + // Use subpacket-relative position for key extraction + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + subpacket_length - 8, 8)); + } + else + { + // Use full packet size for key extraction + crypto->setRC4Key(Crypto::RSADecrypt(p->pBuffer + p->size - 8, 8)); + } + + return 0; +} + +/** + * Send encryption key request to establish secure communication + * Creates and sends a key generation packet to initiate encryption handshake + */ +void EQStream::SendKeyRequest() +{ + // Define key generation packet parameters + constexpr int32 crypto_key_size = 60; + const int16 size = sizeof(KeyGen_Struct) + sizeof(KeyGen_End_Struct) + crypto_key_size; + + // Create key request packet + auto outapp = new EQ2Packet(OP_WSLoginRequestMsg, nullptr, size); + + // Set crypto key size in packet header + std::memcpy(&outapp->pBuffer[0], &crypto_key_size, sizeof(int32)); + + // Fill crypto key area with placeholder data + std::memset(&outapp->pBuffer[4], 0xFF, crypto_key_size); + + // Set packet termination markers + std::memset(&outapp->pBuffer[size - 5], 1, 1); + std::memset(&outapp->pBuffer[size - 1], 1, 1); + + // Queue packet for transmission + EQ2QueuePacket(outapp, true); +} + +/** + * Encrypt packet data using RC4 encryption + * Applies encryption to packet payload, skipping headers and unencrypted sections + * + * @param app - EQ2Packet to encrypt (modified in-place) + * @param compress_offset - Offset for compressed data encryption + * @param offset - Additional offset for uncompressed data + */ +void EQStream::EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset) +{ + // Only encrypt packets that are large enough and encryption is enabled + if (app->size > 2 && crypto->isEncrypted()) + { + app->packet_encrypted = true; + uchar* crypt_buff = app->pBuffer; + + // Choose encryption range based on compression status + if (app->eq2_compressed) + { + // Encrypt from compression offset to end of packet + crypto->RC4Encrypt(crypt_buff + compress_offset, app->size - compress_offset); + } + else + { + // Encrypt from header end plus offset to end of packet + crypto->RC4Encrypt(crypt_buff + 2 + offset, app->size - 2 - offset); + } + } +} + +/** + * Queue EQ2 packet for transmission with optional combining + * Either adds packet to combine queue for batching or sends immediately + * + * @param app - EQ2Packet to queue for transmission + * @param attempted_combine - If true, send immediately; if false, queue for combining + */ +void EQStream::EQ2QueuePacket(EQ2Packet* app, bool attempted_combine) +{ + // Only process packets if stream is active + if (CheckActive()) + { + if (!attempted_combine) + { + // Add packet to combine queue for batching + MCombineQueueLock.lock(); + combine_queue.push_back(app); + MCombineQueueLock.unlock(); + } + else + { + // Prepare and send packet immediately + MCombineQueueLock.lock(); + PreparePacket(app); + MCombineQueueLock.unlock(); + +#ifdef LE_DEBUG + std::printf("After B in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + SendPacket(app); + } + } +} + +/** + * Remove packet preparation headers from EQ2 packet + * Strips protocol-specific headers that were added during packet preparation + * + * @param app - EQ2Packet to unprepare (modified in-place) + */ +void EQStream::UnPreparePacket(EQ2Packet* app) +{ + // Check for specific header pattern that indicates prepared packet + if (app->pBuffer[2] == 0 && app->pBuffer[3] == 19) + { + // Create new buffer without preparation headers + auto new_buffer = std::make_unique(app->size - 3); + std::memcpy(new_buffer.get() + 2, app->pBuffer + 5, app->size - 3); + + // Replace packet buffer with unprepared version + delete[] app->pBuffer; + app->size -= 3; + app->pBuffer = new_buffer.release(); + } +} + +#ifdef WRITE_PACKETS +/** + * Convert byte to printable character for packet dumps + * Returns '.' for non-printable characters + * + * @param in - Input byte to convert + * @return Printable character representation + */ +char EQStream::GetChar(uchar in) +{ + if (in < ' ' || in > '~') + { + return '.'; + } + return static_cast(in); +} +/** + * Write formatted output to packet dump file + * Provides printf-style formatting for packet logging + * + * @param pFormat - Printf-style format string + * @param ... - Variable arguments for formatting + */ +void EQStream::WriteToFile(char* pFormat, ...) +{ + va_list args; + va_start(args, pFormat); + std::vfprintf(write_packets, pFormat, args); + va_end(args); +} + +/** + * Write packet data to debug file with hex dump format + * Creates formatted hex dump with ASCII representation for debugging + * + * @param opcodeName - Name of packet opcode for header + * @param data - Raw packet data to dump + * @param size - Size of packet data in bytes + * @param outgoing - True if outgoing packet, false if incoming + */ +void EQStream::WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing) +{ + MWritePackets.lock(); + + // Format connection information + struct in_addr ip_addr; + ip_addr.s_addr = remote_ip; + char timebuffer[80]; + time_t rawtime; + struct tm* timeinfo; + + std::time(&rawtime); + timeinfo = std::localtime(&rawtime); + std::strftime(timebuffer, 80, "%m/%d/%Y %H:%M:%S", timeinfo); + + // Write packet header + if (outgoing) + { + WriteToFile("-- %s --\n%s\nSERVER -> %s\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); + } + else + { + WriteToFile("-- %s --\n%s\n%s -> SERVER\n", opcodeName, timebuffer, inet_ntoa(ip_addr)); + } + + // Calculate hex dump layout + int nLines = size / 16; + int nExtra = size % 16; + uchar* pPtr = data; + + // Write full 16-byte lines + for (int i = 0; i < nLines; i++) + { + WriteToFile("%4.4X:\t%2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n", + i * 16, pPtr[0], pPtr[1], pPtr[2], pPtr[3], pPtr[4], pPtr[5], pPtr[6], pPtr[7], + pPtr[8], pPtr[9], pPtr[10], pPtr[11], pPtr[12], pPtr[13], pPtr[14], pPtr[15], + GetChar(pPtr[0]), GetChar(pPtr[1]), GetChar(pPtr[2]), GetChar(pPtr[3]), + GetChar(pPtr[4]), GetChar(pPtr[5]), GetChar(pPtr[6]), GetChar(pPtr[7]), + GetChar(pPtr[8]), GetChar(pPtr[9]), GetChar(pPtr[10]), GetChar(pPtr[11]), + GetChar(pPtr[12]), GetChar(pPtr[13]), GetChar(pPtr[14]), GetChar(pPtr[15])); + pPtr += 16; + } + + // Write partial line if remaining bytes + if (nExtra) + { + WriteToFile("%4.4X\t", nLines * 16); + + // Write hex bytes + for (int i = 0; i < nExtra; i++) + { + WriteToFile("%2.2X ", pPtr[i]); + } + + // Pad with spaces + for (int i = nExtra; i < 16; i++) + { + WriteToFile(" "); + } + + // Write ASCII representation + for (int i = 0; i < nExtra; i++) + { + WriteToFile("%c", GetChar(pPtr[i])); + } + WriteToFile("\n"); + } + + WriteToFile("\n\n"); + std::fflush(write_packets); + MWritePackets.unlock(); +} + +/** + * Write EQ2 packet to debug file with automatic version handling + * Convenience wrapper for WritePackets that handles version setup + * + * @param app - EQ2Packet to write to debug file + * @param outgoing - True if outgoing packet, false if incoming + */ +void EQStream::WritePackets(EQ2Packet* app, bool outgoing) +{ + if (app->version == 0) + { + app->version = client_version; + } + WritePackets(app->GetOpcodeName(), app->pBuffer, app->size, outgoing); +} +#endif + +/** + * Prepare EQ2 packet for transmission + * Handles packet preparation, compression, and encryption in sequence + * + * @param app - EQ2Packet to prepare (modified in-place) + * @param offset - Additional offset for encryption + */ +void EQStream::PreparePacket(EQ2Packet* app, int8 offset) +{ + // Set client version and reset compression offset + app->setVersion(client_version); + compressed_offset = 0; + +#ifdef LE_DEBUG + std::printf("Before A in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + + // Prepare packet headers and structure if not already done + if (!app->packet_prepared) + { + if (app->PreparePacket(MaxLen) == 255) // Invalid version + { + return; + } + } + +#ifdef LE_DEBUG + std::printf("After Prepare in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif + +#ifdef WRITE_PACKETS + // Write uncompressed/unencrypted packet to file if enabled + if (!app->eq2_compressed && !app->packet_encrypted) + { + WritePackets(app, true); + } +#endif + + // Compress packet if it's large enough and not already compressed + if (!app->eq2_compressed && app->size > 128) + { + compressed_offset = EQ2_Compress(app); + if (compressed_offset) + { + app->eq2_compressed = true; + } + } + + // Encrypt packet if not already encrypted + if (!app->packet_encrypted) + { + EncryptPacket(app, compressed_offset, offset); + + // Handle special case for zero byte at offset 2 + if (app->size > 2 && app->pBuffer[2] == 0) + { + auto new_buffer = std::make_unique(app->size + 1); + new_buffer[2] = 0; + std::memcpy(new_buffer.get() + 3, app->pBuffer + 2, app->size - 2); + delete[] app->pBuffer; + app->pBuffer = new_buffer.release(); + app->size++; + } + } + +#ifdef LE_DEBUG + std::printf("After A in %s, line %i:\n", __FUNCTION__, __LINE__); + DumpPacket(app); +#endif +} + +/** + * Send EQProtocolPacket with automatic fragmentation for large packets + * Fragments packets that exceed maximum transmission unit into smaller chunks + * + * @param p - EQProtocolPacket to send (ownership transferred, will be deleted) + */ +void EQStream::SendPacket(EQProtocolPacket *p) +{ + uint32 chunksize, used; + uint32 length; + + // Check if packet needs fragmentation + if (p->size > (MaxLen - 8)) // proto-op(2), seq(2), app-op(2) ... data ... crc(2) + { + uchar* tmpbuff = p->pBuffer; + length = p->size - 2; + + // Create first fragment with length header + auto out = new EQProtocolPacket(OP_Fragment, nullptr, MaxLen - 4); + *reinterpret_cast(out->pBuffer + 2) = htonl(length); + used = MaxLen - 10; + std::memcpy(out->pBuffer + 6, tmpbuff + 2, used); + +#ifdef LE_DEBUG + std::printf("(%s, %i) New Fragment:\n ", __FUNCTION__, __LINE__); + DumpPacket(out); +#endif + + SequencedPush(out); + + // Create additional fragments for remaining data + while (used < length) + { + chunksize = std::min(length - used, MaxLen - 6); + out = new EQProtocolPacket(OP_Fragment, nullptr, chunksize + 2); + std::memcpy(out->pBuffer + 2, tmpbuff + used + 2, chunksize); + +#ifdef LE_DEBUG + std::printf("Chunk: \n"); + DumpPacket(out); +#endif + SequencedPush(out); + used += chunksize; + } + +#ifdef LE_DEBUG + std::printf("ChunkDelete: \n"); + DumpPacket(out); +#endif + + delete p; + } + else + { + // Packet fits within MTU, send directly + SequencedPush(p); + } +} +/** + * Send EQApplicationPacket with automatic fragmentation for large packets + * Serializes application packet and fragments if necessary for transmission + * + * @param p - EQApplicationPacket to send (ownership transferred, will be deleted) + */ +void EQStream::SendPacket(EQApplicationPacket *p) +{ + uint32 chunksize, used; + uint32 length; + + // Check if packet needs fragmentation + if (p->size > (MaxLen - 8)) // proto-op(2), seq(2), app-op(2) ... data ... crc(2) + { + // Serialize application packet to buffer + auto tmpbuff = std::make_unique(p->size + 2); + length = p->serialize(tmpbuff.get()); + + // Create first fragment with size header + auto out = new EQProtocolPacket(OP_Fragment, nullptr, MaxLen - 4); + *reinterpret_cast(out->pBuffer + 2) = htonl(p->Size()); + std::memcpy(out->pBuffer + 6, tmpbuff.get(), MaxLen - 10); + used = MaxLen - 10; + SequencedPush(out); + + // Create additional fragments for remaining data + while (used < length) + { + out = new EQProtocolPacket(OP_Fragment, nullptr, MaxLen - 4); + chunksize = std::min(length - used, MaxLen - 6); + std::memcpy(out->pBuffer + 2, tmpbuff.get() + used, chunksize); + out->size = chunksize + 2; + SequencedPush(out); + used += chunksize; + } + + delete p; + } + else + { + // Packet fits within MTU, serialize directly to protocol packet + auto out = new EQProtocolPacket(OP_Packet, nullptr, p->Size() + 2); + p->serialize(out->pBuffer + 2); + SequencedPush(out); + delete p; + } +} + +/** + * Add packet to sequenced transmission queue + * Assigns sequence number and queues packet for reliable delivery + * + * @param p - EQProtocolPacket to queue (ownership transferred) + */ +void EQStream::SequencedPush(EQProtocolPacket *p) +{ + // Set client version for compatibility + p->setVersion(client_version); + + MOutboundQueue.lock(); + + // Assign sequence number to packet header + *reinterpret_cast(p->pBuffer) = htons(NextOutSeq); + SequencedQueue.push_back(p); + p->sequence = NextOutSeq; + NextOutSeq++; + + MOutboundQueue.unlock(); +} + +/** + * Add packet to non-sequenced transmission queue + * Queues packet for immediate transmission without sequence numbering + * + * @param p - EQProtocolPacket to queue (ownership transferred) + */ +void EQStream::NonSequencedPush(EQProtocolPacket *p) +{ + // Set client version for compatibility + p->setVersion(client_version); + + MOutboundQueue.lock(); + NonSequencedQueue.push(p); + MOutboundQueue.unlock(); +} + +/** + * Send acknowledgment packet for received sequence number + * Confirms successful receipt of sequenced packet to peer + * + * @param seq - Sequence number to acknowledge + */ +void EQStream::SendAck(uint16 seq) +{ + uint16 Seq = htons(seq); + SetLastAckSent(seq); + NonSequencedPush(new EQProtocolPacket(OP_Ack, reinterpret_cast(&Seq), sizeof(uint16))); +} + +/** + * Send out-of-order acknowledgment packet + * Notifies peer that a specific packet was received out of sequence + * + * @param seq - Sequence number that was received out of order + */ +void EQStream::SendOutOfOrderAck(uint16 seq) +{ + uint16 Seq = htons(seq); + NonSequencedPush(new EQProtocolPacket(OP_OutOfOrderAck, reinterpret_cast(&Seq), sizeof(uint16))); +} + +/** + * Process packet combination queue to optimize transmission + * Combines multiple small packets into larger ones for efficiency + * + * @return true if all packets processed, false if processing was limited + */ +bool EQStream::CheckCombineQueue() +{ + bool ret = true; // Processed all packets flag + + MCombineQueueLock.lock(); + + if (combine_queue.size() > 0) + { + // Get first packet from queue + EQ2Packet* first = combine_queue.front(); + combine_queue.pop_front(); + + if (combine_queue.size() == 0) + { + // Nothing to combine with, send immediately + EQ2QueuePacket(first, true); + } + else + { + // Prepare first packet for combining + PreparePacket(first); + EQ2Packet* second = nullptr; + bool combine_worked = false; + int16 count = 0; + + // Attempt to combine with additional packets + while (combine_queue.size()) + { + count++; + second = combine_queue.front(); + combine_queue.pop_front(); + PreparePacket(second); + + // Try to combine second packet with first + if (!first->AppCombine(second)) + { + // Combination failed, send first packet + first->SetProtocolOpcode(OP_Packet); + if (combine_worked) + { + SequencedPush(first); + } + else + { + EQ2QueuePacket(first, true); + } + + // Make second packet the new first + first = second; + combine_worked = false; + } + else + { + // Combination succeeded + combine_worked = true; + } + + // Limit processing to prevent blocking other clients + if (count >= 60 || first->size > 4000) + { + ret = false; + break; + } + } + + // Send final packet + if (first) + { + first->SetProtocolOpcode(OP_Packet); + if (combine_worked) + { + SequencedPush(first); + } + else + { + EQ2QueuePacket(first, true); + } + } + } + } + + MCombineQueueLock.unlock(); + return ret; +} + +/** + * Check and handle packet retransmission for unacknowledged packets + * Retries failed packet deliveries up to maximum attempt limit + * + * @param eq_fd - Socket file descriptor for transmission + */ +void EQStream::CheckResend(int eq_fd) +{ + int32 curr = Timer::GetCurrentTime2(); + EQProtocolPacket* packet = nullptr; + + MResendQue.lock(); + + for (auto itr = resend_que.begin(); itr != resend_que.end();) + { + packet = *itr; + + // Check if packet has exceeded maximum retry attempts + if (packet->attempt_count >= 5) + { + // Give up on this packet - client likely has it but didn't ack + safe_delete(packet); + itr = resend_que.erase(itr); + if (itr == resend_que.end()) + { + break; + } + } + else + { + // Check if enough time has passed for retry + if ((curr - packet->sent_time) < 1000) + { + ++itr; + continue; + } + + // Retry packet transmission + packet->sent_time -= 1000; + packet->attempt_count++; + WritePacket(eq_fd, packet); + ++itr; + } + } + + MResendQue.unlock(); +} + + + +/** + * Compare two sequence numbers considering wraparound behavior + * Determines temporal relationship between sequence numbers within sliding window + * + * @param expected_seq - Expected sequence number + * @param seq - Actual sequence number to compare + * @return SeqOrder indicating temporal relationship (past, in-order, future) + */ +EQStream::SeqOrder EQStream::CompareSequence(uint16 expected_seq, uint16 seq) +{ + if (expected_seq == seq) + { + // Sequence numbers match exactly + return SeqInOrder; + } + else if ((seq > expected_seq && static_cast(seq) < (static_cast(expected_seq) + EQStream::MaxWindowSize)) || + seq < (expected_seq - EQStream::MaxWindowSize)) + { + // Sequence is in future window or wrapped around + return SeqFuture; + } + else + { + // Sequence is in past window + return SeqPast; + } +} + +/** + * Process acknowledgment packet and remove acknowledged packets from queue + * Handles sliding window advancement and packet cleanup for reliable delivery + * + * @param seq - Highest sequence number being acknowledged + */ +void EQStream::AckPackets(uint16 seq) +{ + MOutboundQueue.lock(); + + SeqOrder ord = CompareSequence(SequencedBase, seq); + + if (ord == SeqInOrder) + { + // No new acknowledgments - duplicate ack + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with no window advancement (seq %u)", seq); + } + else if (ord == SeqPast) + { + // Backward acknowledgment - protocol error + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack with backward window advancement (they gave %u, our window starts at %u). This is bad", seq, SequencedBase); + } + else + { + // Valid acknowledgment - advance window + LogWrite(PACKET__DEBUG, 9, "Packet", "Received an ack up through sequence %u. Our base is %u", seq, SequencedBase); + + // Process all packets up to acknowledgment point + seq++; // Stop at block right after their ack + + while (SequencedBase != seq) + { + if (SequencedQueue.empty()) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "OUT OF PACKETS acked packet with sequence %u. Next send is %u before this", + static_cast(SequencedBase), SequencedQueue.size()); + SequencedBase = NextOutSeq; + break; + } + + LogWrite(PACKET__DEBUG, 9, "Packet", "Removing acked packet with sequence %u", + static_cast(SequencedBase)); + + // Remove acknowledged packet from queue + delete SequencedQueue.front(); + SequencedQueue.pop_front(); + + // Advance base sequence number + SequencedBase++; + } + + // Validate queue consistency + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) + { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post-Ack on %u Invalid Sequenced queue: BS %u + SQ %u != NOS %u", + seq, SequencedBase, SequencedQueue.size(), NextOutSeq); + } + } + + MOutboundQueue.unlock(); +} + +/** + * Main packet transmission loop - processes outbound queues and sends packets + * Handles rate limiting, packet combining, acknowledgments, and transmission scheduling + * + * @param eq_fd - Socket file descriptor for transmission + */ +void EQStream::Write(int eq_fd) +{ + queue ReadyToSend; + long maxack; + + // Check our rate to make sure we can send more + MRate.lock(); + sint32 threshold=RateThreshold; + MRate.unlock(); + if (BytesWritten > threshold) { + //cout << "Over threshold: " << BytesWritten << " > " << threshold << endl; + return; + } + + MCombinedAppPacket.lock(); + EQApplicationPacket *CombPack=CombinedAppPacket; + CombinedAppPacket=NULL; + MCombinedAppPacket.unlock(); + + if (CombPack) { + SendPacket(CombPack); + } + + // If we got more packets to we need to ack, send an ack on the highest one + MAcks.lock(); + maxack=MaxAckReceived; + // Added from peaks findings + if (NextAckToSend>LastAckSent || LastAckSent == 0x0000ffff) + SendAck(NextAckToSend); + MAcks.unlock(); + + // Lock the outbound queues while we process + MOutboundQueue.lock(); + + // Adjust where we start sending in case we get a late ack + //if (maxack>LastSeqSent) + // LastSeqSent=maxack; + + // Place to hold the base packet t combine into + EQProtocolPacket *p=NULL; + std::deque::iterator sitr; + + // Find the next sequenced packet to send from the "queue" + sitr = SequencedQueue.begin(); + + uint16 count = 0; + // get to start of packets + while (sitr != SequencedQueue.end() && (*sitr)->sent_time > 0) { + ++sitr; + ++count; + } + + bool SeqEmpty = false, NonSeqEmpty = false; + // Loop until both are empty or MaxSends is reached + while (!SeqEmpty || !NonSeqEmpty) { + + // See if there are more non-sequenced packets left + if (!NonSequencedQueue.empty()) { + if (!p) { + // If we don't have a packet to try to combine into, use this one as the base + // And remove it form the queue + p = NonSequencedQueue.front(); + LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with non-seq packet of len %u",p->size); + NonSequencedQueue.pop(); + } + else if (!p->combine(NonSequencedQueue.front())) { + // Trying to combine this packet with the base didn't work (too big maybe) + // So just send the base packet (we'll try this packet again later) + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next non-seq packet is len %u", p->size, (NonSequencedQueue.front())->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + + if (BytesWritten > threshold) { + // Sent enough this round, lets stop to be fair + LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in nonseq (%u > %u)", BytesWritten, threshold); + break; + } + } + else { + // Combine worked, so just remove this packet and it's spot in the queue + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined non-seq packet of len %u, yeilding %u combined", (NonSequencedQueue.front())->size, p->size); + delete NonSequencedQueue.front(); + NonSequencedQueue.pop(); + } + } + else { + // No more non-sequenced packets + NonSeqEmpty = true; + } + + if (sitr != SequencedQueue.end()) { + uint16 seq_send = SequencedBase + count; //just for logging... + + if (SequencedQueue.empty()) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Tried to write a packet with an empty queue (%u is past next out %u)", seq_send, NextOutSeq); + SeqEmpty = true; + continue; + } + + if ((*sitr)->acked || (*sitr)->sent_time != 0) { + ++sitr; + ++count; + if (p) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + } + LogWrite(PACKET__DEBUG, 9, "Packet", "Not retransmitting seq packet %u because already marked as acked", seq_send); + } + else if (!p) { + // If we don't have a packet to try to combine into, use this one as the base + // Copy it first as it will still live until it is acked + p = (*sitr)->Copy(); + LogWrite(PACKET__DEBUG, 9, "Packet", "Starting combined packet with seq packet %u of len %u", seq_send, p->size); + (*sitr)->sent_time = Timer::GetCurrentTime2(); + ++sitr; + ++count; + } + else if (!p->combine(*sitr)) { + // Trying to combine this packet with the base didn't work (too big maybe) + // So just send the base packet (we'll try this packet again later) + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined packet full at len %u, next seq packet %u is len %u", p->size, seq_send + 1, (*sitr)->size); + ReadyToSend.push(p); + BytesWritten += p->size; + p = nullptr; + if ((*sitr)->opcode != OP_Fragment && BytesWritten > threshold) { + // Sent enough this round, lets stop to be fair + LogWrite(PACKET__DEBUG, 9, "Packet", "Exceeded write threshold in seq (%u > %u)", BytesWritten, threshold); + break; + } + } + else { + // Combine worked + LogWrite(PACKET__DEBUG, 9, "Packet", "Combined seq packet %u of len %u, yeilding %u combined", seq_send, (*sitr)->size, p->size); + (*sitr)->sent_time = Timer::GetCurrentTime2(); + ++sitr; + ++count; + } + + if (uint16(SequencedBase + SequencedQueue.size()) != NextOutSeq) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Post send Invalid Sequenced queue: BS %u + SQ %u != NOS %u", SequencedBase, SequencedQueue.size(), NextOutSeq); + } + } + else { + // No more sequenced packets + SeqEmpty = true; + } + } + MOutboundQueue.unlock(); // Unlock the queue + + // We have a packet still, must have run out of both seq and non-seq, so send it + if (p) { + LogWrite(PACKET__DEBUG, 9, "Packet", "Final combined packet not full, len %u", p->size); + ReadyToSend.push(p); + BytesWritten += p->size; + } + + // Send all the packets we "made" + while (!ReadyToSend.empty()) { + p = ReadyToSend.front(); + WritePacket(eq_fd, p); + delete p; + ReadyToSend.pop(); + } + + //see if we need to send our disconnect and finish our close + if (SeqEmpty && NonSeqEmpty) { + //no more data to send + if (GetState() == CLOSING) { + MOutboundQueue.lock(); + if (SequencedQueue.size() > 0 ) { + // retransmission attempts + } + else + { + LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client."); + //we are waiting for the queues to empty, now we can do our disconnect. + //this packet will not actually go out until the next call to Write(). + SendDisconnect(); + //SetState(CLOSED); + } + MOutboundQueue.unlock(); + } + } +} + +/** + * Write individual protocol packet to network socket + * Handles packet serialization, compression, encoding, and UDP transmission + * + * @param eq_fd - Socket file descriptor for transmission + * @param p - EQProtocolPacket to transmit + */ +void EQStream::WritePacket(int eq_fd, EQProtocolPacket *p) +{ + uint32 length = 0; + sockaddr_in address; + + // Set up destination address + address.sin_family = AF_INET; + address.sin_addr.s_addr = remote_ip; + address.sin_port = remote_port; +#ifdef NOWAY + uint32 ip=address.sin_addr.s_addr; + cout << "Sending to: " + << (int)*(unsigned char *)&ip + << "." << (int)*((unsigned char *)&ip+1) + << "." << (int)*((unsigned char *)&ip+2) + << "." << (int)*((unsigned char *)&ip+3) + << "," << (int)ntohs(address.sin_port) << "(" << p->size << ")" << endl; + + p->DumpRaw(); + cout << "-------------" << endl; +#endif + length=p->serialize(buffer); + if (p->opcode!=OP_SessionRequest && p->opcode!=OP_SessionResponse) { + if (compressed) { + BytesWritten -= p->size; + uint32 newlen=EQProtocolPacket::Compress(buffer,length,write_buffer,2048); + memcpy(buffer,write_buffer,newlen); + length=newlen; + BytesWritten += newlen; + } + if (encoded) { + EQProtocolPacket::ChatEncode(buffer,length,Key); + } + *(uint16 *)(buffer+length)=htons(CRC16(buffer,length,Key)); + length+=2; + } + sent_packets++; + //dump_message_column(buffer,length,"Writer: "); + //cout << "Raw Data:\n"; + //DumpPacket(buffer, length); + sendto(eq_fd,(char *)buffer,length,0,(sockaddr *)&address,sizeof(address)); +} + +EQProtocolPacket *EQStream::Read(int eq_fd, sockaddr_in *from) +{ +int socklen; +int length=0; +unsigned char buffer[2048]; +EQProtocolPacket *p=NULL; +char temp[15]; + + socklen=sizeof(sockaddr); +#ifdef WIN32 + length=recvfrom(eq_fd, (char *)buffer, 2048, 0, (struct sockaddr*)from, (int *)&socklen); +#else + length=recvfrom(eq_fd, buffer, 2048, 0, (struct sockaddr*)from, (socklen_t *)&socklen); +#endif + if (length>=2) { + DumpPacket(buffer, length); + p=new EQProtocolPacket(buffer[1],&buffer[2],length-2); + //printf("Read packet: opcode %i length %u, expected-length: %u\n",buffer[1], length, p->size); + uint32 ip=from->sin_addr.s_addr; + sprintf(temp,"%d.%d.%d.%d:%d", + *(unsigned char *)&ip, + *((unsigned char *)&ip+1), + *((unsigned char *)&ip+2), + *((unsigned char *)&ip+3), + ntohs(from->sin_port)); + //cout << timestamp() << "Data from: " << temp << " OpCode 0x" << hex << setw(2) << setfill('0') << (int)p->opcode << dec << endl; + //dump_message(p->pBuffer,p->size,timestamp()); + + } + return p; +} + +/** + * Send session establishment response to client + * Provides session parameters including encryption keys and stream format flags + */ +void EQStream::SendSessionResponse() +{ + auto out = new EQProtocolPacket(OP_SessionResponse, nullptr, sizeof(SessionResponse)); + auto Response = reinterpret_cast(out->pBuffer); + + // Set session parameters + Response->Session = htonl(Session); + Response->MaxLength = htonl(MaxLen); + Response->UnknownA = 2; + Response->Format = 0; + + // Set format flags based on stream configuration + if (compressed) + { + Response->Format |= FLAG_COMPRESSED; + } + if (encoded) + { + Response->Format |= FLAG_ENCODED; + } + + Response->Key = htonl(Key); + out->size = sizeof(SessionResponse); + + NonSequencedPush(out); +} + +/** + * Send session establishment request to server + * Initiates connection handshake with session parameters + */ +void EQStream::SendSessionRequest() +{ + auto out = new EQProtocolPacket(OP_SessionRequest, nullptr, sizeof(SessionRequest)); + auto Request = reinterpret_cast(out->pBuffer); + + // Initialize request structure + std::memset(Request, 0, sizeof(SessionRequest)); + Request->Session = htonl(std::time(nullptr)); + Request->MaxLength = htonl(512); + + NonSequencedPush(out); +} + +/** + * Send session disconnect packet to peer + * Initiates graceful connection termination sequence + * + * @param setstate - If true, set stream state to CLOSING + */ +void EQStream::SendDisconnect(bool setstate) +{ + try + { + // Only send disconnect if in valid state + if (GetState() != ESTABLISHED && GetState() != WAIT_CLOSE) + { + return; + } + + // Create disconnect packet + auto out = new EQProtocolPacket(OP_SessionDisconnect, nullptr, sizeof(uint32) + sizeof(int16)); + *reinterpret_cast(out->pBuffer) = htonl(Session); + out->pBuffer[4] = 0; + out->pBuffer[5] = 6; + + NonSequencedPush(out); + + // Update state if requested + if (setstate) + { + SetState(CLOSING); + } + } + catch (...) + { + // Ignore exceptions during disconnect + } +} + +/** + * Add application packet to inbound queue for processing + * Thread-safe method to queue received packets for application layer + * + * @param p - EQApplicationPacket to add to inbound queue + */ +void EQStream::InboundQueuePush(EQApplicationPacket *p) +{ + MInboundQueue.lock(); + InboundQueue.push_back(p); + MInboundQueue.unlock(); +} + +/** + * Retrieve next application packet from inbound queue + * Thread-safe method to get received packets for application processing + * + * @return Next EQApplicationPacket from queue, or nullptr if queue is empty + */ +EQApplicationPacket *EQStream::PopPacket() +{ + EQApplicationPacket *p = nullptr; + + MInboundQueue.lock(); + if (InboundQueue.size()) + { + p = InboundQueue.front(); + InboundQueue.pop_front(); + } + MInboundQueue.unlock(); + + // Set client version for compatibility + if (p) + { + p->setVersion(client_version); + } + return p; +} + +/** + * Clear all packets from inbound queue + * Thread-safe method to empty and delete all queued packets + */ +void EQStream::InboundQueueClear() +{ + MInboundQueue.lock(); + while (InboundQueue.size()) + { + delete InboundQueue.front(); + InboundQueue.pop_front(); + } + MInboundQueue.unlock(); +} +/** + * Legacy packet encryption function (currently unused) + * Placeholder for packet-level encryption functionality + * + * @param data - Packet data buffer + * @param size - Size of packet data + */ +void EQStream::EncryptPacket(uchar* data, int16 size) +{ + if (size > 6) + { + // Implementation placeholder + } +} +/** + * Check if stream has pending outbound data + * Determines if there are packets waiting to be transmitted + * + * @return true if outbound data is available, false otherwise + */ +bool EQStream::HasOutgoingData() +{ + bool flag; + + // Once closed, we have nothing more to say + if (CheckClosed()) + { + return false; + } + + // Check for queued packets + MOutboundQueue.lock(); + flag = (!NonSequencedQueue.empty()); + if (!flag) + { + flag = (!SequencedQueue.empty()); + } + MOutboundQueue.unlock(); + + // Check for pending acknowledgments + if (!flag) + { + MAcks.lock(); + flag = (NextAckToSend > LastAckSent); + MAcks.unlock(); + } + + // Check for combined application packets + if (!flag) + { + MCombinedAppPacket.lock(); + flag = (CombinedAppPacket != nullptr); + MCombinedAppPacket.unlock(); + } + + return flag; +} + +/** + * Clear all packets from outbound queues + * Thread-safe method to empty and delete all queued outbound packets + */ +void EQStream::OutboundQueueClear() +{ + MOutboundQueue.lock(); + + // Clear non-sequenced queue + while (NonSequencedQueue.size()) + { + delete NonSequencedQueue.front(); + NonSequencedQueue.pop(); + } + + // Clear sequenced queue + while (SequencedQueue.size()) + { + delete SequencedQueue.front(); + SequencedQueue.pop_front(); + } + + MOutboundQueue.unlock(); +} + +/** + * Process incoming raw network data into protocol packets + * Handles decompression, decoding, validation, and packet creation + * + * @param buffer - Raw network data buffer + * @param length - Length of network data + */ +void EQStream::Process(const unsigned char *buffer, const uint32 length) +{ + received_packets++; + static unsigned char newbuffer[2048]; + uint32 newlength = 0; + +#ifdef LE_DEBUG +printf("ProcessBuffer:\n"); +DumpPacket(buffer, length); +#endif + + if (EQProtocolPacket::ValidateCRC(buffer,length,Key)) { + if (compressed) { + newlength=EQProtocolPacket::Decompress(buffer,length,newbuffer,2048); +#ifdef LE_DEBUG + printf("ProcessBufferDecompress:\n"); + DumpPacket(buffer, newlength); +#endif + } else { + memcpy(newbuffer,buffer,length); + newlength=length; + if (encoded) + EQProtocolPacket::ChatDecode(newbuffer,newlength-2,Key); + } + +#ifdef LE_DEBUG + printf("ResultProcessBuffer:\n"); + DumpPacket(buffer, newlength); +#endif + uint16 opcode=ntohs(*(const uint16 *)newbuffer); + //printf("Read packet: opcode %i newlength %u, newbuffer2len: %u, newbuffer3len: %u\n",opcode, newlength, newbuffer[2], newbuffer[3]); + if(opcode > 0 && opcode <= OP_OutOfSession) + { + if (buffer[1]!=0x01 && buffer[1]!=0x02 && buffer[1]!=0x1d) + newlength-=2; + + EQProtocolPacket p(newbuffer,newlength); + ProcessPacket(&p); + } + else + { + cout << "2Orig Packet: " << opcode << endl; + DumpPacket(newbuffer, newlength); + ProcessEmbeddedPacket(newbuffer, newlength, OP_Fragment); + } + ProcessQueue(); + } else { + cout << "Incoming packet failed checksum:" <(buffer),length,"CRC failed: "); + } +} + +/** + * Get the highest sequence number that has been acknowledged + * Thread-safe accessor for maximum acknowledged sequence number + * + * @return Highest acknowledged sequence number + */ +long EQStream::GetMaxAckReceived() +{ + MAcks.lock(); + long l = MaxAckReceived; + MAcks.unlock(); + + return l; +} + +/** + * Get the next sequence number that should be acknowledged + * Thread-safe accessor for next acknowledgment to send + * + * @return Next sequence number to acknowledge + */ +long EQStream::GetNextAckToSend() +{ + MAcks.lock(); + long l = NextAckToSend; + MAcks.unlock(); + + return l; +} + +/** + * Get the last sequence number that was acknowledged + * Thread-safe accessor for last sent acknowledgment + * + * @return Last acknowledged sequence number + */ +long EQStream::GetLastAckSent() +{ + MAcks.lock(); + long l = LastAckSent; + MAcks.unlock(); + + return l; +} + +/** + * Set the maximum acknowledged sequence number + * Updates acknowledgment tracking and cleans up acknowledged packets from resend queue + * + * @param seq - New maximum acknowledged sequence number + */ +void EQStream::SetMaxAckReceived(uint32 seq) +{ + MAcks.lock(); + MaxAckReceived = seq; + MAcks.unlock(); + + MOutboundQueue.lock(); + if (static_cast(seq) > LastSeqSent) + { + LastSeqSent = seq; + } + + // Clean up acknowledged packets from resend queue + MResendQue.lock(); + for (auto itr = resend_que.begin(); itr != resend_que.end();) + { + EQProtocolPacket* packet = *itr; + if (packet && packet->sequence <= seq) + { + safe_delete(packet); + itr = resend_que.erase(itr); + if (itr == resend_que.end()) + { + break; + } + } + else + { + ++itr; + } + } + MResendQue.unlock(); + MOutboundQueue.unlock(); +} + +/** + * Set the next sequence number to acknowledge + * Thread-safe setter for next acknowledgment to send + * + * @param seq - Next sequence number to acknowledge + */ +void EQStream::SetNextAckToSend(uint32 seq) +{ + MAcks.lock(); + NextAckToSend = seq; + MAcks.unlock(); +} + +/** + * Set the last sequence number that was acknowledged + * Thread-safe setter for last sent acknowledgment + * + * @param seq - Last acknowledged sequence number + */ +void EQStream::SetLastAckSent(uint32 seq) +{ + MAcks.lock(); + LastAckSent = seq; + MAcks.unlock(); +} + +/** + * Set the last sequence number that was sent + * Thread-safe setter for last transmitted sequence number + * + * @param seq - Last sent sequence number + */ +void EQStream::SetLastSeqSent(uint32 seq) +{ + MOutboundQueue.lock(); + LastSeqSent = seq; + MOutboundQueue.unlock(); +} + +/** + * Configure stream parameters based on stream type + * Sets opcode size, compression, and encoding flags for different stream types + * + * @param type - EQStreamType to configure stream for + */ +void EQStream::SetStreamType(EQStreamType type) +{ + StreamType = type; + + switch (StreamType) + { + case LoginStream: + app_opcode_size = 1; + compressed = false; + encoded = false; + break; + + case EQ2Stream: + app_opcode_size = 2; + compressed = false; + encoded = false; + break; + + case ChatOrMailStream: + case ChatStream: + case MailStream: + app_opcode_size = 1; + compressed = false; + encoded = true; + break; + + case ZoneStream: + case WorldStream: + default: + app_opcode_size = 2; + compressed = true; + encoded = false; + break; + } +} + +/** + * Process queued out-of-order packets in sequence + * Handles packets that arrived before their predecessors + */ +void EQStream::ProcessQueue() +{ + if (OutOfOrderpackets.empty()) + { + return; + } + + // Process packets in sequence order + EQProtocolPacket* qp = nullptr; + while ((qp = RemoveQueue(NextInSeq)) != nullptr) + { + ProcessPacket(qp); + delete qp; + } +} + +/** + * Remove and return packet with specific sequence number from queue + * Searches out-of-order packet queue for specified sequence + * + * @param seq - Sequence number of packet to remove + * @return EQProtocolPacket if found, nullptr if not found + */ +EQProtocolPacket* EQStream::RemoveQueue(uint16 seq) +{ + EQProtocolPacket* qp = nullptr; + auto itr = OutOfOrderpackets.find(seq); + if (itr != OutOfOrderpackets.end()) + { + qp = itr->second; + OutOfOrderpackets.erase(itr); + } + return qp; +} + +/** + * Apply bandwidth decay and check for packet timeouts + * Reduces byte counter over time and flags timed-out packets for retransmission + */ +void EQStream::Decay() +{ + // Apply bandwidth decay + MRate.lock(); + uint32 rate = DecayRate; + MRate.unlock(); + + if (BytesWritten > 0) + { + BytesWritten -= rate; + if (BytesWritten < 0) + { + BytesWritten = 0; + } + } + + // Check for packet timeouts and flag for retransmission + int count = 0; + MOutboundQueue.lock(); + + for (auto sitr = SequencedQueue.begin(); sitr != SequencedQueue.end(); ++sitr, count++) + { + if (!(*sitr)->acked && (*sitr)->sent_time > 0 && + ((*sitr)->sent_time + retransmittimeout) < Timer::GetCurrentTime2()) + { + (*sitr)->sent_time = 0; // Flag for retransmission + LogWrite(PACKET__DEBUG, 9, "Packet", "Timeout exceeded for seq %u. Flagging packet for retransmission", + SequencedBase + count); + } + } + + MOutboundQueue.unlock(); +} + +/** + * Adjust transmission rates based on network conditions + * Calculates rate thresholds and decay rates from average delta timing + * + * @param average_delta - Average packet transmission time in milliseconds + */ +void EQStream::AdjustRates(uint32 average_delta) +{ + if (average_delta && (average_delta <= AVERAGE_DELTA_MAX)) + { + MRate.lock(); + + // Calculate transmission parameters based on network timing + AverageDelta = average_delta; + RateThreshold = RATEBASE / average_delta; + DecayRate = DECAYBASE / average_delta; + + // Adjust current byte counter if needed + if (BytesWritten > RateThreshold) + { + BytesWritten = RateThreshold + DecayRate; + } + + MRate.unlock(); + } + else + { + // Use maximum delta if invalid value provided + AverageDelta = AVERAGE_DELTA_MAX; + } +} diff --git a/old/common/EQStream.h b/old/common/EQStream.h new file mode 100644 index 0000000..2abfc31 --- /dev/null +++ b/old/common/EQStream.h @@ -0,0 +1,523 @@ +// EQ2Emulator: Everquest II Server Emulator +// Copyright (C) 2007 EQ2EMulator Development Team +// Licensed under GPL v3 - see +#ifndef _EQPROTOCOL_H +#define _EQPROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// Unix networking headers +#include + +// Project headers +#include "EQPacket.h" +#include "Mutex.h" +#include "opcodemgr.h" +#include "misc.h" +#include "Condition.h" +#include "Crypto.h" +#include "zlib.h" +#include "timer.h" + +#ifdef WRITE_PACKETS +#include +#endif + +using namespace std; + +// Forward declarations +class OpcodeManager; +class EQStreamFactory; + +/** + * EverQuest stream connection states. + * Represents the current state of a network stream connection. + */ +typedef enum { + ESTABLISHED, // Active connection ready for data + WAIT_CLOSE, // Waiting for graceful close + CLOSING, // In process of closing + DISCONNECTING, // Actively disconnecting + CLOSED // Connection fully closed +} EQStreamState; + +// Packet flags +#define FLAG_COMPRESSED 0x01 // Packet is compressed +#define FLAG_ENCODED 0x04 // Packet is encoded/encrypted + +// Rate limiting and bandwidth constants +#define RATEBASE 1048576 // Base rate: 1 MB +#define DECAYBASE 78642 // Decay rate: RATEBASE/10 + +// Retransmission timing constants +#ifndef RETRANSMIT_TIMEOUT_MULT +#define RETRANSMIT_TIMEOUT_MULT 3.0 // Timeout multiplier +#endif + +#ifndef RETRANSMIT_TIMEOUT_MAX +#define RETRANSMIT_TIMEOUT_MAX 5000 // Maximum retransmit timeout (ms) +#endif + +#ifndef AVERAGE_DELTA_MAX +#define AVERAGE_DELTA_MAX 2500 // Maximum average delta (ms) +#endif + +#pragma pack(1) + +/** + * Session request packet structure. + * Sent by client to initiate a new session. + */ +struct SessionRequest { + uint32 UnknownA; // Unknown field A + uint32 Session; // Requested session ID + uint32 MaxLength; // Maximum packet length +}; + +/** + * Session response packet structure. + * Sent by server in response to session request. + */ +struct SessionResponse { + uint32 Session; // Assigned session ID + uint32 Key; // Encryption/authentication key + uint8 UnknownA; // Unknown field A + uint8 Format; // Packet format version + uint8 UnknownB; // Unknown field B + uint32 MaxLength; // Maximum packet length + uint32 UnknownD; // Unknown field D +}; + +/** + * Client-side session statistics. + * Deltas are in milliseconds, representing round trip times. + */ +struct ClientSessionStats { +/*000*/ uint16 RequestID; // Statistics request ID +/*002*/ uint32 last_local_delta; // Last local round trip time +/*006*/ uint32 average_delta; // Average round trip time +/*010*/ uint32 low_delta; // Lowest recorded round trip time +/*014*/ uint32 high_delta; // Highest recorded round trip time +/*018*/ uint32 last_remote_delta; // Last remote round trip time +/*022*/ uint64 packets_sent; // Total packets sent +/*030*/ uint64 packets_recieved; // Total packets received +/*038*/ +}; + +/** + * Server-side session statistics. + * Provides server perspective on connection quality. + */ +struct ServerSessionStats { + uint16 RequestID; // Statistics request ID + uint32 current_time; // Current server time + uint32 unknown1; // Unknown field 1 + uint32 received_packets; // Packets received by server + uint32 unknown2; // Unknown field 2 + uint32 sent_packets; // Packets sent by server + uint32 unknown3; // Unknown field 3 + uint32 sent_packets2; // Duplicate sent packets count + uint32 unknown4; // Unknown field 4 + uint32 received_packets2; // Duplicate received packets count +}; + +#pragma pack() + +// External opcode manager +extern OpcodeManager* EQNetworkOpcodeManager; + +/** + * Types of EverQuest network streams. + * Each type handles different aspects of the game protocol. + */ +typedef enum { + UnknownStream = 0, // Unidentified stream type + LoginStream, // Login server authentication + WorldStream, // World server communication + ZoneStream, // Zone server gameplay + ChatOrMailStream, // Combined chat/mail (legacy) + ChatStream, // Chat system only + MailStream, // Mail system only + EQ2Stream, // EverQuest 2 specific stream +} EQStreamType; + +/** + * EverQuest network stream class. + * Handles reliable UDP communication with sequence numbers, acknowledgments, + * compression, encryption, and packet combining for EverQuest protocols. + */ +class EQStream { +protected: + /** + * Sequence number ordering enumeration. + * Used to determine packet sequence relationships. + */ + typedef enum { + SeqPast, // Sequence is from the past (duplicate/old) + SeqInOrder, // Sequence is the expected next sequence + SeqFuture // Sequence is from the future (out of order) + } SeqOrder; + + // Packet statistics + uint32 received_packets; // Total packets received + uint32 sent_packets; // Total packets sent + + // Remote endpoint information + uint32 remote_ip; // Remote IP address + uint16 remote_port; // Remote port number + + // Packet buffers + uint8 buffer[8192]; // Main packet buffer + unsigned char* oversize_buffer; // Buffer for oversized packets + uint32 oversize_offset; // Offset in oversize buffer + uint32 oversize_length; // Length of oversize buffer data + unsigned char* rogue_buffer; // Buffer for rogue/malformed packets + uint32 roguebuf_offset; // Offset in rogue buffer + uint32 roguebuf_size; // Size of rogue buffer + + // Protocol configuration + uint8 app_opcode_size; // Size of application opcodes + EQStreamType StreamType; // Type of this stream + bool compressed; // Stream supports compression + bool encoded; // Stream supports encoding/encryption + + // Write buffer for outgoing packets + unsigned char write_buffer[2048]; + + // Retransmission timing + uint32 retransmittimer; // Current retransmit timer + uint32 retransmittimeout; // Retransmit timeout value + + // Session management + uint16 sessionAttempts; // Number of session attempts + uint16 reconnectAttempt; // Number of reconnect attempts + bool streamactive; // Stream is actively connected + + // Session state + uint32 Session; // Session ID + uint32 Key; // Session encryption key + uint16 NextInSeq; // Next expected incoming sequence + uint16 NextOutSeq; // Next outgoing sequence number + uint16 SequencedBase; // Base sequence for SequencedQueue[0] + uint32 MaxLen; // Maximum packet length + uint16 MaxSends; // Maximum send attempts + int8 timeout_delays; // Number of timeout delays accumulated + + // Thread safety for stream usage + uint8 active_users; // Number of active users of this stream + Mutex MInUse; // Mutex for usage tracking + +#ifdef WRITE_PACKETS + // Packet logging for debugging + FILE* write_packets = nullptr; // File handle for packet dumps + char GetChar(uchar in); // Convert byte to printable character + void WriteToFile(char* pFormat, ...); // Write formatted data to file + void WritePackets(const char* opcodeName, uchar* data, int32 size, bool outgoing); + void WritePackets(EQ2Packet* app, bool outgoing); + Mutex MWritePackets; // Mutex for packet writing +#endif + + // Stream connection state + EQStreamState State; // Current connection state + Mutex MState; // Mutex for state access + + // Timing and general variables + uint32 LastPacket; // Timestamp of last packet activity + Mutex MVarlock; // General variable lock + + // Combined application packet handling + EQApplicationPacket* CombinedAppPacket; // Current combined packet + Mutex MCombinedAppPacket; // Mutex for combined packet access + + // Sequence number tracking + long LastSeqSent; // Last sequence number sent + Mutex MLastSeqSent; // Mutex for last sequence sent + void SetLastSeqSent(uint32 seq); // Set last sent sequence number + + // Acknowledgment sequence tracking + long MaxAckReceived; // Highest ack sequence received + long NextAckToSend; // Next ack sequence to send + long LastAckSent; // Last ack sequence sent + + // Acknowledgment accessor methods + long GetMaxAckReceived(); + long GetNextAckToSend(); + long GetLastAckSent(); + void SetMaxAckReceived(uint32 seq); + void SetNextAckToSend(uint32 seq); + void SetLastAckSent(uint32 seq); + + Mutex MAcks; // Mutex for acknowledgment data + + // Outbound packet queues + queue NonSequencedQueue; // Non-sequenced packets + deque SequencedQueue; // Sequenced packets + map OutOfOrderpackets; // Out-of-order packets + Mutex MOutboundQueue; // Mutex for outbound queues + + // Inbound packet queue + deque InboundQueue; // Packets waiting processing + Mutex MInboundQueue; // Mutex for inbound queue + + // Static configuration + static uint16 MaxWindowSize; // Maximum window size for flow control + + // Rate limiting and flow control + sint32 BytesWritten; // Bytes written this period + Mutex MRate; // Mutex for rate limiting + sint32 RateThreshold; // Rate limiting threshold + sint32 DecayRate; // Rate decay per time period + uint32 AverageDelta; // Average round-trip time + + // Factory reference + EQStreamFactory* Factory; // Factory that created this stream + +public: + // EQ2-specific packet combining + Mutex MCombineQueueLock; // Mutex for combine queue operations + bool CheckCombineQueue(); // Check and process combine queue + deque combine_queue; // Queue of packets to combine + Timer* combine_timer; // Timer for combine operations + + // Encryption and compression + Crypto* crypto; // Cryptographic handler + int8 EQ2_Compress(EQ2Packet* app, int8 offset = 3); // Compress EQ2 packet + z_stream stream; // zlib compression stream + uchar* stream_buffer; // Compression buffer + int32 stream_buffer_size; // Size of compression buffer + bool eq2_compressed; // Stream uses EQ2 compression + int8 compressed_offset; // Offset for compressed data + + // Client version management + int16 client_version; // Client protocol version + int16 GetClientVersion() { return client_version; } + void SetClientVersion(int16 version) { client_version = version; } + + // Session attempt management + void ResetSessionAttempts() { reconnectAttempt = 0; } + bool HasSessionAttempts() { return reconnectAttempt > 0; } + + // Constructors + EQStream() { + init(); + remote_ip = 0; + remote_port = 0; + State = CLOSED; + StreamType = UnknownStream; + compressed = true; + encoded = false; + app_opcode_size = 2; + } + + EQStream(sockaddr_in addr); + // Destructor + virtual ~EQStream() { + // Close stream safely + MOutboundQueue.lock(); + SetState(CLOSED); + MOutboundQueue.unlock(); + + // Clean up data structures + RemoveData(); + + // Clean up allocated resources + safe_delete(crypto); + safe_delete(combine_timer); + safe_delete(resend_que_timer); + safe_delete_array(oversize_buffer); + safe_delete_array(rogue_buffer); + + // Clean up combine queue + MCombineQueueLock.lock(); + for (auto cmb = combine_queue.begin(); cmb != combine_queue.end(); ++cmb) { + safe_delete(*cmb); + } + MCombineQueueLock.unlock(); + + // Clean up compression stream + deflateEnd(&stream); + + // Clean up out-of-order packets + for (auto oop = OutOfOrderpackets.begin(); oop != OutOfOrderpackets.end(); ++oop) { + safe_delete(oop->second); + } + +#ifdef WRITE_PACKETS + if (write_packets) { + fclose(write_packets); + } +#endif + } + // Factory and initialization + void SetFactory(EQStreamFactory* f) { Factory = f; } + void init(bool resetSession = true); + + // Configuration + void SetMaxLen(uint32 length) { MaxLen = length; } + int8 getTimeoutDelays() { return timeout_delays; } + void addTimeoutDelay() { timeout_delays++; } + + // EQ2-specific packet handling + void EQ2QueuePacket(EQ2Packet* app, bool attempted_combine = false); + void PreparePacket(EQ2Packet* app, int8 offset = 0); + void UnPreparePacket(EQ2Packet* app); + void EncryptPacket(EQ2Packet* app, int8 compress_offset, int8 offset); + void FlushCombinedPacket(); + + // General packet transmission + void SendPacket(EQApplicationPacket* p); + void QueuePacket(EQProtocolPacket* p); + void SendPacket(EQProtocolPacket* p); + vector convert(EQApplicationPacket* p); + void NonSequencedPush(EQProtocolPacket* p); + void SequencedPush(EQProtocolPacket* p); + + // Resend queue management + Mutex MResendQue; // Mutex for resend queue + Mutex MCompressData; // Mutex for compression operations + deque resend_que; // Queue of packets needing resend + void CheckResend(int eq_fd); // Check and handle packet resends + + // Acknowledgment and writing + void AckPackets(uint16 seq); // Acknowledge received packets + void Write(int eq_fd); // Write packets to socket + + // Stream state management + void SetActive(bool val) { streamactive = val; } + + // Low-level packet operations + void WritePacket(int fd, EQProtocolPacket* p); + void EncryptPacket(uchar* data, int16 size); + + // Session management + uint32 GetKey() { return Key; } + void SetKey(uint32 k) { Key = k; } + void SetSession(uint32 s) { Session = s; } + void SetLastPacketTime(uint32 t) { LastPacket = t; } + + // Packet processing + void Process(const unsigned char* data, uint32 length); + void ProcessPacket(EQProtocolPacket* p, EQProtocolPacket* lastp = nullptr); + + // Embedded packet handling + bool ProcessEmbeddedPacket(uchar* pBuffer, uint16 length, int8 opcode = OP_Packet); + bool HandleEmbeddedPacket(EQProtocolPacket* p, int16 offset = 2, int16 length = 0); + + // Encryption handling + EQProtocolPacket* ProcessEncryptedPacket(EQProtocolPacket* p); + EQProtocolPacket* ProcessEncryptedData(uchar* data, int32 size, int16 opcode); + + // Virtual packet dispatch (override in derived classes) + virtual void DispatchPacket(EQApplicationPacket* p) { p->DumpRaw(); } + + // Session protocol messages + void SendSessionResponse(); + void SendSessionRequest(); + void SendDisconnect(bool setstate = true); + void SendAck(uint16 seq); + void SendOutOfOrderAck(uint16 seq); + + // Connection health checks + bool CheckTimeout(uint32 now, uint32 timeout = 30) { + return (LastPacket && (now - LastPacket) > timeout); + } + bool Stale(uint32 now, uint32 timeout = 30) { + return (LastPacket && (now - LastPacket) > timeout); + } + + // Inbound queue management + void InboundQueuePush(EQApplicationPacket* p); + EQApplicationPacket* PopPacket(); // Pop packet from inbound queue + void InboundQueueClear(); + + // Outbound queue management + void OutboundQueueClear(); + bool HasOutgoingData(); + + // Key exchange and RSA + void SendKeyRequest(); + int16 processRSAKey(EQProtocolPacket* p, uint16 subpacket_length = 0); + + // Data cleanup + void RemoveData() { + InboundQueueClear(); + OutboundQueueClear(); + if (CombinedAppPacket) { + delete CombinedAppPacket; + CombinedAppPacket = nullptr; + } + } + + // Usage tracking (thread-safe reference counting) + bool IsInUse() { + bool flag; + MInUse.lock(); + flag = (active_users > 0); + MInUse.unlock(); + return flag; + } + + void PutInUse() { + MInUse.lock(); + active_users++; + MInUse.unlock(); + } + + void ReleaseFromUse() { + MInUse.lock(); + if (active_users > 0) { + active_users--; + } + MInUse.unlock(); + } + + // Sequence number utilities + static SeqOrder CompareSequence(uint16 expected_seq, uint16 seq); + + // State management + EQStreamState GetState() { return State; } + void SetState(EQStreamState state) { + MState.lock(); + State = state; + MState.unlock(); + } + + // Remote endpoint access + uint32 GetRemoteIP() { return remote_ip; } + uint32 GetrIP() { return remote_ip; } // Legacy alias + uint16 GetRemotePort() { return remote_port; } + uint16 GetrPort() { return remote_port; } // Legacy alias + + // Static packet reading + static EQProtocolPacket* Read(int eq_fd, sockaddr_in* from); + + // Connection management + void Close() { SendDisconnect(); } + bool CheckActive() { return (GetState() == ESTABLISHED); } + bool CheckClosed() { return GetState() == CLOSED; } + + // Stream configuration + void SetOpcodeSize(uint8 s) { app_opcode_size = s; } + void SetStreamType(EQStreamType t); + EQStreamType GetStreamType() const { return StreamType; } + + // Queue processing + void ProcessQueue(); + EQProtocolPacket* RemoveQueue(uint16 seq); + + // Rate limiting and flow control + void Decay(); + void AdjustRates(uint32 average_delta); + + // Resend timer + Timer* resend_que_timer; // Timer for checking resend queue +}; + +#endif diff --git a/old/common/EQStreamFactory.cpp b/old/common/EQStreamFactory.cpp new file mode 100644 index 0000000..4abe59e --- /dev/null +++ b/old/common/EQStreamFactory.cpp @@ -0,0 +1,546 @@ +// EQ2Emulator: Everquest II Server Emulator +// Copyright (C) 2007 EQ2EMulator Development Team +// Licensed under GPL v3 - see + +#include "EQStreamFactory.h" +#include "Log.h" + +// Unix/Linux networking headers +#include +#include +#include +#include +#include +#include +#include +#include + +// Standard library headers +#include +#include +#include +#include +#include +#include + +// Project headers +#include "op_codes.h" +#include "EQStream.h" +#include "packet_dump.h" +#ifdef WORLD + #include "../WorldServer/client.h" +#endif + +using namespace std; + +#ifdef WORLD +extern ClientList client_list; +#endif + +/** + * Thread entry point for the packet reader loop. + * + * @param eqfs - Pointer to EQStreamFactory instance + * @return Thread return value + */ +ThreadReturnType EQStreamFactoryReaderLoop(void* eqfs) +{ + if (eqfs) { + auto fs = static_cast(eqfs); + fs->ReaderLoop(); + } + THREAD_RETURN(nullptr); +} + +/** + * Thread entry point for the packet writer loop. + * + * @param eqfs - Pointer to EQStreamFactory instance + * @return Thread return value + */ +ThreadReturnType EQStreamFactoryWriterLoop(void* eqfs) +{ + if (eqfs) { + auto fs = static_cast(eqfs); + fs->WriterLoop(); + } + THREAD_RETURN(nullptr); +} + +/** + * Thread entry point for the packet combining loop. + * + * @param eqfs - Pointer to EQStreamFactory instance + * @return Thread return value + */ +ThreadReturnType EQStreamFactoryCombinePacketLoop(void* eqfs) +{ + if (eqfs) { + auto fs = static_cast(eqfs); + fs->CombinePacketLoop(); + } + THREAD_RETURN(nullptr); +} + +/** + * EQStreamFactory constructor with stream type and port. + * + * @param type - Type of streams to create (login/world) + * @param port - Port number to listen on + */ +EQStreamFactory::EQStreamFactory(EQStreamType type, int port) +{ + StreamType = type; + Port = port; + listen_ip_address = nullptr; + sock = -1; + ReaderRunning = false; + WriterRunning = false; + CombinePacketRunning = false; + DecayTimer = nullptr; +} + +/** + * Close the stream factory and clean up all resources. + * Stops all threads, closes socket, and removes all streams. + */ +void EQStreamFactory::Close() +{ + CheckTimeout(true); + Stop(); + if (sock != -1) { + close(sock); + sock = -1; + } +} + +/** + * Open the UDP socket and start worker threads. + * Creates reader, writer, and packet combiner threads. + * + * @return true if successful, false on error + */ +bool EQStreamFactory::Open() +{ + struct sockaddr_in address; + pthread_t t1, t2, t3; + + // Setup socket address structure + memset(reinterpret_cast(&address), 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_port = htons(Port); + + // Set bind address based on configuration +#if defined(LOGIN) || defined(MINILOGIN) + if (listen_ip_address) { + address.sin_addr.s_addr = inet_addr(listen_ip_address); + } else { + address.sin_addr.s_addr = htonl(INADDR_ANY); + } +#else + address.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + + // Create UDP socket + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + return false; + } + + // Bind socket to address + if (::bind(sock, reinterpret_cast(&address), sizeof(address)) < 0) { + close(sock); + sock = -1; + return false; + } + + // Set socket to non-blocking mode + fcntl(sock, F_SETFL, O_NONBLOCK); + + // Log thread startup +#ifdef LOGIN + LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Reader"); + LogWrite(LOGIN__DEBUG, 0, "Login", "Starting factory Writer"); +#elif WORLD + LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Reader"); + LogWrite(WORLD__DEBUG, 0, "World", "Starting factory Writer"); +#endif + + // Create and detach worker threads + pthread_create(&t1, nullptr, EQStreamFactoryReaderLoop, this); + pthread_create(&t2, nullptr, EQStreamFactoryWriterLoop, this); + pthread_create(&t3, nullptr, EQStreamFactoryCombinePacketLoop, this); + pthread_detach(t1); + pthread_detach(t2); + pthread_detach(t3); + + return true; +} + +/** + * Get the next new stream from the queue. + * Thread-safe method to retrieve newly created streams. + * + * @return Next EQStream from queue, or nullptr if none available + */ +EQStream* EQStreamFactory::Pop() +{ + if (!NewStreams.size()) { + return nullptr; + } + + EQStream* s = nullptr; + MNewStreams.lock(); + if (NewStreams.size()) { + s = NewStreams.front(); + NewStreams.pop(); + s->PutInUse(); + } + MNewStreams.unlock(); + + return s; +} + +/** + * Add a new stream to the queue for processing. + * Thread-safe method to queue newly created streams. + * + * @param s - EQStream to add to queue + */ +void EQStreamFactory::Push(EQStream* s) +{ + MNewStreams.lock(); + NewStreams.push(s); + MNewStreams.unlock(); +} + +/** + * Main packet reading loop - runs in separate thread. + * Receives UDP packets and routes them to appropriate streams. + */ +void EQStreamFactory::ReaderLoop() +{ + fd_set readset; + map::iterator stream_itr; + int num; + int length; + unsigned char buffer[2048]; + sockaddr_in from; + socklen_t socklen = sizeof(sockaddr_in); + timeval sleep_time; + + ReaderRunning = true; + while (sock != -1) { + MReaderRunning.lock(); + if (!ReaderRunning) { + MReaderRunning.unlock(); + break; + } + MReaderRunning.unlock(); + + // Setup select() for socket monitoring + FD_ZERO(&readset); + FD_SET(sock, &readset); + + sleep_time.tv_sec = 30; + sleep_time.tv_usec = 0; + + // Wait for incoming data or timeout + num = select(sock + 1, &readset, nullptr, nullptr, &sleep_time); + if (num < 0) { + // Select error - could log this + continue; + } else if (num == 0) { + // Timeout - continue loop + continue; + } + + // Check if our socket has data + if (FD_ISSET(sock, &readset)) { + length = recvfrom(sock, buffer, 2048, 0, + reinterpret_cast(&from), + &socklen); + + if (length < 2) { + // Packet too small - ignore + continue; + } + + // Create address:port string for stream identification + char temp[25]; + snprintf(temp, sizeof(temp), "%u.%d", + ntohl(from.sin_addr.s_addr), ntohs(from.sin_port)); + + MStreams.lock(); + stream_itr = Streams.find(temp); + + // Handle new connections or session requests + if (stream_itr == Streams.end() || buffer[1] == OP_SessionRequest) { + MStreams.unlock(); + + if (buffer[1] == OP_SessionRequest) { + // Close existing stream if present + if (stream_itr != Streams.end() && stream_itr->second) { + stream_itr->second->SetState(CLOSED); + } + + // Create new stream + auto s = new EQStream(from); + s->SetFactory(this); + s->SetStreamType(StreamType); + Streams[temp] = s; + WriterWork.Signal(); + Push(s); + s->Process(buffer, length); + s->SetLastPacketTime(Timer::GetCurrentTime2()); + } + } else { + // Route packet to existing stream + EQStream* curstream = stream_itr->second; + + // Skip closed connections + if (curstream->CheckClosed()) { + curstream = nullptr; + } else { + curstream->PutInUse(); + } + MStreams.unlock(); + + if (curstream) { + curstream->Process(buffer, length); + curstream->SetLastPacketTime(Timer::GetCurrentTime2()); + curstream->ReleaseFromUse(); + } + } + } + } +} + +/** + * Check for timed out streams and clean up closed connections. + * + * @param remove_all - If true, remove all streams regardless of state + */ +void EQStreamFactory::CheckTimeout(bool remove_all) +{ + // Lock streams for the entire timeout check - should be fast + MStreams.lock(); + + unsigned long now = Timer::GetCurrentTime2(); + map::iterator stream_itr; + + for (stream_itr = Streams.begin(); stream_itr != Streams.end();) { + EQStream* s = stream_itr->second; + EQStreamState state = s->GetState(); + + // Transition CLOSING streams to CLOSED when no outgoing data + if (state == CLOSING && !s->HasOutgoingData()) { + stream_itr->second->SetState(CLOSED); + state = CLOSED; + } else if (s->CheckTimeout(now, STREAM_TIMEOUT)) { + // Handle timeout based on current state + const char* stateString; + switch (state) { + case ESTABLISHED: + stateString = "Established"; + break; + case CLOSING: + stateString = "Closing"; + break; + case CLOSED: + stateString = "Closed"; + break; + case WAIT_CLOSE: + stateString = "Wait-Close"; + break; + default: + stateString = "Unknown"; + break; + } + + LogWrite(WORLD__DEBUG, 0, "World", "Timeout up!, state=%s (%u)", + stateString, state); + + if (state == ESTABLISHED) { + s->Close(); + } else if (state == WAIT_CLOSE) { + s->SetState(CLOSING); + state = CLOSING; + } else if (state == CLOSING) { + // If we timeout in closing state, force close + s->SetState(CLOSED); + state = CLOSED; + } + } + + // Remove closed streams (check immediately after state changes) + if (remove_all || state == CLOSED) { + if (!remove_all && s->getTimeoutDelays() < 2) { + s->addTimeoutDelay(); + // Give other threads time to finish with this stream + ++stream_itr; + } else { + // Safe to delete now +#ifdef LOGIN + LogWrite(LOGIN__DEBUG, 0, "Login", "Removing connection..."); +#else + LogWrite(WORLD__DEBUG, 0, "World", "Removing connection..."); +#endif + auto temp = stream_itr; + ++stream_itr; + + // Let client list handle cleanup if in world server +#ifdef WORLD + client_list.RemoveConnection(temp->second); +#endif + EQStream* stream = temp->second; + Streams.erase(temp); + delete stream; + continue; + } + } else { + ++stream_itr; + } + } + + MStreams.unlock(); +} + +/** + * Packet combining optimization loop - runs in separate thread. + * Combines multiple small packets for efficient transmission. + */ +void EQStreamFactory::CombinePacketLoop() +{ + deque combine_que; + CombinePacketRunning = true; + bool packets_waiting = false; + + while (sock != -1) { + if (!CombinePacketRunning) { + break; + } + + MStreams.lock(); + + // Check all streams for combine timer expiration + for (auto& stream_pair : Streams) { + if (!stream_pair.second) { + continue; + } + + if (stream_pair.second->combine_timer && + stream_pair.second->combine_timer->Check()) { + combine_que.push_back(stream_pair.second); + } + } + + // Process streams that need packet combining + packets_waiting = false; + while (!combine_que.empty()) { + EQStream* stream = combine_que.front(); + if (stream->CheckActive()) { + if (!stream->CheckCombineQueue()) { + packets_waiting = true; + } + } + combine_que.pop_front(); + } + + MStreams.unlock(); + + // Sleep longer if no packets are waiting + if (!packets_waiting) { + usleep(25000); // 25ms + } + + usleep(1000); // 1ms + } +} + +/** + * Main packet writing loop - runs in separate thread. + * Handles outgoing packet transmission and resends. + */ +void EQStreamFactory::WriterLoop() +{ + map::iterator stream_itr; + vector wants_write; + vector::iterator cur, end; + deque resend_que; + bool decay = false; + uint32 stream_count; + + Timer DecayTimer(20); + + WriterRunning = true; + DecayTimer.Enable(); + + while (sock != -1) { + Timer::SetCurrentTime(); + + MWriterRunning.lock(); + if (!WriterRunning) { + MWriterRunning.unlock(); + break; + } + MWriterRunning.unlock(); + + wants_write.clear(); + resend_que.clear(); + + decay = DecayTimer.Check(); + + // Copy streams into separate list to minimize lock time + MStreams.lock(); + for (stream_itr = Streams.begin(); stream_itr != Streams.end(); ++stream_itr) { + if (!stream_itr->second) { + // This shouldn't happen, but handle gracefully + continue; + } + + // Apply bandwidth decay if it's time + if (decay) { + stream_itr->second->Decay(); + } + + // Queue streams with outgoing data + if (stream_itr->second->HasOutgoingData()) { + stream_itr->second->PutInUse(); + wants_write.push_back(stream_itr->second); + } + + // Queue streams that need resend processing + if (stream_itr->second->resend_que_timer->Check()) { + resend_que.push_back(stream_itr->second); + } + } + MStreams.unlock(); + + // Perform actual packet writes + for (cur = wants_write.begin(), end = wants_write.end(); + cur != end; ++cur) { + (*cur)->Write(sock); + (*cur)->ReleaseFromUse(); + } + + // Handle packet resends + while (!resend_que.empty()) { + resend_que.front()->CheckResend(sock); + resend_que.pop_front(); + } + + usleep(10000); // 10ms sleep + + // Check if we have any streams - wait if not + MStreams.lock(); + stream_count = Streams.size(); + MStreams.unlock(); + + if (!stream_count) { + WriterWork.Wait(); + } + } +} + + diff --git a/old/common/EQStreamFactory.h b/old/common/EQStreamFactory.h new file mode 100644 index 0000000..c886faa --- /dev/null +++ b/old/common/EQStreamFactory.h @@ -0,0 +1,125 @@ +// EQ2Emulator: Everquest II Server Emulator +// Copyright (C) 2007 EQ2EMulator Development Team +// Licensed under GPL v3 - see +#ifndef _EQSTREAMFACTORY_H +#define _EQSTREAMFACTORY_H + +#include +#include +#include +#include + +#include "../common/EQStream.h" +#include "../common/Condition.h" +#include "../common/opcodemgr.h" +#include "../common/timer.h" + +#define STREAM_TIMEOUT 45000 // Stream timeout in milliseconds + +/** + * Factory class for creating and managing EverQuest network streams. + * Handles UDP socket communication, stream lifecycle, and packet processing + * for both login and world server connections. + */ +class EQStreamFactory { +private: + // Network socket and configuration + int sock; // UDP socket file descriptor + int Port; // Port number to listen on + + // Thread management flags and mutexes + bool ReaderRunning; // Reader thread active flag + Mutex MReaderRunning; // Mutex for reader thread flag + bool WriterRunning; // Writer thread active flag + Mutex MWriterRunning; // Mutex for writer thread flag + bool CombinePacketRunning; // Packet combiner thread active flag + Mutex MCombinePacketRunning; // Mutex for combiner thread flag + + // Thread synchronization + Condition WriterWork; // Condition variable for writer thread + + // Stream management + EQStreamType StreamType; // Type of streams this factory creates + std::queue NewStreams; // Queue of new streams waiting for processing + Mutex MNewStreams; // Mutex for new streams queue + std::map Streams; // Active streams mapped by address:port + Mutex MStreams; // Mutex for streams map + + // Cleanup timer + Timer* DecayTimer; // Timer for periodic cleanup operations + +public: + // Network configuration + char* listen_ip_address; // IP address to bind to (nullptr = any) + + // Stream lifecycle management + void CheckTimeout(bool remove_all = false); + + // Constructors and destructor + EQStreamFactory(EQStreamType type) { + ReaderRunning = false; + WriterRunning = false; + CombinePacketRunning = false; + StreamType = type; + sock = -1; + Port = 0; + listen_ip_address = nullptr; + DecayTimer = nullptr; + } + + EQStreamFactory(EQStreamType type, int port); + + ~EQStreamFactory() { + safe_delete_array(listen_ip_address); + } + + // Stream queue management + EQStream* Pop(); // Get next new stream from queue + void Push(EQStream* s); // Add new stream to queue + + // Network operations + bool loadPublicKey(); // Load encryption keys (if needed) + bool Open(); // Open socket and start threads + bool Open(unsigned long port) { + Port = port; + return Open(); + } + void Close(); // Close socket and stop threads + + // Main thread loops + void ReaderLoop(); // Main packet reading loop + void WriterLoop(); // Main packet writing loop + void CombinePacketLoop(); // Packet combining optimization loop + + // Thread control + void Stop() { + StopReader(); + StopWriter(); + StopCombinePacket(); + } + + void StopReader() { + MReaderRunning.lock(); + ReaderRunning = false; + MReaderRunning.unlock(); + } + + void StopWriter() { + MWriterRunning.lock(); + WriterRunning = false; + MWriterRunning.unlock(); + WriterWork.Signal(); + } + + void StopCombinePacket() { + MCombinePacketRunning.lock(); + CombinePacketRunning = false; + MCombinePacketRunning.unlock(); + } + + void SignalWriter() { + WriterWork.Signal(); + } +}; + +#endif diff --git a/old/GlobalHeaders.h b/old/common/GlobalHeaders.h similarity index 100% rename from old/GlobalHeaders.h rename to old/common/GlobalHeaders.h diff --git a/old/JsonParser.cpp b/old/common/JsonParser.cpp similarity index 100% rename from old/JsonParser.cpp rename to old/common/JsonParser.cpp diff --git a/old/JsonParser.h b/old/common/JsonParser.h similarity index 100% rename from old/JsonParser.h rename to old/common/JsonParser.h diff --git a/old/Log.cpp b/old/common/Log.cpp similarity index 100% rename from old/Log.cpp rename to old/common/Log.cpp diff --git a/old/Log.h b/old/common/Log.h similarity index 100% rename from old/Log.h rename to old/common/Log.h diff --git a/old/LogTypes.h b/old/common/LogTypes.h similarity index 100% rename from old/LogTypes.h rename to old/common/LogTypes.h diff --git a/old/MiscFunctions.cpp b/old/common/MiscFunctions.cpp similarity index 100% rename from old/MiscFunctions.cpp rename to old/common/MiscFunctions.cpp diff --git a/old/MiscFunctions.h b/old/common/MiscFunctions.h similarity index 100% rename from old/MiscFunctions.h rename to old/common/MiscFunctions.h diff --git a/old/Mutex.cpp b/old/common/Mutex.cpp similarity index 100% rename from old/Mutex.cpp rename to old/common/Mutex.cpp diff --git a/old/Mutex.h b/old/common/Mutex.h similarity index 100% rename from old/Mutex.h rename to old/common/Mutex.h diff --git a/old/PacketStruct.cpp b/old/common/PacketStruct.cpp similarity index 100% rename from old/PacketStruct.cpp rename to old/common/PacketStruct.cpp diff --git a/old/PacketStruct.h b/old/common/PacketStruct.h similarity index 100% rename from old/PacketStruct.h rename to old/common/PacketStruct.h diff --git a/old/RC4.cpp b/old/common/RC4.cpp similarity index 100% rename from old/RC4.cpp rename to old/common/RC4.cpp diff --git a/old/RC4.h b/old/common/RC4.h similarity index 100% rename from old/RC4.h rename to old/common/RC4.h diff --git a/old/TCPConnection.cpp b/old/common/TCPConnection.cpp similarity index 100% rename from old/TCPConnection.cpp rename to old/common/TCPConnection.cpp diff --git a/old/TCPConnection.h b/old/common/TCPConnection.h similarity index 100% rename from old/TCPConnection.h rename to old/common/TCPConnection.h diff --git a/old/Web/WebServer.cpp b/old/common/Web/WebServer.cpp similarity index 100% rename from old/Web/WebServer.cpp rename to old/common/Web/WebServer.cpp diff --git a/old/Web/WebServer.h b/old/common/Web/WebServer.h similarity index 100% rename from old/Web/WebServer.h rename to old/common/Web/WebServer.h diff --git a/old/database.cpp b/old/common/database.cpp similarity index 100% rename from old/database.cpp rename to old/common/database.cpp diff --git a/old/database.h b/old/common/database.h similarity index 100% rename from old/database.h rename to old/common/database.h diff --git a/old/dbcore.cpp b/old/common/dbcore.cpp similarity index 100% rename from old/dbcore.cpp rename to old/common/dbcore.cpp diff --git a/old/dbcore.h b/old/common/dbcore.h similarity index 100% rename from old/dbcore.h rename to old/common/dbcore.h diff --git a/old/debug.cpp b/old/common/debug.cpp similarity index 100% rename from old/debug.cpp rename to old/common/debug.cpp diff --git a/old/debug.h b/old/common/debug.h similarity index 100% rename from old/debug.h rename to old/common/debug.h diff --git a/old/emu_opcodes.cpp b/old/common/emu_opcodes.cpp similarity index 100% rename from old/emu_opcodes.cpp rename to old/common/emu_opcodes.cpp diff --git a/old/emu_opcodes.h b/old/common/emu_opcodes.h similarity index 100% rename from old/emu_opcodes.h rename to old/common/emu_opcodes.h diff --git a/old/emu_oplist.h b/old/common/emu_oplist.h similarity index 100% rename from old/emu_oplist.h rename to old/common/emu_oplist.h diff --git a/old/linked_list.h b/old/common/linked_list.h similarity index 100% rename from old/linked_list.h rename to old/common/linked_list.h diff --git a/old/login_oplist.h b/old/common/login_oplist.h similarity index 100% rename from old/login_oplist.h rename to old/common/login_oplist.h diff --git a/old/md5.cpp b/old/common/md5.cpp similarity index 100% rename from old/md5.cpp rename to old/common/md5.cpp diff --git a/old/md5.h b/old/common/md5.h similarity index 100% rename from old/md5.h rename to old/common/md5.h diff --git a/old/misc.cpp b/old/common/misc.cpp similarity index 100% rename from old/misc.cpp rename to old/common/misc.cpp diff --git a/old/misc.h b/old/common/misc.h similarity index 100% rename from old/misc.h rename to old/common/misc.h diff --git a/old/op_codes.h b/old/common/op_codes.h similarity index 100% rename from old/op_codes.h rename to old/common/op_codes.h diff --git a/old/opcodemgr.cpp b/old/common/opcodemgr.cpp similarity index 100% rename from old/opcodemgr.cpp rename to old/common/opcodemgr.cpp diff --git a/old/opcodemgr.h b/old/common/opcodemgr.h similarity index 100% rename from old/opcodemgr.h rename to old/common/opcodemgr.h diff --git a/old/packet_dump.cpp b/old/common/packet_dump.cpp similarity index 100% rename from old/packet_dump.cpp rename to old/common/packet_dump.cpp diff --git a/old/packet_dump.h b/old/common/packet_dump.h similarity index 100% rename from old/packet_dump.h rename to old/common/packet_dump.h diff --git a/old/packet_functions.cpp b/old/common/packet_functions.cpp similarity index 100% rename from old/packet_functions.cpp rename to old/common/packet_functions.cpp diff --git a/old/packet_functions.h b/old/common/packet_functions.h similarity index 100% rename from old/packet_functions.h rename to old/common/packet_functions.h diff --git a/old/queue.h b/old/common/queue.h similarity index 100% rename from old/queue.h rename to old/common/queue.h diff --git a/old/seperator.h b/old/common/seperator.h similarity index 100% rename from old/seperator.h rename to old/common/seperator.h diff --git a/old/servertalk.h b/old/common/servertalk.h similarity index 100% rename from old/servertalk.h rename to old/common/servertalk.h diff --git a/old/sha512.cpp b/old/common/sha512.cpp similarity index 100% rename from old/sha512.cpp rename to old/common/sha512.cpp diff --git a/old/sha512.h b/old/common/sha512.h similarity index 100% rename from old/sha512.h rename to old/common/sha512.h diff --git a/old/string_util.cpp b/old/common/string_util.cpp similarity index 100% rename from old/string_util.cpp rename to old/common/string_util.cpp diff --git a/old/string_util.h b/old/common/string_util.h similarity index 100% rename from old/string_util.h rename to old/common/string_util.h diff --git a/old/timer.cpp b/old/common/timer.cpp similarity index 100% rename from old/timer.cpp rename to old/common/timer.cpp diff --git a/old/timer.h b/old/common/timer.h similarity index 100% rename from old/timer.h rename to old/common/timer.h diff --git a/old/types.h b/old/common/types.h similarity index 100% rename from old/types.h rename to old/common/types.h diff --git a/old/unix.cpp b/old/common/unix.cpp similarity index 100% rename from old/unix.cpp rename to old/common/unix.cpp diff --git a/old/unix.h b/old/common/unix.h similarity index 100% rename from old/unix.h rename to old/common/unix.h diff --git a/old/version.h b/old/common/version.h similarity index 100% rename from old/version.h rename to old/common/version.h diff --git a/old/xmlParser.cpp b/old/common/xmlParser.cpp similarity index 100% rename from old/xmlParser.cpp rename to old/common/xmlParser.cpp diff --git a/old/xmlParser.h b/old/common/xmlParser.h similarity index 100% rename from old/xmlParser.h rename to old/common/xmlParser.h diff --git a/old/login/Character.cpp b/old/login/Character.cpp new file mode 100644 index 0000000..67423d8 --- /dev/null +++ b/old/login/Character.cpp @@ -0,0 +1,20 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#include "Character.h" \ No newline at end of file diff --git a/old/login/Character.h b/old/login/Character.h new file mode 100644 index 0000000..a0e89a8 --- /dev/null +++ b/old/login/Character.h @@ -0,0 +1,25 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. + + EQ2Emulator is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + EQ2Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EQ2Emulator. If not, see . +*/ +#ifndef _EQ2_CHARACTER_ +#define _EQ2_CHARACTER_ +class Character{ + +}; +#endif diff --git a/old/login/LWorld.cpp b/old/login/LWorld.cpp new file mode 100644 index 0000000..6a79dbe --- /dev/null +++ b/old/login/LWorld.cpp @@ -0,0 +1,1561 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" +#include +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include + +#include "../common/unix.h" + +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 + +extern int errno; +#endif + +#include "../common/servertalk.h" +#include "LWorld.h" +#include "net.h" +#include "client.h" +#include "../common/packet_dump.h" +#include "login_opcodes.h" +#include "login_structs.h" +#include "LoginDatabase.h" +#include "PacketHeaders.h" +#include "../common/ConfigReader.h" + +#ifdef WIN32 +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +extern ClientList client_list; +extern NetConnection net; +extern LWorldList world_list; +extern LoginDatabase database; +extern ConfigReader configReader; +extern volatile bool RunLoops; + +#include "../common/Log.h" +using namespace std; +LWorld::LWorld(TCPConnection* in_con, bool in_OutgoingLoginUplink, int32 iIP, int16 iPort, bool iNeverKick) { + Link = in_con; + RemoteID = 0; + LinkWorldID = 0; + if (iIP) + ip = iIP; + else + ip = in_con->GetrIP(); + + struct in_addr in; + in.s_addr = in_con->GetrIP(); + char* ipadd = inet_ntoa(in); + if(ipadd) + strncpy(IPAddr,ipadd,64); + + if (iPort) + port = iPort; + else + port = in_con->GetrPort(); + ID = 0; + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = 0; + accountid = 0; + admin_id = 0; + IsInit = false; + kicked = false; + pNeverKick = iNeverKick; + pPlaceholder = false; + pshowdown = false; + pConnected = in_con->Connected(); + pReconnectTimer = 0; + pStatsTimer = NULL; + isAuthenticated = false; + + if (in_OutgoingLoginUplink) { + pClientPort = port; + ptype = Login; + OutgoingUplink = true; + if (net.GetLoginMode() == Mesh) { + pReconnectTimer = new Timer(INTERSERVER_TIMER); + pReconnectTimer->Trigger(); + } + } + else { + ptype = UnknownW; + OutgoingUplink = false; + } + + in.s_addr = GetIP(); + strcpy(address, inet_ntoa(in)); + isaddressip = true; + + num_players = 0; + num_zones = 0; +} + +LWorld::LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id) { + pPlaceholder = true; + Link = 0; + ip = 0; + port = 0; + ID = 0; + strcpy(IPAddr,""); + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = 0; + accountid = in_accountid; + admin_id = in_admin_id; + IsInit = false; + kicked = false; + pNeverKick = false; + pshowdown = true; + RemoteID = 0; + LinkWorldID = 0; + OutgoingUplink = false; + pReconnectTimer = 0; + pConnected = false; + + pStatsTimer = NULL; + + ptype = World; + strcpy(account, in_accountname); + strcpy(worldname, in_worldname); + + strcpy(address, "none"); + + isaddressip = true; + + num_players = 0; + num_zones = 0; +} + +LWorld::LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID) { + Link = in_RemoteLink; + RemoteID = in_RemoteID; + LinkWorldID = iLinkWorldID; + ip = in_ip; + + struct in_addr in; + if(in_RemoteLink) + in.s_addr = in_RemoteLink->GetrIP(); + else if (in_ip) + in.s_addr = in_ip; + char* ipadd = inet_ntoa(in); + if(ipadd) + strncpy(IPAddr,ipadd,64); + + port = 0; + ID = 0; + pClientPort = 0; + memset(account, 0, sizeof(account)); + memset(address, 0, sizeof(address)); + memset(worldname, 0, sizeof(worldname)); + status = in_status; + accountid = in_accountid; + admin_id = in_adminid; + IsInit = true; + kicked = false; + pNeverKick = false; + pPlaceholder = in_placeholder; + pshowdown = in_showdown; + OutgoingUplink = false; + pReconnectTimer = 0; + pConnected = true; + pStatsTimer = NULL; + + ptype = World; + strcpy(account, in_accountname); + strcpy(worldname, in_worldname); + + strcpy(address, in_address); + + isaddressip = false; + + num_players = 0; + num_zones = 0; +} + +LWorld::~LWorld() { + + safe_delete ( pStatsTimer ); + num_zones = 0; + num_players = 0; + database.UpdateWorldServerStats(this, -4); + + if (ptype == World && RemoteID == 0) { + if (net.GetLoginMode() != Mesh || (!pPlaceholder && IsInit)) { + ServerPacket* pack = new ServerPacket(ServerOP_WorldListRemove, sizeof(int32)); + *((int32*) pack->pBuffer) = GetID(); + world_list.SendPacketLogin(pack); + delete pack; + } + } + + if (Link != 0 && RemoteID == 0) { + world_list.RemoveByLink(Link, 0, this); + if (OutgoingUplink) + delete Link; + else + Link->Free(); + } + Link = 0; + safe_delete(pReconnectTimer); + + world_list.RemoveByID ( this->GetID ( ) ); +} + +bool LWorld::Process() { + bool ret = true; + if (Link == 0) + return true; + if (Link->Connected()) { + if (!pConnected) { + pConnected = true; + } + } + else { + pConnected = false; + if (pReconnectTimer) { + if (pReconnectTimer->Check() && Link->ConnectReady()) { + pReconnectTimer->Start(pReconnectTimer->GetTimerTime() + (rand()%15000), false); + } + return true; + } + return false; + } + if (RemoteID != 0) + return true; + + if(pStatsTimer && pStatsTimer->Check()) + { + if(isAuthenticated && (database.IsServerAccountDisabled(account) || database.IsIPBanned(IPAddr))) + { + this->Kick(ERROR_BADPASSWORD); + return false; + } + + database.UpdateWorldServerStats(this, GetStatus()); + } + + ServerPacket* pack = 0; + while (ret && (pack = Link->PopPacket())) { + + // this stops connections from sending invalid packets without first authenticating + // with the login server to show it is a legit server + if(!isAuthenticated && pack->opcode != ServerOP_LSInfo) + { + Kick("This connection has not authenticated."); + break; + } + + switch(pack->opcode) { + case 0: + break; + case ServerOP_KeepAlive: { + // ignore this + break; + } + case ServerOP_LSFatalError: { + net.Uplink_WrongVersion = true; + ret = false; + kicked = true; + break; + } + case ServerOP_CharacterCreate: { + WorldCharNameFilterResponse_Struct* wcnfr = (WorldCharNameFilterResponse_Struct*) pack->pBuffer; + + Client* client = client_list.FindByLSID(wcnfr->account_id); + if(!client){ + if(wcnfr->account_id == 0){ + client_list.FindByCreateRequest(); + } + break; + } + if(wcnfr->response == 1) + { + client->CharacterApproved(GetID(),wcnfr->char_id); + } + else + { + client->CharacterRejected(wcnfr->response); + } + break; + } + case ServerOP_UsertoWorldReq: { + UsertoWorldRequest_Struct* ustwr = (UsertoWorldRequest_Struct*) pack->pBuffer; + if (ustwr->ToID) { + LWorld* world = world_list.FindByID(ustwr->ToID); + if (!world) { + break; + } + if (this->GetType() != Login) { + break; + } + ustwr->FromID = this->GetID(); + world->SendPacket(pack); + } + break; + } + case ServerOP_UsertoWorldResp: { + if (pack->size != sizeof(UsertoWorldResponse_Struct)) + break; + + UsertoWorldResponse_Struct* seps = (UsertoWorldResponse_Struct*) pack->pBuffer; + if (seps->ToID) { + LWorld* world = world_list.FindByID(seps->ToID); + + if (this->GetType() != Login) { + break; + } + if (world) { + seps->ToID = world->GetRemoteID(); + world->SendPacket(pack); + } + } + else { + Client* client = 0; + client = client_list.FindByLSID(seps->lsaccountid); + if(client == 0) + break; + if(this->GetID() != seps->worldid && this->GetType() != Login) + break; + + client->WorldResponse(GetID(),seps->response, seps->ip_address, seps->port, seps->access_key); + } + break; + } + case ServerOP_CharTimeStamp: { // This is being sent to synch a new timestamp on the login server + if(pack->size != sizeof(CharacterTimeStamp_Struct)) + break; + + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) pack->pBuffer; + + if(!database.UpdateCharacterTimeStamp(cts->account_id,cts->char_id,cts->unix_timestamp,GetAccountID())) + printf("TimeStamp update error with character id %i of account id %i on server %i\n",cts->char_id,cts->account_id,GetAccountID()); + + //Todo: Synch with the other login servers + + break; + } + case ServerOP_GetTableData:{ + Kick("This is not an update server."); + break; + } + case ServerOP_GetTableQuery:{ + Kick("This is not an update server."); + break; + } + case ServerOP_GetLatestTables:{ + Kick("This is not an update server."); + break; + } + case ServerOP_ZoneUpdate:{ + if(pack->size > CHARZONESTRUCT_MAXSIZE) + break; + CharZoneUpdate_Struct* czu = (CharZoneUpdate_Struct*)pack->pBuffer; + database.UpdateCharacterZone(czu->account_id, czu->char_id, czu->zone_id, GetAccountID()); + break; + } + case ServerOP_RaceUpdate: { + + if(pack->size != sizeof(RaceUpdate_Struct)) + break; + + RaceUpdate_Struct* ru = (RaceUpdate_Struct*) pack->pBuffer; + database.UpdateCharacterRace(ru->account_id , ru->char_id , ru->model_type , ru->race , this->GetAccountID ( )); + break; + } + case ServerOP_BasicCharUpdate: { + if(pack->size != sizeof(CharDataUpdate_Struct)) + break; + + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*) pack->pBuffer; + + switch(cdu->update_field) + { + case LEVEL_UPDATE_FLAG: + { + database.UpdateCharacterLevel(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case CLASS_UPDATE_FLAG: + { + database.UpdateCharacterClass(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case GENDER_UPDATE_FLAG: + { + database.UpdateCharacterGender(cdu->account_id,cdu->char_id,cdu->update_data,this->GetAccountID()); + break; + } + case DELETE_UPDATE_FLAG: + { + if(cdu->update_field == 1) + database.DeleteCharacter(cdu->account_id,cdu->char_id,this->GetAccountID()); + break; + } + } + break; + } + case ServerOP_NameCharUpdate: { + CharNameUpdate_Struct* cnu = (CharNameUpdate_Struct*) pack->pBuffer; + if (cnu->name_length > 0 && cnu->name_length < 64) { + char name_buffer[64]; + + // Copy up to name_length characters from cnu->new_name + strncpy(name_buffer, cnu->new_name, cnu->name_length); + + // Null-terminate the string just in case + name_buffer[cnu->name_length] = '\0'; + + database.UpdateCharacterName(cnu->account_id,cnu->char_id,name_buffer,this->GetAccountID()); + } + break; + } + case ServerOP_LSInfo: { + if (pack->size != sizeof(ServerLSInfo_Struct)) { + this->Kick(ERROR_BADVERSION); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else { + ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer; + if (strcmp(lsi->protocolversion, EQEMU_PROTOCOL_VERSION) != 0 || !database.CheckVersion(lsi->serverversion)) { + cout << "ERROR - KICK BAD VERSION: Got versions: protocol: '" << lsi->protocolversion << "', database version: " << lsi->serverversion << endl; + cout << "To allow all world server versions to login, run query on your login database (alternatively replacing * with the database version if preferred): insert into login_versions set version = '*';" << endl; + this->Kick(ERROR_BADVERSION); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else if (!SetupWorld(lsi->name, lsi->address, lsi->account, lsi->password, lsi->serverversion)) { + this->Kick(ERROR_BADPASSWORD); + ret = false; + //struct in_addr in; + //in.s_addr = GetIP(); + } + else{ + isAuthenticated = true; + devel_server = (lsi->servertype == 4); + } + } + break; + } + case ServerOP_LSStatus: { + ServerLSStatus_Struct* lss = (ServerLSStatus_Struct*) pack->pBuffer; + + if(lss->num_players > 5000 || lss->num_zones > 500) { + this->Kick("Your server has exceeded a number of players and/or zone limit."); + ret = false; + break; + } + UpdateStatus(lss->status, lss->num_players, lss->num_zones, lss->world_max_level); + break; + } + case ServerOP_SystemwideMessage: { + if (this->GetType() == Login) { + // no looping plz =p + //world_list.SendPacket(pack, this); + } + else if (this->GetType() == Chat) { + world_list.SendPacket(pack); + } + else { + } + break; + } + case ServerOP_ListWorlds: { + if (pack->size <= 1 || pack->pBuffer[pack->size - 1] != 0) { + break; + } + world_list.SendWorldStatus(this, (char*) pack->pBuffer); + break; + } + case ServerOP_WorldListUpdate: { + break; + } + case ServerOP_WorldListRemove: { + if (this->GetType() != Login) { + // cout << "Error: ServerOP_WorldListRemove from a non-login connection? WTF!" << endl; + break; + } + if (pack->size != sizeof(int32)) { + // cout << "Wrong size on ServerOP_WorldListRemove. Got: " << pack->size << ", Expected: " << sizeof(int32) << endl; + break; + } + cout << "Got world remove for remote #" << *((int32*) pack->pBuffer) << endl; + if ((*((int32*) pack->pBuffer)) > 0) { + LWorld* world = world_list.FindByLink(this->GetLink(), *((int32*) pack->pBuffer)); + if (world && world->GetRemoteID() != 0) { + *((int32*) pack->pBuffer) = world->GetID(); + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + world_list.RemoveByID(*((int32*) pack->pBuffer)); + } + } + else { + // cout << "Error: ServerOP_WorldListRemove: ID = 0? ops!" << endl; + } + break; + } + case ServerOP_TriggerWorldListRefresh: { + world_list.UpdateWorldList(); + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + break; + } + case ServerOP_ZoneUpdates:{ + pack->Inflate(); + ZoneUpdateList_Struct* updates = 0; + if(pack->size >= sizeof(ZoneUpdateList_Struct) && ((ZoneUpdateList_Struct*)pack->pBuffer)->total_updates <= MAX_UPDATE_COUNT){ + updates = (ZoneUpdateList_Struct*)pack->pBuffer; + ZoneUpdate_Struct* zone = 0; + int32 pos = sizeof(ZoneUpdateList_Struct); + sint16 num_updates = 0; + map zone_updates; + LoginZoneUpdate update; + while(pos < pack->size && num_updates < updates->total_updates){ + zone = (ZoneUpdate_Struct*)(pack->pBuffer+pos); + if((pos + zone->zone_name_length + zone->zone_desc_length + sizeof(ZoneUpdate_Struct)) <= pack->size){ + update.name = string(zone->data, zone->zone_name_length); + update.description = string(zone->data + zone->zone_name_length, zone->zone_desc_length); + pos += sizeof(ZoneUpdate_Struct) + zone->zone_name_length + zone->zone_desc_length; + num_updates++; + zone_updates[zone->zone_id] = update; + } + else + break; + } + if(zone_updates.size() == updates->total_updates) + world_list.AddServerZoneUpdates(this, zone_updates); + else + cout << "Error processing zone updates for server: " << GetAccount() << endl; + } + else + Kick("Possible Hacking Attempt"); + break; + } + + case ServerOP_LoginEquipment: { + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode %04X (%i): ServerOP_LoginEquipment", pack->opcode, pack->opcode); + + pack->Inflate(); + EquipmentUpdateList_Struct* updates = 0; + if(pack->size >= sizeof(EquipmentUpdateList_Struct) && ((EquipmentUpdateList_Struct*)pack->pBuffer)->total_updates <= MAX_LOGIN_APPEARANCE_COUNT){ + updates = (EquipmentUpdateList_Struct*)pack->pBuffer; + EquipmentUpdate_Struct* equip = 0; + int32 pos = sizeof(EquipmentUpdateList_Struct); + sint16 num_updates = 0; + map equip_updates; + LoginEquipmentUpdate update; + while(pos < pack->size && num_updates < updates->total_updates){ + equip = (EquipmentUpdate_Struct*)(pack->pBuffer+pos); + update.world_char_id = equip->world_char_id; + update.equip_type = equip->equip_type; + update.red = equip->red; + update.green = equip->green; + update.blue = equip->blue; + update.highlight_red = equip->highlight_red; + update.highlight_green = equip->highlight_green; + update.highlight_blue = equip->highlight_blue; + update.slot = equip->slot; + pos += sizeof(EquipmentUpdate_Struct); + num_updates++; + equip_updates[equip->id] = update; // JohnAdams: I think I need item_appearances.id from World here? + } + + LogWrite(LOGIN__DEBUG, 1, "Login", "Processing %i Login Appearance Updates...", num_updates); + if(equip_updates.size() == updates->total_updates) + { + world_list.AddServerEquipmentUpdates(this, equip_updates); + } + else + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error processing login appearance updates for server: %s\n\t%s, function %s, line %i", GetAccount(), __FILE__, __FUNCTION__, __LINE__); + } + } + else + { + LogWrite(LOGIN__ERROR, 0, "Login", "World ID '%i', Possible Hacking Attempt (func: %s, line: %i", GetAccountID(), __FUNCTION__, __LINE__); + Kick("Possible Hacking Attempt"); + } + break; + } + case ServerOP_BugReport:{ + if(pack->size == sizeof(BugReport)){ + BugReport* report = (BugReport*)pack->pBuffer; + database.SaveBugReport(GetAccountID(), report->category, report->subcategory, report->causes_crash, report->reproducible, report->summary, report->description, report->version, report->player, report->account_id, report->spawn_name, report->spawn_id, report->zone_id); + } + break; + } + case ServerOP_EncapPacket: { + if (this->GetType() != Login) { + // cout << "Error: ServerOP_EncapPacket from a non-login connection? WTF!" << endl; + break; + } + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) pack->pBuffer; + if (seps->ToID == 0xFFFFFFFF) { // Broadcast + ServerPacket* inpack = new ServerPacket(seps->opcode); + inpack->size = seps->size; + // Little trick here to save a memcpy, be careful if you change this any + inpack->pBuffer = seps->data; + world_list.SendPacketLocal(inpack, this); + inpack->pBuffer = 0; + delete inpack; + } + else { + LWorld* world = world_list.FindByID(seps->ToID); + if (world) { + ServerPacket* inpack = new ServerPacket(seps->opcode); + inpack->size = seps->size; + // Little trick here to save a memcpy, be careful if you change this any + inpack->pBuffer = seps->data; + world->SendPacket(inpack); + inpack->pBuffer = 0; + delete inpack; + } + } + if (net.GetLoginMode() != Mesh) + world_list.SendPacketLogin(pack, this); + break; + } + default: + { + cout << "Unknown LoginSOPcode: 0x" << hex << (int)pack->opcode << dec; + cout << " size:" << pack->size << " from " << GetAccount() << endl; + DumpPacket(pack->pBuffer, pack->size); + //Kick("Possible Hacking Attempt"); + break; + } + } + delete pack; + } + return ret; +} + +void LWorld::SendPacket(ServerPacket* pack) { + if (Link == 0) + return; + if (RemoteID) { + ServerPacket* outpack = new ServerPacket(ServerOP_EncapPacket, sizeof(ServerEncapPacket_Struct) + pack->size); + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) outpack->pBuffer; + seps->ToID = RemoteID; + seps->opcode = pack->opcode; + seps->size = pack->size; + memcpy(seps->data, pack->pBuffer, pack->size); + Link->SendPacket(outpack); + delete outpack; + } + else { + Link->SendPacket(pack); + } +} + +void LWorld::Message(const char* to, const char* message, ...) { + va_list argptr; + char buffer[256]; + + va_start(argptr, message); + vsnprintf(buffer, 256, message, argptr); + va_end(argptr); + + ServerPacket* pack = new ServerPacket(ServerOP_EmoteMessage, sizeof(ServerEmoteMessage_Struct) + strlen(buffer) + 1); + ServerEmoteMessage_Struct* sem = (ServerEmoteMessage_Struct*) pack->pBuffer; + strcpy(sem->to, to); + strcpy(sem->message, buffer); + SendPacket(pack); + delete pack; +} + +void LWorld::Kick(const char* message, bool iSetKickedFlag) { + if (iSetKickedFlag) + kicked = true; + if (message) { + ServerPacket* pack = new ServerPacket(ServerOP_LSFatalError, strlen(message) + 1); + strcpy((char*) pack->pBuffer, message); + SendPacket(pack); + delete pack; + } + if (Link && GetRemoteID() == 0) + Link->Disconnect(); +} +bool LWorld::CheckServerName(const char* name) { + if (strlen(name) < 10) + return false; + for (size_t i=0; i= 'a' && name[i] <= 'z') || (name[i] >= 'A' && name[i] <= 'Z') || (name[i] >= '0' && name[i] <= '9') || name[i] == ' ' || name[i] == '\'' || name[i] == '-' || name[i] == '(' || name[i] == ')' || name[i] == '[' || name[i] == ']' || name[i] == '/' || name[i] == '.' || name[i] == ',' || name[i] == '_' || name[i] == '+' || name[i] == '=' || name[i] == ':' || name[i] == '~')) + return false; + } + return true; +} +bool LWorld::SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version) { + if (strlen(in_worldaddress) > 3) { + isaddressip = false; + strcpy(address, in_worldaddress); + } + if (strlen(in_worldname) > 3) { + char tmpAccount[30]; + memcpy(tmpAccount, in_account, 29); + tmpAccount[29] = '\0'; + + int32 id = database.CheckServerAccount(tmpAccount, in_password); + + if(id == 0) + return false; + if(database.IsServerAccountDisabled(tmpAccount) || database.IsIPBanned(IPAddr) || (isaddressip && database.IsIPBanned(address))) + return false; + + LWorld* world = world_list.FindByID(id); + if(world) + world->Kick("Ghost Kick!"); + + ID = id; + accountid = id; + strncpy(account,tmpAccount,30); + char* name = database.GetServerAccountName(id); + if(name) + snprintf(worldname, (sizeof(worldname)) - 1, "%s", name); + else{ //failed to get account + account[0] = 0; + IsInit = false; + this->Kick ( "Could not load server information." ); + return false; + } + //world_list.KickGhostIP(GetIP(), this); + IsInit = true; + ptype = World; + world_list.SendWorldChanged(id, true); + } + else { + // name too short + account[0] = 0; + IsInit = false; + return false; + } + + database.UpdateWorldVersion(GetAccountID(), in_version); + pStatsTimer = new Timer ( 60000 ); + pStatsTimer->Start ( 60000 ); + + return true; +} +void LWorldList::SendWorldChanged(int32 server_id, bool sendtoallclients, Client* sendto){ + EQ2Packet* outapp = new EQ2Packet(OP_WorldStatusChangeMsg, 0, sizeof(LS_WorldStatusChanged)); + LS_WorldStatusChanged* world_changed = (LS_WorldStatusChanged*)outapp->pBuffer; + + world_changed->server_id = server_id; + LWorld* world = world_list.FindByID(server_id); + if(!world || world->ShowDown()) + world_changed->up = 0; + else + world_changed->up = 1; + if(sendtoallclients || sendto == 0) + client_list.SendPacketToAllClients(outapp); + else + sendto->QueuePacket(outapp); + world_list.SetUpdateServerList(true); +} +void LWorld::UpdateWorldList(LWorld* to) { + world_list.SetUpdateServerList( true ); +} + +void LWorld::ChangeToPlaceholder() { + ip = 0; + status = -1; + pPlaceholder = true; + if (Link != 0 && RemoteID == 0) { + Link->Disconnect(); + } + UpdateWorldList(); +} + +void LWorld::SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones) { + ip = in_ip; + accountid = in_accountid; +// strcpy(account, in_account); + strcpy(worldname, in_name); + strcpy(address, in_address); + status = in_status; + admin_id = in_adminid; + num_players = in_players; + num_zones = in_zones; +} + + +LWorldList::LWorldList() { + server_update_thread = true; + NextID = 1; + tcplistener = new TCPServer(net.GetPort(), true); + if (net.GetLoginMode() == Slave) + OutLink = new TCPConnection(true); + else + OutLink = 0; + + UpdateServerList = true; + #ifdef WIN32 + _beginthread(ServerUpdateLoop, 0, this); + #else + pthread_t thread; + pthread_create(&thread, NULL, &ServerUpdateLoop, this); + #endif +} + +LWorldList::~LWorldList() { + server_update_thread = false; + while(!server_update_thread){ + Sleep(100); + } + safe_delete(tcplistener); + safe_delete(OutLink); +} + +void LWorldList::Shutdown() { + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + iterator.RemoveCurrent ( ); + } + + safe_delete(tcplistener); +} + +void LWorldList::Add(LWorld* worldserver) { + LWorld* worldExist = FindByID(worldserver->GetID ( ) ); + if( worldExist ) + { + worldExist->Kick(); + MWorldMap.writelock(); + worldmap.erase(worldExist->GetID()); + MWorldMap.releasewritelock(); + safe_delete(worldExist); + } + MWorldMap.writelock(); + worldmap[worldserver->GetID()] = worldserver; + MWorldMap.releasewritelock(); + database.ResetWorldServerStatsConnectedTime(worldserver); + database.UpdateWorldIPAddress(worldserver->GetID(), worldserver->GetIP()); +} + +void LWorldList::AddInitiateWorld ( LWorld* world ) +{ + list.Insert ( world ); +} + +void LWorldList::KickGhostIP(int32 ip, LWorld* NotMe, int16 iClientPort) { + if (ip == 0) + return; + + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (!world->IsKicked() && world->GetIP() == ip && world != NotMe) { + if ((iClientPort == 0 && world->GetType() == World) || (iClientPort != 0 && world->GetClientPort() == iClientPort)) { + struct in_addr in; + in.s_addr = world->GetIP(); + // cout << "Removing GhostIP LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()); + if (!world->Connected()) + { + // cout << " (it wasnt connected)"; + // cout << endl; + if (NotMe) { + in.s_addr = NotMe->GetIP(); + cout << "NotMe(" << NotMe->GetID() << ") = " << inet_ntoa(in) << ":" << NotMe->GetPort() << " (" << NotMe->GetClientPort() << ")" << endl; + } + world->Kick("Ghost IP kick"); + } + } + } + } + MWorldMap.releasereadlock(); +} + +void LWorldList::KickGhost(ConType in_type, int32 in_accountid, LWorld* ButNotMe) { + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (!world->IsKicked() && world->GetType() == in_type && world != ButNotMe && (in_accountid == 0 || world->GetAccountID() == in_accountid)) { + if (world->GetIP() != 0) { + //struct in_addr in; + //in.s_addr = world->GetIP(); + // cout << "Removing GhostAcc LWorld(" << world->GetID() << ") from ip: " << inet_ntoa(in) << " port: " << (int16)(world->GetPort()) << endl; + } + if (world->GetType() == Login && world->IsOutgoingUplink()) { + world->Kick("Ghost Acc Kick", false); + // cout << "softkick" << endl; + } + else + world->Kick("Ghost Acc Kick"); + } + } + MWorldMap.releasereadlock(); +} + +void LWorldList::UpdateWorldStats(){ + map::iterator map_list; + MWorldMap.readlock(); + for(map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if(world && world->GetAccountID() > 0) + database.UpdateWorldServerStats(world, world->GetStatus()); + } + MWorldMap.releasereadlock(); +} + +void LWorldList::Process() { + TCPConnection* newtcp = 0; + LWorld* newworld = 0; + + LinkedListIterator iterator(list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + if(iterator.GetData( )->GetID ( ) > 0 ) + { + LWorld* world = iterator.GetData ( ); + iterator.RemoveCurrent ( false ); + Add( world ); + } + else + { + if(! iterator.GetData ( )->Process ( ) ) + iterator.RemoveCurrent ( ); + else + iterator.Advance(); + } + } + + while ((newtcp = tcplistener->NewQueuePop())) { + newworld = new LWorld(newtcp); + newworld->SetID(0); + AddInitiateWorld(newworld); + struct in_addr in; + in.s_addr = newtcp->GetrIP(); + LogWrite(LOGIN__INFO, 0, "Login", "New Server connection: %s port %i", inet_ntoa(in), ntohs(newtcp->GetrPort())); + net.numservers++; + net.UpdateWindowTitle(); + world_list.UpdateWorldList(); + } + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); ) { + LWorld* world = map_list->second; + + int32 account_id = world->GetAccountID(); + + if (world->IsKicked() && !world->IsNeverKick()) { + map_list++; + worldmap.erase ( account_id ); + net.numservers--; + net.UpdateWindowTitle(); + safe_delete ( world ); + continue; + } + else if (!world->Process()) { + //struct in_addr in; + //in.s_addr = world->GetIP(); + if (world->GetAccountID() == 0 || !(world->ShowDown()) || world->GetType() == Chat) { + map_list++; + worldmap.erase ( account_id ); + + net.numservers--; + net.UpdateWindowTitle(); + if(account_id > 0){ + LWorld* world2 = FindByID(account_id); + if(world2) + world2->ShowDownActive(true); + } + SendWorldChanged(account_id, true); + safe_delete ( world ); + continue; + } + else { + world->ChangeToPlaceholder(); + } + } + map_list++; + } +} + +// Sends packet to all World and Chat servers, local and remote (but not to remote login server's ::Process()) +void LWorldList::SendPacket(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != butnotme) { + if (world->GetType() == Login) { + ServerPacket* outpack = new ServerPacket(ServerOP_EncapPacket, sizeof(ServerEncapPacket_Struct) + pack->size); + ServerEncapPacket_Struct* seps = (ServerEncapPacket_Struct*) outpack->pBuffer; + seps->ToID = 0xFFFFFFFF; + seps->opcode = pack->opcode; + seps->size = pack->size; + memcpy(seps->data, pack->pBuffer, pack->size); + world->SendPacket(outpack); + delete outpack; + } + else if (world->GetRemoteID() == 0) { + world->SendPacket(pack); + } + } + } +} + +// Sends a packet to every local TCP Connection, all types +void LWorldList::SendPacketLocal(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != butnotme && world->GetRemoteID() == 0) { + world->SendPacket(pack); + } + } +} + +// Sends the packet to all login servers +void LWorldList::SendPacketLogin(ServerPacket* pack, LWorld* butnotme) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++ ) { + LWorld* world = map_list->second; + if (world != butnotme && world->GetType() == Login) { + world->SendPacket(pack); + } + } +} + +void LWorldList::UpdateWorldList(LWorld* to) { + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (net.GetLoginMode() != Mesh || world->GetRemoteID() == 0) + world->UpdateWorldList(to); + } +} + +LWorld* LWorldList::FindByID(int32 LWorldID) { + if(worldmap.count(LWorldID) > 0) + return worldmap[LWorldID]; + return 0; +} + +LWorld* LWorldList::FindByIP(int32 ip) { + map::iterator map_list; + LWorld* world = 0; + LWorld* ret = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetIP() == ip){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByAddress(char* address) { + map::iterator map_list; + LWorld* world = 0; + LWorld* ret = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (strcasecmp(world->GetAddress(), address) == 0){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByLink(TCPConnection* in_link, int32 in_id) { + if (in_link == 0) + return 0; + LWorld* world = 0; + LWorld* ret = 0; + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetLink() == in_link && world->GetRemoteID() == in_id){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +LWorld* LWorldList::FindByAccount(int32 in_accountid, ConType in_type) { + if (in_accountid == 0) + return 0; + LWorld* world = 0; + LWorld* ret = 0; + map::iterator map_list; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + world = map_list->second; + if (world->GetAccountID() == in_accountid && world->GetType() == in_type){ + ret = world; + break; + } + } + MWorldMap.releasereadlock(); + return ret; +} + +int8 LWorld::GetWorldStatus(){ + if(IsDevelServer() && IsLocked() == false) + return 1; + else if(IsInit && IsLocked() == false) + return 0; + else + return 2; +} + +void LWorld::SendDeleteCharacter ( int32 char_id , int32 account_id ) +{ + ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct)); + CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer; + cdu->account_id = account_id; + cdu->char_id = char_id; + cdu->update_field = DELETE_UPDATE_FLAG; + cdu->update_data = 1; + SendPacket(outpack); +} + +vector* LWorldList::GetServerListUpdate(int16 version){ + vector* ret = new vector; + map::iterator map_list; + PacketStruct* packet = 0; + MWorldMap.readlock(); + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + packet = configReader.getStruct("LS_WorldUpdate", version); + if(packet){ + packet->setDataByName("server_id", world->GetID()); + packet->setDataByName("up", 1); + if(world->IsLocked()) + packet->setDataByName("locked", 1); + ret->push_back(packet); + } + } + } + MWorldMap.releasereadlock(); + return ret; +} + +EQ2Packet* LWorldList::MakeServerListPacket(int8 lsadmin, int16 version) { + + // if the latest world list has already been loaded, just return the string + MWorldMap.readlock(); + if (!UpdateServerList && ServerListData.count(version)) + { + MWorldMap.releasereadlock(); + return ServerListData[version]; + } + + //LWorld* world = 0; + int32 ServerNum = 0; + /* while(iterator.MoreElements()){ + world = iterator.GetData(); + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) + ServerNum++; + iterator.Advance(); + } + ServerNum+=3; + */ + uint32 tmpCount = 0; + map::iterator map_list; + for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + tmpCount++; + } + } + + PacketStruct* packet = configReader.getStruct("LS_WorldList", version); + packet->setArrayLengthByName("num_worlds", tmpCount); + + string world_data; + for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if ((world->IsInit || (world->ShowDown() && world->ShowDownActive())) && world->GetType() == World) { + ServerNum++; + packet->setArrayDataByName("id", world->GetID(), ServerNum - 1); + + if (version <= 283) { + packet->setArrayDataByName("name", world->GetName(), ServerNum - 1); + if (!world->ShowDown()) + packet->setArrayDataByName("online", 1, ServerNum - 1); + if (world->IsLocked()) + packet->setArrayDataByName("locked", 1, ServerNum - 1); + packet->setArrayDataByName("unknown2", 1, ServerNum - 1); + packet->setArrayDataByName("unknown3", 1, ServerNum - 1); + packet->setArrayDataByName("load", world->GetWorldStatus(), ServerNum - 1); + } + else + { + if (version < 1212) + packet->setArrayDataByName("allowed_races", 0xFFFFFFFF, ServerNum - 1); + else if (version < 60006) + packet->setArrayDataByName("allowed_races", 0x000FFFFF, ServerNum - 1); // + Freeblood + else + packet->setArrayDataByName("allowed_races", 0x001FFFFF, ServerNum - 1); // + Aerakyn + + packet->setArrayDataByName("number_online_flag", 1, ServerNum - 1); + packet->setArrayDataByName("num_players", world->GetPlayerNum(), ServerNum - 1); + packet->setArrayDataByName("name", world->GetName(), ServerNum - 1); + packet->setArrayDataByName("name2", world->GetName(), ServerNum - 1); + packet->setArrayDataByName("feature_set", 0, ServerNum - 1); + + packet->setArrayDataByName("load", world->GetWorldStatus(), ServerNum - 1); + if (world->IsLocked()) + packet->setArrayDataByName("locked", 1, ServerNum - 1); + + if (world->ShowDown()) + packet->setArrayDataByName("tag", 0, ServerNum - 1); + else + packet->setArrayDataByName("tag", 1, ServerNum - 1); + + if (version < 1212) + packet->setArrayDataByName("unknown", ServerNum, ServerNum - 1); + } + } + } + + EQ2Packet* pack = packet->serialize(); + #ifdef DEBUG + //Only dump these for people trying to debug this... + printf("WorldList:\n"); + DumpPacket(pack->pBuffer, pack->size); + #endif + if (ServerListData.count(version)) + { + map::iterator it = ServerListData.find(version); + EQ2Packet* tmpPack = ServerListData[version]; + safe_delete(tmpPack); + ServerListData.erase(it); + } + ServerListData.insert(make_pair(version, pack)); + MWorldMap.releasereadlock(); + + SetUpdateServerList(false); + + return ServerListData[version]; +} + +void LWorldList::SendWorldStatus(LWorld* chat, char* adminname) { + struct in_addr in; + + int32 count = 0; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world->GetIP() != 0 && world->GetType() == World) { + chat->Message(adminname, "Name: %s", world->GetName()); + in.s_addr = world->GetIP(); + if (world->GetAccountID() != 0) { + chat->Message(adminname, " Account: %s", world->GetAccount()); + } + chat->Message(adminname, " Number of Zones: %i", world->GetZoneNum()); + chat->Message(adminname, " Number of Players: %i", world->GetPlayerNum()); + chat->Message(adminname, " IP: %s", inet_ntoa(in)); + if (!world->IsAddressIP()) { + chat->Message(adminname, " Address: %s", world->GetAddress()); + } + count++; + } + } + chat->Message(adminname, "%i worlds listed.", count); +} + +void LWorldList::RemoveByLink(TCPConnection* in_link, int32 in_id, LWorld* ButNotMe) { + if (in_link == 0) + return; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world != ButNotMe && world->GetLink() == in_link && (in_id == 0 || world->GetRemoteID() == in_id)) { + // world->Link = 0; + map_list++; + worldmap.erase ( world->GetID ( ) ); + safe_delete ( world ); + continue; + } + } +} + +void LWorldList::RemoveByID(int32 in_id) { + if (in_id == 0) + return; + + LWorld* existWorld = FindByID(in_id); + if ( existWorld != NULL ) + { + MWorldMap.writelock(); + worldmap.erase ( in_id ); + MWorldMap.releasewritelock(); + safe_delete ( existWorld ); + } +} + +bool LWorldList::Init() { + + database.ResetWorldStats ( ); + + if (!tcplistener->IsOpen()) { + return tcplistener->Open(net.GetPort()); + } + + return false; +} + +void LWorldList::InitWorlds(){ + vector server_list; + database.GetServerAccounts(&server_list); + vector::iterator iter; + int i = 0; + for(iter = server_list.begin(); iter != server_list.end(); iter++, i++){ + LWorld* world = FindByID(server_list[i]->GetAccountID()); + if(!world){ + server_list[i]->ShowDown(true); + server_list[i]->ShowDownActive(true); + server_list[i]->SetID ( server_list[i]->GetAccountID ( ) ); + Add ( server_list[i] ); + } + } +} + +int32 LWorldList::GetCount(ConType type) { + int32 count = 0; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + if (world->GetType() == type) { + count++; + } + } + return count; +} + +void LWorldList::ListWorldsToConsole() { + struct in_addr in; + + cout << "World List:" << endl; + cout << "============================" << endl; + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + in.s_addr = world->GetIP(); + if (world->GetType() == World) { + if (world->GetRemoteID() == 0) + cout << "ID: " << world->GetID() << ", Name: " << world->GetName() << ", Local, IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", Status: " << world->GetStatus() << ", AccID: " << world->GetAccountID() << endl; + else + cout << "ID: " << world->GetID() << ", Name: " << world->GetName() << ", RemoteID: " << world->GetRemoteID() << ", LinkWorldID: " << world->GetLinkWorldID() << ", IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", Status: " << world->GetStatus() << ", AccID: " << world->GetAccountID() << endl; + } + else if (world->GetType() == Chat) { + cout << "ID: " << world->GetID() << ", Chat Server, IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + else if (world->GetType() == Login) { + if (world->IsOutgoingUplink()) { + if (world->Connected()) + cout << "ID: " << world->GetID() << ", Login Server (out), IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + else + cout << "ID: " << world->GetID() << ", Login Server (nc), IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + else + cout << "ID: " << world->GetID() << ", Login Server (in), IP: " << inet_ntoa(in) << ":" << world->GetPort() << " (" << world->GetClientPort() << "), AccID: " << world->GetAccountID() << endl; + } + else { + cout << "ID: " << world->GetID() << ", Unknown Type, Name: " << world->GetName() << ", IP: " << inet_ntoa(in) << ":" << world->GetPort() << ", AccID: " << world->GetAccountID() << endl; + } + } + cout << "============================" << endl; +} + +void LWorldList::AddServerZoneUpdates(LWorld* world, map updates){ + int32 server_id = world->GetID(); + map::iterator itr; + for(itr = updates.begin(); itr != updates.end(); itr++){ + if(zone_updates_already_used.size() >= 1500 || zone_updates_already_used[server_id].count(itr->first) > 0){ + world->Kick("Hacking attempt."); + return; + } + zone_updates_already_used[server_id][itr->first] = true; + } + server_zone_updates.Put(server_id, updates); +} +//devn00b temp + +void LWorldList::AddServerEquipmentUpdates(LWorld* world, map updates){ + int32 server_id = world->GetID(); + map::iterator itr; + for(itr = updates.begin(); itr != updates.end(); itr++){ + LogWrite(MISC__TODO, 1, "TODO", "JA: Until we learn what this does, can't risk worlds being kicked performing login appearance updates...\n%s, func: %s, line: %i", __FILE__, __FUNCTION__, __LINE__); + + /*if(equip_updates_already_used.size() >= 1500 || equip_updates_already_used[server_id].count(itr->first) > 0) + { + LogWrite(LOGIN__ERROR, 0, "Login", "World ID '%i': Hacking attempt. (function: %s, line: %i", world->GetAccountID(), __FUNCTION__, __LINE__); + world->Kick("Hacking attempt."); + return; + }*/ + equip_updates_already_used[server_id][itr->first] = true; + } + server_equip_updates.Put(server_id, updates); +} + +void LWorldList::RequestServerUpdates(LWorld* world){ + if(world){ + ServerPacket* pack = new ServerPacket(ServerOP_ZoneUpdates, sizeof(ZoneUpdateRequest_Struct)); + ZoneUpdateRequest_Struct* request = (ZoneUpdateRequest_Struct*)pack->pBuffer; + request->max_per_batch = MAX_UPDATE_COUNT; + world->SendPacket(pack); + delete pack; + zone_update_timeouts.Put(world->GetID(), Timer::GetCurrentTime2() + 30000); + } +} + +void LWorldList::ProcessServerUpdates(){ + MutexMap >::iterator itr = server_zone_updates.begin(); + while(itr.Next()){ + if(itr->second.size() > 0){ + database.SetServerZoneDescriptions(itr->first, itr->second); + if(itr->second.size() == MAX_UPDATE_COUNT) + awaiting_zone_update.Put(itr->first, Timer::GetCurrentTime2() + 10000); //only process 20 updates in a 10 second period to avoid network problems + server_zone_updates.erase(itr->first); + } + if(zone_update_timeouts.count(itr->first) == 0 || zone_update_timeouts.Get(itr->first) <= Timer::GetCurrentTime2()){ + zone_update_timeouts.erase(itr->first); + server_zone_updates.erase(itr->first); + } + } + LWorld* world = 0; + MWorldMap.readlock(); + map::iterator map_itr; + for(map_itr = worldmap.begin(); map_itr != worldmap.end(); map_itr++){ + world = map_itr->second; + if(world && world->GetID()){ + if(last_updated.count(world) == 0 || last_updated.Get(world) <= Timer::GetCurrentTime2()){ + zone_updates_already_used[world->GetID()].clear(); + RequestServerUpdates(world); + last_updated.Put(world, Timer::GetCurrentTime2() + 21600000); + } + if(awaiting_zone_update.count(world->GetID()) > 0 && awaiting_zone_update.Get(world->GetID()) <= Timer::GetCurrentTime2()){ + awaiting_zone_update.erase(world->GetID()); + RequestServerUpdates(world); + } + } + } + ProcessLSEquipUpdates(); + MWorldMap.releasereadlock(); + + +} +void LWorldList::RequestServerEquipUpdates(LWorld* world) +{ + if(world) + { + ServerPacket *pack_equip = new ServerPacket(ServerOP_LoginEquipment, sizeof(EquipmentUpdateRequest_Struct)); + EquipmentUpdateRequest_Struct *request_equip = (EquipmentUpdateRequest_Struct *)pack_equip->pBuffer; + request_equip->max_per_batch = MAX_LOGIN_APPEARANCE_COUNT; // item appearance data smaller, request more at a time? + LogWrite(LOGIN__DEBUG, 1, "Login", "Sending equipment update requests to world: (%s)... (Batch Size: %i)", world->GetName(), request_equip->max_per_batch); + world->SendPacket(pack_equip); + delete pack_equip; + equip_update_timeouts.Put(world->GetID(), Timer::GetCurrentTime2() + 30000); + } +} +void LWorldList::ProcessLSEquipUpdates() +{ + // process login_equipment updates + MutexMap >::iterator itr_equip = server_equip_updates.begin(); + while(itr_equip.Next()) + { + if(itr_equip->second.size() > 0) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Setting Login Appearances..."); + database.SetServerEquipmentAppearances(itr_equip->first, itr_equip->second); + if(itr_equip->second.size() == MAX_LOGIN_APPEARANCE_COUNT) + awaiting_equip_update.Put(itr_equip->first, Timer::GetCurrentTime2() + 10000); //only process 100 updates in a 10 second period to avoid network problems + server_equip_updates.erase(itr_equip->first); + } + if(equip_update_timeouts.count(itr_equip->first) == 0 || equip_update_timeouts.Get(itr_equip->first) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Clearing Login Appearances Update Timers..."); + equip_update_timeouts.erase(itr_equip->first); + server_equip_updates.erase(itr_equip->first); + } + } + LWorld* world = 0; + MWorldMap.readlock(); + map::iterator map_itr; + for(map_itr = worldmap.begin(); map_itr != worldmap.end(); map_itr++) + { + world = map_itr->second; + if(world && world->GetID()) + { + if(last_equip_updated.count(world) == 0 || last_equip_updated.Get(world) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Clearing Login Appearances Update Counters..."); + equip_updates_already_used[world->GetID()].clear(); + RequestServerEquipUpdates(world); + last_equip_updated.Put(world, Timer::GetCurrentTime2() + 900000); // every 15 mins + } + if( awaiting_equip_update.count(world->GetID()) > 0 && awaiting_equip_update.Get(world->GetID()) <= Timer::GetCurrentTime2()) + { + LogWrite(LOGIN__DEBUG, 1, "Login", "Erase awaiting equip updates..."); + awaiting_equip_update.erase(world->GetID()); + RequestServerEquipUpdates(world); + } + } + } + MWorldMap.releasereadlock(); +} + + +ThreadReturnType ServerUpdateLoop(void* tmp) { +#ifdef WIN32 + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + if (tmp == 0) { + ThrowError("ServerUpdateLoop(): tmp = 0!"); + THREAD_RETURN(NULL); + } + LWorldList* worldList = (LWorldList*) tmp; + while (worldList->ContinueServerUpdates()) { + Sleep(1000); + worldList->ProcessServerUpdates(); + } + worldList->ResetServerUpdates(); + THREAD_RETURN(NULL); +} diff --git a/old/login/LWorld.h b/old/login/LWorld.h new file mode 100644 index 0000000..d9e3361 --- /dev/null +++ b/old/login/LWorld.h @@ -0,0 +1,253 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef LWORLD_H +#define LWORLD_H + +#include "../common/Mutex.h" + +#define ERROR_BADPASSWORD "Bad password" +#define INVALID_ACCOUNT "Invalid Server Account." +#define ERROR_BADVERSION "Incorrect version" +#define ERROR_UNNAMED "Unnamed servers not allowed to connect to full login servers" +#define ERROR_NOTMASTER "Not a master server" +#define ERROR_NOTMESH "Not a mesh server" +#define ERROR_GHOST "Ghost kick" +#define ERROR_UnknownServerType "Unknown Server Type" +#define ERROR_BADNAME_SERVER "Bad server name, name may not contain the word \"Server\" (case sensitive)" +#define ERROR_BADNAME "Bad server name. Unknown reason." + +#define WORLD_NAME_SUFFIX " Server" + +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from + +#include "../common/linked_list.h" +#include "../WorldServer/MutexList.h" +#include "../WorldServer/MutexMap.h" +#include "../common/timer.h" +#include "../common/types.h" +#include "../common/queue.h" +#include "../common/servertalk.h" +#include "../common/TCPConnection.h" +#include "client.h" + +#define MAX_UPDATE_COUNT 20 +#define MAX_LOGIN_APPEARANCE_COUNT 100 + +#ifdef WIN32 + void ServerUpdateLoop(void* tmp); +#else + void* ServerUpdateLoop(void* tmp); +#endif + +enum ConType { UnknownW, World, Chat, Login }; + +class LWorld +{ +public: + LWorld(TCPConnection* in_con, bool OutgoingLoginUplink = false, int32 iIP = 0, int16 iPort = 0, bool iNeverKick = false); + LWorld(int32 in_accountid, char* in_accountname, char* in_worldname, int32 in_admin_id); + LWorld(TCPConnection* in_RemoteLink, int32 in_ip, int32 in_RemoteID, int32 in_accountid, char* in_accountname, char* in_worldname, char* in_address, sint32 in_status, int32 in_adminid, bool in_showdown, int8 in_authlevel, bool in_placeholder, int32 iLinkWorldID); + ~LWorld(); + + static bool CheckServerName(const char* name); + + bool Process(); + void SendPacket(ServerPacket* pack); + void Message(const char* to, const char* message, ...); + + bool SetupWorld(char* in_worldname, char* in_worldaddress, char* in_account, char* in_password, char* in_version); + void UpdateStatus(sint32 in_status, sint32 in_players, sint32 in_zones, int8 in_level) { + // we don't want the server list to update unless something has changed + if(status != in_status || num_players != in_players || num_zones != in_zones || world_max_level != in_level) + { + status = in_status; + num_players = in_players; + num_zones = in_zones; + world_max_level = in_level; + UpdateWorldList(); + } + } + void UpdateWorldList(LWorld* to = 0); + void SetRemoteInfo(int32 in_ip, int32 in_accountid, char* in_account, char* in_name, char* in_address, int32 in_status, int32 in_adminid, sint32 in_players, sint32 in_zones); + + inline bool IsPlaceholder() { return pPlaceholder; } + inline int32 GetAccountID() { return accountid; } + inline char* GetAccount() { return account; } + inline char* GetAddress() { return address; } + inline int16 GetClientPort() { return pClientPort; } + inline bool IsAddressIP() { return isaddressip; } + inline char* GetName() { return worldname; } + inline sint32 GetStatus() { return status; } + bool IsLocked() { return status==-2; } + inline int32 GetIP() { return ip; } + inline int16 GetPort() { return port; } + inline int32 GetID() { return ID; } + inline int32 GetAdmin() { return admin_id; } + inline bool ShowDown() { return pshowdown; } + inline bool ShowDownActive(){ return show_down_active; } + void ShowDownActive(bool show){ show_down_active = show; } + void ShowDown(bool show){ pshowdown = show; } + inline bool Connected() { return pConnected; } + int8 GetWorldStatus(); + + void ChangeToPlaceholder(); + void Kick(const char* message = ERROR_GHOST, bool iSetKickedFlag = true); + inline bool IsKicked() { return kicked; } + inline bool IsNeverKick() { return pNeverKick; } + inline ConType GetType() { return ptype; } + inline bool IsOutgoingUplink() { return OutgoingUplink; } + inline TCPConnection* GetLink() { return Link; } + inline int32 GetRemoteID() { return RemoteID; } + inline int32 GetLinkWorldID() { return LinkWorldID; } + inline sint32 GetZoneNum() { return num_zones; } + inline sint32 GetPlayerNum() { return num_players; } + void SetID(int32 new_id) { ID = new_id; } + + void SendDeleteCharacter( int32 char_id, int32 account_id ); + bool IsDevelServer(){ return devel_server; } + + inline int8 GetMaxWorldLevel() { return world_max_level; } + + bool IsInit; +protected: + friend class LWorldList; + TCPConnection* Link; + Timer* pReconnectTimer; + Timer* pStatsTimer; +private: + int32 ID; + int32 ip; + char IPAddr[64]; + int16 port; + bool kicked; + bool pNeverKick; + bool pPlaceholder; + bool devel_server; + + int32 accountid; + char account[30]; + char address[250]; + bool isAuthenticated; + int16 pClientPort; + bool isaddressip; + char worldname[200]; + sint32 status; + int32 admin_id; + bool pshowdown; + bool show_down_active; + ConType ptype; + bool OutgoingUplink; + bool pConnected; + sint32 num_players; + sint32 num_zones; + int32 RemoteID; + int32 LinkWorldID; + int8 world_max_level; + +}; + +class LWorldList +{ +public: + + LWorldList(); + ~LWorldList(); + + LWorld* FindByID(int32 WorldID); + LWorld* FindByIP(int32 ip); + LWorld* FindByAddress(char* address); + LWorld* FindByLink(TCPConnection* in_link, int32 in_id); + LWorld* FindByAccount(int32 in_accountid, ConType in_type = World); + + void Add(LWorld* worldserver); + void AddInitiateWorld ( LWorld* world ); + void Process(); + void ReceiveData(); + void SendPacket(ServerPacket* pack, LWorld* butnotme = 0); + void SendPacketLocal(ServerPacket* pack, LWorld* butnotme = 0); + void SendPacketLogin(ServerPacket* pack, LWorld* butnotme = 0); + void SendWorldChanged(int32 server_id, bool sendtoallclients=false, Client* sendto = 0); + vector* GetServerListUpdate(int16 version); + EQ2Packet* MakeServerListPacket(int8 lsadmin, int16 version); + + void UpdateWorldList(LWorld* to = 0); + void UpdateWorldStats(); + void KickGhost(ConType in_type, int32 in_accountid = 0, LWorld* ButNotMe = 0); + void KickGhostIP(int32 ip, LWorld* NotMe = 0, int16 iClientPort = 0); + void RemoveByLink(TCPConnection* in_link, int32 in_id = 0, LWorld* ButNotMe = 0); + void RemoveByID(int32 in_id); + + void SendWorldStatus(LWorld* chat, char* adminname); + + void ConnectUplink(); + bool Init(); + void InitWorlds(); + void Shutdown(); + bool WriteXML(); + + int32 GetCount(ConType type); + void PopulateWorldList(http::response& res); + + void ListWorldsToConsole(); + //devn00b temp + void AddServerEquipmentUpdates(LWorld* world, map updates); + void ProcessLSEquipUpdates(); + void RequestServerEquipUpdates(LWorld* world); + + void SetUpdateServerList ( bool var ) { UpdateServerList = var; } + bool ContinueServerUpdates(){ return server_update_thread; } + void ResetServerUpdates(){server_update_thread = true;} + void ProcessServerUpdates(); + void RequestServerUpdates(LWorld* world); + void AddServerZoneUpdates(LWorld* world, map updates); + +protected: + friend class LWorld; + int32 GetNextID() { return NextID++; } + +private: + Mutex MWorldMap; + map > zone_updates_already_used; //used to determine if someone is trying to DOS us + MutexMap zone_update_timeouts; + MutexMap awaiting_zone_update; + MutexMap last_updated; + MutexMap > server_zone_updates; + bool server_update_thread; + int32 NextID; + + LinkedList list; + + map worldmap; + + TCPServer* tcplistener; + TCPConnection* OutLink; + + //devn00b temp + // JohnAdams: login appearances, copied from above + map > equip_updates_already_used; + MutexMap equip_update_timeouts; + MutexMap awaiting_equip_update; + MutexMap last_equip_updated; + MutexMap > server_equip_updates; + // + /// + + // holds the world server list so we don't have to create it for every character + // logging in + map ServerListData; + bool UpdateServerList; +}; +#endif diff --git a/old/login/LoginAccount.cpp b/old/login/LoginAccount.cpp new file mode 100644 index 0000000..228f0b5 --- /dev/null +++ b/old/login/LoginAccount.cpp @@ -0,0 +1,58 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "LoginAccount.h" + +LoginAccount::LoginAccount(){ +} +LoginAccount::~LoginAccount(){ + vector::iterator iter; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + safe_delete(*iter); + } +} + +void LoginAccount::flushCharacters ( ) +{ + vector::iterator iter; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + safe_delete(*iter); + } + + charlist.clear ( ); +} + +CharSelectProfile* LoginAccount::getCharacter(char* name){ + vector::iterator char_iterator; + CharSelectProfile* profile = 0; + EQ2_16BitString temp; + for(char_iterator = charlist.begin(); char_iterator != charlist.end(); char_iterator++){ + profile = *char_iterator; + temp = profile->packet->getType_EQ2_16BitString_ByName("name"); + if(strcmp(temp.data.c_str(), name)==0) + return profile; + } + return 0; +} +void LoginAccount::removeCharacter(char* name, int16 version){ + vector::iterator iter; + CharSelectProfile* profile = 0; + EQ2_16BitString temp; + for(iter = charlist.begin(); iter != charlist.end(); iter++){ + profile = *iter; + temp = profile->packet->getType_EQ2_16BitString_ByName("name"); + if(strcmp(temp.data.c_str(), name)==0){ + if(version <= 561) { + profile->deleted = true; // workaround for char select crash on old clients + } + else { + safe_delete(*iter); + charlist.erase(iter); + } + return; + } + } +} \ No newline at end of file diff --git a/old/login/LoginAccount.h b/old/login/LoginAccount.h new file mode 100644 index 0000000..b5bb540 --- /dev/null +++ b/old/login/LoginAccount.h @@ -0,0 +1,54 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef _LOGINACCOUNT_ +#define _LOGINACCOUNT_ + +#include +#include +#include +#include "../common/linked_list.h" +#include "PacketHeaders.h" +#include "../common/PacketStruct.h" + +using namespace std; +class LoginAccount { +public: + LoginAccount(); + LoginAccount(int32 id, const char* in_name, const char* in_pass){ + account_id = id; + strcpy(name, in_name); + strcpy(password, in_pass); + } + ~LoginAccount(); + bool SaveAccount(LoginAccount* acct); + vector charlist; + void setName(const char* in_name) { strcpy(name, in_name); } + void setPassword(const char* in_pass) { strcpy(password, in_pass); } + void setAuthenticated(bool in_auth) { authenticated=in_auth; } + void setAccountID(int32 id){ account_id = id; } + void addCharacter(CharSelectProfile* profile){ + charlist.push_back(profile); + } + void removeCharacter(PacketStruct* profile); + void removeCharacter(char* name, int16 version); + void serializeCharacter(uchar* buffer, CharSelectProfile* profile); + + void flushCharacters ( ); + + CharSelectProfile* getCharacter(char* name); + int32 getLoginAccountID(){ return account_id; } + char* getLoginName() { return name; } + char* getLoginPassword() { return password; } + bool getLoginAuthenticated() { return authenticated; } + +private: + int32 account_id; + char name[32]; + char password[32]; + bool authenticated; +}; +#endif \ No newline at end of file diff --git a/old/login/LoginDatabase.cpp b/old/login/LoginDatabase.cpp new file mode 100644 index 0000000..80bfe98 --- /dev/null +++ b/old/login/LoginDatabase.cpp @@ -0,0 +1,1083 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" + +#include +#include +#include +using namespace std; + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define snprintf _snprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include "../common/unix.h" +#include +#endif + +#include "../common/Log.h" +#include "../common/DatabaseNew.h" +#include "LoginDatabase.h" +#include "LoginAccount.h" +#include "../common/MiscFunctions.h" +#include "../common/packet_functions.h" +#include "../common/packet_dump.h" +#include "LWorld.h" + +extern LoginDatabase database; +extern LWorldList world_list; + + +bool LoginDatabase::ConnectNewDatabase() { + return dbLogin.Connect(); +} + +void LoginDatabase::RemoveDeletedCharacterData() +{ + dbLogin.Query("DELETE FROM login_char_colors WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)"); + dbLogin.Query("DELETE FROM login_equipment WHERE login_characters_id IN (SELECT id FROM login_characters WHERE deleted = 1)"); +} + +void LoginDatabase::SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet){ + if(packet){ + Query query; + MYSQL_RES* result = 0; + + if ( version >= 1212 ) + result = query.RunQuery2(Q_SELECT, "SELECT name, description from ls_world_zones where server_id=%i and zone_id=%i", server_id, zone_id); + + MYSQL_ROW row; + if(result && (row = mysql_fetch_row(result))) { + if (row[0]) + packet->setMediumStringByName("zone", row[0]); + else + packet->setMediumStringByName("zone", " "); + if(row[1]) + packet->setMediumStringByName("zonedesc", row[1]); + else + packet->setMediumStringByName("zonedesc", " "); + } + else{ + Query query2; + MYSQL_RES* result2 = 0; + + if (version < 1212) + result2 = query2.RunQuery2(Q_SELECT, "SELECT file, description from zones where id=%i", zone_id); + else + result2 = query2.RunQuery2(Q_SELECT, "SELECT name, description from zones where id=%i", zone_id); + + MYSQL_ROW row2; + if(result2 && (row2 = mysql_fetch_row(result2))) { + + if (version < 546 && version > 561 && version < 1212) + { + if (row2[0]) + { + int len = strlen(row2[0]); + char* zoneName = new char[len + 2]; + strncpy(zoneName, row2[0], len); + zoneName[len] = 0x2E; + zoneName[len + 1] = 0x30; + + packet->setMediumStringByName("zone", zoneName); + safe_delete_array(zoneName); + } + else + packet->setMediumStringByName("zone", ".0"); + } + else + { + if (row2[0]) + packet->setMediumStringByName("zone", row2[0]); + else + packet->setMediumStringByName("zone", " "); + } + if(row2[1]) + packet->setMediumStringByName("zonedesc", row2[1]); + else + packet->setMediumStringByName("zonedesc", " "); + } + } + packet->setMediumStringByName("zonename2"," "); + } +} + +string LoginDatabase::GetZoneDescription(char* name){ + string ret; + Query query; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT description from zones where file=substring_index('%s', '.', 1)", query.escaped_name); + MYSQL_ROW row; + if((row = mysql_fetch_row(result))) { + ret = string(row[0]); + } + return ret; +} + + +int32 LoginDatabase::GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id) +{ + int32 ret; + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id FROM login_characters WHERE server_id = %u AND char_id = %u AND deleted = 0 LIMIT 0,1", server_id, char_id); + MYSQL_ROW row; + if((row = mysql_fetch_row(result))) { + ret = atoi(row[0]); + } + + return ret; +} + +void LoginDatabase::SetServerEquipmentAppearances(int32 server_id, map equip_updates) +{ + + if(equip_updates.size() > 0) + { + + LogWrite(LOGIN__DEBUG, 0, "Login", "Saving appearance info from world %u...", server_id); + + map::iterator equip_itr; + stringstream ss; + ss << "replace into login_equipment (login_characters_id, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue, slot) values"; + int count=0; + int32 char_id = 0; + + for(equip_itr = equip_updates.begin(); equip_itr != equip_updates.end(); equip_itr++) + { + char_id = GetLoginCharacterIDFromWorldCharID(server_id, (int32)equip_itr->second.world_char_id); + + if( char_id == 0 ) // invalid character/world match + continue; + + LogWrite(LOGIN__DEBUG, 5, "Login", "--Processing character %u, slot %i", char_id, (int32)equip_itr->second.slot); + + if(count > 0) + ss << ", "; + + ss << "(" << char_id << ", "; + ss << (int32)equip_itr->second.equip_type << ", "; + ss << (int32)equip_itr->second.red << ", "; + ss << (int32)equip_itr->second.green << ", "; + ss << (int32)equip_itr->second.blue << ", "; + ss << (int32)equip_itr->second.highlight_red << ", "; + ss << (int32)equip_itr->second.highlight_green << ", "; + ss << (int32)equip_itr->second.highlight_blue << ", "; + ss << (int32)equip_itr->second.slot << ")"; + + count++; + } + + Query query; + query.RunQuery2(ss.str(), Q_REPLACE); + + if (query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF) + LogWrite(LOGIN__ERROR, 0, "Login", "Error saving login_equipment data Error: ", query.GetError()); + } +} + + +void LoginDatabase::SetServerZoneDescriptions(int32 server_id, map zone_descriptions){ + if(zone_descriptions.size() > 0){ + map::iterator zone_itr; + string query_string = "replace into ls_world_zones (server_id, zone_id, name, description) values"; + int count=0; + char server_id_str[12] = {0}; + sprintf(server_id_str, "%i", server_id); + for(zone_itr = zone_descriptions.begin(); zone_itr != zone_descriptions.end(); zone_itr++, count++){ + char zone_id_str[12] = {0}; + sprintf(zone_id_str, "%i", zone_itr->first); + if(count > 0) + query_string.append(", "); + query_string.append("(").append(server_id_str).append(","); + query_string.append(zone_id_str).append(","); + query_string.append("'").append(getSafeEscapeString(zone_itr->second.name.c_str()).c_str()).append("', '"); + query_string.append(getSafeEscapeString(zone_itr->second.description.c_str()).c_str()).append("')"); + } + Query query; + query.RunQuery2(query_string, Q_REPLACE); + } +} + +//this is really just for the version that doesn't send the server id in its play request +int32 LoginDatabase::GetServer(int32 accountID, int32 charID, string name) { + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT server_id from login_characters where account_id=%i and char_id=%i and name='%s'", accountID, charID, query.escaped_name); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +void LoginDatabase::LoadCharacters(LoginAccount* acct, int16 version){ + if(acct != NULL) + acct->flushCharacters ( ); + + Query query; + Query query2; + int32 id = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lc.char_id, lc.server_id, lc.name, lc.race, lc.class, lc.gender, lc.deity, lc.body_size, lc.body_age, lc.current_zone_id, lc.level, lc.soga_wing_type, lc.soga_chest_type, lc.soga_legs_type, lc.soga_hair_type, lc.legs_type, lc.chest_type, lc.wing_type, lc.hair_type, unix_timestamp(lc.created_date), unix_timestamp(lc.last_played), lc.id, lw.name, lc.facial_hair_type, lc.soga_facial_hair_type, lc.soga_model_type, lc.model_type from login_characters lc, login_worldservers lw where lw.id = lc.server_id and lc.account_id=%i and lc.deleted=0",acct->getLoginAccountID()); + if(result) { + MYSQL_ROW row; + MYSQL_ROW row2; + MYSQL_ROW row3; + while ((row = mysql_fetch_row(result))) { + CharSelectProfile* player = new CharSelectProfile(version); + id = atoul(row[0]); + //for (int i = 0; i < 10; i++) + // player->packet->setDataByName("hair_type", 0, i); + //player->packet->setDataByName("test23", 413); + //player->packet->setDataByName("test24", 414); + player->packet->setDataByName("charid", id); + player->packet->setDataByName("server_id", atoul(row[1])); + player->packet->setMediumStringByName("name", row[2]); + player->packet->setDataByName("race", atoi(row[3])); + player->packet->setDataByName("class", atoi(row[4])); + player->packet->setDataByName("gender", atoi(row[5])); + player->packet->setDataByName("deity", atoi(row[6])); + player->packet->setDataByName("body_size", atof(row[7])); + player->packet->setDataByName("body_age", atof(row[8])); + SetZoneInformation(atoi(row[1]), atoi(row[9]), version, player->packet); + player->packet->setDataByName("level", atoi(row[10])); + if(atoi(row[11]) > 0) + player->packet->setDataByName("soga_wing_type", atoi(row[11])); + else + player->packet->setDataByName("soga_wing_type", atoi(row[17])); + if(atoi(row[12]) > 0) + player->packet->setDataByName("soga_chest_type", atoi(row[12])); + else + player->packet->setDataByName("soga_chest_type", atoi(row[16])); + if(atoi(row[13]) > 0) + player->packet->setDataByName("soga_legs_type", atoi(row[13])); + else + player->packet->setDataByName("soga_legs_type", atoi(row[15])); + if(atoi(row[14]) > 0) + player->packet->setDataByName("soga_hair_type", atoi(row[14])); + else + player->packet->setDataByName("soga_hair_type", atoi(row[18])); + player->packet->setDataByName("legs_type", atoi(row[15])); + player->packet->setDataByName("chest_type", atoi(row[16])); + player->packet->setDataByName("wing_type", atoi(row[17])); + player->packet->setDataByName("hair_type", atoi(row[18])); + player->packet->setDataByName("created_date", atol(row[19])); + if (row[20]) + player->packet->setDataByName("last_played", atol(row[20])); + if(version == 546 || version == 561) + player->packet->setDataByName("version", 11); + else if(version >= 887) + player->packet->setDataByName("version", 6); + else + player->packet->setDataByName("version", 5); + player->packet->setDataByName("account_id", acct->getLoginAccountID()); + player->packet->setDataByName("account_id2", acct->getLoginAccountID()); + + LoadAppearanceData(atoul(row[21]), player->packet); + + if(row[22]) + player->packet->setMediumStringByName("server_name", row[22]); + player->packet->setDataByName("hair_face_type", atoi(row[23])); + if(atoi(row[24]) > 0) + player->packet->setDataByName("soga_hair_face_type", atoi(row[24])); + else + player->packet->setDataByName("soga_hair_face_type", atoi(row[23])); + if(atoi(row[25]) > 0) + player->packet->setDataByName("soga_race_type", atoi(row[25])); + else + player->packet->setDataByName("soga_race_type", atoi(row[26])); + player->packet->setDataByName("race_type", atoi(row[26])); + + player->packet->setDataByName("unknown3", 57); + player->packet->setDataByName("unknown4", 56); + player->packet->setDataByName("unknown6", 1, 1); //if not here will not display character + player->packet->setDataByName("unknown8", 15); + player->packet->setDataByName("unknown13", 212); + player->packet->setColorByName("unknown14", 0xFF, 0xFF, 0xFF); + + uchar tmp[] = {0xFF, 0xFF, 0xFF, 0x61, 0x00, 0x2C, 0x04, 0xA5, 0x09, 0x02, 0x0F, 0x00, 0x00}; + for(size_t y=0;ypacket->setDataByName("unknown11", tmp[y], y); + MYSQL_RES* result3 = query2.RunQuery2(Q_SELECT, "SELECT slot, equip_type, red, green, blue, highlight_red, highlight_green, highlight_blue from login_equipment where login_characters_id=%i order by slot",atoi(row[21])); + if(result3){ + for(int i=0;(row3 = mysql_fetch_row(result3)) && i<24; i++){ + player->packet->setEquipmentByName("equip", atoi(row3[1]), atoi(row3[2]), atoi(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoi(row3[7]), atoi(row3[0])); + } + } + player->packet->setDataByName("mount", 1377); + player->packet->setDataByName("mount_color1", 57); + /* + enum NetAppearance::NetAppearanceFlags + { + NAF_INVISIBLE=1, + NAF_SHOW_HOOD=2 + }; + */ + acct->addCharacter(player); + } + } + else + LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoadCharacters query '%s': %s", query.GetQuery(), query.GetError()); + +} + +void LoginDatabase::CheckCharacterTimeStamps(LoginAccount* acct){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT char_id, unix_timestamp from login_characters where account_id=%i",acct->getLoginAccountID()); + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + + ServerPacket* outpack = new ServerPacket(ServerOP_CharTimeStamp, sizeof(CharacterTimeStamp_Struct)); + CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) outpack->pBuffer; + cts->account_id = acct->getLoginAccountID(); + int32 server_id = 0; + LWorld* world_server = 0; + while ((row = mysql_fetch_row(result))) { + server_id = atoi(row[1]); + if(server_id != 0) + world_server = world_list.FindByAccount(server_id, World); + if(world_server) // If the pointer is 0, the world server must be down, we can't do any updates... + { + cts->char_id = atoi(row[0]); + cts->unix_timestamp = atoi(row[1]); + world_server->SendPacket(outpack); + //Reset for next character + world_server = 0; + server_id = 0; + } + } + safe_delete(outpack); + } +} + +void LoginDatabase::SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3,float multiplier){ + Query query; + string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue, signed_value) values(%i,'%s',%i,%i,%i, 1)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, (sint8)(float1*multiplier), (sint8)(float2*multiplier), (sint8)(float3*multiplier)); +} + +void LoginDatabase::SaveCharacterColors(int32 char_id, char* type, EQ2_Color color){ + Query query; + string create_char = string("insert into login_char_colors (login_characters_id, type, red, green, blue) values(%i,'%s',%i,%i,%i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), char_id, type, color.red, color.green, color.blue); +} + +void LoginDatabase::LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type, signed_value, red, green, blue from login_char_colors where login_characters_id = %i",char_id); + while((row = mysql_fetch_row(result))){ + if(atoi(row[1]) == 0) + char_select_packet->setColorByName(row[0], atoul(row[2]), atoul(row[3]), atoul(row[4])); + else{ + char_select_packet->setDataByName(row[0], atoi(row[2]), 0); + char_select_packet->setDataByName(row[0], atoi(row[3]), 1); + char_select_packet->setDataByName(row[0], atoi(row[4]), 2); + } + } +} +int16 LoginDatabase::GetAppearanceID(string name){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name.c_str()); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT appearance_id from appearances where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +void LoginDatabase::DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id){ + Query query; + query.RunQuery2(Q_UPDATE, "update login_characters set deleted=1 where char_id=%u and server_id=%u and id!=%u",char_id,server_id,exception_id); +} + +int32 LoginDatabase::SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version){ + int32 ret_id = 0; + Query query; + string create_char = + string("Insert into login_characters (account_id, server_id, char_id, name, race, class, gender, deity, body_size, body_age, soga_wing_type, soga_chest_type, soga_legs_type, soga_hair_type, soga_facial_hair_type, legs_type, chest_type, wing_type, hair_type, facial_hair_type, soga_model_type, model_type)" + " values(%i, %i, %i, '%s', %i, %i, %i, %i, %f, %f, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i)"); + query.RunQuery2(Q_INSERT, create_char.c_str(), + acct->getLoginAccountID(), + create->getType_int32_ByName("server_id"), world_charid, + create->getType_EQ2_16BitString_ByName("name").data.c_str(), + create->getType_int8_ByName("race"), + create->getType_int8_ByName("class"), + create->getType_int8_ByName("gender"), + create->getType_int8_ByName("deity"), + create->getType_float_ByName("body_size"), + create->getType_float_ByName("body_age"), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("legs_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("chest_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("wing_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("hair_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("face_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("soga_race_file").data), + GetAppearanceID(create->getType_EQ2_16BitString_ByName("race_file").data)); + if(query.GetError() && strlen(query.GetError()) > 0){ + LogWrite(LOGIN__ERROR, 0, "Login", "Error in SaveCharacter query '%s': %s", query.GetQuery(), query.GetError()); + return 0; + } + + int32 last_insert_id = query.GetLastInsertedID(); + + //mark any remaining characters with same id as deleted (creates problems if world deleted their db and started assigning new char ids) + DeactivateCharID(create->getType_int32_ByName("server_id"), world_charid, last_insert_id); + int32 char_id = last_insert_id; + if (client_version <= 561) { + float classic_multiplier = 250.0f; + SaveCharacterFloats(char_id, "skin_color", create->getType_float_ByName("skin_color", 0), create->getType_float_ByName("skin_color", 1), create->getType_float_ByName("skin_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "eye_color", create->getType_float_ByName("eye_color", 0), create->getType_float_ByName("eye_color", 1), create->getType_float_ByName("eye_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color1", create->getType_float_ByName("hair_color1", 0), create->getType_float_ByName("hair_color1", 1), create->getType_float_ByName("hair_color1", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_color2", create->getType_float_ByName("hair_color2", 0), create->getType_float_ByName("hair_color2", 1), create->getType_float_ByName("hair_color2", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_highlight", create->getType_float_ByName("hair_highlight", 0), create->getType_float_ByName("hair_highlight", 1), create->getType_float_ByName("hair_highlight", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_color", create->getType_float_ByName("hair_type_color", 0), create->getType_float_ByName("hair_type_color", 1), create->getType_float_ByName("hair_type_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_type_highlight_color", create->getType_float_ByName("hair_type_highlight_color", 0), create->getType_float_ByName("hair_type_highlight_color", 1), create->getType_float_ByName("hair_type_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_color", create->getType_float_ByName("hair_face_color", 0), create->getType_float_ByName("hair_face_color", 1), create->getType_float_ByName("hair_face_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "hair_face_highlight_color", create->getType_float_ByName("hair_face_highlight_color", 0), create->getType_float_ByName("hair_face_highlight_color", 1), create->getType_float_ByName("hair_face_highlight_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "shirt_color", create->getType_float_ByName("shirt_color", 0), create->getType_float_ByName("shirt_color", 1), create->getType_float_ByName("shirt_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_chest_color", create->getType_float_ByName("unknown_chest_color", 0), create->getType_float_ByName("unknown_chest_color", 1), create->getType_float_ByName("unknown_chest_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "pants_color", create->getType_float_ByName("pants_color", 0), create->getType_float_ByName("pants_color", 1), create->getType_float_ByName("pants_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown_legs_color", create->getType_float_ByName("unknown_legs_color", 0), create->getType_float_ByName("unknown_legs_color", 1), create->getType_float_ByName("unknown_legs_color", 2), classic_multiplier); + SaveCharacterFloats(char_id, "unknown9", create->getType_float_ByName("unknown9", 0), create->getType_float_ByName("unknown9", 1), create->getType_float_ByName("unknown9", 2), classic_multiplier); + } + else { + SaveCharacterColors(char_id, "skin_color", create->getType_EQ2_Color_ByName("skin_color")); + SaveCharacterColors(char_id, "model_color", create->getType_EQ2_Color_ByName("model_color")); + SaveCharacterColors(char_id, "eye_color", create->getType_EQ2_Color_ByName("eye_color")); + SaveCharacterColors(char_id, "hair_color1", create->getType_EQ2_Color_ByName("hair_color1")); + SaveCharacterColors(char_id, "hair_color2", create->getType_EQ2_Color_ByName("hair_color2")); + SaveCharacterColors(char_id, "hair_highlight", create->getType_EQ2_Color_ByName("hair_highlight")); + SaveCharacterColors(char_id, "hair_type_color", create->getType_EQ2_Color_ByName("hair_type_color")); + SaveCharacterColors(char_id, "hair_type_highlight_color", create->getType_EQ2_Color_ByName("hair_type_highlight_color")); + SaveCharacterColors(char_id, "hair_face_color", create->getType_EQ2_Color_ByName("hair_face_color")); + SaveCharacterColors(char_id, "hair_face_highlight_color", create->getType_EQ2_Color_ByName("hair_face_highlight_color")); + SaveCharacterColors(char_id, "wing_color1", create->getType_EQ2_Color_ByName("wing_color1")); + SaveCharacterColors(char_id, "wing_color2", create->getType_EQ2_Color_ByName("wing_color2")); + SaveCharacterColors(char_id, "shirt_color", create->getType_EQ2_Color_ByName("shirt_color")); + SaveCharacterColors(char_id, "unknown_chest_color", create->getType_EQ2_Color_ByName("unknown_chest_color")); + SaveCharacterColors(char_id, "pants_color", create->getType_EQ2_Color_ByName("pants_color")); + SaveCharacterColors(char_id, "unknown_legs_color", create->getType_EQ2_Color_ByName("unknown_legs_color")); + SaveCharacterColors(char_id, "unknown9", create->getType_EQ2_Color_ByName("unknown9")); + + SaveCharacterColors(char_id, "soga_skin_color", create->getType_EQ2_Color_ByName("soga_skin_color")); + SaveCharacterColors(char_id, "soga_model_color", create->getType_EQ2_Color_ByName("soga_model_color")); + SaveCharacterColors(char_id, "soga_eye_color", create->getType_EQ2_Color_ByName("soga_eye_color")); + SaveCharacterColors(char_id, "soga_hair_color1", create->getType_EQ2_Color_ByName("soga_hair_color1")); + SaveCharacterColors(char_id, "soga_hair_color2", create->getType_EQ2_Color_ByName("soga_hair_color2")); + SaveCharacterColors(char_id, "soga_hair_highlight", create->getType_EQ2_Color_ByName("soga_hair_highlight")); + SaveCharacterColors(char_id, "soga_hair_type_color", create->getType_EQ2_Color_ByName("soga_hair_type_color")); + SaveCharacterColors(char_id, "soga_hair_type_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_type_highlight_color")); + SaveCharacterColors(char_id, "soga_hair_face_color", create->getType_EQ2_Color_ByName("soga_hair_face_color")); + SaveCharacterColors(char_id, "soga_hair_face_highlight_color", create->getType_EQ2_Color_ByName("soga_hair_face_highlight_color")); + SaveCharacterColors(char_id, "soga_wing_color1", create->getType_EQ2_Color_ByName("soga_wing_color1")); + SaveCharacterColors(char_id, "soga_wing_color2", create->getType_EQ2_Color_ByName("soga_wing_color2")); + SaveCharacterColors(char_id, "soga_shirt_color", create->getType_EQ2_Color_ByName("soga_shirt_color")); + SaveCharacterColors(char_id, "soga_unknown_chest_color", create->getType_EQ2_Color_ByName("soga_unknown_chest_color")); + SaveCharacterColors(char_id, "soga_pants_color", create->getType_EQ2_Color_ByName("soga_pants_color")); + SaveCharacterColors(char_id, "soga_unknown_legs_color", create->getType_EQ2_Color_ByName("soga_unknown_legs_color")); + SaveCharacterColors(char_id, "soga_unknown13", create->getType_EQ2_Color_ByName("soga_unknown13")); + SaveCharacterFloats(char_id, "soga_eye_type", create->getType_float_ByName("soga_eyes2", 0), create->getType_float_ByName("soga_eyes2", 1), create->getType_float_ByName("soga_eyes2", 2)); + SaveCharacterFloats(char_id, "soga_ear_type", create->getType_float_ByName("soga_ears", 0), create->getType_float_ByName("soga_ears", 1), create->getType_float_ByName("soga_ears", 2)); + SaveCharacterFloats(char_id, "soga_eye_brow_type", create->getType_float_ByName("soga_eye_brows", 0), create->getType_float_ByName("soga_eye_brows", 1), create->getType_float_ByName("soga_eye_brows", 2)); + SaveCharacterFloats(char_id, "soga_cheek_type", create->getType_float_ByName("soga_cheeks", 0), create->getType_float_ByName("soga_cheeks", 1), create->getType_float_ByName("soga_cheeks", 2)); + SaveCharacterFloats(char_id, "soga_lip_type", create->getType_float_ByName("soga_lips", 0), create->getType_float_ByName("soga_lips", 1), create->getType_float_ByName("soga_lips", 2)); + SaveCharacterFloats(char_id, "soga_chin_type", create->getType_float_ByName("soga_chin", 0), create->getType_float_ByName("soga_chin", 1), create->getType_float_ByName("soga_chin", 2)); + SaveCharacterFloats(char_id, "soga_nose_type", create->getType_float_ByName("soga_nose", 0), create->getType_float_ByName("soga_nose", 1), create->getType_float_ByName("soga_nose", 2)); + } + SaveCharacterFloats(char_id, "eye_type", create->getType_float_ByName("eyes2", 0), create->getType_float_ByName("eyes2", 1), create->getType_float_ByName("eyes2", 2)); + SaveCharacterFloats(char_id, "ear_type", create->getType_float_ByName("ears", 0), create->getType_float_ByName("ears", 1), create->getType_float_ByName("ears", 2)); + SaveCharacterFloats(char_id, "eye_brow_type", create->getType_float_ByName("eye_brows", 0), create->getType_float_ByName("eye_brows", 1), create->getType_float_ByName("eye_brows", 2)); + SaveCharacterFloats(char_id, "cheek_type", create->getType_float_ByName("cheeks", 0), create->getType_float_ByName("cheeks", 1), create->getType_float_ByName("cheeks", 2)); + SaveCharacterFloats(char_id, "lip_type", create->getType_float_ByName("lips", 0), create->getType_float_ByName("lips", 1), create->getType_float_ByName("lips", 2)); + SaveCharacterFloats(char_id, "chin_type", create->getType_float_ByName("chin", 0), create->getType_float_ByName("chin", 1), create->getType_float_ByName("chin", 2)); + SaveCharacterFloats(char_id, "nose_type", create->getType_float_ByName("nose", 0), create->getType_float_ByName("nose", 1), create->getType_float_ByName("nose", 2)); + SaveCharacterFloats(char_id, "body_size", create->getType_float_ByName("body_size", 0), 0, 0); + return ret_id; +} + +bool LoginDatabase::DeleteCharacter(int32 account_id, int32 character_id, int32 server_id){ + Query query; + string delete_char = string("delete from login_characters where char_id=%i and account_id=%i and server_id=%i"); + query.RunQuery2(Q_DELETE, delete_char.c_str(),character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + //No error just in case ppl try doing stupid stuff + return false; + } + + return true; +} + +string LoginDatabase::GetCharacterName(int32 char_id, int32 server_id, int32 account_id){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_characters where char_id=%lu and server_id=%lu and account_id=%lu and deleted = 0 limit 1", char_id, server_id, account_id); + + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + return string(row[0]); + } + return string(""); +} + +bool LoginDatabase::UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id){ + Query query; + string update_charts = string("update login_characters set unix_timestamp=%lu where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),timestamp_update,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterTimeStamp query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id){ + Query query; + string update_charts = string("update login_characters set level=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_level,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterLevel query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id){ + Query query; + string update_charts = string("update login_characters set race_type=%i, race=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_racetype,in_race,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterRace query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id){ + Query query; + string update_chars = string("update login_characters set current_zone_id=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_chars.c_str(), zone_id, character_id, account_id, server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterZone query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id){ + Query query; + string update_charts = string("update login_characters set class=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_class,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id){ + Query query; + string update_charts = string("update login_characters set gender=%i where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),in_gender,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterClass query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +bool LoginDatabase::UpdateCharacterName(int32 account_id, int32 character_id, char* newName, int32 server_id){ + Query query; + string update_charts = string("update login_characters set name='%s' where char_id=%lu and account_id=%lu and server_id=%lu"); + query.RunQuery2(Q_UPDATE, update_charts.c_str(),newName,character_id,account_id,server_id); + if(!query.GetAffectedRows()) + { + LogWrite(LOGIN__ERROR, 0, "Login", "Error in UpdateCharacterName query '%s': %s", query.GetQuery(), query.GetError()); + return false; + } + + return true; +} + +LoginAccount* LoginDatabase::LoadAccount(const char* name, const char* password, bool attemptAccountCreation){ + LoginAccount* acct = NULL; + Query query; + query.escaped_name = getEscapeString(name); + query.escaped_pass = getEscapeString(password); + time_t now = time(0); //get the current epoc time + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s' and passwd=sha2('%s',512)", query.escaped_name, query.escaped_pass); + if(result){ + if (mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + int32 id = atol(row[0]); + + acct = new LoginAccount(id, name, password); + acct->setAuthenticated(true); + } + else if(mysql_num_rows(result) > 0) + LogWrite(LOGIN__ERROR, 0, "Login", "Error in LoginAccount: more than one account returned for '%s'", name); + else if (attemptAccountCreation && !database.GetAccountIDByName(name)) + { + Query newquery; + newquery.RunQuery2(Q_INSERT, "insert into account set name='%s',passwd=sha2('%s',512), created_date=%i", query.escaped_name, query.escaped_pass, now); + // re-run the query for select only not account creation + return LoadAccount(name, password, false); + } + + } + return acct; +} + +int32 LoginDatabase::GetAccountIDByName(const char* name) { + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from account where name='%s'", query.escaped_name); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + id = atoi(row[0]); + } + return id; +} + +int32 LoginDatabase::CheckServerAccount(char* name, char* passwd){ + int32 id = 0; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT lower(password), id from login_worldservers where account='%s' and disabled = 0", query.escaped_name); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccount Account=%s\nSHA=%s", (char*)query.escaped_name, passwd); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResult Account=%s\nPassword=%s", (char*)query.escaped_name, (row && row[0]) ? row[0] : "(NULL)"); + + if (memcmp(row[0], passwd, strnlen(row[0], 256)) == 0) + { + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer CheckServerAccountResultMatch Account=%s", (char*)query.escaped_name); + id = atoi(row[1]); + } + } + return id; +} + +bool LoginDatabase::IsServerAccountDisabled(char* name){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_worldservers where account='%s' and disabled = 1", query.escaped_name); + + LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerAccountDisabled Account=%s", (char*)query.escaped_name); + if(result && mysql_num_rows(result) > 0){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerAccountDisabled Match Account=%s", (char*)query.escaped_name); + + return true; + } + return false; +} + +bool LoginDatabase::IsIPBanned(char* ipaddr){ + if(!ipaddr) + return false; + + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT ip from login_bannedips where '%s' LIKE CONCAT(ip ,'%%')", ipaddr); + + LogWrite(LOGIN__DEBUG, 0, "Login", "WorldServer IsServerIPBanned IPPartial=%s", (char*)ipaddr); + if(result && mysql_num_rows(result) > 0){ + row = mysql_fetch_row(result); + + LogWrite(LOGIN__INFO, 0, "Login", "WorldServer IsServerIPBanned Match IPBan=%s", row[0]); + + return true; + } + return false; +} + +void LoginDatabase::GetServerAccounts(vector* server_list){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, account, name, admin_id from login_worldservers"); + while((row = mysql_fetch_row(result))){ + LWorld* world = new LWorld(atol(row[0]), row[1], row[2], atoi(row[3])); + world->SetID(world->GetAccountID()); + server_list->push_back(world); + } +} +void LoginDatabase::SaveClientLog(const char* type, const char* message, const char* player_name, int16 version){ + Query query; + query.escaped_data1 = getEscapeString(message); + query.escaped_name = getEscapeString(player_name); + query.RunQuery2(Q_INSERT, "insert into log_messages (type, message, name, version) values('%s', '%s', '%s', %i)", type, query.escaped_data1, query.escaped_name, version); +} +bool LoginDatabase::VerifyDelete(int32 account_id, int32 character_id, const char* name){ + Query query; + query.escaped_name = getEscapeString(name); + query.RunQuery2(Q_UPDATE, "update login_characters set deleted = 1 where char_id=%i and account_id=%i and name='%s'", character_id, account_id, query.escaped_name); + if(query.GetAffectedRows() == 1) + return true; + else + return false; +} +char* LoginDatabase::GetServerAccountName(int32 id){ + Query query; + MYSQL_ROW row; + char* name = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name from login_worldservers where id=%lu", id); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + if(strlen(row[0]) > 0){ + name = new char[strlen(row[0])+1]; + strcpy(name, row[0]); + } + } + return name; +} +int32 LoginDatabase::GetRaceID(char* name){ + int32 ret = 1487; + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT race_type from login_races where name='%s'", query.escaped_name); + if(result && mysql_num_rows(result) == 1){ + row = mysql_fetch_row(result); + ret = atol(row[0]); + } + else if(!result || mysql_num_rows(result) == 0) + UpdateRaceID(query.escaped_name); + return ret; +} +void LoginDatabase::UpdateRaceID(char* name){ + Query query; + query.RunQuery2(Q_UPDATE, "insert into login_races (name) values('%s')", name); +} +bool LoginDatabase::CheckVersion(char* in_version){ + Query query; + query.escaped_data1 = getEscapeString(in_version); + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id from login_versions where version='%s' or version='*'", query.escaped_data1); + if(result && mysql_num_rows(result) > 0) + return true; + else + return false; +} +void LoginDatabase::GetLatestTableVersions(LatestTableVersions* table_versions){ + Query query; + MYSQL_ROW row; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name, max(version) from login_table_versions group by name order by id"); + if(result && mysql_num_rows(result) > 0){ + table_versions->SetTableSize(mysql_num_rows(result)); + } + else // we need to return if theres no result, otherwise it will crash attempting to loop through rows + + return; + while((row = mysql_fetch_row(result))){ + if(VerifyDataTable(row[0])) + table_versions->AddTable(row[0], atoi(row[1]), GetDataVersion(row[0])); + else + table_versions->AddTable(row[0], atoi(row[1]), 0); + } +} +bool LoginDatabase::VerifyDataTable(char* name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT table_name from download_tables where table_name='%s'", name); + if(result && mysql_num_rows(result) > 0) + return true; + return false; +} +string LoginDatabase::GetColumnNames(char* name){ + Query query; + MYSQL_ROW row; + string columns = "("; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "show columns from %s", name); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + while((row = mysql_fetch_row(result))){ + if(strcmp(row[0], "table_data_version") != 0){ + if(i>0) + columns.append(","); + columns.append(row[0]); + i++; + } + } + } + columns.append(") "); + return columns; +} +TableDataQuery* LoginDatabase::GetTableDataQuery(int32 server_ip, char* name, int16 version){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + TableDataQuery* table_query = 0; + MYSQL_RES* result = 0; + string columns; + + if(VerifyDataTable(query.escaped_name)){ + result = query.RunQuery2(Q_SELECT, "SELECT * from %s where table_data_version > %i", query.escaped_name, version); + columns = GetColumnNames(query.escaped_name); + } + if(result && mysql_num_rows(result) > 0){ + table_query = new TableDataQuery(query.escaped_name); + table_query->num_queries = mysql_num_rows(result); + table_query->columns_size = columns.length() + 1; + table_query->columns = new char[table_query->columns_size + 1]; + table_query->version = GetDataVersion(query.escaped_name); + strcpy(table_query->columns, (char*)columns.c_str()); + string query_data; + MYSQL_FIELD* field; + int* int_list = new int[mysql_num_fields(result)]; + int16 ndx = 0; + while((field = mysql_fetch_field(result))){ + int_list[ndx] = IS_NUM(field->type); + if(strcmp(field->name,"table_data_version") == 0) + int_list[ndx] = 2; + ndx++; + } + ndx = 0; + while((row = mysql_fetch_row(result))){ + query_data = ""; + for(int i=0;i0) + query_data.append(","); + if(!int_list[i]){ + query_data.append("'").append(getEscapeString(row[i])).append("'"); + } + else + query_data.append(row[i]); + } + } + TableData* new_query = new TableData; + new_query->size = query_data.length() + 1; + new_query->query = new char[query_data.length() + 1]; + strcpy(new_query->query, query_data.c_str()); + table_query->queries.push_back(new_query); + ndx++; + } + safe_delete_array(int_list); + } + else{ + string query2 = string("The user tried to download the following table: ").append(query.escaped_name); + SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Data", server_ip); + } + return table_query; +} +TableQuery* LoginDatabase::GetLatestTableQuery(int32 server_ip, char* name, int16 version){ + Query query; + MYSQL_ROW row; + query.escaped_name = getEscapeString(name); + TableQuery* table_query = 0; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT query, version from login_table_versions where name = '%s' and version>=%i order by version", query.escaped_name, version + 1); + if(result && mysql_num_rows(result) > 0){ + int16 i = 0; + table_query = new TableQuery; + while((row = mysql_fetch_row(result))){ + char* rowdata = row[0]; + if(strstr(rowdata, ";")){ + char* token = strtok(rowdata,";"); + while(token){ + char* new_query = new char[strlen(token) + 1]; + strcpy(new_query, token); + table_query->AddQuery(new_query); + token = strtok(NULL, ";"); + } + } + else + table_query->AddQuery(rowdata); + table_query->latest_version = atoi(row[1]); + } + strcpy(table_query->tablename, name); + table_query->your_version = version; + } + else{ + string query2 = string("The following was the DB Query: ").append(query.GetQuery()); + SaveClientLog("Possible Hacking Attempt", (char*)query2.c_str(), "Hacking Query", server_ip); + } + return table_query; +} +sint16 LoginDatabase::GetDataVersion(char* name){ + Query query; + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT max(table_data_version) from %s", name); + sint16 ret_version = 0; + if(result && mysql_num_rows(result) > 0) { + MYSQL_ROW row; + row = mysql_fetch_row(result); + if(row[0]) + ret_version = atoi(row[0]); + } + return ret_version; +} + +void LoginDatabase::RemoveOldWorldServerStats(){ + Query query; + query.RunQuery2(Q_DELETE, "delete from login_worldstats where (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 86400"); +} + + +void LoginDatabase::UpdateWorldServerStats(LWorld* world, sint32 status) +{ + Query query; + query.RunQuery2(Q_INSERT, "insert into login_worldstats (world_id, world_status, current_players, current_zones, last_update, world_max_level) values(%u, %i, %i, %i, NOW(), %i) ON DUPLICATE KEY UPDATE current_players=%i,current_zones=%i,world_max_level=%i,world_status=%i,last_update=NOW()", + world->GetAccountID(), status, world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), world->GetPlayerNum(), world->GetZoneNum(), world->GetMaxWorldLevel(), status); + + string update_stats = string("update login_worldservers set lastseen=%u where id=%i"); + query.RunQuery2(Q_UPDATE, update_stats.c_str(), Timer::GetUnixTimeStamp(), world->GetAccountID()); +} + +bool LoginDatabase::ResetWorldServerStatsConnectedTime(LWorld* world){ + if(!world || world->GetAccountID() == 0) + return false; + + Query query; + string update_stats = string("update login_worldstats set connected_time=now() where world_id=%i and (UNIX_TIMESTAMP(NOW())-UNIX_TIMESTAMP(last_update)) > 300"); + query.RunQuery2(Q_UPDATE, update_stats.c_str(),world->GetAccountID()); + + return true; +} + +void LoginDatabase::ResetWorldStats ( ) +{ + Query query; + string update_stats = string("update login_worldstats set world_status=-4, current_players=0, current_zones=0"); + query.RunQuery2(update_stats.c_str(), Q_UPDATE); +} + +void LoginDatabase::SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id){ + Query query; + string bug_report = string("insert into bugs (world_id, category, subcategory, causes_crash, reproducible, summary, description, version, player, account_id, spawn_name, spawn_id, zone_id) values(%lu, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %lu, '%s', %lu, %lu)"); + query.RunQuery2(Q_INSERT, bug_report.c_str(), world_id, database.getSafeEscapeString(category).c_str(), database.getSafeEscapeString(subcategory).c_str(), + database.getSafeEscapeString(causes_crash).c_str(), database.getSafeEscapeString(reproducible).c_str(), database.getSafeEscapeString(summary).c_str(), + database.getSafeEscapeString(description).c_str(), database.getSafeEscapeString(version).c_str(), database.getSafeEscapeString(player).c_str(), account_id, + database.getSafeEscapeString(spawn_name).c_str(), spawn_id, zone_id); + FixBugReport(); +} + +void LoginDatabase::FixBugReport(){ + Query query; + string bug_report = string("update bugs set description = REPLACE(description,SUBSTRING(description,INSTR(description,'%'), 3),char(CONV(SUBSTRING(description,INSTR(description,'%')+1, 2), 16, 10))), summary = REPLACE(summary,SUBSTRING(summary,INSTR(summary,'%'), 3),char(CONV(SUBSTRING(summary,INSTR(summary,'%')+1, 2), 16, 10)))"); + query.RunQuery2(bug_report.c_str(), Q_UPDATE); +} + +void LoginDatabase::UpdateWorldIPAddress(int32 world_id, int32 address){ + struct in_addr in; + in.s_addr = address; + Query query; + query.RunQuery2(Q_UPDATE, "update login_worldservers set ip_address='%s' where id=%lu", inet_ntoa(in), world_id); +} + +void LoginDatabase::UpdateAccountIPAddress(int32 account_id, int32 address){ + struct in_addr in; + in.s_addr = address; + Query query; + query.RunQuery2(Q_UPDATE, "update account set ip_address='%s' where id=%lu", inet_ntoa(in), account_id); +} + +//devn00b: There is no rulesystem for login, so im going to use login_config for future things like this. +//devn00b: Returns the number of characters a player may create per account. This should be set by server owners -> login, +//devn00b: However, better semi-working for now than not working at all. +//devn00b: TODO: EQ2World sends max char per acct. +int8 LoginDatabase::GetMaxCharsSetting() { + //live defaults to 7 for GOLD members. + int8 max_chars = 7; + Query query; + MYSQL_ROW row; + + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select config_value from login_config where config_name='max_characters_per_account'"); + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + if (row[0]) + max_chars = atoi(row[0]); + } + //if nothing else return the default. + return max_chars; +} + +int16 LoginDatabase::GetAccountBonus(int32 acct_id) { + int32 bonus = 0; + int16 world_id = 0; + Query query; + MYSQL_ROW row; + Query query2; + MYSQL_ROW row2; + + //get the world ID for the character. TODO: Support multi server characters. + MYSQL_RES* result2 = query2.RunQuery2(Q_SELECT, "select server_id from login_characters where account_id=%i", acct_id); + + if (result2 && mysql_num_rows(result2) >= 1) { + row2 = mysql_fetch_row(result2); + if (row2[0]) + world_id = atoi(row2[0]); + } + + //pull all characters greater than the max level from the server + MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT COUNT(id) FROM login_characters WHERE LEVEL >= (select world_max_level from login_worldstats where world_id=%i) AND account_id=%i", world_id, acct_id); + + if (result && mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + if(row[0]) + bonus = atoi(row[0]); + } + return bonus; +} + +void LoginDatabase::UpdateWorldVersion(int32 world_id, char* version) { + Query query; + query.RunQuery2(Q_UPDATE, "update login_worldservers set login_version='%s' where id=%u", version, world_id); +} + +void LoginDatabase::UpdateAccountClientDataVersion(int32 account_id, int16 version) +{ + Query query; + query.RunQuery2(Q_UPDATE, "UPDATE account SET last_client_version='%i' WHERE id = %u", version, account_id); +} + +//devn00b todo: finish this. +void LoginDatabase::SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture) { + stringstream ss_hex; + stringstream ss_query; + ss_hex.flags(ios::hex); + for (int32 i = 0; i < picture_size; i++) + ss_hex << setfill('0') << setw(2) << (int32)picture[i]; + + ss_query << "INSERT INTO `ls_character_picture` (`server_id`, `account_id`, `character_id`, `picture`) VALUES (" << server_id << ", " << account_id << ", " << character_id << ", '" << ss_hex.str() << "') ON DUPLICATE KEY UPDATE `picture` = '" << ss_hex.str() << "'"; + + if (!dbLogin.Query(ss_query.str().c_str())) + LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", dbLogin.GetError(), dbLogin.GetErrorMsg()); +} \ No newline at end of file diff --git a/old/login/LoginDatabase.h b/old/login/LoginDatabase.h new file mode 100644 index 0000000..27ba85b --- /dev/null +++ b/old/login/LoginDatabase.h @@ -0,0 +1,96 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef EQ2LOGIN_EMU_DATABASE_H +#define EQ2LOGIN_EMU_DATABASE_H + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN + #include + #include +#endif +#include +#include +#include + +#include "../common/database.h" +#include "../common/DatabaseNew.h" +#include "../common/types.h" +#include "../common/MiscFunctions.h" +#include "../common/servertalk.h" +#include "../common/Mutex.h" +#include "PacketHeaders.h" +#include "LoginAccount.h" +#include "LWorld.h" +#include "../common/PacketStruct.h" + +using namespace std; +#pragma pack() +class LoginDatabase : public Database +{ +public: + void FixBugReport(); + void UpdateAccountIPAddress(int32 account_id, int32 address); + void UpdateWorldIPAddress(int32 world_id, int32 address); + void SaveBugReport(int32 world_id, char* category, char* subcategory, char* causes_crash, char* reproducible, char* summary, char* description, char* version, char* player, int32 account_id, char* spawn_name, int32 spawn_id, int32 zone_id); + LoginAccount* LoadAccount(const char* name, const char* password, bool attemptAccountCreation=true); + int32 GetAccountIDByName(const char* name); + int32 CheckServerAccount(char* name, char* passwd); + bool IsServerAccountDisabled(char* name); + bool IsIPBanned(char* ipaddr); + void GetServerAccounts(vector* server_list); + char* GetServerAccountName(int32 id); + bool VerifyDelete(int32 account_id, int32 character_id, const char* name); + void SetServerZoneDescriptions(int32 server_id, map zone_descriptions); + int32 GetServer(int32 accountID, int32 charID, string name); + void LoadCharacters(LoginAccount* acct, int16 version); + void CheckCharacterTimeStamps(LoginAccount* acct); + string GetCharacterName(int32 char_id , int32 server_id, int32 account_id); + void SaveCharacterColors(int32 char_id, char* type, EQ2_Color color); + void SaveCharacterFloats(int32 char_id, char* type, float float1, float float2, float float3, float multiplier=100.0f); + int16 GetAppearanceID(string name); + void DeactivateCharID(int32 server_id, int32 char_id, int32 exception_id); + int32 SaveCharacter(PacketStruct* create, LoginAccount* acct, int32 world_charid, int32 client_version); + void LoadAppearanceData(int32 char_id, PacketStruct* char_select_packet); + bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp_update, int32 server_id); + bool UpdateCharacterLevel(int32 account_id, int32 character_id, int8 in_level, int32 server_id); + bool UpdateCharacterRace(int32 account_id, int32 character_id, int16 in_racetype, int8 in_race, int32 server_id); + bool UpdateCharacterClass(int32 account_id, int32 character_id, int8 in_class, int32 server_id); + bool UpdateCharacterName(int32 account_id, int32 character_id, char* newName, int32 server_id); + bool UpdateCharacterZone(int32 account_id, int32 character_id, int32 zone_id, int32 server_id); + bool UpdateCharacterGender(int32 account_id, int32 character_id, int8 in_gender, int32 server_id); + int32 GetRaceID(char* name); + void UpdateRaceID(char* name); + bool DeleteCharacter(int32 account_id, int32 character_id, int32 server_id); + void SaveClientLog(const char* type, const char* message, const char* player_name, int16 version); + bool CheckVersion(char* version); + void GetLatestTableVersions(LatestTableVersions* table_versions); + TableQuery* GetLatestTableQuery(int32 server_ip, char* name, int16 version); + bool VerifyDataTable(char* name); + sint16 GetDataVersion(char* name); + void SetZoneInformation(int32 server_id, int32 zone_id, int32 version, PacketStruct* packet); + string GetZoneDescription(char* name); + string GetColumnNames(char* name); + TableDataQuery* GetTableDataQuery(int32 server_ip, char* name, int16 version); + + void UpdateWorldServerStats( LWorld* world, sint32 status); + bool ResetWorldServerStatsConnectedTime( LWorld* world ); + void RemoveOldWorldServerStats(); + void ResetWorldStats(); + //devn00b temp + bool ConnectNewDatabase(); + void SetServerEquipmentAppearances(int32 server_id, map equip_updates); // JohnAdams: login appearances + int32 GetLoginCharacterIDFromWorldCharID(int32 server_id, int32 char_id); // JohnAdams: login appearances + void RemoveDeletedCharacterData(); + int8 GetMaxCharsSetting(); + int16 GetAccountBonus(int32 acct_id); + void UpdateWorldVersion(int32 world_id, char* version); + void UpdateAccountClientDataVersion(int32 account_id, int16 version); + void SaveCharacterPicture(int32 account_id, int32 character_id, int32 server_id, int16 picture_size, uchar* picture); + + DatabaseNew dbLogin; +}; +#endif \ No newline at end of file diff --git a/old/login/PacketHeaders.cpp b/old/login/PacketHeaders.cpp new file mode 100644 index 0000000..4511f6a --- /dev/null +++ b/old/login/PacketHeaders.cpp @@ -0,0 +1,88 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "PacketHeaders.h" +#include "../common/MiscFunctions.h" +#include "LoginDatabase.h" +#include "LWorld.h" + +extern LWorldList world_list; +extern LoginDatabase database; + +void LS_DeleteCharacterRequest::loadData(EQApplicationPacket* packet){ + InitializeLoadData(packet->pBuffer, packet->size); + LoadData(character_number); + LoadData(server_id); + LoadData(spacer); + LoadDataString(name); +} + +EQ2Packet* LS_CharSelectList::serialize(int16 version){ + Clear(); + AddData(num_characters); + AddData(char_data); + if (version <= 561) { + LS_CharListAccountInfoEarlyClient account_info; + account_info.account_id = account_id; + account_info.unknown1 = 0xFFFFFFFF; + account_info.unknown2 = 0; + account_info.maxchars = 7; //live has a max of 7 on gold accounts base. + account_info.unknown4 = 0; + AddData(account_info); + } + else { + LS_CharListAccountInfo account_info; + account_info.account_id = account_id; + account_info.unknown1 = 0xFFFFFFFF; + account_info.unknown2 = 0; + account_info.maxchars = database.GetMaxCharsSetting(); + account_info.vet_adv_bonus = database.GetAccountBonus(account_id); + account_info.vet_trade_bonus = 0; + account_info.unknown4 = 0; + for (int i = 0; i < 3; i++) + account_info.unknown5[i] = 0xFFFFFFFF; + account_info.unknown5[3] = 0; + + AddData(account_info); + } + return new EQ2Packet(OP_AllCharactersDescReplyMsg, getData(), getDataSize()); +} + +void LS_CharSelectList::addChar(uchar* data, int16 size){ + char_data.append((char*)data, size); +} + +void LS_CharSelectList::loadData(int32 account, vector charlist, int16 version){ + vector::iterator itr; + account_id = account; + num_characters = 0; + char_data = ""; + CharSelectProfile* character = 0; + for(itr = charlist.begin();itr != charlist.end();itr++){ + character = *itr; + int32 serverID = character->packet->getType_int32_ByName("server_id"); + if(character->deleted) { // workaround for old clients <= 561 that crash if you delete a char (Doesn't refresh the char panel correctly) + character->packet->setDataByName("name", "(deleted)"); + character->packet->setDataByName("charid", 0xFFFFFFFF); + character->packet->setDataByName("name", 0xFFFFFFFF); + character->packet->setDataByName("server_id", 0xFFFFFFFF); + character->packet->setDataByName("created_date", 0xFFFFFFFF); + character->packet->setDataByName("unknown1", 0xFFFFFFFF); + character->packet->setDataByName("unknown2", 0xFFFFFFFF); + character->packet->setDataByName("flags", 0xFF); + } + else if(serverID == 0 || !world_list.FindByID(serverID)) + continue; + num_characters++; + character->SaveData(version); + addChar(character->getData(), character->getDataSize()); + } +} + +void CharSelectProfile::SaveData(int16 in_version){ + Clear(); + AddData(*packet->serializeString()); +} diff --git a/old/login/PacketHeaders.h b/old/login/PacketHeaders.h new file mode 100644 index 0000000..69a30c5 --- /dev/null +++ b/old/login/PacketHeaders.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef __PACKET_HEADERS__ +#define __PACKET_HEADERS__ + +#include "../common/types.h" +#include "../common/EQPacket.h" +#include "../common/EQ2_Common_Structs.h" +#include "login_structs.h" +#include "../common/DataBuffer.h" +#include "../common/GlobalHeaders.h" +#include "../common/ConfigReader.h" +#include + +extern ConfigReader configReader; + +class CharSelectProfile : public DataBuffer{ +public: + CharSelectProfile(int16 version){ + deleted = false; + packet = configReader.getStruct("CharSelectProfile",version); + for(int8 i=0;i<24;i++){ + packet->setEquipmentByName("equip",0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,i); + } + } + + ~CharSelectProfile(){ + safe_delete(packet); + } + PacketStruct* packet; + + void SaveData(int16 in_version); + void Data(); + int16 size; + bool deleted; +}; + +class LS_CharSelectList : public DataBuffer { +public: + int8 num_characters; + int32 account_id; + + EQ2Packet* serialize(int16 version); + void addChar(uchar* data, int16 size); + string char_data; + void loadData(int32 account, vector charlist, int16 version); +}; + +class LS_DeleteCharacterRequest : public DataBuffer{ +public: + int32 character_number; + int32 server_id; + int32 spacer; + EQ2_16BitString name; + void loadData(EQApplicationPacket* packet); +}; +#endif \ No newline at end of file diff --git a/old/login/Web/LoginWeb.cpp b/old/login/Web/LoginWeb.cpp new file mode 100644 index 0000000..03eb554 --- /dev/null +++ b/old/login/Web/LoginWeb.cpp @@ -0,0 +1,67 @@ +#include "../net.h" +#include "../LWorld.h" + +#include +#include +#include + +extern ClientList client_list; +extern LWorldList world_list; +extern NetConnection net; + +void NetConnection::Web_loginhandle_status(const http::request& req, http::response& res) { + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree pt; + + pt.put("web_status", "online"); + pt.put("login_status", net.login_running ? "online" : "offline"); + pt.put("login_uptime", (getCurrentTimestamp() - net.login_uptime)); + auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - net.login_uptime)); + std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds)); + pt.put("login_uptime_string", uptime_str); + pt.put("world_count", world_list.GetCount(ConType::World)); + pt.put("client_count", net.numclients); + + std::ostringstream oss; + boost::property_tree::write_json(oss, pt); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + +void NetConnection::Web_loginhandle_worlds(const http::request& req, http::response& res) { + world_list.PopulateWorldList(res); +} + +void LWorldList::PopulateWorldList(http::response& res) { + + struct in_addr in; + res.set(http::field::content_type, "application/json"); + boost::property_tree::ptree maintree; + + std::ostringstream oss; + + map::iterator map_list; + for( map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) { + LWorld* world = map_list->second; + in.s_addr = world->GetIP(); + if (world->GetType() == World) { + + boost::property_tree::ptree pt; + pt.put("id", world->GetID()); + pt.put("world_name", world->GetName()); + pt.put("status", (world->GetStatus() == 1) ? "online" : "offline"); + pt.put("num_players", world->GetPlayerNum()); + pt.put("ip_addr", inet_ntoa(in)); + maintree.push_back(std::make_pair("", pt)); + } + } + + boost::property_tree::ptree result; + result.add_child("WorldServers", maintree); + boost::property_tree::write_json(oss, result); + std::string json = oss.str(); + res.body() = json; + res.prepare_payload(); +} + diff --git a/old/login/client.cpp b/old/login/client.cpp new file mode 100644 index 0000000..d41728f --- /dev/null +++ b/old/login/client.cpp @@ -0,0 +1,813 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#include +#include +#include +#include + +#include "net.h" +#include "client.h" +#include "../common/EQStream.h" +#include "../common/packet_dump.h" +#include "../common/packet_functions.h" +#include "../common/emu_opcodes.h" +#include "../common/MiscFunctions.h" +#include "LWorld.h" +#include "LoginDatabase.h" +#include "../common/ConfigReader.h" +#include "../common/Log.h" + +extern NetConnection net; +extern LWorldList world_list; +extern ClientList client_list; +extern LoginDatabase database; +extern mapEQOpcodeManager; +extern ConfigReader configReader; +using namespace std; +Client::Client(EQStream* ieqnc) { + eqnc = ieqnc; + ip = eqnc->GetrIP(); + port = ntohs(eqnc->GetrPort()); + account_id = 0; + lsadmin = 0; + worldadmin = 0; + lsstatus = 0; + version = 0; + kicked = false; + verified = false; + memset(bannedreason, 0, sizeof(bannedreason)); + //worldresponse_timer = new Timer(10000); + //worldresponse_timer->Disable(); + memset(key,0,10); + LoginMode = None; + num_updates = 0; + updatetimer = new Timer(500); + updatelisttimer = new Timer(10000); + //keepalive = new Timer(5000); + //logintimer = new Timer(500); // Give time for the servers to send updates + //keepalive->Start(); + //updatetimer->Start(); + //logintimer->Disable(); + disconnectTimer = 0; + memset(ClientSession,0,25); + request_num = 0; + login_account = 0; + createRequest = 0; + playWaitTimer = NULL; + start = false; + update_position = 0; + update_packets = 0; + needs_world_list = true; + sent_character_list = false; +} + +Client::~Client() { + //safe_delete(worldresponse_timer); + //safe_delete(logintimer); + safe_delete(login_account); + eqnc->Close(); + safe_delete(playWaitTimer); + safe_delete(createRequest); + safe_delete(disconnectTimer); + safe_delete(updatetimer); +} + +bool Client::Process() { + if(!start && !eqnc->CheckActive()){ + if(!playWaitTimer) + playWaitTimer = new Timer(5000); + else if(playWaitTimer->Check()){ + safe_delete(playWaitTimer); + return false; + } + return true; + } + else if(!start){ + safe_delete(playWaitTimer); + start = true; + } + + if (disconnectTimer && disconnectTimer->Check()) + { + safe_delete(disconnectTimer); + getConnection()->SendDisconnect(); + } + + if (!kicked) { + /************ Get all packets from packet manager out queue and process them ************/ + EQApplicationPacket *app = 0; + /*if(logintimer && logintimer->Check()) + { + database.LoadCharacters(GetLoginAccount()); + SendLoginAccepted(); + logintimer->Disable(); + }*/ + /*if(worldresponse_timer && worldresponse_timer->Check()) + { + FatalError(WorldDownErrorMessage); + worldresponse_timer->Disable(); + }*/ + + if(playWaitTimer != NULL && playWaitTimer->Check ( ) ) + { + SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT); + safe_delete(playWaitTimer); + } + if(!needs_world_list && updatetimer && updatetimer->Check()){ + if(updatelisttimer && updatelisttimer->Check()){ + if(num_updates >= 180){ //30 minutes + getConnection()->SendDisconnect(); + } + else{ + vector::iterator itr; + if(update_packets){ + for(itr = update_packets->begin(); itr != update_packets->end(); itr++){ + safe_delete(*itr); + } + } + safe_delete(update_packets); + update_packets = world_list.GetServerListUpdate(version); + } + num_updates++; + } + else{ + if(!update_packets){ + update_packets = world_list.GetServerListUpdate(version); + } + else{ + if(update_position < update_packets->size()){ + QueuePacket(update_packets->at(update_position)->serialize()); + update_position++; + } + else + update_position = 0; + } + } + } + + while(app = eqnc->PopPacket()) + { + switch(app->GetOpcode()) + { + case OP_LoginRequestMsg:{ + DumpPacket(app); + PacketStruct* packet = configReader.getStruct("LS_LoginRequest", 1); + if(packet && packet->LoadPacketData(app->pBuffer,app->size)){ + version = packet->getType_int16_ByName("version"); + LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version); + + if (version == 0 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) + { + safe_delete(packet); + packet = configReader.getStruct("LS_LoginRequest", 1208); + if (packet && packet->LoadPacketData(app->pBuffer, app->size)) { + version = packet->getType_int16_ByName("version"); + } + else + break; + } + //[7:19 PM] Kirmmin: Well, I very quickly learned that unknown3 in LS_LoginRequest packet is the same value as cl_eqversion in the eq2_defaults.ini file. + + LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version); + + if (EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) { + LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version); + SendLoginDenied(); + return false; + } + + if(EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()){ + getConnection()->SetClientVersion(GetVersion()); + EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username"); + EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password"); + LoginAccount* acct = database.LoadAccount(username.data.c_str(),password.data.c_str(), net.IsAllowingAccountCreation()); + if(acct){ + Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID()); + if(otherclient) + otherclient->getConnection()->SendDisconnect(); // This person is already logged in, we don't want them logged in twice, kick the previous client as it might be a ghost + } + if(acct){ + SetAccountName(username.data.c_str()); + database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP()); + database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version); + LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", (char*)username.data.c_str()); + } + else + { + if (username.size > 0) + LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", (char*)username.data.c_str()); + else + LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!"); + } + + if(!acct) + SendLoginDenied(); + else{ + needs_world_list = true; + SetLoginAccount(acct); + SendLoginAccepted(); + } + } + else{ + cout << "Error bad version: " << version << endl; + SendLoginDeniedBadVersion(); + } + } + else{ + cout << "Error loading LS_LoginRequest packet: \n"; + //DumpPacket(app); + } + safe_delete(packet); + break; + } + case OP_KeymapLoadMsg:{ + // cout << "Received OP_KeymapNoneMsg\n"; + //dunno what this is for + break; + } + case OP_AllWSDescRequestMsg:{ + SendWorldList(); + needs_world_list = false; + if(!sent_character_list) { + database.LoadCharacters(GetLoginAccount(), GetVersion()); + sent_character_list = true; + } + SendCharList(); + break; + } + case OP_LsClientCrashlogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Crash Log", GetVersion()); + break; + } + case OP_LsClientVerifylogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Verify Log", GetVersion()); + break; + } + case OP_LsClientAlertlogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Alert Log", GetVersion()); + break; + } + case OP_LsClientBaselogReplyMsg:{ +// DumpPacket(app); + SaveErrorsToDB(app, "Base Log", GetVersion()); + break; + } + case OP_AllCharactersDescRequestMsg:{ + break; + } + case OP_CreateCharacterRequestMsg:{ + PacketStruct* packet = configReader.getStruct("CreateCharacter", GetVersion()); + + DumpPacket(app); + playWaitTimer = new Timer ( 15000 ); + playWaitTimer->Start ( ); + + LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", GetAccountName()); + if(packet->LoadPacketData(app->pBuffer,app->size, GetVersion() <= 561 ? false : true)){ + DumpPacket(app->pBuffer, app->size); + packet->setDataByName("account_id",GetAccountID()); + LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id")); + if(!world_server) + { + DumpPacket(app->pBuffer, app->size); + cout << GetAccountName() << " attempted creation of character with an invalid server id of: " << packet->getType_int32_ByName("server_id") << "\n"; + break; + } + else + { + createRequest = packet; + ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, app->size+sizeof(int16)); + int16 out_version = GetVersion(); + memcpy(outpack->pBuffer, &out_version, sizeof(int16)); + memcpy(outpack->pBuffer + sizeof(int16), app->pBuffer, app->size); + uchar* tmp = outpack->pBuffer; + + if(out_version<=283) + tmp+=2; + else if(out_version == 373) { + tmp += 6; + } + else + tmp += 7; + + int32 account_id = GetAccountID(); + memcpy(tmp, &account_id, sizeof(int32)); + world_server->SendPacket(outpack); + safe_delete(outpack); + } + } + else{ + LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", GetAccountName()); + safe_delete(packet); + } + // world_list.SendWorldChanged(create.profile.server_id, false, this); + break; + } + case OP_PlayCharacterRequestMsg:{ + int32 char_id = 0; + int32 server_id = 0; + PacketStruct* request = configReader.getStruct("LS_PlayRequest",GetVersion()); + if(request && request->LoadPacketData(app->pBuffer,app->size)){ + char_id = request->getType_int32_ByName("char_id"); + if (GetVersion() <= 283) { + server_id = database.GetServer(GetAccountID(), char_id, request->getType_EQ2_16BitString_ByName("name").data); + } + else { + server_id = request->getType_int32_ByName("server_id"); + } + LWorld* world = world_list.FindByID(server_id); + string name = database.GetCharacterName(char_id,server_id,GetAccountID()); + if(world && name.length() > 0){ + pending_play_char_id = char_id; + ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct)); + UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer; + req->char_id = char_id; + req->lsaccountid = GetAccountID(); + req->worldid = server_id; + + struct in_addr in; + in.s_addr = GetIP(); + strcpy(req->ip_address, inet_ntoa(in)); + world->SendPacket(outpack); + delete outpack; + + safe_delete(playWaitTimer); + + playWaitTimer = new Timer ( 5000 ); + playWaitTimer->Start ( ); + } + else{ + cout << GetAccountName() << " sent invalid Play Request: \n"; + SendPlayFailed(PLAY_ERROR_PROBLEM); + DumpPacket(app); + } + } + safe_delete(request); + break; + } + case OP_DeleteCharacterRequestMsg:{ + PacketStruct* request = configReader.getStruct("LS_DeleteCharacterRequest", GetVersion()); + PacketStruct* response = configReader.getStruct("LS_DeleteCharacterResponse", GetVersion()); + if(request && response && request->LoadPacketData(app->pBuffer,app->size)){ + EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name"); + int32 acct_id = GetAccountID(); + int32 char_id = request->getType_int32_ByName("char_id"); + int32 server_id = request->getType_int32_ByName("server_id"); + if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){ + response->setDataByName("response", 1); + GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion()); + LWorld* world_server = world_list.FindByID(server_id); + if(world_server != NULL) + world_server->SendDeleteCharacter ( char_id , acct_id ); + } + else + response->setDataByName("response", 0); + response->setDataByName("server_id", server_id); + response->setDataByName("char_id", char_id); + response->setDataByName("account_id", account_id); + response->setMediumStringByName("name", (char*)name.data.c_str()); + response->setDataByName("max_characters", 10); + + EQ2Packet* outapp = response->serialize(); + QueuePacket(outapp); + + this->SendCharList(); + } + safe_delete(request); + safe_delete(response); + break; + } + default: { + const char* name = app->GetOpcodeName(); + if (name) + LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode()); + else + LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode()); + } + } + delete app; + } + } + + if (!eqnc->CheckActive()) { + return false; + } + + return true; +} + +void Client::SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version){ + int32 size = 0; + z_stream zstream; + if (version >= 546) { + memcpy(&size, app->pBuffer + sizeof(int32), sizeof(int32)); + zstream.next_in = app->pBuffer + 8; + zstream.avail_in = app->size - 8; + } + else { //box set + size = 0xFFFF; + zstream.next_in = app->pBuffer + 2; + zstream.avail_in = app->size - 2; + } + size++; + char* message = new char[size]; + memset(message, 0, size); + + int zerror = 0; + + zstream.next_out = (BYTE*)message; + zstream.avail_out = size; + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + + zerror = inflateInit( &zstream); + if(zerror != Z_OK) { + safe_delete_array(message); + return; + } + zerror = inflate( &zstream, 0 ); + if(message && strlen(message) > 0) + database.SaveClientLog(type, message, GetLoginAccount()->getLoginName(), GetVersion()); + safe_delete_array(message); +} + +void Client::CharacterApproved(int32 server_id,int32 char_id) +{ + if(createRequest && server_id == createRequest->getType_int32_ByName("server_id")){ + LWorld* world_server = world_list.FindByID(server_id); + if(!world_server) + return; + + PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion()); + if(packet){ + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("unknown", 0xFFFFFFFF); + packet->setDataByName("response", CREATESUCCESS_REPLY); + packet->setMediumStringByName("name", (char*)createRequest->getType_EQ2_16BitString_ByName("name").data.c_str()); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + database.SaveCharacter(createRequest, GetLoginAccount(),char_id, GetVersion()); + + // refresh characters for this account + database.LoadCharacters(GetLoginAccount(), GetVersion()); + + SendCharList(); + + if (GetVersion() <= 561) + { + pending_play_char_id = char_id; + ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct)); + UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer; + req->char_id = char_id; + req->lsaccountid = GetAccountID(); + req->worldid = server_id; + + struct in_addr in; + in.s_addr = GetIP(); + strcpy(req->ip_address, inet_ntoa(in)); + world_server->SendPacket(outpack); + delete outpack; + } + } + } + else{ + cout << GetAccountName() << " received invalid CharacterApproval from server: " << server_id << endl; + } + safe_delete(createRequest); +} + +void Client::CharacterRejected(int8 reason_number) +{ + PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion()); + if(createRequest && packet){ + packet->setDataByName("account_id", GetAccountID()); + int8 clientReasonNum = reason_number; + // reason numbers change and instead of updating the world server + // the login server will hold the up to date #'s +/* + switch(reason_number) + { + // these error codes seem to be removed now, they shutdown the client rather immediately + // for now we are just going to play a joke on them and say they can't create a new character. + case INVALIDRACE_REPLY: + case INVALIDGENDER_REPLY: + clientReasonNum = 8; + break; + case BADNAMELENGTH_REPLY: + clientReasonNum = 9; + break; + case NAMEINVALID_REPLY: + clientReasonNum = 10; + break; + case NAMEFILTER_REPLY: + clientReasonNum = 11; + break; + case NAMETAKEN_REPLY: + clientReasonNum = 12; + break; + case OVERLOADEDSERVER_REPLY: + clientReasonNum = 13; + break; + } +*/ + packet->setDataByName("response", clientReasonNum); + packet->setMediumStringByName("name", ""); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } + /*LS_CreateCharacterReply reply(GetAccountID(), reason_number, create.profile.name.data); + EQ2Packet* outapp = reply.serialize(); + QueuePacket(outapp); + create.Clear();*/ +} + +void Client::SendCharList(){ + /*PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply"); + packet->setDataByName("account_id", GetAccountID()); + packet->setDataByName("response", reason_number); + packet->setDataByName("name", &create.profile.name); + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet);*/ + LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName()); + LS_CharSelectList list; + list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion()); + EQ2Packet* outapp = list.serialize(GetVersion()); + DumpPacket(outapp->pBuffer, outapp->size); + QueuePacket(outapp); + +} +void Client::SendLoginDeniedBadVersion(){ + EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse)); + LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer; + ls_response->reply_code = 6; + ls_response->unknown03 = 0xFFFFFFFF; + ls_response->unknown04 = 0xFFFFFFFF; + QueuePacket(app); + StartDisconnectTimer(); +} +void Client::SendLoginDenied(){ + EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse)); + LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer; + ls_response->reply_code = 1; + // reply_codes for AoM: + /* 1 = Login rejected: Invalid username or password. Please try again. + 2 = Login rejected: Server thinks your account is currently playing; you may have to wait " + "a few minutes for it to clear, then try again + 6 = Login rejected: The client's version does not match the server's. Please re-run the patcher. + 7 = Login rejected: You have no scheduled playtimes. + 8 = Your account does not have the features required to play on this server. + 11 = The client's build does not match the server's. Please re-run the patcher. + 12 = You must update your password in order to log in. Pressing OK will op" + "en your web browser to the SOE password management page + Other Value > 1 = Login rejected for an unknown reason. + */ + ls_response->unknown03 = 0xFFFFFFFF; + ls_response->unknown04 = 0xFFFFFFFF; + QueuePacket(app); + StartDisconnectTimer(); +} + +void Client::SendLoginAccepted(int32 account_id, int8 login_response) { + PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion()); + int i = 0; + if (packet) + { + packet->setDataByName("account_id", account_id); + + packet->setDataByName("login_response", login_response); + + packet->setDataByName("do_not_force_soga", 1); + + // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership + // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable + // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard + // sub_level = 0 forces popup on close to web browser + packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel()); + packet->setDataByName("race_flag", 0x1FFFFF); + packet->setDataByName("class_flag", 0x7FFFFFE); + packet->setMediumStringByName("username", GetAccountName()); + packet->setMediumStringByName("password", GetAccountName()); + + // unknown5 + // full support = 0x7CFF + // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city + // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city + packet->setDataByName("unknown5", net.GetExpansionFlag()); + packet->setDataByName("unknown6", 0xFF); + packet->setDataByName("unknown6", 0xFF, 1); + packet->setDataByName("unknown6", 0xFF, 2); + + // controls class access / playable characters + packet->setDataByName("unknown10", 0xFF); + + // packet->setDataByName("unknown7a", 0x0101); + // packet->setDataByName("race_unknown", 0x01); + packet->setDataByName("unknown7", net.GetEnabledRaces()); // 0x01-0xFF disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits + packet->setDataByName("unknown7a", 0xEE); + packet->setDataByName("unknown8", net.GetCitiesFlag(), 1); // dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) + + /* + 1 = city of qeynos + 2 = city of freeport + 4 = city of kelethin + 8 = city of neriak + 16 = gorowyn + 32 = new halas + 64 = queens colony + 128 = outpost overlord + */ + + EQ2Packet* outapp = packet->serialize(); + QueuePacket(outapp); + safe_delete(packet); + } +} + +void Client::SendWorldList(){ + EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin, version); + EQ2Packet* dupe = pack->Copy(); + DumpPacket(dupe->pBuffer,dupe->size); + QueuePacket(dupe); + + SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags + return; +} + +void Client::QueuePacket(EQ2Packet* app){ + eqnc->EQ2QueuePacket(app); +} + +void Client::WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key) +{ + LWorld* world = world_list.FindByID(worldid); + if(world == 0) { + FatalError(0); + return; + } + if(response != 1){ + if(response == PLAY_ERROR_CHAR_NOT_LOADED){ + string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID()); + if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())){ + GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion()); + } + } + FatalError(response); + return; + } + + PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion()); + if(response_packet){ + safe_delete(playWaitTimer); + response_packet->setDataByName("response", 1); + response_packet->setSmallStringByName("server", ip_address); + response_packet->setDataByName("port", port); + response_packet->setDataByName("account_id", GetAccountID()); + response_packet->setDataByName("access_code", access_key); + EQ2Packet* outapp = response_packet->serialize(); + QueuePacket(outapp); + safe_delete(response_packet); + } + return; +} +void Client::FatalError(int8 response) { + safe_delete(playWaitTimer); + SendPlayFailed(response); +} + +void Client::SendPlayFailed(int8 response){ + PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion()); + if(response_packet){ + response_packet->setDataByName("response", response); + response_packet->setSmallStringByName("server", ""); + response_packet->setDataByName("port", 0); + response_packet->setDataByName("account_id", GetAccountID()); + response_packet->setDataByName("access_code", 0); + EQ2Packet* outapp = response_packet->serialize(); + QueuePacket(outapp); + safe_delete(response_packet); + } +} + +void ClientList::Add(Client* client) { + MClientList.writelock(); + client_list[client] = true; + MClientList.releasewritelock(); +} + +Client* ClientList::Get(int32 ip, int16 port) { + Client* ret = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->GetIP() == ip && itr->first->GetPort() == port){ + ret = itr->first; + break; + } + } + MClientList.releasereadlock(); + return ret; +} + +void ClientList::FindByCreateRequest(){ + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->AwaitingCharCreationRequest()){ + if(!client) + client = itr->first; + else{ + client = 0;//more than 1 character waiting, dont want to send rejection to wrong one + break; + } + } + } + MClientList.releasereadlock(); + if(client) + client->CharacterRejected(UNKNOWNERROR_REPLY); +} + +Client* ClientList::FindByLSID(int32 lsaccountid) { + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + if(itr->first->GetAccountID() == lsaccountid){ + client = itr->first; + break; + } + } + MClientList.releasereadlock(); + return client; +} +void ClientList::SendPacketToAllClients(EQ2Packet* app){ + Client* client = 0; + map::iterator itr; + MClientList.readlock(); + if(client_list.size() > 0){ + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + itr->first->QueuePacket(app->Copy()); + } + } + safe_delete(app); + MClientList.releasereadlock(); +} +void ClientList::Process() { + Client* client = 0; + vector erase_list; + map::iterator itr; + MClientList.readlock(); + for(itr = client_list.begin(); itr != client_list.end(); itr++){ + client = itr->first; + if(!client->Process()) + erase_list.push_back(client); + } + MClientList.releasereadlock(); + if(erase_list.size() > 0){ + vector::iterator erase_itr; + MClientList.writelock(); + for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++){ + client = *erase_itr; + struct in_addr in; + in.s_addr = client->getConnection()->GetRemoteIP(); + net.numclients--; + LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName()); + client->getConnection()->Close(); + net.UpdateWindowTitle(); + client_list.erase(client); + } + MClientList.releasewritelock(); + } +} + + +void Client::StartDisconnectTimer() { + if (!disconnectTimer) + { + disconnectTimer = new Timer(1000); + disconnectTimer->Start(); + } +} diff --git a/old/login/client.h b/old/login/client.h new file mode 100644 index 0000000..4d3936e --- /dev/null +++ b/old/login/client.h @@ -0,0 +1,131 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef CLIENT_H +#define CLIENT_H + +#include "../common/linked_list.h" +#include "../common/timer.h" +#include "../common/TCPConnection.h" +#include "login_structs.h" +#include "LoginAccount.h" +#include "../common/PacketStruct.h" +#include +#include + +enum eLoginMode { None, Normal, Registration }; +class DelayQue; +class Client +{ +public: + Client(EQStream* ieqnc); + ~Client(); + void SendLoginDenied(); + void SendLoginDeniedBadVersion(); + void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0); + void SendWorldList(); + void SendCharList(); + int16 AddWorldToList2(uchar* buffer, char* name, int32 id, int16* flags); + void GenerateChecksum(EQApplicationPacket* outapp); + int8 LoginKey[10]; + int8 ClientSession[25]; + bool Process(); + void SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version); + void CharacterApproved(int32 server_id,int32 char_id); + void CharacterRejected(int8 reason_number); + EQStream* getConnection() { return eqnc; } + LoginAccount* GetLoginAccount() { return login_account; } + void SetLoginAccount(LoginAccount* in_account) { + login_account = in_account; + if(in_account) + account_id = in_account->getLoginAccountID(); + } + int16 GetVersion(){ return version; } + char* GetKey() { return key; } + void SetKey(char* in_key) { strcpy(key,in_key); } + int32 GetIP() { return ip; } + int16 GetPort() { return port; } + int32 GetAccountID() { return account_id; } + const char* GetAccountName(){ return (char*)account_name.c_str(); } + void SetAccountName(const char* name){ account_name = string(name); } + void ProcessLogin(char* name, char* pass,int seq=0); + void QueuePacket(EQ2Packet* app); + void FatalError(int8 response); + void WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key); + bool AwaitingCharCreationRequest(){ + if(createRequest) + return true; + else + return false; + } + Timer* updatetimer; + Timer* updatelisttimer; + Timer* disconnectTimer; + //Timer* keepalive; + //Timer* logintimer; + int16 packettotal; + int32 requested_server_id; + int32 request_num; + LinkedList delay_que; + void SendPlayFailed(int8 response); + + void StartDisconnectTimer(); +private: + string pending_play_char_name; + int32 pending_play_char_id; + int8 update_position; + int16 num_updates; + vector* update_packets; + LoginAccount* login_account; + EQStream* eqnc; + + int32 ip; + int16 port; + + int32 account_id; + string account_name; + char key[10]; + int8 lsadmin; + sint16 worldadmin; + int lsstatus; + bool kicked; + bool verified; + bool start; + bool needs_world_list; + int16 version; + char bannedreason[30]; + bool sent_character_list; + eLoginMode LoginMode; + PacketStruct* createRequest; + Timer* playWaitTimer; +}; + +class ClientList +{ +public: + ClientList() {} + ~ClientList() {} + + void Add(Client* client); + Client* Get(int32 ip, int16 port); + Client* FindByLSID(int32 lsaccountid); + void FindByCreateRequest(); + void SendPacketToAllClients(EQ2Packet* app); + void Process(); +private: + Mutex MClientList; + map client_list; +}; +class DelayQue { +public: + DelayQue(Timer* in_timer, EQApplicationPacket* in_packet){ + timer = in_timer; + packet = in_packet; + }; + Timer* timer; + EQApplicationPacket* packet; +}; +#endif diff --git a/old/login/login_opcodes.h b/old/login/login_opcodes.h new file mode 100644 index 0000000..734ae2f --- /dev/null +++ b/old/login/login_opcodes.h @@ -0,0 +1,52 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ + +#ifndef LOGIN_OPCODES_H +#define LOGIN_OPCODES_H +#define OP_Login2 0x0200 +#define OP_GetLoginInfo 0x0300 +#define OP_SendServersFragment 0x0D00 +#define OP_LoginInfo 0x0100 +#define OP_SessionId 0x0900 +#define OP_Disconnect 0x0500 +//#define OP_Reg_SendPricing 0x0400 +#define OP_AllFinish 0x0500 +#define OP_Ack5 0x1500 +#define OP_Chat_ChannelList 0x0600 +#define OP_Chat_JoinChannel 0x0700 +#define OP_Chat_PartChannel 0x0800 +#define OP_Chat_ChannelMessage 0x0930 +#define OP_Chat_Tell 0x0a00 +#define OP_Chat_SysMsg 0x0b00 +#define OP_Chat_CreateChannel 0x0c00 +#define OP_Chat_ChangeChannel 0x0d00 +#define OP_Chat_DeleteChannel 0x0e00 +#define OP_Chat_UserList 0x1000 +#define OP_Reg_GetPricing 0x1a00 // for new account signup +#define OP_Reg_SendPricing 0x1b00 +#define OP_RegisterAccount 0x2300 +#define OP_Chat_ChannelWelcome 0x2400 +#define OP_Chat_PopupMakeWindow 0x3000 +#define OP_BillingInfoAccepted 0x3300 // i THINK =p +#define OP_CheckGameCardValid 0x3400 +#define OP_GameCardTimeLeft 0x3600 +#define OP_AccountExpired 0x4200 +#define OP_Reg_GetPricing2 0x4400 // for re-registering +#define OP_ChangePassword 0x4500 +#define OP_ServerList 0x4600 +#define OP_SessionKey 0x4700 +#define OP_RequestServerStatus 0x4800 +#define OP_SendServerStatus 0x4A00 +#define OP_Reg_ChangeAcctLogin 0x5100 +#define OP_LoginBanner 0x5200 +#define OP_Chat_GuildsList 0x5500 +#define OP_Chat_GuildEdit 0x5700 +#define OP_Version 0x5900 +#define OP_RenewAccountBillingInfo 0x7a00 + +#endif /* LOGIN_OPCODES_H */ + diff --git a/old/login/login_structs.h b/old/login/login_structs.h new file mode 100644 index 0000000..bd8e9b9 --- /dev/null +++ b/old/login/login_structs.h @@ -0,0 +1,61 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifndef LOGIN_STRUCTS_H +#define LOGIN_STRUCTS_H + +#include "../common/types.h" +#include "PacketHeaders.h" + +#pragma pack(1) +struct LS_LoginRequest{ + EQ2_16BitString AccessCode; + EQ2_16BitString unknown1; + EQ2_16BitString username; + EQ2_16BitString password; + EQ2_16BitString unknown2[4]; + int16 unknown3; + int32 unknown4[2]; +}; +struct LS_WorldStatusChanged{ + int32 server_id; + int8 up; + int8 locked; + int8 hidden; +}; +struct LS_PlayCharacterRequest{ + int32 character_id; + int32 server_id; + int16 unknown1; +}; +struct LS_OLDPlayCharacterRequest{ + int32 character_id; + EQ2_16BitString name; +}; + +struct LS_CharListAccountInfoEarlyClient { + int32 account_id; + int32 unknown1; + int16 unknown2; + int32 maxchars; + int8 unknown4; // 15 bytes total + // int8 unknown7; // adds 'free' option.. +}; + +struct LS_CharListAccountInfo{ + int32 account_id; + int32 unknown1; + int16 unknown2; + int32 maxchars; + // DoF does not have the following data + int8 unknown4; + int32 unknown5[4]; + int8 vet_adv_bonus; // sets Veteran Bonus under 'Select Character' yellow (vs greyed out), adventure/tradeskill bonus 200% + int8 vet_trade_bonus; // when 1 (count?) provides free upgrade option for character to lvl 90 (heroic character) -- its a green 'Free' up arrow next to the character that is selected in char select +}; // 33 bytes +#pragma pack() + +#endif diff --git a/old/login/net.cpp b/old/login/net.cpp new file mode 100644 index 0000000..fe6b8cd --- /dev/null +++ b/old/login/net.cpp @@ -0,0 +1,363 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#include "../common/debug.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "../common/queue.h" +#include "../common/timer.h" + +#include "../common/seperator.h" + +#include "net.h" +#include "client.h" + +#include "LoginDatabase.h" +#include "LWorld.h" +#include "../common/packet_functions.h" +#include "../common/EQStreamFactory.h" +#include "../common/MiscFunctions.h" +#include "../common/version.h" + +#include "../common/PacketStruct.h" +#include "../common/DataBuffer.h" +#include "../common/ConfigReader.h" +#include "../common/Log.h" +#include "../common/JsonParser.h" +#include "../common/Common_Defines.h" + +#ifdef WIN32 + #define snprintf _snprintf + #define vsnprintf _vsnprintf + #define strncasecmp _strnicmp + #define strcasecmp _stricmp + #include +#else + #include + #include "../common/unix.h" +#endif +EQStreamFactory eqsf(LoginStream); +mapEQOpcodeManager; +//TCPServer eqns(5999); +NetConnection net; +ClientList client_list; +LWorldList world_list; +LoginDatabase database; +ConfigReader configReader; +map EQOpcodeVersions; +Timer statTimer(60000); + +volatile bool RunLoops = true; +bool ReadLoginConfig(); + +#ifdef PUBLICLOGIN +char version[200], consoletitle[200]; +#endif +#include "../common/timer.h" + +#include "../common/CRC16.h" +#include + +int main(int argc, char** argv){ +#ifdef _DEBUG + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif + if (signal(SIGINT, CatchSignal) == SIG_ERR) { + cerr << "Could not set signal handler" << endl; + } + + LogStart(); + + LogParseConfigs(); + net.WelcomeHeader(); + + srand(time(NULL)); + + if(!net.ReadLoginConfig()) + return 1; + + net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword()); + + const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" }; + + for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++) + { + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]); + if (configReader.processXML_Elements(structList[s])) + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]); + else + { + LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]); + return 1; + } + } + + + LogWrite(INIT__INFO, 0, "Init", "Initialize World List.."); + world_list.Init(); + + if(eqsf.listen_ip_address) + LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort()); + else + LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort()); + /*} + else { + cout << "EQNetworkServer.Open() error" << endl; + return 1; + }*/ + if (!eqsf.Open(net.GetPort())) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort()); + return 1; + } + net.login_running = true; + net.login_uptime = getCurrentTimestamp(); + + net.UpdateWindowTitle(); + EQStream* eqs; + Timer* TimeoutTimer = new Timer(5000); + TimeoutTimer->Start(); + while(RunLoops) { + Timer::SetCurrentTime(); + while ((eqs = eqsf.Pop())) { + struct in_addr in; + in.s_addr = eqs->GetRemoteIP(); + + LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort())); + Client* client = new Client(eqs); + eqs->SetClientVersion(0); + client_list.Add(client); + net.numclients++; + net.UpdateWindowTitle(); + } + if(TimeoutTimer->Check()){ + eqsf.CheckTimeout(); + } + if(statTimer.Check()){ + world_list.UpdateWorldStats(); + database.RemoveOldWorldServerStats(); + database.FixBugReport(); + } + client_list.Process(); + world_list.Process(); +#ifdef WIN32 + if(kbhit()) + { + int hitkey = getch(); + net.HitKey(hitkey); + } +#endif + Sleep(1); + } + //close + //eqns.Close(); + eqsf.Close(); + world_list.Shutdown(); + return 0; +} +#ifdef WIN32 +void NetConnection::HitKey(int keyhit) +{ + switch(keyhit) + { + case 'l': + case 'L': { + world_list.ListWorldsToConsole(); + break; + } + case 'v': + case 'V': + { + printf("========Version Info=========\n"); + printf("%s %s\n", EQ2EMU_MODULE, CURRENT_VERSION); + printf("Last Compiled on %s %s\n", COMPILE_DATE, COMPILE_TIME); + printf("=============================\n\n"); + break; + } + case 'H': + case 'h': { + printf("===========Help=============\n"); + printf("Available Commands:\n"); + printf("l = Listing of World Servers\n"); + printf("v = Login Version\n"); +// printf("0 = Kick all connected world servers\n"); + printf("============================\n\n"); + break; + } + default: + printf("Invalid Command.\n"); + break; + } +} +#endif + +void CatchSignal(int sig_num) { + cout << "Got signal " << sig_num << endl; + RunLoops = false; +} + +bool NetConnection::ReadLoginConfig() { + JsonParser parser(MAIN_CONFIG_FILE); + if(!parser.IsLoaded()) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); + return false; + } + std::string serverport = parser.getValue("loginconfig.serverport"); + std::string serverip = parser.getValue("loginconfig.serverip"); + + if (!parser.convertStringToUnsignedShort(serverport, port)) { + LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport.."); + return false; + } + + if(serverip.size() > 0) { + eqsf.listen_ip_address = new char[serverip.size() + 1]; + strcpy(eqsf.listen_ip_address, serverip.c_str()); + } + else { + safe_delete(eqsf.listen_ip_address); + eqsf.listen_ip_address = nullptr; + } + + std::string acctcreate_str = parser.getValue("loginconfig.accountcreation"); + int16 allow_acct = 0; + parser.convertStringToUnsignedShort(acctcreate_str, allow_acct); + allowAccountCreation = allow_acct > 0 ? true : false; + + std::string expflag_str = parser.getValue("loginconfig.expansionflag"); + parser.convertStringToUnsignedInt(expflag_str, expansionFlag); + + std::string citiesflag_str = parser.getValue("loginconfig.citiesflag"); + parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag); + + std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel"); + parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel); + + std::string enableraces_str = parser.getValue("loginconfig.enabledraces"); + parser.convertStringToUnsignedInt(enableraces_str, enabledRaces); + + web_loginaddress = parser.getValue("loginconfig.webloginaddress"); + web_certfile = parser.getValue("loginconfig.webcertfile"); + web_keyfile = parser.getValue("loginconfig.webkeyfile"); + web_keypassword = parser.getValue("loginconfig.webkeypassword"); + web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser"); + web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword"); + + std::string webloginport_str = parser.getValue("loginconfig.webloginport"); + parser.convertStringToUnsignedShort(webloginport_str, web_loginport); + + LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE); + + + LogWrite(INIT__INFO, 0, "Init", "Database init begin.."); + //remove this when all database calls are using the new database class + if (!database.Init()) { + LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!"); + LogStop(); + return false; + } + + LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0.."); + EQOpcodeVersions = database.GetVersions(); + map::iterator version_itr2; + int16 version1 = 0; + for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) { + version1 = version_itr2->first; + EQOpcodeManager[version1] = new RegularOpcodeManager(); + map eq = database.GetOpcodes(version1); + if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) { + LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); + return false; + } + } + + return true; +} + +void NetConnection::UpdateWindowTitle(char* iNewTitle) { +#ifdef WIN32 + char tmp[500]; + if (iNewTitle) { + snprintf(tmp, sizeof(tmp), "Login: %s", iNewTitle); + } + else { + snprintf(tmp, sizeof(tmp), "%s, Version: %s: %i Server(s), %i Client(s) Connected", EQ2EMU_MODULE, CURRENT_VERSION, net.numservers, net.numclients); + } + SetConsoleTitle(tmp); +#endif +} + +void NetConnection::WelcomeHeader() +{ +#ifdef _WIN32 + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); +#endif + printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n"); + printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); + printf("it under the terms of the GNU General Public License as published by\n"); + printf("the Free Software Foundation, either version 3 of the License, or\n"); + printf("(at your option) any later version.\n\n"); + printf("EQ2Emulator is distributed in the hope that it will be useful,\n"); + printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); + printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); + printf("GNU General Public License for more details.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD); +#endif + printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"); + printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"); + printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"); + printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"); + printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"); + printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"); + printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"); + printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"); + printf(" \\__/ \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD); +#endif + printf(" Website : https://eq2emu.com \n"); + printf(" Wiki : https://wiki.eq2emu.com \n"); + printf(" Git : https://git.eq2emu.com \n"); + printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); +#endif + printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"); +#ifdef _WIN32 + SetConsoleTextAttribute(console, FOREGROUND_WHITE); +#endif + + fflush(stdout); +} + +void NetConnection::InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password) { + if(web_ipaddr.size() > 0 && web_port > 0) { + try { + login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); + + login_webserver->register_route("/status", NetConnection::Web_loginhandle_status); + login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds); + login_webserver->run(); + LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); + } + catch (const std::exception& e) { + LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); + } + } +} \ No newline at end of file diff --git a/old/login/net.h b/old/login/net.h new file mode 100644 index 0000000..0f7b628 --- /dev/null +++ b/old/login/net.h @@ -0,0 +1,147 @@ +/* + EQ2Emulator: Everquest II Server Emulator + Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) + + This file is part of EQ2Emulator. +*/ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN + #include + #include +#else + #include + #include + #include + #include +#endif + +//#include +#include +#include +#include + +#include "../common/types.h" +#include "../common/Web/WebServer.h" +#include "../common/MiscFunctions.h" + +void CatchSignal(int sig_num); +enum eServerMode { Standalone, Master, Slave, Mesh }; + +class NetConnection +{ +public: + NetConnection() { + port = 5999; + listening_socket = 0; + memset(masteraddress, 0, sizeof(masteraddress)); + uplinkport = 0; + memset(uplinkaccount, 0, sizeof(uplinkaccount)); + memset(uplinkpassword, 0, sizeof(uplinkpassword)); + LoginMode = Standalone; + Uplink_WrongVersion = false; + numclients = 0; + numservers = 0; + allowAccountCreation = true; + + // full support = 0x7CFF + // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city + // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city + expansionFlag = 0x7CFF; // 0x4CF5 + + /* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) + 1 = city of qeynos + 2 = city of freeport + 4 = city of kelethin + 8 = city of neriak + 16 = gorowyn + 32 = new halas + 64 = queens colony + 128 = outpost overlord + */ + citiesFlag = 0xFF; + + // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership + // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable + // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard + // sub_level = 0 forces popup on close to web browser + defaultSubscriptionLevel = 0xFFFFFFFF; + + // disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits + enabledRaces = 0xFFFF; // 0xCFFF + + web_loginport = 0; + + login_webserver = nullptr; + + login_running = false; + login_uptime = getCurrentTimestamp(); + } + + ~NetConnection() { + safe_delete(login_webserver); + } + + void UpdateWindowTitle(char* iNewTitle = 0); + bool Init(); + void ListenNewClients(); + void HitKey(int keyhit); + char address[1024]; + int32 numclients; + int32 numservers; + + int16 GetPort() { return port; } + void SetPort(int16 in_port) { port = in_port; } + eServerMode GetLoginMode() { return LoginMode; } + + bool ReadLoginConfig(); + char* GetMasterAddress() { return masteraddress; } + int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; } + char* GetUplinkAccount() { return uplinkaccount; } + char* GetUplinkPassword() { return uplinkpassword; } + + bool IsAllowingAccountCreation() { return allowAccountCreation; } + int32 GetExpansionFlag() { return expansionFlag; } + int8 GetCitiesFlag() { return citiesFlag; } + int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; } + int32 GetEnabledRaces() { return enabledRaces; } + std::string GetWebLoginAddress() { return web_loginaddress; } + inline int16 GetWebLoginPort() { return web_loginport; } + std::string GetWebCertFile() { return web_certfile; } + std::string GetWebKeyFile() { return web_keyfile; } + std::string GetWebKeyPassword() { return web_keypassword; } + std::string GetWebHardcodeUser() { return web_hardcodeuser; } + std::string GetWebHardcodePassword() { return web_hardcodepassword; } + void WelcomeHeader(); + + void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password); + + static void Web_loginhandle_status(const http::request& req, http::response& res); + static void Web_loginhandle_worlds(const http::request& req, http::response& res); + + bool login_running; + std::atomic login_uptime; +protected: + friend class LWorld; + bool Uplink_WrongVersion; +private: + int16 port; + int listening_socket; + char masteraddress[300]; + int16 uplinkport; + char uplinkaccount[300]; + char uplinkpassword[300]; + eServerMode LoginMode; + bool allowAccountCreation; + int32 expansionFlag; + int8 citiesFlag; + int32 defaultSubscriptionLevel; + int32 enabledRaces; + std::string web_loginaddress; + std::string web_certfile; + std::string web_keyfile; + std::string web_keypassword; + std::string web_hardcodeuser; + std::string web_hardcodepassword; + int16 web_loginport; + WebServer* login_webserver; +}; diff --git a/opcodes.go b/opcodes.go new file mode 100644 index 0000000..ab0e42a --- /dev/null +++ b/opcodes.go @@ -0,0 +1,56 @@ +package eq2net + +// Protocol opcodes for UDP packet types +const ( + OPSessionRequest uint8 = 0x01 + OPSessionResponse uint8 = 0x02 + OPCombined uint8 = 0x03 + OPSessionDisconnect uint8 = 0x05 + OPKeepAlive uint8 = 0x06 + OPServerKeyRequest uint8 = 0x07 + OPSessionStatResponse uint8 = 0x08 + OPPacket uint8 = 0x09 + OPFragment uint8 = 0x0d + OPOutOfOrderAck uint8 = 0x11 + OPAck uint8 = 0x15 + OPAppCombined uint8 = 0x19 + OPOutOfSession uint8 = 0x1d +) + +// Application opcodes +type EmuOpcode uint16 + +const ( + OPUnknown EmuOpcode = iota + OPLoginReplyMsg + OPLoginByNumRequestMsg + OPWSLoginRequestMsg + OPESInitMsg + OPESReadyForClientsMsg + OPCreateZoneInstanceMsg + OPZoneInstanceCreateReplyMsg + OPZoneInstanceDestroyedMsg + OPExpectClientAsCharacterRequest + OPExpectClientAsCharacterReplyMs + OPZoneInfoMsg + OPCreateCharacterRequestMsg + OPDoneLoadingZoneResourcesMsg + OPDoneSendingInitialEntitiesMsg + OPDoneLoadingEntityResourcesMsg + OPDoneLoadingUIResourcesMsg + OPPredictionUpdateMsg + OPRemoteCmdMsg + OPSetRemoteCmdsMsg + OPGameWorldTimeMsg + OPMOTDMsg + OPZoneMOTDMsg + OPGuildRecruitingMemberInfo + OPGuildRecruiting + OPGuildRecruitingDetails + OPGuildRecruitingImage + OPAvatarCreatedMsg + OPAvatarDestroyedMsg + OPRequestCampMsg + OPMapRequest + OPCampStartedMsg +) \ No newline at end of file diff --git a/packet.go b/packet.go new file mode 100644 index 0000000..8f8eff7 --- /dev/null +++ b/packet.go @@ -0,0 +1,356 @@ +package eq2net + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "fmt" + "net" + "time" + "unsafe" +) + +// Packet represents a base packet structure +type Packet struct { + Buffer []byte + Size uint32 + SrcIP net.IP + SrcPort uint16 + DstIP net.IP + DstPort uint16 + Priority uint32 + Timestamp time.Time + Version int16 + Opcode uint16 +} + +// NewPacket creates a new packet +func NewPacket(op uint16, buf []byte) *Packet { + p := &Packet{ + Opcode: op, + Size: uint32(len(buf)), + Timestamp: time.Now(), + } + if buf != nil { + p.Buffer = make([]byte, len(buf)) + copy(p.Buffer, buf) + } + return p +} + +// SetSrcInfo sets source IP and port +func (p *Packet) SetSrcInfo(ip net.IP, port uint16) { + p.SrcIP = ip + p.SrcPort = port +} + +// SetDstInfo sets destination IP and port +func (p *Packet) SetDstInfo(ip net.IP, port uint16) { + p.DstIP = ip + p.DstPort = port +} + +// GetRawOpcode returns the raw opcode +func (p *Packet) GetRawOpcode() uint16 { + return p.Opcode +} + +// ProtocolPacket represents a protocol-level packet +type ProtocolPacket struct { + *Packet + EQ2Compressed bool + PacketPrepared bool + PacketEncrypted bool + Acked bool + SentTime int32 + AttemptCount int8 + Sequence uint16 +} + +// NewProtocolPacket creates a new protocol packet from raw data +func NewProtocolPacket(buf []byte, opcode int) *ProtocolPacket { + if len(buf) < 2 { + return nil + } + + var op uint16 + if opcode >= 0 { + op = uint16(opcode) + } else { + op = binary.BigEndian.Uint16(buf[:2]) + buf = buf[2:] + } + + pp := &ProtocolPacket{ + Packet: NewPacket(op, buf), + } + return pp +} + +// NewProtocolPacketWithOp creates a new protocol packet with specific opcode +func NewProtocolPacketWithOp(op uint16, buf []byte) *ProtocolPacket { + return &ProtocolPacket{ + Packet: NewPacket(op, buf), + } +} + +// ValidateCRC validates the CRC of a packet +func ValidateCRC(buffer []byte, length int, key uint32) bool { + // SessionRequest, SessionResponse, OutOfSession are not CRC'd + if len(buffer) >= 2 && buffer[0] == 0x00 && + (buffer[1] == OPSessionRequest || buffer[1] == OPSessionResponse || buffer[1] == OPOutOfSession) { + return true + } + + // Check for special case + if len(buffer) >= 4 && buffer[2] == 0x00 && buffer[3] == 0x19 { + return true + } + + // Calculate and compare CRC + if length < 2 { + return false + } + + compCRC := CalculateCRC16(buffer[:length-2], key) + packetCRC := binary.BigEndian.Uint16(buffer[length-2:]) + + return packetCRC == 0 || compCRC == packetCRC +} + +// Serialize serializes the protocol packet +func (p *ProtocolPacket) Serialize(offset int8) []byte { + // Calculate size including opcode + totalSize := len(p.Buffer) + 2 + if offset != 0 { + totalSize += int(offset) + } + + result := make([]byte, totalSize) + pos := 0 + + // Add offset if needed + if offset != 0 { + pos = int(offset) + } + + // Write opcode + binary.BigEndian.PutUint16(result[pos:], p.Opcode) + pos += 2 + + // Copy buffer + if p.Buffer != nil { + copy(result[pos:], p.Buffer) + } + + return result +} + +// Copy creates a copy of the protocol packet +func (p *ProtocolPacket) Copy() *ProtocolPacket { + newPacket := &ProtocolPacket{ + Packet: NewPacket(p.Opcode, p.Buffer), + EQ2Compressed: p.EQ2Compressed, + PacketPrepared: p.PacketPrepared, + PacketEncrypted: p.PacketEncrypted, + Acked: p.Acked, + SentTime: p.SentTime, + AttemptCount: p.AttemptCount, + Sequence: p.Sequence, + } + newPacket.Version = p.Version + newPacket.Priority = p.Priority + return newPacket +} + +// Combine combines two protocol packets +func (p *ProtocolPacket) Combine(rhs *ProtocolPacket) bool { + if rhs.Opcode != p.Opcode { + return false + } + + // Create new buffer with combined size + newSize := len(p.Buffer) + len(rhs.Buffer) + newBuffer := make([]byte, newSize) + + copy(newBuffer, p.Buffer) + copy(newBuffer[len(p.Buffer):], rhs.Buffer) + + p.Buffer = newBuffer + p.Size = uint32(newSize) + + return true +} + +// IsProtocolPacket checks if a buffer contains a protocol packet +func IsProtocolPacket(inBuff []byte, length uint32, trimCRC bool) bool { + if length < 2 { + return false + } + + opcode := binary.BigEndian.Uint16(inBuff) + + switch uint8(opcode & 0xFF) { + case OPSessionRequest, OPSessionResponse, OPCombined, + OPSessionDisconnect, OPKeepAlive, OPServerKeyRequest, + OPSessionStatResponse, OPPacket, OPFragment, + OPOutOfOrderAck, OPAck, OPAppCombined, OPOutOfSession: + return true + } + + return false +} + +// Decompress decompresses a buffer +func Decompress(buffer []byte, length uint32, newbuf []byte, newbufsize uint32) (uint32, error) { + reader := bytes.NewReader(buffer[:length]) + zlibReader, err := zlib.NewReader(reader) + if err != nil { + return 0, err + } + defer zlibReader.Close() + + buf := bytes.NewBuffer(newbuf[:0]) + n, err := buf.ReadFrom(zlibReader) + if err != nil { + return 0, err + } + + if uint32(n) > newbufsize { + return 0, fmt.Errorf("decompressed size exceeds buffer size") + } + + return uint32(n), nil +} + +// Compress compresses a buffer +func Compress(buffer []byte, length uint32, newbuf []byte, newbufsize uint32) (uint32, error) { + var buf bytes.Buffer + writer := zlib.NewWriter(&buf) + + _, err := writer.Write(buffer[:length]) + if err != nil { + return 0, err + } + + err = writer.Close() + if err != nil { + return 0, err + } + + compressed := buf.Bytes() + if uint32(len(compressed)) > newbufsize { + return 0, fmt.Errorf("compressed size exceeds buffer size") + } + + copy(newbuf, compressed) + return uint32(len(compressed)), nil +} + +// ChatDecode decodes chat data +func ChatDecode(buffer []byte, size int, decodeKey int) { + for i := 0; i+4 <= size; i++ { + c := buffer[i] + buffer[i] = buffer[i+2] + buffer[i+2] = c + c = buffer[i+1] + buffer[i+1] = buffer[i+3] + buffer[i+3] = c + + key := uint32(decodeKey) + p := (*uint32)(unsafe.Pointer(&buffer[i])) + *p = (*p) ^ key + i += 3 + } +} + +// ChatEncode encodes chat data +func ChatEncode(buffer []byte, size int, encodeKey int) { + for i := 0; i+4 <= size; i++ { + key := uint32(encodeKey) + p := (*uint32)(unsafe.Pointer(&buffer[i])) + *p = (*p) ^ key + + c := buffer[i] + buffer[i] = buffer[i+2] + buffer[i+2] = c + c = buffer[i+1] + buffer[i+1] = buffer[i+3] + buffer[i+3] = c + i += 3 + } +} + +// ApplicationPacket represents an application-level packet +type ApplicationPacket struct { + *Packet + EmuOpcode EmuOpcode + AppOpcodeSize uint8 +} + +// NewApplicationPacket creates a new application packet +func NewApplicationPacket(op EmuOpcode, buf []byte) *ApplicationPacket { + ap := &ApplicationPacket{ + Packet: NewPacket(0, buf), + EmuOpcode: op, + AppOpcodeSize: 2, // Default for world/zone + } + return ap +} + +// SetOpcode sets the opcode +func (p *ApplicationPacket) SetOpcode(op EmuOpcode) { + p.EmuOpcode = op + p.Opcode = uint16(op) +} + +// GetOpcode returns the opcode +func (p *ApplicationPacket) GetOpcode() EmuOpcode { + return p.EmuOpcode +} + +// Copy creates a copy of the application packet +func (p *ApplicationPacket) Copy() *ApplicationPacket { + newPacket := &ApplicationPacket{ + Packet: NewPacket(p.Opcode, p.Buffer), + EmuOpcode: p.EmuOpcode, + AppOpcodeSize: p.AppOpcodeSize, + } + newPacket.Version = p.Version + return newPacket +} + +// Serialize serializes the application packet +func (p *ApplicationPacket) Serialize() []byte { + size := len(p.Buffer) + int(p.AppOpcodeSize) + result := make([]byte, size) + + // Write opcode based on size + if p.AppOpcodeSize == 1 { + result[0] = uint8(p.Opcode) + copy(result[1:], p.Buffer) + } else { + binary.LittleEndian.PutUint16(result, p.Opcode) + copy(result[2:], p.Buffer) + } + + return result +} + +// Combine combines two application packets +func (p *ApplicationPacket) Combine(rhs *ApplicationPacket) bool { + if rhs.EmuOpcode != p.EmuOpcode { + return false + } + + newSize := len(p.Buffer) + len(rhs.Buffer) + newBuffer := make([]byte, newSize) + + copy(newBuffer, p.Buffer) + copy(newBuffer[len(p.Buffer):], rhs.Buffer) + + p.Buffer = newBuffer + p.Size = uint32(newSize) + + return true +} \ No newline at end of file diff --git a/stream.go b/stream.go new file mode 100644 index 0000000..b387b76 --- /dev/null +++ b/stream.go @@ -0,0 +1,675 @@ +package eq2net + +import ( + "encoding/binary" + "fmt" + "net" + "sync" + "time" +) + +// StreamState represents the connection state +type StreamState int + +const ( + Established StreamState = iota + WaitClose + Closing + Disconnecting + Closed +) + +// StreamType represents different stream types +type StreamType int + +const ( + UnknownStream StreamType = iota + LoginStream + WorldStream + ZoneStream + ChatOrMailStream + ChatStream + MailStream + EQ2Stream +) + +// SessionRequest packet structure +type SessionRequest struct { + UnknownA uint32 + Session uint32 + MaxLength uint32 +} + +// SessionResponse packet structure +type SessionResponse struct { + Session uint32 + Key uint32 + UnknownA uint8 + Format uint8 + UnknownB uint8 + MaxLength uint32 + UnknownD uint32 +} + +// ClientSessionStats for tracking connection statistics +type ClientSessionStats struct { + RequestID uint16 + LastLocalDelta uint32 + AverageDelta uint32 + LowDelta uint32 + HighDelta uint32 + LastRemoteDelta uint32 + PacketsSent uint64 + PacketsReceived uint64 +} + +// ServerSessionStats for server-side statistics +type ServerSessionStats struct { + RequestID uint16 + CurrentTime uint32 + Unknown1 uint32 + ReceivedPackets uint32 + Unknown2 uint32 + SentPackets uint32 + Unknown3 uint32 + SentPackets2 uint32 + Unknown4 uint32 + ReceivedPackets2 uint32 +} + +// Stream represents a connection stream +type Stream struct { + mu sync.RWMutex + + // Connection info + remoteAddr *net.UDPAddr + remoteIP net.IP + remotePort uint16 + localAddr *net.UDPAddr + sessionID uint32 + key uint32 + state StreamState + streamType StreamType + + // Buffers + buffer [8192]byte + oversizeBuffer []byte + oversizeOffset uint32 + oversizeLength uint32 + rogueBuffer []byte + rogueBufOffset uint32 + rogueBufSize uint32 + + // Packet tracking + receivedPackets uint32 + sentPackets uint32 + nextInSeq uint16 + nextOutSeq uint16 + lastAckSent uint16 + lastAckReceived uint16 + + // Timing + lastReceiveTime time.Time + lastSendTime time.Time + avgRoundTrip time.Duration + minRoundTrip time.Duration + maxRoundTrip time.Duration + retransmitTimeout time.Duration + + // Queues + incomingQueue []*ProtocolPacket + outgoingQueue []*ProtocolPacket + sentQueue map[uint16]*ProtocolPacket + futurePackets map[uint16]*ProtocolPacket + + // Options + compressed bool + encoded bool + maxLength uint32 + opcodeSize uint8 + + // Callbacks + onPacket func(*ApplicationPacket) + onDisconnect func() +} + +// NewStream creates a new stream +func NewStream(remoteAddr *net.UDPAddr, streamType StreamType) *Stream { + s := &Stream{ + remoteAddr: remoteAddr, + remoteIP: remoteAddr.IP, + remotePort: uint16(remoteAddr.Port), + streamType: streamType, + state: Established, + sentQueue: make(map[uint16]*ProtocolPacket), + futurePackets: make(map[uint16]*ProtocolPacket), + lastReceiveTime: time.Now(), + lastSendTime: time.Now(), + retransmitTimeout: 500 * time.Millisecond, + maxLength: 512, + opcodeSize: 2, + } + + // Set opcode size based on stream type + if streamType == LoginStream || streamType == ChatStream || streamType == MailStream { + s.opcodeSize = 1 + } + + return s +} + +// SetSessionInfo sets the session ID and key +func (s *Stream) SetSessionInfo(sessionID uint32, key uint32) { + s.mu.Lock() + defer s.mu.Unlock() + s.sessionID = sessionID + s.key = key +} + +// GetSessionID returns the session ID +func (s *Stream) GetSessionID() uint32 { + s.mu.RLock() + defer s.mu.RUnlock() + return s.sessionID +} + +// GetKey returns the session key +func (s *Stream) GetKey() uint32 { + s.mu.RLock() + defer s.mu.RUnlock() + return s.key +} + +// GetState returns the current stream state +func (s *Stream) GetState() StreamState { + s.mu.RLock() + defer s.mu.RUnlock() + return s.state +} + +// SetState sets the stream state +func (s *Stream) SetState(state StreamState) { + s.mu.Lock() + defer s.mu.Unlock() + s.state = state +} + +// GetRemoteAddr returns the remote address +func (s *Stream) GetRemoteAddr() *net.UDPAddr { + return s.remoteAddr +} + +// Process processes an incoming packet +func (s *Stream) Process(data []byte) error { + s.mu.Lock() + defer s.mu.Unlock() + + s.lastReceiveTime = time.Now() + s.receivedPackets++ + + // Parse the packet + if len(data) < 2 { + return fmt.Errorf("packet too small") + } + + opcode := binary.BigEndian.Uint16(data) + + // Handle different packet types + switch uint8(opcode & 0xFF) { + case OPSessionRequest: + return s.handleSessionRequest(data) + case OPSessionResponse: + return s.handleSessionResponse(data) + case OPKeepAlive: + return s.handleKeepAlive(data) + case OPPacket: + return s.handlePacket(data) + case OPFragment: + return s.handleFragment(data) + case OPAck: + return s.handleAck(data) + case OPOutOfOrderAck: + return s.handleOutOfOrderAck(data) + case OPSessionDisconnect: + return s.handleDisconnect(data) + case OPCombined: + return s.handleCombined(data) + case OPAppCombined: + return s.handleAppCombined(data) + default: + return fmt.Errorf("unknown opcode: %02x", opcode&0xFF) + } +} + +// handleSessionRequest handles session request packets +func (s *Stream) handleSessionRequest(data []byte) error { + if len(data) < 14 { // 2 byte opcode + 12 byte SessionRequest + return fmt.Errorf("session request too small") + } + + req := &SessionRequest{} + req.UnknownA = binary.LittleEndian.Uint32(data[2:6]) + req.Session = binary.LittleEndian.Uint32(data[6:10]) + req.MaxLength = binary.LittleEndian.Uint32(data[10:14]) + + s.sessionID = req.Session + s.maxLength = req.MaxLength + + // Send session response + return s.sendSessionResponse() +} + +// handleSessionResponse handles session response packets +func (s *Stream) handleSessionResponse(data []byte) error { + if len(data) < 21 { // 2 byte opcode + 19 byte SessionResponse + return fmt.Errorf("session response too small") + } + + resp := &SessionResponse{} + resp.Session = binary.LittleEndian.Uint32(data[2:6]) + resp.Key = binary.LittleEndian.Uint32(data[6:10]) + resp.UnknownA = data[10] + resp.Format = data[11] + resp.UnknownB = data[12] + resp.MaxLength = binary.LittleEndian.Uint32(data[13:17]) + resp.UnknownD = binary.LittleEndian.Uint32(data[17:21]) + + s.sessionID = resp.Session + s.key = resp.Key + s.maxLength = resp.MaxLength + s.compressed = (resp.Format & 0x01) != 0 + s.encoded = (resp.Format & 0x04) != 0 + + return nil +} + +// handleKeepAlive handles keep-alive packets +func (s *Stream) handleKeepAlive(data []byte) error { + // Send keep-alive response + return s.sendKeepAliveResponse() +} + +// handlePacket handles regular data packets +func (s *Stream) handlePacket(data []byte) error { + if len(data) < 4 { + return fmt.Errorf("packet too small") + } + + // Extract sequence number + sequence := binary.BigEndian.Uint16(data[2:4]) + + // Validate CRC + if !ValidateCRC(data, len(data), s.key) { + return fmt.Errorf("CRC validation failed") + } + + // Create protocol packet + packet := NewProtocolPacket(data[4:len(data)-2], -1) // Skip header and CRC + packet.Sequence = sequence + + // Handle sequence + if sequence == s.nextInSeq { + // In order packet + s.processInOrderPacket(packet) + s.nextInSeq++ + + // Check for any future packets that are now in order + s.processFuturePackets() + + // Send ACK + s.sendAck(sequence) + } else if sequence > s.nextInSeq { + // Future packet - store it + s.futurePackets[sequence] = packet + // Send out of order ACK + s.sendOutOfOrderAck(sequence) + } + // Ignore past packets (already processed) + + return nil +} + +// handleFragment handles fragmented packets +func (s *Stream) handleFragment(data []byte) error { + if len(data) < 8 { + return fmt.Errorf("fragment too small") + } + + sequence := binary.BigEndian.Uint16(data[2:4]) + totalSize := binary.BigEndian.Uint32(data[4:8]) + + // Validate CRC + if !ValidateCRC(data, len(data), s.key) { + return fmt.Errorf("CRC validation failed") + } + + // Initialize oversize buffer if needed + if s.oversizeBuffer == nil || s.oversizeLength != totalSize { + s.oversizeBuffer = make([]byte, totalSize) + s.oversizeOffset = 0 + s.oversizeLength = totalSize + } + + // Copy fragment data + fragmentData := data[8 : len(data)-2] // Skip header and CRC + copy(s.oversizeBuffer[s.oversizeOffset:], fragmentData) + s.oversizeOffset += uint32(len(fragmentData)) + + // Send ACK + s.sendAck(sequence) + s.nextInSeq = sequence + 1 + + // Check if we have the complete packet + if s.oversizeOffset >= s.oversizeLength { + // Process the complete packet + packet := NewProtocolPacket(s.oversizeBuffer[:s.oversizeLength], -1) + s.processInOrderPacket(packet) + + // Reset oversize buffer + s.oversizeBuffer = nil + s.oversizeOffset = 0 + s.oversizeLength = 0 + } + + return nil +} + +// handleAck handles acknowledgment packets +func (s *Stream) handleAck(data []byte) error { + if len(data) < 4 { + return fmt.Errorf("ack too small") + } + + sequence := binary.BigEndian.Uint16(data[2:4]) + + // Remove acknowledged packet from sent queue + if packet, ok := s.sentQueue[sequence]; ok { + packet.Acked = true + delete(s.sentQueue, sequence) + + // Update RTT based on this ACK + if packet.SentTime > 0 { + rtt := time.Since(time.Unix(int64(packet.SentTime), 0)) + s.updateRTT(rtt) + } + } + + s.lastAckReceived = sequence + + return nil +} + +// handleOutOfOrderAck handles out-of-order acknowledgments +func (s *Stream) handleOutOfOrderAck(data []byte) error { + if len(data) < 4 { + return fmt.Errorf("out of order ack too small") + } + + sequence := binary.BigEndian.Uint16(data[2:4]) + + // Resend packets between lastAckReceived and sequence + for seq := s.lastAckReceived + 1; seq < sequence; seq++ { + if packet, ok := s.sentQueue[seq]; ok { + s.resendPacket(packet) + } + } + + return nil +} + +// handleDisconnect handles disconnect packets +func (s *Stream) handleDisconnect(data []byte) error { + s.state = Disconnecting + + // Call disconnect callback + if s.onDisconnect != nil { + s.onDisconnect() + } + + return nil +} + +// handleCombined handles combined packets +func (s *Stream) handleCombined(data []byte) error { + if len(data) < 3 { + return fmt.Errorf("combined packet too small") + } + + offset := 2 // Skip opcode + + for offset < len(data)-2 { // Leave room for CRC + if offset+1 >= len(data) { + break + } + + // Read sub-packet length + length := uint16(data[offset]) + offset++ + + if length == 0xFF { + // Extended length + if offset+2 >= len(data) { + break + } + length = binary.BigEndian.Uint16(data[offset:]) + offset += 2 + } + + // Extract sub-packet + if offset+int(length) > len(data) { + break + } + + subPacket := data[offset : offset+int(length)] + offset += int(length) + + // Process sub-packet + s.Process(subPacket) + } + + return nil +} + +// handleAppCombined handles application-level combined packets +func (s *Stream) handleAppCombined(data []byte) error { + // Similar to handleCombined but for application packets + return s.handleCombined(data) +} + +// processInOrderPacket processes an in-order packet +func (s *Stream) processInOrderPacket(packet *ProtocolPacket) { + // Decompress if needed + if s.compressed && packet.EQ2Compressed { + decompressed := make([]byte, 8192) + size, err := Decompress(packet.Buffer, uint32(len(packet.Buffer)), decompressed, 8192) + if err == nil { + packet.Buffer = decompressed[:size] + packet.Size = size + } + } + + // Decode if needed + if s.encoded && packet.PacketEncrypted { + ChatDecode(packet.Buffer, len(packet.Buffer), int(s.key)) + packet.PacketEncrypted = false + } + + // Convert to application packet and call callback + if s.onPacket != nil { + appPacket := s.makeApplicationPacket(packet) + if appPacket != nil { + s.onPacket(appPacket) + } + } +} + +// processFuturePackets processes any future packets that are now in order +func (s *Stream) processFuturePackets() { + for { + if packet, ok := s.futurePackets[s.nextInSeq]; ok { + s.processInOrderPacket(packet) + delete(s.futurePackets, s.nextInSeq) + s.nextInSeq++ + } else { + break + } + } +} + +// makeApplicationPacket converts a protocol packet to an application packet +func (s *Stream) makeApplicationPacket(packet *ProtocolPacket) *ApplicationPacket { + if len(packet.Buffer) < int(s.opcodeSize) { + return nil + } + + var opcode uint16 + var data []byte + + if s.opcodeSize == 1 { + opcode = uint16(packet.Buffer[0]) + data = packet.Buffer[1:] + } else { + opcode = binary.LittleEndian.Uint16(packet.Buffer) + data = packet.Buffer[2:] + } + + appPacket := NewApplicationPacket(EmuOpcode(opcode), data) + appPacket.AppOpcodeSize = s.opcodeSize + + return appPacket +} + +// Send queues a packet for sending +func (s *Stream) Send(packet *ApplicationPacket) error { + s.mu.Lock() + defer s.mu.Unlock() + + // Convert to protocol packet + data := packet.Serialize() + protocolPacket := NewProtocolPacketWithOp(uint16(OPPacket), data) + + // Add to outgoing queue + s.outgoingQueue = append(s.outgoingQueue, protocolPacket) + + return nil +} + +// sendSessionResponse sends a session response +func (s *Stream) sendSessionResponse() error { + resp := &SessionResponse{ + Session: s.sessionID, + Key: s.key, + Format: 0, + MaxLength: s.maxLength, + } + + if s.compressed { + resp.Format |= 0x01 + } + if s.encoded { + resp.Format |= 0x04 + } + + // Build response packet + data := make([]byte, 21) + binary.BigEndian.PutUint16(data[0:2], uint16(OPSessionResponse)) + binary.LittleEndian.PutUint32(data[2:6], resp.Session) + binary.LittleEndian.PutUint32(data[6:10], resp.Key) + data[10] = resp.UnknownA + data[11] = resp.Format + data[12] = resp.UnknownB + binary.LittleEndian.PutUint32(data[13:17], resp.MaxLength) + binary.LittleEndian.PutUint32(data[17:21], resp.UnknownD) + + // Session response doesn't have CRC + return s.sendRaw(data) +} + +// sendKeepAliveResponse sends a keep-alive response +func (s *Stream) sendKeepAliveResponse() error { + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(OPKeepAlive)) + return s.sendWithCRC(data) +} + +// sendAck sends an acknowledgment +func (s *Stream) sendAck(sequence uint16) error { + data := make([]byte, 4) + binary.BigEndian.PutUint16(data[0:2], uint16(OPAck)) + binary.BigEndian.PutUint16(data[2:4], sequence) + return s.sendWithCRC(data) +} + +// sendOutOfOrderAck sends an out-of-order acknowledgment +func (s *Stream) sendOutOfOrderAck(sequence uint16) error { + data := make([]byte, 4) + binary.BigEndian.PutUint16(data[0:2], uint16(OPOutOfOrderAck)) + binary.BigEndian.PutUint16(data[2:4], sequence) + return s.sendWithCRC(data) +} + +// sendWithCRC sends data with CRC +func (s *Stream) sendWithCRC(data []byte) error { + // Calculate CRC + crc := CalculateCRC16(data, s.key) + + // Append CRC + fullData := make([]byte, len(data)+2) + copy(fullData, data) + binary.BigEndian.PutUint16(fullData[len(data):], crc) + + return s.sendRaw(fullData) +} + +// sendRaw sends raw data (should be called from factory) +func (s *Stream) sendRaw(data []byte) error { + s.lastSendTime = time.Now() + s.sentPackets++ + // The actual sending will be done by StreamFactory + return nil +} + +// resendPacket resends a packet +func (s *Stream) resendPacket(packet *ProtocolPacket) { + packet.AttemptCount++ + packet.SentTime = int32(time.Now().Unix()) + // Re-queue for sending + s.outgoingQueue = append(s.outgoingQueue, packet) +} + +// updateRTT updates the round-trip time statistics +func (s *Stream) updateRTT(rtt time.Duration) { + if s.minRoundTrip == 0 || rtt < s.minRoundTrip { + s.minRoundTrip = rtt + } + if rtt > s.maxRoundTrip { + s.maxRoundTrip = rtt + } + + // Update average (simple moving average) + if s.avgRoundTrip == 0 { + s.avgRoundTrip = rtt + } else { + s.avgRoundTrip = (s.avgRoundTrip*3 + rtt) / 4 + } + + // Update retransmit timeout + s.retransmitTimeout = s.avgRoundTrip * 3 + if s.retransmitTimeout > 5*time.Second { + s.retransmitTimeout = 5 * time.Second + } +} + +// SetOnPacket sets the packet callback +func (s *Stream) SetOnPacket(callback func(*ApplicationPacket)) { + s.mu.Lock() + defer s.mu.Unlock() + s.onPacket = callback +} + +// SetOnDisconnect sets the disconnect callback +func (s *Stream) SetOnDisconnect(callback func()) { + s.mu.Lock() + defer s.mu.Unlock() + s.onDisconnect = callback +} \ No newline at end of file diff --git a/stream_factory.go b/stream_factory.go new file mode 100644 index 0000000..0284aa3 --- /dev/null +++ b/stream_factory.go @@ -0,0 +1,471 @@ +package eq2net + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "net" + "sync" + "time" +) + +// StreamFactory manages multiple UDP streams +type StreamFactory struct { + mu sync.RWMutex + + // Network + conn *net.UDPConn + listenAddr *net.UDPAddr + streamType StreamType + + // Streams + streams map[string]*Stream // Key is "IP:Port" + streamsByID map[uint32]*Stream // Key is session ID + + // Options + maxStreams int + readTimeout time.Duration + writeTimeout time.Duration + + // State + running bool + stopChan chan struct{} + + // Callbacks + onNewStream func(*Stream) + onStreamClosed func(*Stream) +} + +// NewStreamFactory creates a new stream factory +func NewStreamFactory(listenAddr string, streamType StreamType) (*StreamFactory, error) { + addr, err := net.ResolveUDPAddr("udp", listenAddr) + if err != nil { + return nil, err + } + + sf := &StreamFactory{ + listenAddr: addr, + streamType: streamType, + streams: make(map[string]*Stream), + streamsByID: make(map[uint32]*Stream), + maxStreams: 1000, + readTimeout: 30 * time.Second, + writeTimeout: 5 * time.Second, + stopChan: make(chan struct{}), + } + + return sf, nil +} + +// Start starts the stream factory +func (sf *StreamFactory) Start() error { + sf.mu.Lock() + defer sf.mu.Unlock() + + if sf.running { + return fmt.Errorf("stream factory already running") + } + + // Create UDP connection + conn, err := net.ListenUDP("udp", sf.listenAddr) + if err != nil { + return err + } + sf.conn = conn + + // Set buffer sizes + conn.SetReadBuffer(65536) + conn.SetWriteBuffer(65536) + + sf.running = true + + // Start worker goroutines + go sf.readLoop() + go sf.writeLoop() + go sf.maintenanceLoop() + + return nil +} + +// Stop stops the stream factory +func (sf *StreamFactory) Stop() { + sf.mu.Lock() + defer sf.mu.Unlock() + + if !sf.running { + return + } + + sf.running = false + close(sf.stopChan) + + if sf.conn != nil { + sf.conn.Close() + } + + // Close all streams + for _, stream := range sf.streams { + stream.SetState(Closed) + if stream.onDisconnect != nil { + stream.onDisconnect() + } + } + + sf.streams = make(map[string]*Stream) + sf.streamsByID = make(map[uint32]*Stream) +} + +// readLoop reads packets from the UDP connection +func (sf *StreamFactory) readLoop() { + buffer := make([]byte, 65536) + + for { + select { + case <-sf.stopChan: + return + default: + // Set read deadline + sf.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + + // Read packet + n, remoteAddr, err := sf.conn.ReadFromUDP(buffer) + if err != nil { + // Check if it's a timeout (expected) + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + continue + } + // Actual error + continue + } + + // Process packet + data := make([]byte, n) + copy(data, buffer[:n]) + go sf.processPacket(data, remoteAddr) + } + } +} + +// writeLoop handles writing packets to streams +func (sf *StreamFactory) writeLoop() { + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-sf.stopChan: + return + case <-ticker.C: + sf.processOutgoingPackets() + } + } +} + +// maintenanceLoop performs periodic maintenance +func (sf *StreamFactory) maintenanceLoop() { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-sf.stopChan: + return + case <-ticker.C: + sf.performMaintenance() + } + } +} + +// processPacket processes an incoming packet +func (sf *StreamFactory) processPacket(data []byte, remoteAddr *net.UDPAddr) { + if len(data) < 2 { + return + } + + // Get or create stream + stream := sf.getOrCreateStream(remoteAddr, data) + if stream == nil { + return + } + + // Process packet in stream + err := stream.Process(data) + if err != nil { + // Handle error (log it, etc.) + } +} + +// getOrCreateStream gets an existing stream or creates a new one +func (sf *StreamFactory) getOrCreateStream(remoteAddr *net.UDPAddr, data []byte) *Stream { + sf.mu.Lock() + defer sf.mu.Unlock() + + key := fmt.Sprintf("%s:%d", remoteAddr.IP.String(), remoteAddr.Port) + + // Check if stream exists + if stream, ok := sf.streams[key]; ok { + return stream + } + + // Check if this is a session request + if len(data) >= 2 { + opcode := binary.BigEndian.Uint16(data) + if uint8(opcode&0xFF) == OPSessionRequest { + // Create new stream + stream := NewStream(remoteAddr, sf.streamType) + + // Generate session ID and key + sessionID := sf.generateSessionID() + sessionKey := sf.generateKey() + stream.SetSessionInfo(sessionID, sessionKey) + + // Add to maps + sf.streams[key] = stream + sf.streamsByID[sessionID] = stream + + // Call new stream callback + if sf.onNewStream != nil { + sf.onNewStream(stream) + } + + return stream + } + } + + return nil +} + +// processOutgoingPackets processes outgoing packets for all streams +func (sf *StreamFactory) processOutgoingPackets() { + sf.mu.RLock() + streams := make([]*Stream, 0, len(sf.streams)) + for _, stream := range sf.streams { + streams = append(streams, stream) + } + sf.mu.RUnlock() + + for _, stream := range streams { + sf.processStreamOutgoing(stream) + } +} + +// processStreamOutgoing processes outgoing packets for a stream +func (sf *StreamFactory) processStreamOutgoing(stream *Stream) { + stream.mu.Lock() + defer stream.mu.Unlock() + + // Process outgoing queue + for len(stream.outgoingQueue) > 0 { + packet := stream.outgoingQueue[0] + stream.outgoingQueue = stream.outgoingQueue[1:] + + // Assign sequence number + packet.Sequence = stream.nextOutSeq + stream.nextOutSeq++ + + // Build packet data + data := sf.buildPacketData(stream, packet) + + // Send packet + sf.sendPacket(stream.remoteAddr, data) + + // Add to sent queue for acknowledgment tracking + packet.SentTime = int32(time.Now().Unix()) + stream.sentQueue[packet.Sequence] = packet + } + + // Check for retransmissions + now := time.Now() + for seq, packet := range stream.sentQueue { + sentTime := time.Unix(int64(packet.SentTime), 0) + if now.Sub(sentTime) > stream.retransmitTimeout { + if packet.AttemptCount < 3 { + // Retransmit + packet.AttemptCount++ + packet.SentTime = int32(now.Unix()) + + data := sf.buildPacketData(stream, packet) + sf.sendPacket(stream.remoteAddr, data) + } else { + // Give up on this packet + delete(stream.sentQueue, seq) + } + } + } +} + +// buildPacketData builds the complete packet data including headers and CRC +func (sf *StreamFactory) buildPacketData(stream *Stream, packet *ProtocolPacket) []byte { + // Check if we need to fragment + maxDataSize := int(stream.maxLength) - 6 // Header (4) + CRC (2) + + if len(packet.Buffer) > maxDataSize { + // Need to fragment + return sf.buildFragmentData(stream, packet) + } + + // Build regular packet + data := make([]byte, len(packet.Buffer)+6) + + // Header + binary.BigEndian.PutUint16(data[0:2], uint16(OPPacket)) + binary.BigEndian.PutUint16(data[2:4], packet.Sequence) + + // Data + copy(data[4:], packet.Buffer) + + // CRC + crc := CalculateCRC16(data[:len(data)-2], stream.key) + binary.BigEndian.PutUint16(data[len(data)-2:], crc) + + return data +} + +// buildFragmentData builds a fragment packet +func (sf *StreamFactory) buildFragmentData(stream *Stream, packet *ProtocolPacket) []byte { + // For now, just truncate (proper fragmentation would split into multiple packets) + // This is a simplified version - real implementation would handle multiple fragments + + maxDataSize := int(stream.maxLength) - 10 // Header (8) + CRC (2) + dataSize := len(packet.Buffer) + if dataSize > maxDataSize { + dataSize = maxDataSize + } + + data := make([]byte, dataSize+10) + + // Header + binary.BigEndian.PutUint16(data[0:2], uint16(OPFragment)) + binary.BigEndian.PutUint16(data[2:4], packet.Sequence) + binary.BigEndian.PutUint32(data[4:8], uint32(len(packet.Buffer))) + + // Data + copy(data[8:], packet.Buffer[:dataSize]) + + // CRC + crc := CalculateCRC16(data[:len(data)-2], stream.key) + binary.BigEndian.PutUint16(data[len(data)-2:], crc) + + return data +} + +// sendPacket sends a packet +func (sf *StreamFactory) sendPacket(addr *net.UDPAddr, data []byte) error { + sf.conn.SetWriteDeadline(time.Now().Add(sf.writeTimeout)) + _, err := sf.conn.WriteToUDP(data, addr) + return err +} + +// performMaintenance performs periodic maintenance tasks +func (sf *StreamFactory) performMaintenance() { + sf.mu.Lock() + defer sf.mu.Unlock() + + now := time.Now() + toRemove := []string{} + + for key, stream := range sf.streams { + stream.mu.RLock() + lastReceive := stream.lastReceiveTime + state := stream.state + stream.mu.RUnlock() + + // Check for timeout + if now.Sub(lastReceive) > sf.readTimeout && state == Established { + stream.SetState(Disconnecting) + toRemove = append(toRemove, key) + } + + // Remove closed streams + if state == Closed || state == Disconnecting { + toRemove = append(toRemove, key) + } + } + + // Remove dead streams + for _, key := range toRemove { + if stream, ok := sf.streams[key]; ok { + delete(sf.streams, key) + delete(sf.streamsByID, stream.GetSessionID()) + + // Call closed callback + if sf.onStreamClosed != nil { + sf.onStreamClosed(stream) + } + } + } +} + +// generateSessionID generates a random session ID +func (sf *StreamFactory) generateSessionID() uint32 { + var id uint32 + for { + binary.Read(rand.Reader, binary.BigEndian, &id) + // Make sure it's not already in use + if _, exists := sf.streamsByID[id]; !exists && id != 0 { + return id + } + } +} + +// generateKey generates a random key +func (sf *StreamFactory) generateKey() uint32 { + var key uint32 + binary.Read(rand.Reader, binary.BigEndian, &key) + return key +} + +// GetStream gets a stream by remote address +func (sf *StreamFactory) GetStream(remoteAddr *net.UDPAddr) *Stream { + sf.mu.RLock() + defer sf.mu.RUnlock() + + key := fmt.Sprintf("%s:%d", remoteAddr.IP.String(), remoteAddr.Port) + return sf.streams[key] +} + +// GetStreamByID gets a stream by session ID +func (sf *StreamFactory) GetStreamByID(sessionID uint32) *Stream { + sf.mu.RLock() + defer sf.mu.RUnlock() + + return sf.streamsByID[sessionID] +} + +// GetStreamCount returns the number of active streams +func (sf *StreamFactory) GetStreamCount() int { + sf.mu.RLock() + defer sf.mu.RUnlock() + + return len(sf.streams) +} + +// SetOnNewStream sets the new stream callback +func (sf *StreamFactory) SetOnNewStream(callback func(*Stream)) { + sf.mu.Lock() + defer sf.mu.Unlock() + sf.onNewStream = callback +} + +// SetOnStreamClosed sets the stream closed callback +func (sf *StreamFactory) SetOnStreamClosed(callback func(*Stream)) { + sf.mu.Lock() + defer sf.mu.Unlock() + sf.onStreamClosed = callback +} + +// Broadcast sends a packet to all connected streams +func (sf *StreamFactory) Broadcast(packet *ApplicationPacket) { + sf.mu.RLock() + streams := make([]*Stream, 0, len(sf.streams)) + for _, stream := range sf.streams { + if stream.GetState() == Established { + streams = append(streams, stream) + } + } + sf.mu.RUnlock() + + for _, stream := range streams { + stream.Send(packet) + } +} \ No newline at end of file