mirror of
https://github.com/HikikoMarmy/Champions-Reborn-Server.git
synced 2026-04-04 16:49:47 -03:00
422 lines
9.9 KiB
C++
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 );
|
|
}
|
|
} |