Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ log/
logs/
vcpkg/
perl/

compile_commands.json
.idea/*
*cbp

Expand Down
2 changes: 2 additions & 0 deletions common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,9 @@ SET(common_headers
server_event_scheduler.h
serverinfo.h
servertalk.h
queue_packets.h
server_reload_types.h
queue_packets.h
sha1.h
shareddb.h
skills.h
Expand Down
5 changes: 4 additions & 1 deletion common/net/servertalk_server_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet
req.PutUInt32(i, req_in->FromID); i += 4;
req.PutUInt32(i, req_in->ToID); i += 4;
req.PutData(i, req_in->IPAddr, 64); i += 64;

req.PutData(i, req_in->forum_name, 31); i += 31;
req.PutData(i, req_in->client_key, 31); i += 31;

EQ::Net::DynamicPacket out;
out.PutUInt16(0, ServerOP_UsertoWorldResp);
out.PutUInt16(2, req.Length() + 4);
Expand All @@ -56,6 +58,7 @@ void EQ::Net::ServertalkServerConnection::Send(uint16_t opcode, EQ::Net::Packet
req.PutUInt16(i, req_in->is_world_admin); i += 2;
req.PutUInt32(i, req_in->ip_address); i += 4;
req.PutUInt8(i, req_in->is_client_from_local_network); i += 1;
req.PutData(i, req_in->forum_name, 31); i += 31;

EQ::Net::DynamicPacket out;
out.PutUInt16(0, ServerOP_LSClientAuth);
Expand Down
54 changes: 54 additions & 0 deletions common/queue_packets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef EQEMU_QUEUE_PACKETS_H
#define EQEMU_QUEUE_PACKETS_H

#include "types.h"

#pragma pack(1)

// Queue-related opcodes (separate from main servertalk.h to avoid full rebuilds)
#define ServerOP_QueueAutoConnect 0x4013
#define ServerOP_QueueDirectUpdate 0x4016 // World->Login: Send pre-built server list packet to specific client
#define ServerOP_QueueBatchUpdate 0x4018 // World->Login: Batch queue position updates for multiple clients
#define ServerOP_RemoveFromQueue 0x4019 // Login->World: Remove single disconnected client from queue

// Queue-related packet structures
struct ServerQueueAutoConnect_Struct {
uint32 loginserver_account_id;
uint32 world_id;
uint32 from_id;
uint32 to_id;
uint32 ip_address;
char ip_addr_str[64];
char forum_name[31];
char client_key[11]; // Unique key of the client that was authorized (10 chars + null terminator)
};

struct ServerQueuePositionQuery_Struct {
uint32 loginserver_account_id; // Which account to query position for
};

struct ServerQueuePositionResponse_Struct {
uint32 loginserver_account_id; // Account this response is for
uint32 queue_position; // 0 = not queued, >0 = position in queue
};

struct ServerQueueDirectUpdate_Struct {
uint32 ls_account_id; // Account identifier for lookup
uint32 ip_address;
uint32 queue_position; // New queue position (0 = not queued)
uint32 estimated_wait; // Estimated wait time in seconds
};

struct ServerQueueBatchUpdate_Struct {
uint32 update_count; // Number of queue updates in this batch
// Followed by update_count instances of ServerQueueDirectUpdate_Struct
// Use: ServerQueueDirectUpdate_Struct* updates = (ServerQueueDirectUpdate_Struct*)((char*)packet_data + sizeof(ServerQueueBatchUpdate_Struct));
};

struct ServerQueueRemoval_Struct {
uint32 ls_account_id; // Account to remove from queue when client disconnects
};

#pragma pack()

#endif // EQEMU_QUEUE_PACKETS_H
10 changes: 10 additions & 0 deletions common/ruletypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ RULE_BOOL( World, DontBootDynamics, false, "If true, dynamic zones will not boot
RULE_BOOL(World, EnableDevTools, true, "Enable or Disable the Developer Tools globally (Most of the time you want this enabled)")
RULE_BOOL(World, UseOldShadowKnightClassExport, true, "Disable to have Shadowknight show as Shadow Knight (live-like)")
RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to have this be used instead of variables table 'motd' value")
RULE_INT(World, TestPopulationOffset, 0, "Test population offset for queue testing")
RULE_INT(World, IPDatabaseSyncInterval, 300, "Interval in seconds between database synchronization of IP reservation data")
RULE_INT(World, MaxPlayersOnline, 1200, "Maximum number of players allowed online before queue is activated")
RULE_BOOL(World, EnableQueue, true, "Enable the login queue system when server population is at capacity")
RULE_BOOL(World, QueueBypassGMLevel, true, "Allow GMs (status >= 80) to bypass the queue")
RULE_BOOL(World, EnableQueueLogging, true, "Enable detailed logging for queue system operations")
RULE_BOOL(World, FreezeQueue, false, "Freeze queue advancement - players remain at current positions")
RULE_BOOL(World, EnableQueuePersistence, true, "Enable saving queue state to database for crash recovery")
RULE_INT(World, DefaultGracePeriod, 60, "Default grace period in seconds for IP reservations when not specified")
RULE_INT(World, IPCleanupInterval, 5, "Interval in seconds between IP reservation cleanup cycles")
RULE_CATEGORY_END()

RULE_CATEGORY( Zone )
Expand Down
1 change: 1 addition & 0 deletions common/servertalk.h
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ struct UsertoWorldRequest {
uint32 ToID;
char IPAddr[64];
char forum_name[31];
char client_key[31];
};

struct UsertoWorldResponse {
Expand Down
4 changes: 3 additions & 1 deletion loginserver/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Client::Client(std::shared_ptr<EQStreamInterface> c, LSClientVersion v)
m_sent_session_info = false;
m_play_server_id = 0;
m_play_sequence_id = 0;
m_queue_server_id = 0;
m_queue_position = 0;
}

bool Client::Process()
Expand Down Expand Up @@ -358,7 +360,7 @@ void Client::Handle_Play(const char* data)
}

if (data) {
server.server_manager->SendUserToWorldRequest(data, m_account_id, m_connection->GetRemoteIP());
server.server_manager->SendUserToWorldRequest(data, m_account_id, m_connection->GetRemoteIP(), false, m_key);
}
}

Expand Down
22 changes: 21 additions & 1 deletion loginserver/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,23 @@ class Client
*/
unsigned int GetMacClientVersion() const { return m_client_mac_version; }


/**
* Queue position management
*/
void SetQueuePosition(uint32 server_id, uint32 position) {
m_queue_server_id = server_id;
m_queue_position = position;
}

uint32 GetQueueServerID() const { return m_queue_server_id; }
uint32 GetQueuePosition() const { return m_queue_position; }

void ClearQueuePosition() {
m_queue_server_id = 0;
m_queue_position = 0;
}

bool HasQueuePosition() const { return m_queue_server_id > 0 && m_queue_position > 0; }

private:
Saltme m_salt;
Expand All @@ -157,6 +173,10 @@ class Client
unsigned int m_play_server_id;
unsigned int m_play_sequence_id;
std::string m_key;

// Queue position tracking for this client
uint32 m_queue_server_id;
uint32 m_queue_position;
};

#endif
Expand Down
18 changes: 18 additions & 0 deletions loginserver/client_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,16 @@ void ClientManager::ProcessDisconnect()
std::shared_ptr<EQStreamInterface> c = (*iter)->GetConnection();
if (c->CheckState(CLOSED)) {
c->ReleaseFromUse();
// Get client account ID before deletion for queue removal
uint32 account_id = (*iter)->GetAccountID();

LogInfo("Client disconnected from the server, removing client.");

// Remove from queue on all world servers if account ID is valid
if ((*iter)->HasQueuePosition() && account_id > 0 && server.server_manager) {
server.server_manager->RemovePlayerFromAllQueues(account_id);
}

delete (*iter);
iter = clients.erase(iter);
}
Expand Down Expand Up @@ -163,3 +172,12 @@ Client *ClientManager::GetClient(unsigned int account_id)
return cur;
}

Client *ClientManager::GetClientByKey(const std::string& key)
{
for (Client* client : clients) {
if (client && client->GetKey() == key) {
return client;
}
}
return nullptr;
}
6 changes: 6 additions & 0 deletions loginserver/client_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ class ClientManager
* Gets a client (if exists) by their account id.
*/
Client *GetClient(unsigned int account_id);

/**
* Gets a client by their unique key (for multi-client authorization matching).
*/
Client *GetClientByKey(const std::string& key);

private:

/**
Expand Down
2 changes: 1 addition & 1 deletion loginserver/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Database::Database(std::string user, std::string pass, std::string host, std::st
exit(1);
}
else {
Log(Logs::General, Logs::Status, "Using database '%s' at %s:%d", m_database, host, port);
Log(Logs::General, Logs::Status, "Using database '%s' at %s:%d", m_database, host.c_str(), atoi(port.c_str()));
}
}

Expand Down
3 changes: 2 additions & 1 deletion loginserver/login_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ struct LoginServer
Options options;
ServerManager *server_manager;
ClientManager *client_manager;

// TODO: Revist auto-connect logic
// void ProcessQueueAutoConnect(uint16_t opcode, const EQ::Net::Packet& p);
};

#endif
Expand Down
63 changes: 61 additions & 2 deletions loginserver/server_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,22 @@

#include "../common/eqemu_logsys.h"
#include "../common/ip_util.h"
#include <fmt/format.h>
#ifndef _WINDOWS
#include <arpa/inet.h>
#endif
#include <vector>
#include <tuple>
#include <map>
#include "../common/queue_packets.h"

extern EQEmuLogSys LogSys;
extern LoginServer server;
extern bool run_server;

// Global cache for server effective population by server ID
extern std::map<uint32, uint32> g_server_populations;

ServerManager::ServerManager()
{
int listen_port = server.config.GetVariableInt("client_configuration", "listen_port", 5998);
Expand Down Expand Up @@ -191,6 +202,13 @@ EQApplicationPacket* ServerManager::CreateServerListPacket(Client* c)
slsf->flags = 0x1;
slsf->worldid = (*iter)->GetServerId();
slsf->usercount = (*iter)->GetStatus();
// Check if client has a queue position for this server - show queue position
if (c->HasQueuePosition() && c->GetQueueServerID() == (*iter)->GetServerId()) {
slsf->usercount = c->GetQueuePosition();
} else if (g_server_populations.find((*iter) ->GetServerId()) != g_server_populations.end()) /* INF Only uses it if it's initated so won't impact TAKP */{
slsf->usercount = g_server_populations[(*iter)->GetServerId()];
}

data_ptr += sizeof(ServerListServerFlags_Struct);
++iter;
}
Expand All @@ -199,7 +217,7 @@ EQApplicationPacket* ServerManager::CreateServerListPacket(Client* c)
return outapp;
}

void ServerManager::SendUserToWorldRequest(const char* server_id, unsigned int client_account_id, uint32 ip)
void ServerManager::SendUserToWorldRequest(const char* server_id, unsigned int client_account_id, uint32 ip, bool is_auto_connect, const std::string& client_key)
{
auto iter = m_world_servers.begin();
bool found = false;
Expand All @@ -214,6 +232,15 @@ void ServerManager::SendUserToWorldRequest(const char* server_id, unsigned int c

utwr->lsaccountid = client_account_id;
utwr->ip = ip;

// Encode auto-connect status in FromID field
// 0 = manual PLAY request, 1 = auto-connect request
utwr->FromID = is_auto_connect ? 1 : 0;
utwr->ToID = 0; // Not used

strncpy(utwr->client_key, client_key.c_str(), sizeof(utwr->client_key) - 1);
utwr->client_key[sizeof(utwr->client_key) - 1] = '\0';

(*iter)->GetConnection()->Send(ServerOP_UsertoWorldReq, outapp);
found = true;

Expand Down Expand Up @@ -266,4 +293,36 @@ void ServerManager::DestroyServerByName(std::string l_name, std::string s_name,

++iter;
}
}
}

void ServerManager::RemovePlayerFromAllQueues(uint32 ls_account_id)
{
if (ls_account_id == 0) {
return;
}

// Create removal packet once - reuse for all world servers
auto removal_pack = new ServerPacket(ServerOP_RemoveFromQueue, sizeof(ServerQueueRemoval_Struct));
ServerQueueRemoval_Struct* removal = (ServerQueueRemoval_Struct*)removal_pack->pBuffer;
removal->ls_account_id = ls_account_id;

uint32 packets_sent = 0;

// Send removal packet to all connected world servers
auto iter = m_world_servers.begin();
while (iter != m_world_servers.end()) {
if ((*iter)->GetConnection()) {
// Send to this world server
(*iter)->GetConnection()->SendPacket(removal_pack);
packets_sent++;
}
++iter;
}

// Clean up packet after sending to all servers
delete removal_pack;

if (packets_sent > 0) {
LogInfo("Sent queue removal for disconnected client [{}] to [{}] world servers", ls_account_id, packets_sent);
}
}
7 changes: 6 additions & 1 deletion loginserver/server_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ServerManager
/**
* Sends a request to world to see if the client is banned or suspended.
*/
void SendUserToWorldRequest(const char* ServerIP, unsigned int client_account_id, uint32 ip);
void SendUserToWorldRequest(const char* ServerIP, unsigned int client_account_id, uint32 ip, bool is_auto_connect = false, const std::string& client_key = "");

/**
* Creates a server list packet for the older client.
Expand All @@ -62,6 +62,11 @@ class ServerManager
*/
void DestroyServerByName(std::string l_name, std::string s_name, WorldServer *ignore = nullptr);

/**
* Removes a player from all world server queues when they disconnect
*/
void RemovePlayerFromAllQueues(uint32 ls_account_id);

private:
/**
* Retrieves a server(if exists) by ip address
Expand Down
Loading