l2-unlegits/L2C_Server/GS.cpp
2012-02-01 05:25:08 +00:00

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;
}