// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team - GPL License #pragma once #include #include #include #include #include #include #include #include #include #include #include #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 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::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 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* 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::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::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::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::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 erase_list; map::iterator itr; MClientList.readlock(); for(itr = client_list.begin(); itr != client_list.end(); itr++) { client = itr->first; if(!client->Process()) erase_list.push_back(client); } MClientList.releasereadlock(); if(erase_list.size() > 0) { vector::iterator erase_itr; MClientList.writelock(); for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++) { client = *erase_itr; struct in_addr in; in.s_addr = client->getConnection()->GetRemoteIP(); net.numclients--; LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName()); client->getConnection()->Close(); net.UpdateWindowTitle(); client_list.erase(client); } MClientList.releasewritelock(); } } private: Mutex MClientList; // Mutex for thread-safe client list access map client_list; // Map of active clients };