1
0
Protocol/old/login/client.cpp

1125 lines
33 KiB
C++

/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
*/
// Debug includes
#include "../common/debug.h"
// Linux network headers
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
// Standard library includes
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
// Project includes
#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"
// Global instances
extern NetConnection net;
extern LWorldList world_list;
extern ClientList client_list;
extern LoginDatabase database;
extern std::map<std::int16_t, OpcodeManager*> EQOpcodeManager;
extern ConfigReader configReader;
using namespace std;
/**
* @brief Constructs a new Client instance
* @param ieqnc The EQStream connection for this client
*/
Client::Client(EQStream* ieqnc)
: eqnc_{ieqnc}
, ip_{ieqnc->GetrIP()}
, port_{ntohs(ieqnc->GetrPort())}
, account_id_{0}
, lsadmin_{0}
, worldadmin_{0}
, lsstatus_{0}
, version_{0}
, kicked_{false}
, verified_{false}
, login_mode_{LoginMode::None}
, num_updates_{0}
, request_num_{0}
, createRequest_{nullptr}
, playWaitTimer_{nullptr}
, start_{false}
, update_position_{0}
, update_packets_{nullptr}
, needs_world_list_{true}
, sent_character_list_{false}
{
// Clear critical buffers
std::memset(bannedreason_, 0, sizeof(bannedreason_));
std::memset(key_, 0, sizeof(key_));
std::memset(ClientSession, 0, sizeof(ClientSession));
// Initialize timers
updatetimer = std::make_unique<Timer>(500);
updatelisttimer = std::make_unique<Timer>(10000);
}
/**
* @brief Destructor - cleans up all client resources
*/
Client::~Client()
{
// Close the connection
if (eqnc_)
{
eqnc_->Close();
}
// Clean up character creation request
safe_delete(createRequest_);
// Clean up update packets
if (update_packets_)
{
for (auto* packet : *update_packets_)
{
safe_delete(packet);
}
safe_delete(update_packets_);
}
}
/**
* @brief Main processing function for the client
* @return true if client is still active, false if should be disconnected
*/
bool Client::Process()
{
// Handle initial connection state
if (!start_ && !eqnc_->CheckActive())
{
if (!playWaitTimer_)
{
playWaitTimer_ = std::make_unique<Timer>(5000);
}
else if (playWaitTimer_->Check())
{
playWaitTimer_.reset();
return false;
}
return true;
}
else if (!start_)
{
playWaitTimer_.reset();
start_ = true;
}
// Check disconnect timer
if (disconnectTimer && disconnectTimer->Check())
{
disconnectTimer.reset();
getConnection()->SendDisconnect();
}
// Process packets if not kicked
if (!kicked_)
{
// Handle play wait timeout
if (playWaitTimer_ != nullptr && playWaitTimer_->Check())
{
SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT);
playWaitTimer_.reset();
}
// Process world list updates
if (!needs_world_list_ && updatetimer && updatetimer->Check())
{
if (updatelisttimer && updatelisttimer->Check())
{
// Disconnect after 30 minutes of updates
if (num_updates_ >= 180)
{
getConnection()->SendDisconnect();
}
else
{
// Clean up old update packets
if (update_packets_)
{
for (auto* packet : *update_packets_)
{
safe_delete(packet);
}
}
safe_delete(update_packets_);
// Get new update packets
update_packets_ = world_list.GetServerListUpdate(version_);
}
num_updates_++;
}
else
{
// Send update packets in sequence
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;
}
}
}
}
// Process incoming packets
EQApplicationPacket* app = nullptr;
while ((app = eqnc_->PopPacket()))
{
switch (app->GetOpcode())
{
case OP_LoginRequestMsg:
{
ProcessLoginRequest(app);
break;
}
case OP_KeymapLoadMsg:
{
// Keymap message - currently unused
break;
}
case OP_AllWSDescRequestMsg:
{
ProcessWorldListRequest();
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:
{
// Character description request - handled elsewhere
break;
}
case OP_CreateCharacterRequestMsg:
{
ProcessCharacterCreation(app);
break;
}
case OP_PlayCharacterRequestMsg:
{
ProcessPlayCharacter(app);
break;
}
case OP_DeleteCharacterRequestMsg:
{
ProcessDeleteCharacter(app);
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());
}
break;
}
}
delete app;
}
}
// Check if connection is still active
if (!eqnc_->CheckActive())
{
return false;
}
return true;
}
/**
* @brief Processes a login request packet
* @param app The login request packet
*/
void Client::ProcessLoginRequest(EQApplicationPacket* app)
{
DumpPacket(app);
// Try loading with version 1 structure
auto packet = std::unique_ptr<PacketStruct>(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_);
// Check if we need to try the newer structure
if (version_ == 0 || EQOpcodeManager.count(GetOpcodeVersion(version_)) == 0)
{
packet.reset(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_);
// Verify client version is supported
if (EQOpcodeManager.count(GetOpcodeVersion(version_)) == 0)
{
LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version_);
SendLoginDenied();
return;
}
// Process login credentials
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");
// Load account from database
LoginAccount* acct = database.LoadAccount(username.data.c_str(),
password.data.c_str(),
net.IsAllowingAccountCreation());
// Check for duplicate login
if (acct)
{
Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID());
if (otherclient)
{
// Kick the previous client (might be a ghost)
otherclient->getConnection()->SendDisconnect();
}
}
// Process successful 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.",
username.data.c_str());
needs_world_list_ = true;
SetLoginAccount(acct);
SendLoginAccepted();
}
else
{
// Login failed
if (username.size > 0)
{
LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!",
username.data.c_str());
}
else
{
LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!");
}
SendLoginDenied();
}
}
else
{
std::cout << "Error bad version: " << version_ << std::endl;
SendLoginDeniedBadVersion();
}
}
else
{
std::cout << "Error loading LS_LoginRequest packet" << std::endl;
}
}
/**
* @brief Processes a world list request
*/
void Client::ProcessWorldListRequest()
{
SendWorldList();
needs_world_list_ = false;
if (!sent_character_list_)
{
database.LoadCharacters(GetLoginAccount(), GetVersion());
sent_character_list_ = true;
}
SendCharList();
}
/**
* @brief Processes a character creation request
* @param app The character creation packet
*/
void Client::ProcessCharacterCreation(EQApplicationPacket* app)
{
auto packet = std::unique_ptr<PacketStruct>(
configReader.getStruct("CreateCharacter", GetVersion()));
DumpPacket(app);
// Start play wait timer
playWaitTimer_ = std::make_unique<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());
// Find the target world server
LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id"));
if (!world_server)
{
DumpPacket(app->pBuffer, app->size);
std::cout << GetAccountName() << " attempted creation of character with an invalid server id of: "
<< packet->getType_int32_ByName("server_id") << "\n";
return;
}
// Store creation request and forward to world server
createRequest_ = packet.release();
auto outpack = std::make_unique<ServerPacket>(ServerOP_CharacterCreate,
app->size + sizeof(std::int16_t));
std::int16_t out_version = GetVersion();
std::memcpy(outpack->pBuffer, &out_version, sizeof(std::int16_t));
std::memcpy(outpack->pBuffer + sizeof(std::int16_t), app->pBuffer, app->size);
// Adjust pointer based on version
unsigned char* tmp = outpack->pBuffer;
if (out_version <= 283)
{
tmp += 2;
}
else if (out_version == 373)
{
tmp += 6;
}
else
{
tmp += 7;
}
// Add account ID
std::int32_t account_id = GetAccountID();
std::memcpy(tmp, &account_id, sizeof(std::int32_t));
world_server->SendPacket(outpack.get());
}
else
{
LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!",
GetAccountName());
}
}
/**
* @brief Processes a play character request
* @param app The play character packet
*/
void Client::ProcessPlayCharacter(EQApplicationPacket* app)
{
std::int32_t char_id = 0;
std::int32_t server_id = 0;
auto request = std::unique_ptr<PacketStruct>(
configReader.getStruct("LS_PlayRequest", GetVersion()));
if (request && request->LoadPacketData(app->pBuffer, app->size))
{
char_id = request->getType_int32_ByName("char_id");
// Handle version-specific server ID retrieval
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");
}
// Find world server and character
LWorld* world = world_list.FindByID(server_id);
std::string name = database.GetCharacterName(char_id, server_id, GetAccountID());
if (world && !name.empty())
{
pending_play_char_id_ = char_id;
// Create user to world request
auto outpack = std::make_unique<ServerPacket>(ServerOP_UsertoWorldReq,
sizeof(UsertoWorldRequest_Struct));
auto* req = reinterpret_cast<UsertoWorldRequest_Struct*>(outpack->pBuffer);
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
// Set IP address
struct in_addr in;
in.s_addr = GetIP();
std::strcpy(req->ip_address, inet_ntoa(in));
world->SendPacket(outpack.get());
// Start play wait timer
playWaitTimer_.reset();
playWaitTimer_ = std::make_unique<Timer>(5000);
playWaitTimer_->Start();
}
else
{
std::cout << GetAccountName() << " sent invalid Play Request" << std::endl;
SendPlayFailed(PLAY_ERROR_PROBLEM);
DumpPacket(app);
}
}
}
/**
* @brief Processes a delete character request
* @param app The delete character packet
*/
void Client::ProcessDeleteCharacter(EQApplicationPacket* app)
{
auto request = std::unique_ptr<PacketStruct>(
configReader.getStruct("LS_DeleteCharacterRequest", GetVersion()));
auto response = std::unique_ptr<PacketStruct>(
configReader.getStruct("LS_DeleteCharacterResponse", GetVersion()));
if (request && response && request->LoadPacketData(app->pBuffer, app->size))
{
EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name");
std::int32_t acct_id = GetAccountID();
std::int32_t char_id = request->getType_int32_ByName("char_id");
std::int32_t server_id = request->getType_int32_ByName("server_id");
// Verify deletion
if (database.VerifyDelete(acct_id, char_id, name.data.c_str()))
{
response->setDataByName("response", 1);
GetLoginAccount()->removeCharacter(name.data.c_str(), GetVersion());
// Notify world server
LWorld* world_server = world_list.FindByID(server_id);
if (world_server != nullptr)
{
world_server->SendDeleteCharacter(char_id, acct_id);
}
}
else
{
response->setDataByName("response", 0);
}
// Build response
response->setDataByName("server_id", server_id);
response->setDataByName("char_id", char_id);
response->setDataByName("account_id", account_id_);
response->setMediumStringByName("name", name.data.c_str());
response->setDataByName("max_characters", 10);
EQ2Packet* outapp = response->serialize();
QueuePacket(outapp);
// Refresh character list
SendCharList();
}
}
/**
* @brief Saves client error logs to the database
* @param app Packet containing error data
* @param type Type of error log
* @param version Client version
*/
void Client::SaveErrorsToDB(EQApplicationPacket* app, const char* type, std::int32_t version)
{
std::int32_t size = 0;
z_stream zstream{};
// Extract size and data based on version
if (version >= 546)
{
std::memcpy(&size, app->pBuffer + sizeof(std::int32_t), sizeof(std::int32_t));
zstream.next_in = app->pBuffer + 8;
zstream.avail_in = app->size - 8;
}
else
{
// Box set version
size = 0xFFFF;
zstream.next_in = app->pBuffer + 2;
zstream.avail_in = app->size - 2;
}
size++;
auto message = std::make_unique<char[]>(size);
std::memset(message.get(), 0, size);
// Setup decompression
zstream.next_out = reinterpret_cast<Bytef*>(message.get());
zstream.avail_out = size;
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
// Decompress message
int zerror = inflateInit(&zstream);
if (zerror != Z_OK)
{
return;
}
zerror = inflate(&zstream, 0);
inflateEnd(&zstream);
// Save to database if message is valid
if (message && std::strlen(message.get()) > 0)
{
database.SaveClientLog(type, message.get(), GetLoginAccount()->getLoginName(), GetVersion());
}
}
/**
* @brief Handles character creation approval from world server
* @param server_id World server ID
* @param char_id New character ID
*/
void Client::CharacterApproved(std::int32_t server_id, std::int32_t char_id)
{
if (createRequest_ && server_id == createRequest_->getType_int32_ByName("server_id"))
{
LWorld* world_server = world_list.FindByID(server_id);
if (!world_server)
{
return;
}
// Send success response
auto packet = std::unique_ptr<PacketStruct>(
configReader.getStruct("LS_CreateCharacterReply", GetVersion()));
if (packet)
{
packet->setDataByName("account_id", GetAccountID());
packet->setDataByName("unknown", 0xFFFFFFFF);
packet->setDataByName("response", CREATESUCCESS_REPLY);
packet->setMediumStringByName("name",
createRequest_->getType_EQ2_16BitString_ByName("name").data.c_str());
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
// Save character to database
database.SaveCharacter(createRequest_, GetLoginAccount(), char_id, GetVersion());
// Refresh character list
database.LoadCharacters(GetLoginAccount(), GetVersion());
SendCharList();
// Auto-enter world for older clients
if (GetVersion() <= 561)
{
pending_play_char_id_ = char_id;
auto outpack = std::make_unique<ServerPacket>(ServerOP_UsertoWorldReq,
sizeof(UsertoWorldRequest_Struct));
auto* req = reinterpret_cast<UsertoWorldRequest_Struct*>(outpack->pBuffer);
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
std::strcpy(req->ip_address, inet_ntoa(in));
world_server->SendPacket(outpack.get());
}
}
}
else
{
std::cout << GetAccountName() << " received invalid CharacterApproval from server: "
<< server_id << std::endl;
}
safe_delete(createRequest_);
}
/**
* @brief Handles character creation rejection
* @param reason_number Rejection reason code
*/
void Client::CharacterRejected(std::int8_t reason_number)
{
auto packet = std::unique_ptr<PacketStruct>(
configReader.getStruct("LS_CreateCharacterReply", GetVersion()));
if (createRequest_ && packet)
{
packet->setDataByName("account_id", GetAccountID());
packet->setDataByName("response", reason_number);
packet->setMediumStringByName("name", "");
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
}
safe_delete(createRequest_);
}
/**
* @brief Sends the character list to the client
*/
void Client::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);
}
/**
* @brief Sends login denied due to bad version
*/
void Client::SendLoginDeniedBadVersion()
{
auto app = std::make_unique<EQ2Packet>(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
auto* ls_response = reinterpret_cast<LS_LoginResponse*>(app->pBuffer);
ls_response->reply_code = 6; // Version mismatch
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app.release());
StartDisconnectTimer();
}
/**
* @brief Sends login denied message
*/
void Client::SendLoginDenied()
{
auto app = std::make_unique<EQ2Packet>(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
auto* ls_response = reinterpret_cast<LS_LoginResponse*>(app->pBuffer);
// Reply codes for AoM:
// 1 = Invalid username or password
// 2 = Account already playing
// 6 = Version mismatch
// 7 = No scheduled playtimes
// 8 = Missing features
// 11 = Build mismatch
// 12 = Must update password
ls_response->reply_code = 1;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app.release());
StartDisconnectTimer();
}
/**
* @brief Sends login accepted message
* @param account_id Account ID
* @param login_response Login response code
*/
void Client::SendLoginAccepted(std::int32_t account_id, std::int8_t login_response)
{
auto packet = std::unique_ptr<PacketStruct>(
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);
// Set subscription and expansion flags
packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
packet->setDataByName("race_flag", 0x1FFFFF);
packet->setDataByName("class_flag", 0x7FFFFFE);
packet->setMediumStringByName("username", GetAccountName());
packet->setMediumStringByName("password", GetAccountName());
// Expansion flags
packet->setDataByName("unknown5", net.GetExpansionFlag());
packet->setDataByName("unknown6", 0xFF);
packet->setDataByName("unknown6", 0xFF, 1);
packet->setDataByName("unknown6", 0xFF, 2);
// Class access
packet->setDataByName("unknown10", 0xFF);
// Race and city flags
packet->setDataByName("unknown7", net.GetEnabledRaces());
packet->setDataByName("unknown7a", 0xEE);
packet->setDataByName("unknown8", net.GetCitiesFlag(), 1);
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
}
}
/**
* @brief Sends the world server list
*/
void Client::SendWorldList()
{
EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin_, version_);
EQ2Packet* dupe = pack->Copy();
DumpPacket(dupe->pBuffer, dupe->size);
QueuePacket(dupe);
// Trigger special client code path
SendLoginAccepted(0, 10);
}
/**
* @brief Queues a packet for sending
* @param app Packet to queue
*/
void Client::QueuePacket(EQ2Packet* app)
{
eqnc_->EQ2QueuePacket(app);
}
/**
* @brief Handles world server response for play request
* @param worldid World server ID
* @param response Response code
* @param ip_address World server IP
* @param port World server port
* @param access_key Access key
*/
void Client::WorldResponse(std::int32_t worldid, std::int8_t response,
char* ip_address, std::int32_t port, std::int32_t access_key)
{
LWorld* world = world_list.FindByID(worldid);
if (world == nullptr)
{
FatalError(0);
return;
}
if (response != 1)
{
// Handle character not loaded error
if (response == PLAY_ERROR_CHAR_NOT_LOADED)
{
std::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(pending_play_char_name.c_str(), GetVersion());
}
}
FatalError(response);
return;
}
// Send successful play response
auto response_packet = std::unique_ptr<PacketStruct>(
configReader.getStruct("LS_PlayResponse", GetVersion()));
if (response_packet)
{
playWaitTimer_.reset();
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);
}
}
/**
* @brief Sends a fatal error to the client
* @param response Error response code
*/
void Client::FatalError(std::int8_t response)
{
playWaitTimer_.reset();
SendPlayFailed(response);
}
/**
* @brief Sends play failed message
* @param response Failure reason code
*/
void Client::SendPlayFailed(std::int8_t response)
{
auto response_packet = std::unique_ptr<PacketStruct>(
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);
}
}
/**
* @brief Starts the disconnect timer
*/
void Client::StartDisconnectTimer()
{
if (!disconnectTimer)
{
disconnectTimer = std::make_unique<Timer>(1000);
disconnectTimer->Start();
}
}
// ClientList implementation
/**
* @brief Adds a client to the list
* @param client Client to add
*/
void ClientList::Add(Client* client)
{
std::lock_guard<std::mutex> lock(mutex_);
client_list_[client] = true;
}
/**
* @brief Finds a client by IP and port
* @param ip Client IP
* @param port Client port
* @return Client pointer or nullptr
*/
Client* ClientList::Get(std::int32_t ip, std::int16_t port)
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [client, active] : client_list_)
{
if (client->GetIP() == ip && client->GetPort() == port)
{
return client;
}
}
return nullptr;
}
/**
* @brief Finds clients waiting for character creation
*/
void ClientList::FindByCreateRequest()
{
Client* found_client = nullptr;
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [client, active] : client_list_)
{
if (client->AwaitingCharCreationRequest())
{
if (!found_client)
{
found_client = client;
}
else
{
// More than one waiting, don't send rejection
found_client = nullptr;
break;
}
}
}
}
if (found_client)
{
found_client->CharacterRejected(UNKNOWNERROR_REPLY);
}
}
/**
* @brief Finds a client by login server account ID
* @param lsaccountid Account ID
* @return Client pointer or nullptr
*/
Client* ClientList::FindByLSID(std::int32_t lsaccountid)
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [client, active] : client_list_)
{
if (client->GetAccountID() == lsaccountid)
{
return client;
}
}
return nullptr;
}
/**
* @brief Sends a packet to all connected clients
* @param app Packet to send
*/
void ClientList::SendPacketToAllClients(EQ2Packet* app)
{
{
std::lock_guard<std::mutex> lock(mutex_);
if (!client_list_.empty())
{
for (auto& [client, active] : client_list_)
{
client->QueuePacket(app->Copy());
}
}
}
safe_delete(app);
}
/**
* @brief Processes all clients in the list
*/
void ClientList::Process()
{
std::vector<Client*> erase_list;
// Process clients and identify disconnected ones
{
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [client, active] : client_list_)
{
if (!client->Process())
{
erase_list.push_back(client);
}
}
}
// Remove disconnected clients
if (!erase_list.empty())
{
std::lock_guard<std::mutex> lock(mutex_);
for (Client* client : erase_list)
{
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);
delete client;
}
}
}