394 lines
11 KiB
C++
394 lines
11 KiB
C++
#include "pch.h"
|
|
#include "Log.h"
|
|
#include "GS.h"
|
|
#include "utils/Debugging.h"
|
|
#include "utils/Exception.h"
|
|
#include "world/model/base/ClassIdTree.h"
|
|
#include "datatables/CharNameTable.h"
|
|
#include "datatables/CharTemplateTable.h"
|
|
#include "datatables/ItemTable.h"
|
|
|
|
// init class static vars
|
|
GameServer *GameServer::_self = NULL;
|
|
int GameServer::_referenceCount = 0;
|
|
|
|
GameServer *GameServer::getInstance()
|
|
{
|
|
if( !_self )
|
|
{
|
|
_self = new GameServer();
|
|
_referenceCount++;
|
|
}
|
|
return _self;
|
|
}
|
|
|
|
void GameServer::freeInstance()
|
|
{
|
|
if( _self )
|
|
{
|
|
_referenceCount--;
|
|
if( _referenceCount == 0 )
|
|
{
|
|
delete _self;
|
|
_self = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
GameServer::GameServer()
|
|
{
|
|
m_serverStatus = SERVER_STATUS_DOWN;
|
|
m_cfg = new GameConfig();
|
|
m_mysql = new MysqlConnectionManager();
|
|
m_loginConnection = new LoginConnection();
|
|
m_isRunning = m_flagStop = false;
|
|
m_clientPool = new ClientPool();
|
|
m_idf = new IdFactory();
|
|
}
|
|
|
|
GameServer::~GameServer()
|
|
{
|
|
stop();
|
|
if( m_cfg ) { delete m_cfg; m_cfg = NULL; }
|
|
if( m_mysql ) { delete m_mysql; m_mysql = NULL; }
|
|
if( m_loginConnection ) { delete m_loginConnection; m_loginConnection = NULL; }
|
|
if( m_clientPool ) { delete m_clientPool; m_clientPool = NULL; }
|
|
if( m_idf ) { delete m_idf; m_idf = NULL; }
|
|
}
|
|
|
|
bool GameServer::start()
|
|
{
|
|
if( m_isRunning ) return false;
|
|
HANDLE hThread = NULL;
|
|
unsigned int threadId = 0;
|
|
m_gsLock.Lock();
|
|
{
|
|
m_flagStop = false;
|
|
m_isRunning = false;
|
|
hThread = (HANDLE)_beginthreadex( NULL, 0,
|
|
(unsigned int (__stdcall *)(void *))(GameServer::GS_Thread), (void *)this,
|
|
0, &threadId );
|
|
if( hThread )
|
|
{
|
|
m_isRunning = true;
|
|
CloseHandle( hThread );
|
|
}
|
|
else
|
|
{
|
|
m_isRunning = false;
|
|
}
|
|
}
|
|
m_gsLock.Unlock();
|
|
return (hThread != NULL);
|
|
}
|
|
|
|
bool GameServer::stop()
|
|
{
|
|
if( !m_isRunning ) return false;
|
|
m_gsLock.Lock();
|
|
{
|
|
this->m_flagStop = true;
|
|
while( this->m_isRunning ) Sleep( 20 );
|
|
this->m_flagStop = false;
|
|
}
|
|
m_gsLock.Unlock();
|
|
return true;
|
|
}
|
|
|
|
bool GameServer::isRunning() const
|
|
{
|
|
return m_isRunning;
|
|
}
|
|
|
|
GameConfig *GameServer::getConfig() const
|
|
{
|
|
return m_cfg;
|
|
}
|
|
|
|
MysqlConnection *GameServer::getDBConnection()
|
|
{
|
|
MysqlConnection *ret = m_mysql->getConnection();
|
|
if( ret ) return ret;
|
|
LogError( L"GameServer::getDBConnection(): not enough free connections, %d/%d busy",
|
|
m_mysql->getCountActiveConnections(), m_mysql->getCountMaxConnections() );
|
|
throw Exception( "GameServer::getDBConnection(): no free connections!" );
|
|
}
|
|
|
|
bool GameServer::releaseDBConnection( MysqlConnection *con )
|
|
{
|
|
if( !con ) return false;
|
|
return m_mysql->releaseConnection( con );
|
|
}
|
|
|
|
void GameServer::LogFile( const char *fn, const char *_Format, ... )
|
|
{
|
|
char ffn[256] = {0};
|
|
sprintf( ffn, ".\\log\\game_%s.log", fn );
|
|
FILE *f = fopen( ffn, "at" );
|
|
if( !f ) return;
|
|
SYSTEMTIME st;
|
|
GetLocalTime( &st );
|
|
fprintf( f, "%02d.%02d.%04d %02d:%02d:%03d: ", (int)st.wDay, (int)st.wMonth, (int)st.wYear,
|
|
(int)st.wHour, (int)st.wMinute, (int)st.wMilliseconds );
|
|
va_list _ArgList;
|
|
va_start( _ArgList, _Format );
|
|
vfprintf( f, _Format, _ArgList );
|
|
fclose( f );
|
|
}
|
|
|
|
const wchar_t *GameServer::getServerName() const
|
|
{
|
|
if( !m_loginConnection ) return L"NULL";
|
|
return m_loginConnection->getRegisteredServerName();
|
|
}
|
|
|
|
int GameServer::getServerId() const
|
|
{
|
|
if( !m_loginConnection ) return 0;
|
|
return m_loginConnection->getRegisteredServerId();
|
|
}
|
|
|
|
/* Server will be considered as Good when the number of online players
|
|
* is less than half the maximum. as Normal between half and 4/5
|
|
* and Full when there's more than 4/5 of the maximum number of players */
|
|
ServerStatus GameServer::getServerStatus()
|
|
{
|
|
// TODO: ServerStatus may also be GM_ONLY
|
|
// SERVER_STATUS_DOWN is set when server is shut down
|
|
if( m_serverStatus != SERVER_STATUS_DOWN )
|
|
{
|
|
int curOnline = getCurrentOnline();
|
|
int maxOnline = getMaxPlayers();
|
|
float ratio = 100.0f * ((float)curOnline) / ((float)maxOnline);
|
|
if( ratio <= 0.5f ) m_serverStatus = SERVER_STATUS_GOOD;
|
|
if( ratio > 0.5f && ratio <= 0.8f ) m_serverStatus = SERVER_STATUS_NORMAL;
|
|
if( ratio > 0.8f ) m_serverStatus = SERVER_STATUS_FULL;
|
|
}
|
|
return m_serverStatus;
|
|
}
|
|
|
|
int GameServer::getMaxPlayers() const
|
|
{
|
|
return m_cfg->max_players;
|
|
}
|
|
|
|
int GameServer::getCurrentOnline()
|
|
{
|
|
if( !m_clientPool ) return 0;
|
|
return m_clientPool->getCurrentOnline();
|
|
}
|
|
|
|
bool GameServer::checkFreePort( const wchar_t *bind_addr, int bind_port )
|
|
{
|
|
char a_addr[128] = {0};
|
|
l2c_unicode_to_ansi( bind_addr, a_addr, 128 );
|
|
unsigned short s_port = (unsigned short)( bind_port & 0xFFFF );
|
|
SOCKET s = L2PNet_TCPsocket_create( true );
|
|
if( s == INVALID_SOCKET ) return false;
|
|
int r = L2PNet_bind( s, a_addr, s_port );
|
|
L2PNet_shutdown( s );
|
|
L2PNet_closesocket( s );
|
|
return (r==0);
|
|
}
|
|
|
|
ClientPool *GameServer::getClientPool() const
|
|
{
|
|
return m_clientPool;
|
|
}
|
|
|
|
IdFactory *GameServer::getIdFactory()
|
|
{
|
|
return m_idf;
|
|
}
|
|
|
|
bool GameServer::kickPlayerByAccount( const wchar_t *accountName, bool reasonDualLogin )
|
|
{
|
|
if( !accountName ) return false;
|
|
if( !m_clientPool ) return false;
|
|
bool ret = m_clientPool->disconnectPlayerByAccount( accountName, reasonDualLogin );
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool GameServer::logoutAccountFromLoginServer( const wchar_t *accountName )
|
|
{
|
|
if( !m_loginConnection || !accountName ) return false;
|
|
m_loginConnection->send_PlayerLogOut( accountName );
|
|
return true;
|
|
}
|
|
|
|
void GameServer::addWaitingClientAndSendPlayerAuthRequest( const wchar_t *accountName, unsigned char *loginKey, unsigned char *playKey )
|
|
{
|
|
if( !m_loginConnection ) return;
|
|
m_loginConnection->send_PlayerAuthRequest( accountName, loginKey, playKey );
|
|
}
|
|
|
|
void GameServer::notifyLoginPlayerAuth( const wchar_t *account, int ok )
|
|
{
|
|
if( !m_clientPool ) return;
|
|
m_clientPool->notifyClientAuthResponse( account, ok ? true : false );
|
|
}
|
|
|
|
|
|
DWORD WINAPI GameServer::GS_Thread( LPVOID lpvParam )
|
|
{
|
|
GameServer *gs = (GameServer *)lpvParam;
|
|
// remember load time start
|
|
DWORD startTick = GetTickCount();
|
|
DWORD endTick = 0;
|
|
int secs_load = 0;
|
|
LogFull( false, true, RGB(0,200,0), RGB(255,255,255), L"===================================" );
|
|
Log( L"Starting game server..." );
|
|
|
|
SOCKET gs_acceptor = INVALID_SOCKET;
|
|
|
|
try
|
|
{
|
|
|
|
///////////////////////////////////
|
|
// begin server initialization
|
|
///////////////////////////////////
|
|
// ensure log dir exists
|
|
if( CreateDirectory( TEXT(".\\log"), NULL ) ) Log( L"Created logs directory." );
|
|
// Load config
|
|
if( !gs->m_cfg->load() ) throw Exception( "GS_Thread: error loading config!" );
|
|
LogDebug( L"Config loaded." );
|
|
|
|
// check binding port is free
|
|
if( !checkFreePort( gs->m_cfg->game_server_bind_address, gs->m_cfg->game_server_bind_port ) )
|
|
{
|
|
LogError( L"Error: bind address %s:%d already in use (another GS is running?)",
|
|
gs->m_cfg->game_server_bind_address, gs->m_cfg->game_server_bind_port );
|
|
throw Exception( "Check free bind port failed" );
|
|
}
|
|
|
|
// Initialize MySQL
|
|
if( !gs->m_mysql->initialize( gs->m_cfg->mysql_max_connections, gs->m_cfg->mysql_host,
|
|
gs->m_cfg->mysql_user, gs->m_cfg->mysql_pass, gs->m_cfg->mysql_db ) )
|
|
throw Exception( "MySQL init error!" );
|
|
MysqlConnection *test_con = gs->m_mysql->getConnection();
|
|
if( !test_con ) throw Exception( "MySQL init error (no free connections???)" );
|
|
if( !test_con->isConnected() )
|
|
{
|
|
char comment[256] = {0};
|
|
sprintf( comment, "MySQL test connection error: %S", test_con->getErrorStr() );
|
|
gs->m_mysql->releaseConnection( test_con );
|
|
throw Exception( comment );
|
|
}
|
|
LogDebug( L"MySQL connection test OK" );
|
|
|
|
// init IdFactory
|
|
gs->m_idf->init();
|
|
Log( L"IdFactory: used %u object IDs, %I64u bytes", gs->m_idf->getUsedCount(), gs->m_idf->getUsedMemoryBytes() );
|
|
|
|
// init & load datatables
|
|
ItemTable::getInstance()->load();
|
|
ClassIdTree::getInstance();
|
|
CharTemplateTable::getInstance()->load();
|
|
CharNameTable::getInstance();
|
|
|
|
// TODO: load skills...
|
|
// TODO: load scripts/quests... TODO
|
|
|
|
// create listening socket
|
|
gs_acceptor = L2PNet_TCPsocket_create( true );
|
|
char a_bind_ip[128] = {0};
|
|
l2c_unicode_to_ansi( gs->m_cfg->game_server_bind_address, a_bind_ip, 128 );
|
|
if( L2PNet_bind( gs_acceptor, a_bind_ip, (unsigned short)gs->m_cfg->game_server_bind_port ) != 0 )
|
|
{
|
|
LogError( L"GS acceptor bind error at %s:%d!", gs->m_cfg->game_server_bind_address,
|
|
gs->m_cfg->game_server_bind_port );
|
|
throw Exception( "GS acceptor bind error!" );
|
|
}
|
|
L2PNet_listen( gs_acceptor );
|
|
|
|
// measure load time
|
|
endTick = GetTickCount();
|
|
secs_load = (endTick - startTick) / 1000;
|
|
Log( L"Game server loaded in %d seconds (%d ms).", secs_load, (int)(endTick - startTick) );
|
|
LogFull( false, true, RGB(0,200,0), RGB(255,255,255), L"===================================" );
|
|
|
|
// mark server as running
|
|
gs->m_serverStatus = SERVER_STATUS_AUTO;
|
|
|
|
// Initiaite login server connection thread
|
|
gs->m_loginConnection->start( gs->m_cfg->login_server_address,
|
|
gs->m_cfg->login_server_auth_port, gs->m_cfg->login_protocol_version );
|
|
|
|
////////////////////////////////////////////
|
|
// initiate main server loop
|
|
////////////////////////////////////////////
|
|
while( !gs->m_flagStop )
|
|
{
|
|
int r = 0, bReadyRead = 0, bReadyWrite = 0;
|
|
r = L2PNet_select( gs_acceptor, L2PNET_SELECT_READ, 200, &bReadyRead, &bReadyWrite );
|
|
if( r == 0 ) continue; // timeout
|
|
if( r == -1 ) // error
|
|
throw Exception( "GS acceptor select() error!" );
|
|
// accepted client?
|
|
if( bReadyRead )
|
|
{
|
|
char clientIp[32] = {0};
|
|
unsigned short clientPort = 0;
|
|
SOCKET s_cl = L2PNet_accept( gs_acceptor, clientIp, &clientPort );
|
|
if( s_cl != INVALID_SOCKET )
|
|
{
|
|
LogDebug( L"Accepted client %S:%d", clientIp, (int)clientPort );
|
|
if( !gs->m_clientPool->addClient( s_cl, clientIp, (int)clientPort ) )
|
|
{
|
|
LogError( L"Failed to add client %S:%d to clients list!", clientIp, (int)clientPort );
|
|
}
|
|
}
|
|
else LogWarning( L"gs acceptor: invalid_socket accepted :(" );
|
|
}
|
|
}
|
|
///////////////////////////////////////////
|
|
// end main server loop
|
|
///////////////////////////////////////////
|
|
}
|
|
catch( std::exception& e )
|
|
{
|
|
LogError( L"GS_Thread: std::exception: %S\n", e.what() );
|
|
}
|
|
catch( Exception& e )
|
|
{
|
|
LogError( L"GS_Thread: Exception: %S", e.what() );
|
|
e.logStackTrace();
|
|
}
|
|
|
|
////////////////////////////////////////////
|
|
// initiate shutdown procedures
|
|
////////////////////////////////////////////
|
|
// notify login server that server will now be offline
|
|
gs->m_serverStatus = SERVER_STATUS_DOWN;
|
|
gs->m_loginConnection->send_ServerStatus();
|
|
|
|
// disconnect all players
|
|
gs->m_clientPool->disconnectAllPlayers();
|
|
|
|
// TODO: save all data
|
|
// close GS acceptor socket
|
|
if( gs_acceptor != INVALID_SOCKET )
|
|
{
|
|
L2PNet_shutdown( gs_acceptor );
|
|
L2PNet_closesocket( gs_acceptor );
|
|
gs_acceptor = INVALID_SOCKET;
|
|
}
|
|
|
|
// cleanup datatables
|
|
CharNameTable::freeInstance();
|
|
CharTemplateTable::freeInstance();
|
|
ClassIdTree::freeInstance();
|
|
ItemTable::freeInstance();
|
|
|
|
// close id factory
|
|
gs->m_idf->clear();
|
|
|
|
// stop login conn thread
|
|
gs->m_loginConnection->stop();
|
|
|
|
// mark as not running
|
|
gs->m_isRunning = false;
|
|
return 0;
|
|
}
|