Files
2026-03-02 12:37:07 +00:00

422 lines
9.9 KiB
C++

// ╔═╗ ╦ ╦ ╔═╗ ╔╦╗ ╔═╗ ╦ ╔═╗ ╔╗╔ ╔═╗
// ║ ╠═╣ ╠═╣ ║║║ ╠═╝ ║ ║ ║ ║║║ ╚═╗
// ╚═╝ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╚═╝ ╝╚╝ ╚═╝
// ╦ ╔═╗╔╗ ╔╗ ╦ ╦ ╔═╗╔═╗╦═╗╦ ╦╔═╗╦═╗
// ║ ║ ║╠╩╗╠╩╗╚╦╝ ╚═╗║╣ ╠╦╝╚╗╔╝║╣ ╠╦╝
// ╩═╝╚═╝╚═╝╚═╝ ╩ ╚═╝╚═╝╩╚═ ╚╝ ╚═╝╩╚═
#include "Lobby Server/LobbyServer.hpp"
#include "Game/RealmUserManager.hpp"
#include "Network/Events.hpp"
#include "configuration.hpp"
#include "logging.hpp"
LobbyServer::LobbyServer()
{
m_running = false;
m_conSocket.reset();
m_rtaSocket.reset();
m_gatewaySockets.clear();
m_clientSockets.clear();
m_recvBuffer.resize( 1024 );
}
LobbyServer::~LobbyServer()
{
Log::Info( "Lobby Server stopped." );
for( auto &socket : m_clientSockets )
{
if( socket->fd != INVALID_SOCKET )
{
closesocket( socket->fd );
}
}
for( auto &socket : m_gatewaySockets )
{
if( socket->fd != INVALID_SOCKET )
{
closesocket( socket->fd );
}
}
}
void LobbyServer::Start( std::string ip )
{
// Champions of Norrath Gateway Sockets
for( int i = 0; i < 8; i++ )
{
auto gatewaySocket = OpenListenerSocket( ip, 40800 + i, RealmGameType::CHAMPIONS_OF_NORRATH );
if( nullptr == gatewaySocket ) return;
gatewaySocket->flag.is_gateway = true;
m_gatewaySockets.push_back( gatewaySocket );
}
// Return to Arms Gateway Sockets
for( int i = 0; i < 8; i++ )
{
auto gatewaySocket = OpenListenerSocket( ip, 40810 + i, RealmGameType::RETURN_TO_ARMS );
if( nullptr == gatewaySocket ) return;
gatewaySocket->flag.is_gateway = true;
m_gatewaySockets.push_back( gatewaySocket );
}
m_conSocket = OpenListenerSocket( ip, Config::con_lobby_port, RealmGameType::CHAMPIONS_OF_NORRATH );
m_rtaSocket = OpenListenerSocket( ip, Config::rta_lobby_port, RealmGameType::RETURN_TO_ARMS );
m_running = true;
m_thread = std::thread( &LobbyServer::Run, this );
Log::Info( "Lobby Server started" );
}
void LobbyServer::Stop()
{
Log::Info( "Stopping Lobby Server..." );
for( auto &client : m_clientSockets )
{
client->send( NotifyForcedLogout() );
}
m_running = false;
if( m_thread.joinable() )
{
m_thread.join();
}
}
sptr_socket LobbyServer::OpenListenerSocket( std::string ip, int32_t port, RealmGameType type )
{
SOCKET sock = ::WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED );
if( sock == INVALID_SOCKET )
{
Log::Error( "WSASocket() failed on port {}", port );
return nullptr;
}
sockaddr_in service{};
service.sin_family = AF_INET;
service.sin_port = htons( port );
if( ip == "0.0.0.0" )
{
service.sin_addr.s_addr = INADDR_ANY;
}
else if( InetPtonA( AF_INET, ip.c_str(), &service.sin_addr) != 1 )
{
Log::Error( "Invalid IP address format: {}", ip );
closesocket( sock );
return nullptr;
}
if( bind( sock, reinterpret_cast< SOCKADDR * >( &service ), sizeof( service ) ) == SOCKET_ERROR )
{
Log::Error( "bind() failed on port {}", port );
closesocket( sock );
return nullptr;
}
if( listen( sock, SOMAXCONN ) == SOCKET_ERROR )
{
Log::Error( "listen() failed on port {}", port );
closesocket( sock );
return nullptr;
}
auto realmSocket = std::make_shared< RealmSocket >();
realmSocket->fd = sock;
realmSocket->remote_ip = ip;
realmSocket->remote_port = port;
realmSocket->gameType = type;
realmSocket->flag.is_listener = true;
Log::Info( "Socket Opened on {}:{}", ip, port );
return realmSocket;
}
void LobbyServer::Run()
{
FD_SET readSet;
FD_SET writeSet;
timeval timeout = { 0, 1000 };
while( m_running )
{
FD_ZERO( &readSet );
FD_ZERO( &writeSet );
FD_SET( m_conSocket->fd, &readSet );
FD_SET( m_rtaSocket->fd, &readSet );
for( const auto &sock : m_gatewaySockets )
{
if( sock != nullptr && sock->fd != INVALID_SOCKET )
{
FD_SET( sock->fd, &readSet );
}
}
CheckSocketProblem();
for( const auto &client : m_clientSockets )
{
FD_SET( client->fd, &readSet );
FD_SET( client->fd, &writeSet );
}
auto result = select( 0, &readSet, &writeSet, NULL, &timeout );
if( result == SOCKET_ERROR )
{
std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
continue;
}
for( auto &sock : m_gatewaySockets )
{
if( sock == nullptr || sock->fd == INVALID_SOCKET )
continue;
if( FD_ISSET( sock->fd, &readSet ) )
{
AcceptConnection( sock );
}
}
if( FD_ISSET( m_conSocket->fd, &readSet ) )
{
AcceptConnection( m_conSocket );
}
if( FD_ISSET( m_rtaSocket->fd, &readSet ) )
{
AcceptConnection( m_rtaSocket );
}
for( auto &client : m_clientSockets )
{
if( FD_ISSET( client->fd, &readSet ) )
{
ReadSocket( client );
}
if( FD_ISSET( client->fd, &writeSet ) )
{
WriteSocket( client );
}
}
}
}
void LobbyServer::CheckSocketProblem()
{
auto now = std::chrono::steady_clock::now();
for( auto it = m_clientSockets.begin(); it != m_clientSockets.end(); )
{
auto &socket = *it;
auto elapsed = std::chrono::duration_cast< std::chrono::seconds >( now - socket->last_recv_time );
// Check for client timeouts
if( elapsed.count() > 30 )
{
socket->flag.disconnected_forced = true;
Log::Info( "[LOBBY] Client Timeout : ({})", socket->remote_ip );
}
// Check if we're waiting to disconnect after sending buffered data
if( socket->flag.disconnected_wait && socket->m_pendingWriteBuffer.empty() )
{
socket->flag.disconnected_forced = true;
}
if( socket->flag.disconnected_forced )
{
UserManager::Get().RemoveUser( socket );
Log::Info( "[LOBBY] Client Disconnected : ({})", socket->remote_ip );
it = m_clientSockets.erase( it );
}
else
{
++it;
}
}
}
void LobbyServer::AcceptConnection( sptr_socket srcSocket )
{
sockaddr_in clientInfo{};
int32_t addrSize = sizeof( clientInfo );
SOCKET clientSocket = accept( srcSocket->fd, ( SOCKADDR * )&clientInfo, &addrSize );
if( clientSocket == INVALID_SOCKET )
{
Log::Error( "accept() failed" );
return;
}
auto new_socket = std::make_shared< RealmSocket >();
auto gameType = srcSocket->gameType;
new_socket->fd = clientSocket;
new_socket->remote_addr = clientInfo;
new_socket->remote_ip = Util::IPFromAddr( clientInfo );
new_socket->remote_port = ntohs( clientInfo.sin_port );
new_socket->gameType = gameType;
m_clientSockets.push_back( new_socket );
if( !srcSocket->flag.is_gateway )
{
UserManager::Get().CreateUser( new_socket, gameType );
}
Log::Info( "New Client Connected : ({}) From Port {}", new_socket->remote_ip, srcSocket->remote_port );
}
void LobbyServer::ReadSocket( sptr_socket socket )
{
if( socket->flag.disconnected_forced )
{
return;
}
const auto bytesReceived = recv( socket->fd, reinterpret_cast< char * >( m_recvBuffer.data() ), static_cast< int >( m_recvBuffer.size() ), 0 );
if( bytesReceived == SOCKET_ERROR )
{
Log::Info( "Socket Error [{}].", WSAGetLastError() );
socket->flag.disconnected_forced = true;
return;
}
if( bytesReceived == 0 )
{
socket->flag.disconnected_forced = true;
return;
}
socket->last_recv_time = std::chrono::steady_clock::now();
// Append received data to socket's pending buffer
socket->m_pendingReadBuffer.insert(
socket->m_pendingReadBuffer.end(),
m_recvBuffer.begin(),
m_recvBuffer.begin() + bytesReceived
);
// Process packets in the buffer
while( socket->m_pendingReadBuffer.size() >= 4 )
{
int32_t packetSize = ntohl( *reinterpret_cast< const int32_t * >( &socket->m_pendingReadBuffer[ 0 ] ) );
if( packetSize <= 0 || packetSize > 2048 )
{
Log::Error( "Invalid packet size: {}. Disconnecting client.", packetSize );
socket->flag.disconnected_wait = true;
break;
}
if( socket->m_pendingReadBuffer.size() < static_cast< size_t >( packetSize ) )
{
break;
}
auto stream = std::make_shared< ByteBuffer >( socket->m_pendingReadBuffer.data() + 4, packetSize - 4 );
// Erase the packet from the buffer
socket->m_pendingReadBuffer.erase(
socket->m_pendingReadBuffer.begin(),
socket->m_pendingReadBuffer.begin() + packetSize
);
if( stream->m_buffer.size() == 0 ) break;
// Process the packet
HandleRequest( socket, stream );
}
}
void LobbyServer::WriteSocket( sptr_socket socket )
{
if( socket->flag.disconnected_forced || socket->m_pendingWriteBuffer.empty() )
return;
socket->last_send_time = std::chrono::steady_clock::now();
size_t totalBytesSent = 0;
const size_t bufferSize = socket->m_pendingWriteBuffer.size();
while( totalBytesSent < bufferSize )
{
const size_t remaining = bufferSize - totalBytesSent;
const int chunkSize = static_cast< int >( std::min<size_t>( remaining, 1024 ) );
int bytesSent = send(
socket->fd,
reinterpret_cast< const char * >( socket->m_pendingWriteBuffer.data() + totalBytesSent ),
chunkSize,
0
);
if( bytesSent == SOCKET_ERROR )
{
const int err = WSAGetLastError();
if( err == WSAEWOULDBLOCK )
break;
Log::Error( "Send failed: {}", err );
socket->flag.disconnected_wait = true;
return;
}
if( bytesSent == 0 )
{
socket->flag.disconnected_wait = true;
return;
}
totalBytesSent += bytesSent;
if( bytesSent < chunkSize )
{
break;
}
}
std::lock_guard< std::mutex > lock( socket->write_mutex );
socket->m_pendingWriteBuffer.erase(
socket->m_pendingWriteBuffer.begin(),
socket->m_pendingWriteBuffer.begin() + totalBytesSent
);
}
void LobbyServer::HandleRequest( sptr_socket socket, sptr_byte_stream stream )
{
auto packetId = stream->read< uint16_t >();
stream->set_position( 0 );
auto it = REQUEST_EVENT.find( packetId );
if( it == REQUEST_EVENT.end() )
{
Log::Error( "[LOBBY] Unknown packet id : {}", packetId );
Log::Packet( stream->m_buffer, stream->m_buffer.size(), false );
return;
}
auto request = it->second();
if( auto res = request->ProcessRequest( socket, stream ) )
{
socket->send( res );
}
}