upload old login
This commit is contained in:
parent
f8236efffd
commit
76fa77d590
88
crc16.go
Normal file
88
crc16.go
Normal file
@ -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))
|
||||
}
|
661
old/EQPacket.cpp
661
old/EQPacket.cpp
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "debug.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "EQPacket.h"
|
||||
#include "misc.h"
|
||||
#include "op_codes.h"
|
||||
#include "CRC16.h"
|
||||
#include "opcodemgr.h"
|
||||
#include "packet_dump.h"
|
||||
#include <map>
|
||||
#include "Log.h"
|
||||
#include <time.h>
|
||||
|
||||
using namespace std;
|
||||
extern map<int16,OpcodeManager*>EQOpcodeManager;
|
||||
|
||||
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<unsigned char *>(buffer+flag_offset+1),length-(flag_offset+1)-2,newbuf+flag_offset,newbufsize-flag_offset)+2;
|
||||
|
||||
// something went bad with zlib
|
||||
if (newlen == -1)
|
||||
{
|
||||
LogWrite(PACKET__ERROR, 0, "Packet", "Debug Bad Inflate!");
|
||||
DumpPacket(buffer, length);
|
||||
memcpy(newbuf, buffer, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
newbuf[newlen++]=buffer[length-2];
|
||||
newbuf[newlen++]=buffer[length-1];
|
||||
} else if (length>2 && buffer[flag_offset]==0xa5) {
|
||||
LogWrite(PACKET__DEBUG, 0, "Packet", "In Decompress 2");
|
||||
memcpy(newbuf+flag_offset,buffer+flag_offset+1,length-(flag_offset+1));
|
||||
newlen=length-1;
|
||||
} else {
|
||||
memcpy(newbuf,buffer,length);
|
||||
newlen=length;
|
||||
}
|
||||
|
||||
return newlen;
|
||||
}
|
||||
|
||||
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<unsigned char *>(buffer+flag_offset),length-flag_offset,newbuf+flag_offset+1,newbufsize);
|
||||
*(newbuf+flag_offset)=0x5a;
|
||||
newlength+=flag_offset+1;
|
||||
} else {
|
||||
memmove(newbuf+flag_offset+1,buffer+flag_offset,length-flag_offset);
|
||||
*(newbuf+flag_offset)=0xa5;
|
||||
newlength=length+1;
|
||||
}
|
||||
//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);
|
||||
}
|
||||
|
||||
|
209
old/EQPacket.h
209
old/EQPacket.h
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef _EQPACKET_H
|
||||
#define _EQPACKET_H
|
||||
|
||||
#include "types.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <time.h>
|
||||
#include <WinSock2.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#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
|
1921
old/EQStream.cpp
1921
old/EQStream.cpp
File diff suppressed because it is too large
Load Diff
375
old/EQStream.h
375
old/EQStream.h
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef _EQPROTOCOL_H
|
||||
#define _EQPROTOCOL_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#ifndef WIN32
|
||||
#include <netinet/in.h>
|
||||
#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 <stdarg.h>
|
||||
#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<EQProtocolPacket*> NonSequencedQueue;
|
||||
deque<EQProtocolPacket*> SequencedQueue;
|
||||
map<uint16, EQProtocolPacket *> OutOfOrderpackets;
|
||||
Mutex MOutboundQueue;
|
||||
|
||||
// Packes waiting to be processed
|
||||
deque<EQApplicationPacket *> InboundQueue;
|
||||
Mutex MInboundQueue;
|
||||
|
||||
static uint16 MaxWindowSize;
|
||||
|
||||
sint32 BytesWritten;
|
||||
|
||||
Mutex MRate;
|
||||
sint32 RateThreshold;
|
||||
sint32 DecayRate;
|
||||
uint32 AverageDelta;
|
||||
|
||||
EQStreamFactory *Factory;
|
||||
|
||||
public:
|
||||
Mutex MCombineQueueLock;
|
||||
bool CheckCombineQueue();
|
||||
deque<EQ2Packet*> 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<EQ2Packet*>::iterator cmb;
|
||||
MCombineQueueLock.lock();
|
||||
for (cmb = combine_queue.begin(); cmb != combine_queue.end(); cmb++){
|
||||
safe_delete(*cmb);
|
||||
}
|
||||
MCombineQueueLock.unlock();
|
||||
deflateEnd(&stream);
|
||||
map<int16, EQProtocolPacket*>::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<EQProtocolPacket *> convert(EQApplicationPacket *p);
|
||||
void NonSequencedPush(EQProtocolPacket *p);
|
||||
void SequencedPush(EQProtocolPacket *p);
|
||||
|
||||
Mutex MResendQue;
|
||||
Mutex MCompressData;
|
||||
deque<EQProtocolPacket*>resend_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
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EQStreamFactory.h"
|
||||
#include "Log.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <WinSock2.h>
|
||||
#include <windows.h>
|
||||
#include <process.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#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<string,EQStream *>::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<string,EQStream *>::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<string,EQStream *>::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<EQStream*> combine_que;
|
||||
CombinePacketRunning = true;
|
||||
bool packets_waiting = false;
|
||||
while(sock!=-1) {
|
||||
if (!CombinePacketRunning)
|
||||
break;
|
||||
MStreams.lock();
|
||||
map<string,EQStream *>::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<string,EQStream *>::iterator stream_itr;
|
||||
vector<EQStream *> wants_write;
|
||||
vector<EQStream *>::iterator cur,end;
|
||||
deque<EQStream*> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef _EQSTREAMFACTORY_H
|
||||
|
||||
#define _EQSTREAMFACTORY_H
|
||||
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#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<EQStream *> NewStreams;
|
||||
Mutex MNewStreams;
|
||||
|
||||
map<string,EQStream *> 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
|
969
old/common/EQPacket.cpp
Normal file
969
old/common/EQPacket.cpp
Normal file
@ -0,0 +1,969 @@
|
||||
// EQ2Emulator: Everquest II Server Emulator
|
||||
// Copyright (C) 2007 EQ2EMulator Development Team
|
||||
// Licensed under GPL v3 - see <http://www.gnu.org/licenses/>
|
||||
#include "debug.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <time.h>
|
||||
|
||||
#include "EQPacket.h"
|
||||
#include "misc.h"
|
||||
#include "op_codes.h"
|
||||
#include "CRC16.h"
|
||||
#include "opcodemgr.h"
|
||||
#include "packet_dump.h"
|
||||
#include "Log.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Global opcode manager map
|
||||
extern map<int16, OpcodeManager*> EQOpcodeManager;
|
||||
|
||||
// Default opcode size for application packets
|
||||
uint8 EQApplicationPacket::default_opcode_size = 2;
|
||||
|
||||
/**
|
||||
* EQPacket constructor - creates a packet with specified opcode and data.
|
||||
*
|
||||
* @param op - The packet opcode
|
||||
* @param buf - Source buffer to copy data from (can be nullptr)
|
||||
* @param len - Length of data to allocate/copy
|
||||
*/
|
||||
EQPacket::EQPacket(const uint16 op, const unsigned char* buf, uint32 len)
|
||||
{
|
||||
this->opcode = op;
|
||||
this->pBuffer = nullptr;
|
||||
this->size = 0;
|
||||
version = 0;
|
||||
setTimeInfo(0, 0);
|
||||
|
||||
if (len > 0) {
|
||||
this->size = len;
|
||||
pBuffer = new unsigned char[this->size];
|
||||
if (buf) {
|
||||
memcpy(this->pBuffer, buf, this->size);
|
||||
} else {
|
||||
memset(this->pBuffer, 0, this->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable name of this EQ2 packet's opcode.
|
||||
*
|
||||
* @return Opcode name string, or nullptr if not found
|
||||
*/
|
||||
const char* EQ2Packet::GetOpcodeName()
|
||||
{
|
||||
int16 OpcodeVersion = GetOpcodeVersion(version);
|
||||
if (EQOpcodeManager.count(OpcodeVersion) > 0) {
|
||||
return EQOpcodeManager[OpcodeVersion]->EmuToName(login_op);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an EQ2 packet for transmission by adding protocol headers.
|
||||
* Converts the emulator opcode to network opcode and adds necessary headers.
|
||||
*
|
||||
* @param MaxLen - Maximum allowed packet length
|
||||
* @return Offset value for the prepared packet, or -1 on error
|
||||
*/
|
||||
int8 EQ2Packet::PreparePacket(int16 MaxLen)
|
||||
{
|
||||
int16 OpcodeVersion = GetOpcodeVersion(version);
|
||||
|
||||
// Validate that we have an opcode manager for this version
|
||||
if (EQOpcodeManager.count(OpcodeVersion) == 0) {
|
||||
LogWrite(PACKET__ERROR, 0, "Packet",
|
||||
"Version %i is not listed in the opcodes table.", version);
|
||||
return -1;
|
||||
}
|
||||
|
||||
packet_prepared = true;
|
||||
|
||||
// Convert emulator opcode to network opcode
|
||||
int16 login_opcode = EQOpcodeManager[OpcodeVersion]->EmuToEQ(login_op);
|
||||
if (login_opcode == 0xcdcd) {
|
||||
LogWrite(PACKET__ERROR, 0, "Packet",
|
||||
"Version %i is not listed in the opcodes table for opcode %s",
|
||||
version, EQOpcodeManager[OpcodeVersion]->EmuToName(login_op));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int8 offset = 0;
|
||||
// Calculate new size: sequence (int16) + compressed flag (int8) + opcode + data
|
||||
int32 new_size = size + sizeof(int16) + sizeof(int8);
|
||||
bool oversized = false;
|
||||
|
||||
// Handle different opcode sizes and formats
|
||||
if (login_opcode != 2) {
|
||||
new_size += sizeof(int8); // for opcode type
|
||||
if (login_opcode >= 255) {
|
||||
new_size += sizeof(int16); // oversized opcode needs extra bytes
|
||||
oversized = true;
|
||||
} else {
|
||||
login_opcode = ntohs(login_opcode);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate new buffer and build the packet
|
||||
uchar* new_buffer = new uchar[new_size];
|
||||
memset(new_buffer, 0, new_size);
|
||||
uchar* ptr = new_buffer + sizeof(int16); // skip sequence field
|
||||
|
||||
if (login_opcode != 2) {
|
||||
if (oversized) {
|
||||
ptr += sizeof(int8); // compressed flag position
|
||||
int8 addon = 0xff; // oversized marker
|
||||
memcpy(ptr, &addon, sizeof(int8));
|
||||
ptr += sizeof(int8);
|
||||
}
|
||||
memcpy(ptr, &login_opcode, sizeof(int16));
|
||||
ptr += sizeof(int16);
|
||||
} else {
|
||||
memcpy(ptr, &login_opcode, sizeof(int8));
|
||||
ptr += sizeof(int8);
|
||||
}
|
||||
|
||||
// Copy original packet data
|
||||
memcpy(ptr, pBuffer, size);
|
||||
|
||||
// Replace old buffer with new prepared buffer
|
||||
safe_delete_array(pBuffer);
|
||||
pBuffer = new_buffer;
|
||||
offset = new_size - size - 1;
|
||||
size = new_size;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this protocol packet into a destination buffer.
|
||||
*
|
||||
* @param dest - Destination buffer to write to
|
||||
* @param offset - Offset into source buffer to start copying from
|
||||
* @return Total bytes written to destination
|
||||
*/
|
||||
uint32 EQProtocolPacket::serialize(unsigned char* dest, int8 offset) const
|
||||
{
|
||||
// Write opcode (2 bytes, handling both 8-bit and 16-bit opcodes)
|
||||
if (opcode > 0xff) {
|
||||
*(uint16*)dest = opcode;
|
||||
} else {
|
||||
*(dest) = 0;
|
||||
*(dest + 1) = opcode;
|
||||
}
|
||||
|
||||
// Copy packet data after the opcode
|
||||
memcpy(dest + 2, pBuffer + offset, size - offset);
|
||||
|
||||
return size + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this application packet into a destination buffer.
|
||||
* Handles special opcode encoding rules for application-level packets.
|
||||
*
|
||||
* @param dest - Destination buffer to write to
|
||||
* @return Total bytes written to destination
|
||||
*/
|
||||
uint32 EQApplicationPacket::serialize(unsigned char* dest) const
|
||||
{
|
||||
uint8 OpCodeBytes = app_opcode_size;
|
||||
|
||||
if (app_opcode_size == 1) {
|
||||
// Single-byte opcode
|
||||
*(unsigned char*)dest = opcode;
|
||||
} else {
|
||||
// Two-byte opcode with special encoding rules
|
||||
// Application opcodes with low byte = 0x00 need extra 0x00 prefix
|
||||
if ((opcode & 0x00ff) == 0) {
|
||||
*(uint8*)dest = 0;
|
||||
*(uint16*)(dest + 1) = opcode;
|
||||
++OpCodeBytes;
|
||||
} else {
|
||||
*(uint16*)dest = opcode;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy packet data after opcode
|
||||
memcpy(dest + app_opcode_size, pBuffer, size);
|
||||
|
||||
return size + OpCodeBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* EQPacket destructor - cleans up allocated buffer memory.
|
||||
*/
|
||||
EQPacket::~EQPacket()
|
||||
{
|
||||
safe_delete_array(pBuffer);
|
||||
pBuffer = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump packet header with timestamp information to file.
|
||||
*
|
||||
* @param seq - Sequence number to display
|
||||
* @param to - File pointer to write to (default: stdout)
|
||||
*/
|
||||
void EQPacket::DumpRawHeader(uint16 seq, FILE* to) const
|
||||
{
|
||||
// Note: Timestamp formatting code is commented out but preserved
|
||||
// for potential future use in debugging
|
||||
/*
|
||||
if (timestamp.tv_sec) {
|
||||
char temp[20];
|
||||
tm t;
|
||||
const time_t sec = timestamp.tv_sec;
|
||||
localtime_s(&t, &sec);
|
||||
strftime(temp, 20, "%F %T", &t);
|
||||
fprintf(to, "%s.%06lu ", temp, timestamp.tv_usec);
|
||||
}
|
||||
*/
|
||||
|
||||
DumpRawHeaderNoTime(seq, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human-readable name of this packet's opcode.
|
||||
*
|
||||
* @return Opcode name string, or nullptr if not found
|
||||
*/
|
||||
const char* EQPacket::GetOpcodeName()
|
||||
{
|
||||
int16 OpcodeVersion = GetOpcodeVersion(version);
|
||||
if (EQOpcodeManager.count(OpcodeVersion) > 0) {
|
||||
return EQOpcodeManager[OpcodeVersion]->EQToName(opcode);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump packet header without timestamp to file.
|
||||
* Shows network addresses, sequence number, opcode, and size.
|
||||
*
|
||||
* @param seq - Sequence number to display (0xffff = don't show)
|
||||
* @param to - File pointer to write to
|
||||
*/
|
||||
void EQPacket::DumpRawHeaderNoTime(uint16 seq, FILE* to) const
|
||||
{
|
||||
// Show network addressing if available
|
||||
if (src_ip) {
|
||||
string sIP = long2ip(src_ip);
|
||||
string dIP = long2ip(dst_ip);
|
||||
fprintf(to, "[%s:%d->%s:%d] ", sIP.c_str(), src_port, dIP.c_str(), dst_port);
|
||||
}
|
||||
|
||||
// Show sequence number if valid
|
||||
if (seq != 0xffff) {
|
||||
fprintf(to, "[Seq=%u] ", seq);
|
||||
}
|
||||
|
||||
// Get opcode name for display
|
||||
string name;
|
||||
int16 OpcodeVersion = GetOpcodeVersion(version);
|
||||
if (EQOpcodeManager.count(OpcodeVersion) > 0) {
|
||||
name = EQOpcodeManager[OpcodeVersion]->EQToName(opcode);
|
||||
}
|
||||
|
||||
fprintf(to, "[OpCode 0x%04x (%s) Size=%u]\n", opcode, name.c_str(), size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump complete packet (header + data) to file in formatted columns.
|
||||
*
|
||||
* @param to - File pointer to write to (default: stdout)
|
||||
*/
|
||||
void EQPacket::DumpRaw(FILE* to) const {
|
||||
DumpRawHeader();
|
||||
if (pBuffer && size) {
|
||||
dump_message_column(pBuffer, size, " ", to);
|
||||
}
|
||||
fprintf(to, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* EQProtocolPacket constructor from raw buffer.
|
||||
* Parses opcode from buffer or uses provided opcode override.
|
||||
*
|
||||
* @param buf - Raw packet buffer
|
||||
* @param len - Length of buffer
|
||||
* @param in_opcode - Optional opcode override (-1 = parse from buffer)
|
||||
*/
|
||||
EQProtocolPacket::EQProtocolPacket(const unsigned char* buf, uint32 len, int in_opcode)
|
||||
{
|
||||
uint32 offset = 0;
|
||||
|
||||
if (in_opcode >= 0) {
|
||||
// Use provided opcode override
|
||||
opcode = in_opcode;
|
||||
} else {
|
||||
// Parse opcode from buffer (first 2 bytes)
|
||||
if (len < 2 || buf == nullptr) {
|
||||
// Insufficient data - set safe defaults
|
||||
opcode = 0;
|
||||
offset = len; // consume entire buffer
|
||||
} else {
|
||||
offset = 2;
|
||||
opcode = ntohs(*(const uint16*)buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate and copy payload data after opcode
|
||||
if (len > offset) {
|
||||
size = len - offset;
|
||||
pBuffer = new unsigned char[size];
|
||||
if (buf) {
|
||||
memcpy(pBuffer, buf + offset, size);
|
||||
} else {
|
||||
memset(pBuffer, 0, size);
|
||||
}
|
||||
} else {
|
||||
pBuffer = nullptr;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
// Initialize protocol packet state
|
||||
version = 0;
|
||||
eq2_compressed = false;
|
||||
packet_prepared = false;
|
||||
packet_encrypted = false;
|
||||
sent_time = 0;
|
||||
attempt_count = 0;
|
||||
sequence = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine this EQ2 packet with another EQ2 packet for efficient transmission.
|
||||
* Implements EQ2's application-level packet combining protocol.
|
||||
*
|
||||
* @param rhs - Right-hand side packet to combine with this one
|
||||
* @return true if packets were successfully combined, false otherwise
|
||||
*/
|
||||
bool EQ2Packet::AppCombine(EQ2Packet* rhs)
|
||||
{
|
||||
bool result = false;
|
||||
uchar* tmpbuffer = nullptr;
|
||||
bool over_sized_packet = false;
|
||||
int32 new_size = 0;
|
||||
|
||||
// Case 1: This packet is already a combined packet
|
||||
if (opcode == OP_AppCombined && ((size + rhs->size + 3) < 255)) {
|
||||
int16 tmp_size = rhs->size - 2; // Subtract opcode bytes
|
||||
|
||||
// Check if we need oversized packet encoding
|
||||
if (tmp_size >= 255) {
|
||||
new_size = size + tmp_size + 3; // Extra bytes for oversized encoding
|
||||
over_sized_packet = true;
|
||||
} else {
|
||||
new_size = size + tmp_size + 1; // One byte for size prefix
|
||||
}
|
||||
|
||||
tmpbuffer = new uchar[new_size];
|
||||
uchar* ptr = tmpbuffer;
|
||||
|
||||
// Copy existing combined packet data
|
||||
memcpy(ptr, pBuffer, size);
|
||||
ptr += size;
|
||||
|
||||
// Add size information for the new packet
|
||||
if (over_sized_packet) {
|
||||
*ptr++ = 255; // Oversized marker
|
||||
tmp_size = htons(tmp_size);
|
||||
memcpy(ptr, &tmp_size, sizeof(int16));
|
||||
ptr += sizeof(int16);
|
||||
} else {
|
||||
*ptr++ = static_cast<uint8>(tmp_size);
|
||||
}
|
||||
|
||||
// Copy packet data (skip opcode)
|
||||
memcpy(ptr, rhs->pBuffer + 2, rhs->size - 2);
|
||||
|
||||
// Replace buffer and clean up
|
||||
delete[] pBuffer;
|
||||
size = new_size;
|
||||
pBuffer = tmpbuffer;
|
||||
safe_delete(rhs);
|
||||
result = true;
|
||||
}
|
||||
// Case 2: Neither packet is combined - create new combined packet
|
||||
else if (rhs->size > 2 && size > 2 && (size + rhs->size + 6) < 255) {
|
||||
int32 tmp_size = size - 2;
|
||||
int32 tmp_size2 = rhs->size - 2;
|
||||
bool over_sized_packet2 = false;
|
||||
|
||||
// Calculate new size with headers
|
||||
new_size = 4; // Base combined packet header
|
||||
|
||||
// First packet size encoding
|
||||
if (tmp_size >= 255) {
|
||||
new_size += tmp_size + 3; // Oversized encoding
|
||||
over_sized_packet = true;
|
||||
} else {
|
||||
new_size += tmp_size + 1; // Normal encoding
|
||||
}
|
||||
|
||||
// Second packet size encoding
|
||||
if (tmp_size2 >= 255) {
|
||||
new_size += tmp_size2 + 3; // Oversized encoding
|
||||
over_sized_packet2 = true;
|
||||
} else {
|
||||
new_size += tmp_size2 + 1; // Normal encoding
|
||||
}
|
||||
|
||||
tmpbuffer = new uchar[new_size];
|
||||
|
||||
// Set combined packet header
|
||||
tmpbuffer[2] = 0;
|
||||
tmpbuffer[3] = 0x19; // Combined packet marker
|
||||
uchar* ptr = tmpbuffer + 4;
|
||||
|
||||
// Add first packet
|
||||
if (over_sized_packet) {
|
||||
*ptr++ = 255; // Oversized marker
|
||||
tmp_size = htons(tmp_size);
|
||||
memcpy(ptr, &tmp_size, sizeof(int16));
|
||||
ptr += sizeof(int16);
|
||||
} else {
|
||||
*ptr++ = static_cast<uint8>(tmp_size);
|
||||
}
|
||||
memcpy(ptr, pBuffer + 2, size - 2);
|
||||
ptr += (size - 2);
|
||||
|
||||
// Add second packet
|
||||
if (over_sized_packet2) {
|
||||
*ptr++ = 255; // Oversized marker
|
||||
tmp_size2 = htons(tmp_size2);
|
||||
memcpy(ptr, &tmp_size2, sizeof(int16));
|
||||
ptr += sizeof(int16);
|
||||
} else {
|
||||
*ptr++ = static_cast<uint8>(tmp_size2);
|
||||
}
|
||||
memcpy(ptr, rhs->pBuffer + 2, rhs->size - 2);
|
||||
|
||||
// Replace buffer and update opcode
|
||||
delete[] pBuffer;
|
||||
size = new_size;
|
||||
pBuffer = tmpbuffer;
|
||||
opcode = OP_AppCombined;
|
||||
safe_delete(rhs);
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine this protocol packet with another protocol packet.
|
||||
* Used for efficient transmission of multiple small packets together.
|
||||
*
|
||||
* @param rhs - Right-hand side packet to combine with this one
|
||||
* @return true if packets were successfully combined, false otherwise
|
||||
*/
|
||||
bool EQProtocolPacket::combine(const EQProtocolPacket* rhs)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// Case 1: This packet is already combined - append to it
|
||||
if (opcode == OP_Combined && size + rhs->size + 5 < 256) {
|
||||
auto tmpbuffer = new unsigned char[size + rhs->size + 3];
|
||||
|
||||
// Copy existing combined data
|
||||
memcpy(tmpbuffer, pBuffer, size);
|
||||
uint32 offset = size;
|
||||
|
||||
// Add size prefix for new packet
|
||||
tmpbuffer[offset++] = rhs->Size();
|
||||
|
||||
// Serialize and append new packet
|
||||
offset += rhs->serialize(tmpbuffer + offset);
|
||||
|
||||
// Update buffer
|
||||
size = offset;
|
||||
delete[] pBuffer;
|
||||
pBuffer = tmpbuffer;
|
||||
result = true;
|
||||
}
|
||||
// Case 2: Neither packet is combined - create new combined packet
|
||||
else if (size + rhs->size + 7 < 256) {
|
||||
auto tmpbuffer = new unsigned char[size + rhs->size + 6];
|
||||
uint32 offset = 0;
|
||||
|
||||
// Add first packet with size prefix
|
||||
tmpbuffer[offset++] = Size();
|
||||
offset += serialize(tmpbuffer + offset);
|
||||
|
||||
// Add second packet with size prefix
|
||||
tmpbuffer[offset++] = rhs->Size();
|
||||
offset += rhs->serialize(tmpbuffer + offset);
|
||||
|
||||
// Update buffer and mark as combined
|
||||
size = offset;
|
||||
delete[] pBuffer;
|
||||
pBuffer = tmpbuffer;
|
||||
opcode = OP_Combined;
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* EQApplicationPacket constructor from raw buffer.
|
||||
* Used by EQProtocolPacket to create application packets from network data.
|
||||
*
|
||||
* @param buf - Raw packet buffer (starts with opcode)
|
||||
* @param len - Length of buffer
|
||||
* @param opcode_size - Size of opcode in bytes (0 = use default)
|
||||
*/
|
||||
EQApplicationPacket::EQApplicationPacket(const unsigned char* buf, uint32 len, uint8 opcode_size)
|
||||
{
|
||||
uint32 offset = 0;
|
||||
app_opcode_size = (opcode_size == 0) ? EQApplicationPacket::default_opcode_size : opcode_size;
|
||||
|
||||
// Extract opcode based on size
|
||||
if (app_opcode_size == 1) {
|
||||
opcode = *(const unsigned char*)buf;
|
||||
offset++;
|
||||
} else {
|
||||
opcode = *(const uint16*)buf;
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
// Copy remaining data as payload
|
||||
if ((len - offset) > 0) {
|
||||
pBuffer = new unsigned char[len - offset];
|
||||
memcpy(pBuffer, buf + offset, len - offset);
|
||||
size = len - offset;
|
||||
} else {
|
||||
pBuffer = nullptr;
|
||||
size = 0;
|
||||
}
|
||||
|
||||
emu_opcode = OP_Unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine this application packet with another application packet.
|
||||
* Currently not implemented for application-level packets.
|
||||
*
|
||||
* @param rhs - Right-hand side packet to combine
|
||||
* @return false (combining not supported at application level)
|
||||
*/
|
||||
bool EQApplicationPacket::combine(const EQApplicationPacket* rhs)
|
||||
{
|
||||
// Application packet combining is not implemented
|
||||
// Use protocol-level combining instead
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the opcode for this application packet.
|
||||
* Converts emulator opcode to network protocol opcode.
|
||||
*
|
||||
* @param emu_op - Emulator opcode to set
|
||||
*/
|
||||
void EQApplicationPacket::SetOpcode(EmuOpcode emu_op)
|
||||
{
|
||||
if (emu_op == OP_Unknown) {
|
||||
opcode = 0;
|
||||
emu_opcode = OP_Unknown;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert emulator opcode to network opcode
|
||||
opcode = EQOpcodeManager[GetOpcodeVersion(version)]->EmuToEQ(emu_op);
|
||||
|
||||
if (opcode == OP_Unknown) {
|
||||
LogWrite(PACKET__DEBUG, 0, "Packet",
|
||||
"Unable to convert Emu opcode %s (%d) into an EQ opcode.",
|
||||
OpcodeNames[emu_op], emu_op);
|
||||
}
|
||||
|
||||
// Cache the emulator opcode for future lookups
|
||||
emu_opcode = emu_op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the emulator opcode for this application packet.
|
||||
* Converts network opcode to emulator opcode if not cached.
|
||||
*
|
||||
* @return Emulator opcode constant
|
||||
*/
|
||||
const EmuOpcode EQApplicationPacket::GetOpcodeConst() const
|
||||
{
|
||||
// Return cached opcode if available
|
||||
if (emu_opcode != OP_Unknown) {
|
||||
return emu_opcode;
|
||||
}
|
||||
|
||||
// Handle special invalid opcode case
|
||||
if (opcode == 10000) {
|
||||
return OP_Unknown;
|
||||
}
|
||||
|
||||
// Convert network opcode to emulator opcode
|
||||
EmuOpcode emu_op = EQOpcodeManager[GetOpcodeVersion(version)]->EQToEmu(opcode);
|
||||
if (emu_op == OP_Unknown) {
|
||||
LogWrite(PACKET__DEBUG, 1, "Packet",
|
||||
"Unable to convert EQ opcode 0x%.4X (%i) to an emu opcode (%s)",
|
||||
opcode, opcode, __FUNCTION__);
|
||||
}
|
||||
|
||||
return emu_op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this protocol packet to an application packet.
|
||||
* Handles opcode format conversion between protocol and application layers.
|
||||
*
|
||||
* @param opcode_size - Size of application opcode (0 = use default)
|
||||
* @return New application packet, or nullptr on failure
|
||||
*/
|
||||
EQApplicationPacket* EQProtocolPacket::MakeApplicationPacket(uint8 opcode_size) const
|
||||
{
|
||||
auto res = new EQApplicationPacket;
|
||||
res->app_opcode_size = (opcode_size == 0) ? EQApplicationPacket::default_opcode_size : opcode_size;
|
||||
|
||||
if (res->app_opcode_size == 1) {
|
||||
// Single-byte opcode format
|
||||
res->pBuffer = new unsigned char[size + 1];
|
||||
memcpy(res->pBuffer + 1, pBuffer, size);
|
||||
*(res->pBuffer) = htons(opcode) & 0xff;
|
||||
res->opcode = opcode & 0xff;
|
||||
res->size = size + 1;
|
||||
} else {
|
||||
// Two-byte opcode format
|
||||
res->pBuffer = new unsigned char[size];
|
||||
memcpy(res->pBuffer, pBuffer, size);
|
||||
res->opcode = opcode;
|
||||
res->size = size;
|
||||
}
|
||||
|
||||
// Copy network and timing information
|
||||
res->copyInfo(this);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the CRC checksum of a network packet.
|
||||
* Some packet types (session packets) are exempt from CRC validation.
|
||||
*
|
||||
* @param buffer - Packet buffer to validate
|
||||
* @param length - Length of packet buffer
|
||||
* @param Key - CRC key for validation
|
||||
* @return true if CRC is valid or packet is exempt, false otherwise
|
||||
*/
|
||||
bool EQProtocolPacket::ValidateCRC(const unsigned char* buffer, int length, uint32 Key)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
// Session packets are not CRC protected
|
||||
if (buffer[0] == 0x00 &&
|
||||
(buffer[1] == OP_SessionRequest ||
|
||||
buffer[1] == OP_SessionResponse ||
|
||||
buffer[1] == OP_OutOfSession)) {
|
||||
valid = true;
|
||||
}
|
||||
// Combined application packets are also exempt
|
||||
else if (buffer[2] == 0x00 && buffer[3] == 0x19) {
|
||||
valid = true;
|
||||
}
|
||||
// All other packets must have valid CRC
|
||||
else {
|
||||
uint16 comp_crc = CRC16(buffer, length - 2, Key);
|
||||
uint16 packet_crc = ntohs(*(const uint16*)(buffer + length - 2));
|
||||
|
||||
#ifdef EQN_DEBUG
|
||||
if (packet_crc && comp_crc != packet_crc) {
|
||||
cout << "CRC mismatch: comp=" << hex << comp_crc
|
||||
<< ", packet=" << packet_crc << dec << endl;
|
||||
}
|
||||
#endif
|
||||
// Valid if no CRC present (packet_crc == 0) or CRCs match
|
||||
valid = (!packet_crc || comp_crc == packet_crc);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompress a network packet using zlib or simple encoding.
|
||||
* Supports both zlib compression (0x5a) and simple encoding (0xa5).
|
||||
*
|
||||
* @param buffer - Compressed packet buffer
|
||||
* @param length - Length of compressed buffer
|
||||
* @param newbuf - Destination buffer for decompressed data
|
||||
* @param newbufsize - Size of destination buffer
|
||||
* @return Length of decompressed data
|
||||
*/
|
||||
uint32 EQProtocolPacket::Decompress(const unsigned char* buffer, uint32 length, unsigned char* newbuf, uint32 newbufsize)
|
||||
{
|
||||
uint32 newlen = 0;
|
||||
uint32 flag_offset = 0;
|
||||
|
||||
// Copy opcode header
|
||||
newbuf[0] = buffer[0];
|
||||
if (buffer[0] == 0x00) {
|
||||
flag_offset = 2; // Two-byte opcode
|
||||
newbuf[1] = buffer[1];
|
||||
} else {
|
||||
flag_offset = 1; // One-byte opcode
|
||||
}
|
||||
|
||||
// Check compression type
|
||||
if (length > 2 && buffer[flag_offset] == 0x5a) {
|
||||
// Zlib compression
|
||||
LogWrite(PACKET__DEBUG, 0, "Packet", "Decompressing zlib packet");
|
||||
newlen = Inflate(const_cast<unsigned char*>(buffer + flag_offset + 1),
|
||||
length - (flag_offset + 1) - 2, // Subtract CRC bytes
|
||||
newbuf + flag_offset,
|
||||
newbufsize - flag_offset) + flag_offset;
|
||||
|
||||
// Handle decompression failure
|
||||
if (newlen == static_cast<uint32>(-1)) {
|
||||
LogWrite(PACKET__ERROR, 0, "Packet", "Zlib decompression failed!");
|
||||
DumpPacket(buffer, length);
|
||||
// Fallback: copy original buffer
|
||||
memcpy(newbuf, buffer, length);
|
||||
return length;
|
||||
}
|
||||
|
||||
// Copy CRC bytes to end
|
||||
newbuf[newlen++] = buffer[length - 2];
|
||||
newbuf[newlen++] = buffer[length - 1];
|
||||
} else if (length > 2 && buffer[flag_offset] == 0xa5) {
|
||||
// Simple encoding - just remove the encoding flag
|
||||
LogWrite(PACKET__DEBUG, 0, "Packet", "Decompressing simple encoded packet");
|
||||
memcpy(newbuf + flag_offset, buffer + flag_offset + 1, length - (flag_offset + 1));
|
||||
newlen = length - 1;
|
||||
} else {
|
||||
// No compression - direct copy
|
||||
memcpy(newbuf, buffer, length);
|
||||
newlen = length;
|
||||
}
|
||||
|
||||
return newlen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress a network packet using zlib or simple encoding.
|
||||
* Uses zlib for packets > 30 bytes, simple encoding for smaller packets.
|
||||
*
|
||||
* @param buffer - Source packet buffer
|
||||
* @param length - Length of source buffer
|
||||
* @param newbuf - Destination buffer for compressed data
|
||||
* @param newbufsize - Size of destination buffer
|
||||
* @return Length of compressed data
|
||||
*/
|
||||
uint32 EQProtocolPacket::Compress(const unsigned char* buffer, uint32 length, unsigned char* newbuf, uint32 newbufsize)
|
||||
{
|
||||
uint32 flag_offset = 1;
|
||||
uint32 newlength;
|
||||
|
||||
// Copy opcode header
|
||||
newbuf[0] = buffer[0];
|
||||
if (buffer[0] == 0) {
|
||||
flag_offset = 2; // Two-byte opcode
|
||||
newbuf[1] = buffer[1];
|
||||
}
|
||||
|
||||
// Choose compression method based on packet size
|
||||
if (length > 30) {
|
||||
// Use zlib compression for larger packets
|
||||
newlength = Deflate(const_cast<unsigned char*>(buffer + flag_offset),
|
||||
length - flag_offset,
|
||||
newbuf + flag_offset + 1,
|
||||
newbufsize);
|
||||
*(newbuf + flag_offset) = 0x5a; // Zlib compression flag
|
||||
newlength += flag_offset + 1;
|
||||
} else {
|
||||
// Use simple encoding for smaller packets
|
||||
memmove(newbuf + flag_offset + 1, buffer + flag_offset, length - flag_offset);
|
||||
*(newbuf + flag_offset) = 0xa5; // Simple encoding flag
|
||||
newlength = length + 1;
|
||||
}
|
||||
|
||||
return newlength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode chat packet data using XOR encryption.
|
||||
* Uses a rolling XOR key that updates with each 4-byte block.
|
||||
*
|
||||
* @param buffer - Buffer containing chat data to decode
|
||||
* @param size - Size of buffer
|
||||
* @param DecodeKey - Initial decoding key
|
||||
*/
|
||||
void EQProtocolPacket::ChatDecode(unsigned char* buffer, int size, int DecodeKey)
|
||||
{
|
||||
// Skip decoding for certain packet types
|
||||
if (buffer[1] != 0x01 && buffer[0] != 0x02 && buffer[0] != 0x1d) {
|
||||
int Key = DecodeKey;
|
||||
auto test = static_cast<unsigned char*>(malloc(size));
|
||||
|
||||
// Skip the first 2 bytes (opcode)
|
||||
buffer += 2;
|
||||
size -= 2;
|
||||
|
||||
// Decode 4-byte blocks with rolling key
|
||||
int i;
|
||||
for (i = 0; i + 4 <= size; i += 4) {
|
||||
int pt = (*(int*)&buffer[i]) ^ Key;
|
||||
Key = (*(int*)&buffer[i]); // Update key with encrypted data
|
||||
*(int*)&test[i] = pt;
|
||||
}
|
||||
|
||||
// Decode remaining bytes with last key byte
|
||||
unsigned char KC = Key & 0xFF;
|
||||
for (; i < size; i++) {
|
||||
test[i] = buffer[i] ^ KC;
|
||||
}
|
||||
|
||||
// Copy decoded data back
|
||||
memcpy(buffer, test, size);
|
||||
free(test);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode chat packet data using XOR encryption.
|
||||
* Uses a rolling XOR key that updates with each encrypted 4-byte block.
|
||||
*
|
||||
* @param buffer - Buffer containing chat data to encode
|
||||
* @param size - Size of buffer
|
||||
* @param EncodeKey - Initial encoding key
|
||||
*/
|
||||
void EQProtocolPacket::ChatEncode(unsigned char* buffer, int size, int EncodeKey)
|
||||
{
|
||||
// Skip encoding for certain packet types
|
||||
if (buffer[1] != 0x01 && buffer[0] != 0x02 && buffer[0] != 0x1d) {
|
||||
int Key = EncodeKey;
|
||||
auto test = static_cast<char*>(malloc(size));
|
||||
|
||||
// Skip the first 2 bytes (opcode)
|
||||
buffer += 2;
|
||||
size -= 2;
|
||||
|
||||
// Encode 4-byte blocks with rolling key
|
||||
int i;
|
||||
for (i = 0; i + 4 <= size; i += 4) {
|
||||
int pt = (*(int*)&buffer[i]) ^ Key;
|
||||
Key = pt; // Update key with encrypted data
|
||||
*(int*)&test[i] = pt;
|
||||
}
|
||||
|
||||
// Encode remaining bytes with last key byte
|
||||
unsigned char KC = Key & 0xFF;
|
||||
for (; i < size; i++) {
|
||||
test[i] = buffer[i] ^ KC;
|
||||
}
|
||||
|
||||
// Copy encoded data back
|
||||
memcpy(buffer, test, size);
|
||||
free(test);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a buffer contains a valid EverQuest protocol packet.
|
||||
* Validates the opcode against known protocol opcodes.
|
||||
*
|
||||
* @param in_buff - Input buffer to check
|
||||
* @param len - Length of input buffer
|
||||
* @param bTrimCRC - Whether CRC should be trimmed (unused)
|
||||
* @return true if buffer contains a valid protocol packet
|
||||
*/
|
||||
bool EQProtocolPacket::IsProtocolPacket(const unsigned char* in_buff, uint32_t len, bool bTrimCRC)
|
||||
{
|
||||
bool ret = false;
|
||||
uint16_t opcode = ntohs(*(uint16_t*)in_buff);
|
||||
|
||||
// Check against known protocol opcodes
|
||||
switch (opcode) {
|
||||
case OP_SessionRequest:
|
||||
case OP_SessionDisconnect:
|
||||
case OP_KeepAlive:
|
||||
case OP_SessionStatResponse:
|
||||
case OP_Packet:
|
||||
case OP_Combined:
|
||||
case OP_Fragment:
|
||||
case OP_Ack:
|
||||
case OP_OutOfOrderAck:
|
||||
case OP_OutOfSession:
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
// Unknown opcode - not a protocol packet
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump application packet data in hexadecimal format.
|
||||
*
|
||||
* @param app - Application packet to dump
|
||||
*/
|
||||
void DumpPacketHex(const EQApplicationPacket* app)
|
||||
{
|
||||
DumpPacketHex(app->pBuffer, app->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump application packet data in ASCII format.
|
||||
*
|
||||
* @param app - Application packet to dump
|
||||
*/
|
||||
void DumpPacketAscii(const EQApplicationPacket* app)
|
||||
{
|
||||
DumpPacketAscii(app->pBuffer, app->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump protocol packet data in hexadecimal format.
|
||||
*
|
||||
* @param app - Protocol packet to dump
|
||||
*/
|
||||
void DumpPacket(const EQProtocolPacket* app)
|
||||
{
|
||||
DumpPacketHex(app->pBuffer, app->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump application packet with optional header information.
|
||||
*
|
||||
* @param app - Application packet to dump
|
||||
* @param iShowInfo - Whether to show packet information header
|
||||
*/
|
||||
void DumpPacket(const EQApplicationPacket* app, bool iShowInfo)
|
||||
{
|
||||
if (iShowInfo) {
|
||||
cout << "Dumping Applayer: 0x" << hex << setfill('0') << setw(4)
|
||||
<< app->GetOpcode() << dec;
|
||||
cout << " size:" << app->size << endl;
|
||||
}
|
||||
DumpPacketHex(app->pBuffer, app->size);
|
||||
// ASCII dump is commented out but available:
|
||||
// DumpPacketAscii(app->pBuffer, app->size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump application packet data in binary format.
|
||||
*
|
||||
* @param app - Application packet to dump
|
||||
*/
|
||||
void DumpPacketBin(const EQApplicationPacket* app)
|
||||
{
|
||||
DumpPacketBin(app->pBuffer, app->size);
|
||||
}
|
||||
|
||||
|
310
old/common/EQPacket.h
Normal file
310
old/common/EQPacket.h
Normal file
@ -0,0 +1,310 @@
|
||||
// EQ2Emulator: Everquest II Server Emulator
|
||||
// Copyright (C) 2007 EQ2EMulator Development Team
|
||||
// Licensed under GPL v3 - see <http://www.gnu.org/licenses/>
|
||||
#ifndef _EQPACKET_H
|
||||
#define _EQPACKET_H
|
||||
|
||||
#include "types.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <memory>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <time.h>
|
||||
#include <WinSock2.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#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<EQApplicationPacket>();
|
||||
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
|
2680
old/common/EQStream.cpp
Normal file
2680
old/common/EQStream.cpp
Normal file
File diff suppressed because it is too large
Load Diff
523
old/common/EQStream.h
Normal file
523
old/common/EQStream.h
Normal file
@ -0,0 +1,523 @@
|
||||
// EQ2Emulator: Everquest II Server Emulator
|
||||
// Copyright (C) 2007 EQ2EMulator Development Team
|
||||
// Licensed under GPL v3 - see <http://www.gnu.org/licenses/>
|
||||
#ifndef _EQPROTOCOL_H
|
||||
#define _EQPROTOCOL_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
// Unix networking headers
|
||||
#include <netinet/in.h>
|
||||
|
||||
// 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 <stdarg.h>
|
||||
#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<EQProtocolPacket*> NonSequencedQueue; // Non-sequenced packets
|
||||
deque<EQProtocolPacket*> SequencedQueue; // Sequenced packets
|
||||
map<uint16, EQProtocolPacket*> OutOfOrderpackets; // Out-of-order packets
|
||||
Mutex MOutboundQueue; // Mutex for outbound queues
|
||||
|
||||
// Inbound packet queue
|
||||
deque<EQApplicationPacket*> 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<EQ2Packet*> 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<EQProtocolPacket*> 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<EQProtocolPacket*> 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
|
546
old/common/EQStreamFactory.cpp
Normal file
546
old/common/EQStreamFactory.cpp
Normal file
@ -0,0 +1,546 @@
|
||||
// EQ2Emulator: Everquest II Server Emulator
|
||||
// Copyright (C) 2007 EQ2EMulator Development Team
|
||||
// Licensed under GPL v3 - see <http://www.gnu.org/licenses/>
|
||||
|
||||
#include "EQStreamFactory.h"
|
||||
#include "Log.h"
|
||||
|
||||
// Unix/Linux networking headers
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Standard library headers
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
// 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<EQStreamFactory*>(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<EQStreamFactory*>(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<EQStreamFactory*>(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<char*>(&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<struct sockaddr*>(&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<string, EQStream*>::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<struct sockaddr*>(&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<string, EQStream*>::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<EQStream*> 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<string, EQStream*>::iterator stream_itr;
|
||||
vector<EQStream*> wants_write;
|
||||
vector<EQStream*>::iterator cur, end;
|
||||
deque<EQStream*> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
125
old/common/EQStreamFactory.h
Normal file
125
old/common/EQStreamFactory.h
Normal file
@ -0,0 +1,125 @@
|
||||
// EQ2Emulator: Everquest II Server Emulator
|
||||
// Copyright (C) 2007 EQ2EMulator Development Team
|
||||
// Licensed under GPL v3 - see <http://www.gnu.org/licenses/>
|
||||
#ifndef _EQSTREAMFACTORY_H
|
||||
#define _EQSTREAMFACTORY_H
|
||||
|
||||
#include <queue>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#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<EQStream*> NewStreams; // Queue of new streams waiting for processing
|
||||
Mutex MNewStreams; // Mutex for new streams queue
|
||||
std::map<std::string, EQStream*> 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
|
20
old/login/Character.cpp
Normal file
20
old/login/Character.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "Character.h"
|
25
old/login/Character.h
Normal file
25
old/login/Character.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef _EQ2_CHARACTER_
|
||||
#define _EQ2_CHARACTER_
|
||||
class Character{
|
||||
|
||||
};
|
||||
#endif
|
1561
old/login/LWorld.cpp
Normal file
1561
old/login/LWorld.cpp
Normal file
File diff suppressed because it is too large
Load Diff
253
old/login/LWorld.h
Normal file
253
old/login/LWorld.h
Normal file
@ -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 <boost/algorithm/string.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
namespace beast = boost::beast; // from <boost/beast.hpp>
|
||||
namespace http = beast::http; // from <boost/beast/http.hpp>
|
||||
|
||||
#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<PacketStruct*>* 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<http::string_body>& res);
|
||||
|
||||
void ListWorldsToConsole();
|
||||
//devn00b temp
|
||||
void AddServerEquipmentUpdates(LWorld* world, map<int32, LoginEquipmentUpdate> 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<int32, LoginZoneUpdate> updates);
|
||||
|
||||
protected:
|
||||
friend class LWorld;
|
||||
int32 GetNextID() { return NextID++; }
|
||||
|
||||
private:
|
||||
Mutex MWorldMap;
|
||||
map<int32, map<int32, bool> > zone_updates_already_used; //used to determine if someone is trying to DOS us
|
||||
MutexMap<int32, int32> zone_update_timeouts;
|
||||
MutexMap<int32, int32> awaiting_zone_update;
|
||||
MutexMap<LWorld*, int32> last_updated;
|
||||
MutexMap<int32, map<int32, LoginZoneUpdate> > server_zone_updates;
|
||||
bool server_update_thread;
|
||||
int32 NextID;
|
||||
|
||||
LinkedList<LWorld*> list;
|
||||
|
||||
map<int32,LWorld*> worldmap;
|
||||
|
||||
TCPServer* tcplistener;
|
||||
TCPConnection* OutLink;
|
||||
|
||||
//devn00b temp
|
||||
// JohnAdams: login appearances, copied from above
|
||||
map<int32, map<int32, bool> > equip_updates_already_used;
|
||||
MutexMap<int32, int32> equip_update_timeouts;
|
||||
MutexMap<int32, int32> awaiting_equip_update;
|
||||
MutexMap<LWorld*, int32> last_equip_updated;
|
||||
MutexMap<int32, map<int32, LoginEquipmentUpdate> > server_equip_updates;
|
||||
//
|
||||
///
|
||||
|
||||
// holds the world server list so we don't have to create it for every character
|
||||
// logging in
|
||||
map<int32,EQ2Packet*> ServerListData;
|
||||
bool UpdateServerList;
|
||||
};
|
||||
#endif
|
58
old/login/LoginAccount.cpp
Normal file
58
old/login/LoginAccount.cpp
Normal file
@ -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<CharSelectProfile*>::iterator iter;
|
||||
for(iter = charlist.begin(); iter != charlist.end(); iter++){
|
||||
safe_delete(*iter);
|
||||
}
|
||||
}
|
||||
|
||||
void LoginAccount::flushCharacters ( )
|
||||
{
|
||||
vector<CharSelectProfile*>::iterator iter;
|
||||
for(iter = charlist.begin(); iter != charlist.end(); iter++){
|
||||
safe_delete(*iter);
|
||||
}
|
||||
|
||||
charlist.clear ( );
|
||||
}
|
||||
|
||||
CharSelectProfile* LoginAccount::getCharacter(char* name){
|
||||
vector<CharSelectProfile*>::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<CharSelectProfile*>::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;
|
||||
}
|
||||
}
|
||||
}
|
54
old/login/LoginAccount.h
Normal file
54
old/login/LoginAccount.h
Normal file
@ -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 <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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<CharSelectProfile*> 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
|
1083
old/login/LoginDatabase.cpp
Normal file
1083
old/login/LoginDatabase.cpp
Normal file
File diff suppressed because it is too large
Load Diff
96
old/login/LoginDatabase.h
Normal file
96
old/login/LoginDatabase.h
Normal file
@ -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 <winsock.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <mysql.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<LWorld*>* server_list);
|
||||
char* GetServerAccountName(int32 id);
|
||||
bool VerifyDelete(int32 account_id, int32 character_id, const char* name);
|
||||
void SetServerZoneDescriptions(int32 server_id, map<int32, LoginZoneUpdate> 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<int32, LoginEquipmentUpdate> 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
|
88
old/login/PacketHeaders.cpp
Normal file
88
old/login/PacketHeaders.cpp
Normal file
@ -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<CharSelectProfile*> charlist, int16 version){
|
||||
vector<CharSelectProfile*>::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());
|
||||
}
|
61
old/login/PacketHeaders.h
Normal file
61
old/login/PacketHeaders.h
Normal file
@ -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 <vector>
|
||||
|
||||
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<CharSelectProfile*> 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
|
67
old/login/Web/LoginWeb.cpp
Normal file
67
old/login/Web/LoginWeb.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "../net.h"
|
||||
#include "../LWorld.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
extern ClientList client_list;
|
||||
extern LWorldList world_list;
|
||||
extern NetConnection net;
|
||||
|
||||
void NetConnection::Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& 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<http::string_body>& req, http::response<http::string_body>& res) {
|
||||
world_list.PopulateWorldList(res);
|
||||
}
|
||||
|
||||
void LWorldList::PopulateWorldList(http::response<http::string_body>& res) {
|
||||
|
||||
struct in_addr in;
|
||||
res.set(http::field::content_type, "application/json");
|
||||
boost::property_tree::ptree maintree;
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
map<int32,LWorld*>::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();
|
||||
}
|
||||
|
813
old/login/client.cpp
Normal file
813
old/login/client.cpp
Normal file
@ -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 <windows.h>
|
||||
#include <winsock.h>
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <iomanip>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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 map<int16,OpcodeManager*>EQOpcodeManager;
|
||||
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<PacketStruct*>::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<Client*, bool>::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<Client*, bool>::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<Client*, bool>::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<Client*, bool>::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<Client*> erase_list;
|
||||
map<Client*, bool>::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<Client*>::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();
|
||||
}
|
||||
}
|
131
old/login/client.h
Normal file
131
old/login/client.h
Normal file
@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<DelayQue*> 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<PacketStruct*>* 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*, bool> client_list;
|
||||
};
|
||||
class DelayQue {
|
||||
public:
|
||||
DelayQue(Timer* in_timer, EQApplicationPacket* in_packet){
|
||||
timer = in_timer;
|
||||
packet = in_packet;
|
||||
};
|
||||
Timer* timer;
|
||||
EQApplicationPacket* packet;
|
||||
};
|
||||
#endif
|
52
old/login/login_opcodes.h
Normal file
52
old/login/login_opcodes.h
Normal file
@ -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 */
|
||||
|
61
old/login/login_structs.h
Normal file
61
old/login/login_structs.h
Normal file
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user