eq2go/old/LoginServer/client.hpp
2025-08-06 19:00:30 -05:00

851 lines
27 KiB
C++

// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team - GPL License
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <vector>
#include <map>
#include "net.hpp"
#include "lworld.hpp"
#include "login_structs.hpp"
#include "login_account.hpp"
#include "login_database.hpp"
#include "../common/tcp.hpp"
#include "../common/timer.hpp"
#include "../common/linked_list.hpp"
#include "../common/log.hpp"
#include "../common/debug.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/stream/eq_stream.hpp"
#include "../common/packet/packet_dump.hpp"
#include "../common/packet/packet_struct.hpp"
#include "../common/packet/packet_functions.hpp"
#include "../common/opcodes/emu_opcodes.hpp"
using namespace std;
enum eLoginMode { None, Normal, Registration };
extern NetConnection net;
extern LWorldList world_list;
extern ClientList client_list;
extern LoginDatabase database;
extern map<int16,OpcodeManager*> EQOpcodeManager;
extern ConfigReader configReader;
class DelayQue
{
public:
// Constructor initializes timer and packet for delayed queue processing
DelayQue(Timer* in_timer, EQApplicationPacket* in_packet)
{
timer = in_timer;
packet = in_packet;
}
Timer* timer; // Timer for delay processing
EQApplicationPacket* packet; // Packet to be processed after delay
};
class Client
{
public:
// Constructor initializes client connection and default values
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));
memset(key,0,10);
LoginMode = None;
num_updates = 0;
updatetimer = new Timer(500);
updatelisttimer = new Timer(10000);
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;
}
// Destructor cleans up allocated resources and closes connection
~Client()
{
safe_delete(login_account);
eqnc->Close();
safe_delete(playWaitTimer);
safe_delete(createRequest);
safe_delete(disconnectTimer);
safe_delete(updatetimer);
}
// Main processing loop for client connections and packet handling
bool 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) {
EQApplicationPacket *app = 0;
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:
HandleLoginRequest(app);
break;
case OP_KeymapLoadMsg:
// Keymap message - currently unused
break;
case OP_AllWSDescRequestMsg:
HandleWorldServerListRequest();
break;
case OP_LsClientCrashlogReplyMsg:
SaveErrorsToDB(app, "Crash Log", GetVersion());
break;
case OP_LsClientVerifylogReplyMsg:
SaveErrorsToDB(app, "Verify Log", GetVersion());
break;
case OP_LsClientAlertlogReplyMsg:
SaveErrorsToDB(app, "Alert Log", GetVersion());
break;
case OP_LsClientBaselogReplyMsg:
SaveErrorsToDB(app, "Base Log", GetVersion());
break;
case OP_AllCharactersDescRequestMsg:
break;
case OP_CreateCharacterRequestMsg:
HandleCreateCharacterRequest(app);
break;
case OP_PlayCharacterRequestMsg:
HandlePlayCharacterRequest(app);
break;
case OP_DeleteCharacterRequestMsg:
HandleDeleteCharacterRequest(app);
break;
default:
HandleUnknownOpcode(app);
}
delete app;
}
}
if(!eqnc->CheckActive()) {
return false;
}
return true;
}
// Handles login request packets and validates credentials
void HandleLoginRequest(EQApplicationPacket* app)
{
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
return;
}
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();
safe_delete(packet);
return;
}
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(); // Prevent double login
}
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";
}
safe_delete(packet);
}
// Handles world server list request and sends character list
void HandleWorldServerListRequest()
{
SendWorldList();
needs_world_list = false;
if(!sent_character_list) {
database.LoadCharacters(GetLoginAccount(), GetVersion());
sent_character_list = true;
}
SendCharList();
}
// Handles character creation requests from clients
void HandleCreateCharacterRequest(EQApplicationPacket* app)
{
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";
}
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);
}
}
// Handles character play requests and forwards to world server
void HandlePlayCharacterRequest(EQApplicationPacket* app)
{
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);
}
// Handles character deletion requests
void HandleDeleteCharacterRequest(EQApplicationPacket* app)
{
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);
}
// Handles unknown opcodes and logs them for debugging
void HandleUnknownOpcode(EQApplicationPacket* app)
{
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());
}
// Saves client error logs to database after decompression
void 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);
}
// Handles character creation approval from world server
void 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);
}
// Handles character creation rejection from world server
void CharacterRejected(int8 reason_number)
{
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(createRequest && packet) {
packet->setDataByName("account_id", GetAccountID());
int8 clientReasonNum = reason_number;
packet->setDataByName("response", clientReasonNum);
packet->setMediumStringByName("name", "");
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
// Sends character list to client
void SendCharList()
{
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);
}
// Sends login denied response with bad version error
void 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();
}
// Sends login denied response for invalid credentials
void SendLoginDenied()
{
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 1;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
// Sends login accepted response with account details
void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0)
{
PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion());
if(packet) {
packet->setDataByName("account_id", account_id);
packet->setDataByName("login_response", login_response);
packet->setDataByName("do_not_force_soga", 1);
packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
packet->setDataByName("race_flag", 0x1FFFFF);
packet->setDataByName("class_flag", 0x7FFFFFE);
packet->setMediumStringByName("username", GetAccountName());
packet->setMediumStringByName("password", GetAccountName());
packet->setDataByName("unknown5", net.GetExpansionFlag());
packet->setDataByName("unknown6", 0xFF);
packet->setDataByName("unknown6", 0xFF, 1);
packet->setDataByName("unknown6", 0xFF, 2);
packet->setDataByName("unknown10", 0xFF);
packet->setDataByName("unknown7", net.GetEnabledRaces());
packet->setDataByName("unknown7a", 0xEE);
packet->setDataByName("unknown8", net.GetCitiesFlag(), 1);
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
// Sends world server list to client
void 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
}
// Queues packet for transmission to client
void QueuePacket(EQ2Packet* app)
{
eqnc->EQ2QueuePacket(app);
}
// Handles world server response for character play requests
void 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);
}
}
// Handles fatal errors and sends play failed response
void FatalError(int8 response)
{
safe_delete(playWaitTimer);
SendPlayFailed(response);
}
// Sends play failed response to client
void 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);
}
}
// Starts timer for delayed client disconnection
void StartDisconnectTimer()
{
if(!disconnectTimer) {
disconnectTimer = new Timer(1000);
disconnectTimer->Start();
}
}
// Accessor methods
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); }
bool AwaitingCharCreationRequest() {
if(createRequest)
return true;
else
return false;
}
// Public member variables
int8 LoginKey[10]; // Login encryption key
int8 ClientSession[25]; // Client session identifier
Timer* updatetimer; // Timer for client updates
Timer* updatelisttimer; // Timer for update list processing
Timer* disconnectTimer; // Timer for delayed disconnection
int16 packettotal; // Total packet count
int32 requested_server_id; // Server ID requested by client
int32 request_num; // Request number counter
LinkedList<DelayQue*> delay_que; // Queue for delayed packet processing
private:
string pending_play_char_name; // Character name for pending play request
int32 pending_play_char_id; // Character ID for pending play request
int8 update_position; // Position in update packet list
int16 num_updates; // Number of updates sent
vector<PacketStruct*>* update_packets; // List of update packets
LoginAccount* login_account; // Associated login account
EQStream* eqnc; // Network connection stream
int32 ip; // Client IP address
int16 port; // Client port number
int32 account_id; // Account ID
string account_name; // Account name
char key[10]; // Client authentication key
int8 lsadmin; // Login server admin level
sint16 worldadmin; // World server admin level
int lsstatus; // Login server status
bool kicked; // Whether client has been kicked
bool verified; // Whether client is verified
bool start; // Whether client has started
bool needs_world_list; // Whether client needs world list
int16 version; // Client version
char bannedreason[30]; // Reason for ban if applicable
bool sent_character_list; // Whether character list has been sent
eLoginMode LoginMode; // Current login mode
PacketStruct* createRequest; // Pending character creation request
Timer* playWaitTimer; // Timer for play request timeout
};
class ClientList
{
public:
// Constructor and destructor for client list management
ClientList() {}
~ClientList() {}
// Adds a new client to the list
void Add(Client* client)
{
MClientList.writelock();
client_list[client] = true;
MClientList.releasewritelock();
}
// Finds client by IP address and port
Client* 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;
}
// Finds clients waiting for character creation and handles rejections
void 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);
}
// Finds client by login server account ID
Client* 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;
}
// Sends packet to all connected clients
void 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();
}
// Processes all clients and removes inactive ones
void 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();
}
}
private:
Mutex MClientList; // Mutex for thread-safe client list access
map<Client*, bool> client_list; // Map of active clients
};