/* * Ascent MMORPG Server * Copyright (C) 2005-2007 Ascent Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "Master.h" #include "CConsole.h" #include "../shared/CrashHandler.h" #include "../game/StdAfx.h" #include "../shared/ascent_getopt.h" #ifdef HOARD #define BANNER "Ascent r%u/%s-%s-%s-Hoard :: World Server" #else #define BANNER "Ascent r%u/%s-%s-%s :: World Server" #endif #ifndef WIN32 #include #endif #include "../shared/svn_revision.h" #include "../game/WorldSession.h" #include "LogonCommClient.h" #include createFileSingleton(Master); std::string LogFileName; bool bLogChat; bool crashed = false; volatile bool Master::m_stopEvent = false; // Database defines. SERVER_DECL Database* Database_Character; SERVER_DECL Database* Database_World; // mainserv defines SessionLogWriter * GMCommand_Log; SessionLogWriter * Anticheat_Log; SessionLogWriter * Player_Log; void Master::_OnSignal(int s) { switch (s) { #ifndef WIN32 case SIGHUP: sWorld.Rehash(true); break; #endif case SIGINT: case SIGTERM: case SIGABRT: #ifdef _WIN32 case SIGBREAK: #endif Master::m_stopEvent = true; break; } signal(s, _OnSignal); } Master::Master() { m_ShutdownTimer = 0; m_ShutdownEvent = false; m_restartEvent = false; } Master::~Master() { } struct Addr { unsigned short sa_family; /* sa_data */ unsigned short Port; unsigned long IP; // inet_addr unsigned long unusedA; unsigned long unusedB; }; #define DEF_VALUE_NOT_SET 0xDEADBEEF #ifdef WIN32 static const char * default_config_file = "ascent.conf"; static const char * default_realm_config_file = "realms.conf"; #else static const char * default_config_file = CONFDIR "/ascent.conf"; static const char * default_realm_config_file = CONFDIR "/realms.conf"; #endif const char banner[] = "" " I33ctueuJ7 \n" " ICLL5seOue37 \n" " 7zLeYuvYee27 \n" " 7uOCaYaaYs7 \n" " 71v22C5s0nC5JJJSJ \n" " 7z2zvvCtolljjljC57 \n" " lzesCIoooonCoaCe7 \n" " 7Oz3JlooI8xtgu57 \n" " tOj3sloootvlolu3 \n" " 55jjuejlollojI357\n" " 7ves5tlojeYejjllsa4Yr\n" " 12n2n5eOLnnnnnnCuunuunnn5enCC5Clloooj3LuCoI304sr\n" " i2uCotzueolljljjljjllllloojjljjooooltzoJfhaa09Yr\n" " 7esjljjlllllllllllllllllooooooooovCn3JunYhTal7\n" " Jevlooooooooooooooooooooooooooolo3oljz5ir777 \n" " cuvloooooooooooooooooooooooooooot2JlolC3 \n" " 7nCjooooooooooooooooooooooooooool3JlojCn7 \n" " 7LJlooooooooooooooooooooooooooooollljzu7 \n" " 7LvloooooooololoooooooooooooooooojoJee1 \n" " 7OCjoooooool3o2tooooooooolloooolozT0C7 \"We love our goats\"\n" " JLlooooolvnlJ5jlllljjjjjtvoooltes0r \n" " 7eejoooolv3OatllotvJ222CY8loojuCzL7 \n" " 7L3loolt2sULunnCuCCzJtIeLjoolzOInv \n" " 7JYolol3Otun7 7unIlllJOrzC \n" " 7JLzlll3OoIuu7 rsuJtv3a6h3 \n" " 7n5ool3OC1CC7 7spXk9pFhei \n" " 7LOicilSxxO7 7oneoeJ7 \n" " 1OUU9fPfYj7 \n" " 7vVGf277 \n" "\n"; bool Master::Run(int argc, char ** argv) { char * config_file = (char*)default_config_file; char * realm_config_file = (char*)default_realm_config_file; int file_log_level = DEF_VALUE_NOT_SET; int screen_log_level = DEF_VALUE_NOT_SET; int do_check_conf = 0; int do_version = 0; struct ascent_option longopts[] = { { "checkconf", ascent_no_argument, &do_check_conf, 1 }, { "screenloglevel", ascent_required_argument, &screen_log_level, 1 }, { "fileloglevel", ascent_required_argument, &file_log_level, 1 }, { "version", ascent_no_argument, &do_version, 1 }, { "conf", ascent_required_argument, NULL, 'c' }, { "realmconf", ascent_required_argument, NULL, 'r' }, { 0, 0, 0, 0 } }; char c; while ((c = ascent_getopt_long_only(argc, argv, ":f:", longopts, NULL)) != -1) { switch (c) { case 'c': config_file = new char[strlen(ascent_optarg)]; strcpy(config_file, ascent_optarg); break; case 'r': realm_config_file = new char[strlen(ascent_optarg)]; strcpy(realm_config_file, ascent_optarg); break; case 0: break; default: sLog.m_fileLogLevel = -1; sLog.m_screenLogLevel = 3; printf("Usage: %s [--checkconf] [--screenloglevel ] [--fileloglevel ] [--conf ] [--realmconf ] [--version]\n", argv[0]); return true; } } // Startup banner UNIXTIME = time(NULL); if(!do_version && !do_check_conf) { sLog.Init(-1, 3); } else { sLog.m_fileLogLevel = -1; sLog.m_screenLogLevel = 3; } puts(banner); printf(BANNER, g_getRevision(), CONFIG, PLATFORM_TEXT, ARCH); printf("\nCopyright (C) 2005-2007 Ascent Team. http://www.ascentemu.com/\n"); printf("This program comes with ABSOLUTELY NO WARRANTY, and is FREE SOFTWARE.\n"); printf("You are welcome to redistribute it under the terms of the GNU General\n"); printf("Public License, either version 3 or any later version. For a copy of\n"); printf("this license, see the COPYING file provided with this distribution.\n"); Log.Line(); if(do_version) return true; if(do_check_conf) { Log.Notice("Config", "Checking config file: %s", config_file); if(Config.MainConfig.SetSource(config_file, true)) Log.Success("Config", "Passed without errors."); else Log.Warning("Config", "Encountered one or more errors."); Log.Notice("Config", "Checking config file: %s\n", realm_config_file); if(Config.RealmConfig.SetSource(realm_config_file, true)) Log.Success("Config", "Passed without errors.\n"); else Log.Warning("Config", "Encountered one or more errors.\n"); /* test for die variables */ string die; if(Config.MainConfig.GetString("die", "msg", &die) || Config.MainConfig.GetString("die2", "msg", &die)) Log.Warning("Config", "Die directive received: %s", die.c_str()); return true; } printf("The key combination will safely shut down the server at any time.\n"); Log.Line(); uint32 seed = time(NULL); new MTRand(seed); srand(seed); Log.Success("MTRand", "Initialized Random Number Generators."); new ThreadMgr; Log.Success("ThreadMgr", "Started."); uint32 LoadingTime = getMSTime(); Log.Notice("Config", "Loading Config Files...\n"); if(Config.MainConfig.SetSource(config_file)) Log.Success("Config", ">> ascent.conf"); else { Log.Error("Config", ">> ascent.conf"); return false; } string die; if(Config.MainConfig.GetString("die", "msg", &die) || Config.MainConfig.GetString("die2", "msg", &die)) { Log.Warning("Config", "Die directive received: %s", die.c_str()); return false; } if(Config.RealmConfig.SetSource(realm_config_file)) Log.Success("Config", ">> realms.conf"); else { Log.Error("Config", ">> realms.conf"); return false; } if(!_StartDB()) { return false; } /*Log.Color(TWHITE); int left = 3; bool dodb = false; printf("\nHit F1 within the next 3 seconds to enter database maintenance mode."); fflush(stdout); while(left) { dodb = sConsole.PollForD(); if(dodb) break; left--; printf("."); fflush(stdout); } if(dodb) { Log.Color(TNORMAL); printf("\nEntering database maintenance mode.\n\n"); new DatabaseCleaner; DatabaseCleaner::getSingleton().Run(); delete DatabaseCleaner::getSingletonPtr(); Log.Color(TYELLOW); printf("\nMaintenence finished. Take a moment to review the output, and hit space to continue startup."); Log.Color(TNORMAL); fflush(stdout); sConsole.WaitForSpace(); } else Log.Color(TNORMAL);*/ Log.Line(); sLog.outString(""); ScriptSystem = new ScriptEngine; ScriptSystem->Reload(); new EventMgr; new World; // open cheat log file Anticheat_Log = new SessionLogWriter(FormatOutputString("logs", "cheaters", false).c_str(), false); GMCommand_Log = new SessionLogWriter(FormatOutputString("logs", "gmcommand", false).c_str(), false); Player_Log = new SessionLogWriter(FormatOutputString("logs", "players", false).c_str(), false); /* load the config file */ sWorld.Rehash(false); /* set new log levels */ if(screen_log_level != (int)DEF_VALUE_NOT_SET) sLog.SetScreenLoggingLevel(screen_log_level); if(file_log_level != (int)DEF_VALUE_NOT_SET) sLog.SetFileLoggingLevel(file_log_level); // Initialize Opcode Table WorldSession::InitPacketHandlerTable(); string host = Config.MainConfig.GetStringDefault("Listen", "Host", DEFAULT_HOST); int wsport = Config.MainConfig.GetIntDefault("Listen", "WorldServerPort", DEFAULT_WORLDSERVER_PORT); new ScriptMgr; sWorld.SetInitialWorldSettings(); sWorld.SetStartTime((uint32)time(NULL)); _HookSignals(); launch_thread(new CConsoleThread); uint32 realCurrTime, realPrevTime; realCurrTime = realPrevTime = getMSTime(); // initialize thread system sThreadMgr.Initialize(); // Socket loop! uint32 start; uint32 diff; uint32 last_time = now(); uint32 etime; uint32 next_printout = getMSTime(), next_send = getMSTime(); // Start Network Subsystem sLog.outString("Starting network subsystem..."); new SocketMgr; new SocketGarbageCollector; sSocketMgr.SpawnWorkerThreads(); sScriptMgr.LoadScripts(); sLog.outString("Threading system initialized, currently %u threads are active.", sThreadMgr.GetThreadCount()); LoadingTime = getMSTime() - LoadingTime; sLog.outString ("\nServer is ready for connections. Startup time: %ums\n", LoadingTime ); /* write pid file */ FILE * fPid = fopen("ascent.pid", "w"); if(fPid) { uint32 pid; #ifdef WIN32 pid = GetCurrentProcessId(); #else pid = getpid(); #endif fprintf(fPid, "%u", (unsigned int)pid); fclose(fPid); } #ifndef CLUSTERING /* Connect to realmlist servers / logon servers */ new LogonCommHandler(); sLogonCommHandler.Startup(); // Create listener ListenSocket * ls = new ListenSocket(host.c_str(), wsport); bool listnersockcreate = ls->IsOpen(); while(!m_stopEvent && listnersockcreate) #else new ClusterInterface; sClusterInterface.ConnectToRealmServer(); while(!m_stopEvent) #endif { /* Update global UnixTime variable */ UNIXTIME = time(NULL); start = now(); diff = start - last_time; #ifndef CLUSTERING sLogonCommHandler.UpdateSockets(); ls->Update(); #else sClusterInterface.Update(); #endif sSocketGarbageCollector.Update(); /* UPDATE */ last_time = now(); etime = last_time - start; if(m_ShutdownEvent) { if(getMSTime() >= next_printout) { if(m_ShutdownTimer > 60000.0f) { if(!((int)(m_ShutdownTimer)%60000)) sLog.outString("Server shutdown in %i minutes.", (int)(m_ShutdownTimer / 60000.0f)); } else sLog.outString("Server shutdown in %i seconds.", (int)(m_ShutdownTimer / 1000.0f)); next_printout = getMSTime() + 500; } if(getMSTime() >= next_send) { // broadcast packet. WorldPacket data(20); data.SetOpcode(SMSG_SERVER_MESSAGE); data << uint32(SERVER_MSG_SHUTDOWN_TIME); int time = m_ShutdownTimer / 1000; if(time > 0) { int mins = 0, secs = 0; if(time > 60) mins = time / 60; if(mins) time -= (mins*60); secs = time; char str[20]; snprintf(str, 20, "%02u:%02u", mins, secs); data << str; sWorld.SendGlobalMessage(&data, NULL); } next_send = getMSTime() + 1000; } if(diff >= m_ShutdownTimer) break; else m_ShutdownTimer -= diff; } Database_Character->CheckConnections(); Database_World->CheckConnections(); sWorld.UpdateQueuedSessions(diff); if(50 > etime) Sleep(50 - etime); } _UnhookSignals(); /* Shut down console system */ sCConsole.Kill(); sLog.outString("Killing all sockets and network subsystem."); #ifndef CLUSTERING ls->Close(); delete ls; #endif #ifdef WIN32 sSocketMgr.ShutdownThreads(); #endif sSocketMgr.CloseAll(); // begin server shutdown time_t st = time(NULL); sLog.outString("Server shutdown initiated at %s", ctime(&st)); // send a query to wake it up if its inactive sLog.outString("Executing pending database queries and closing database thread..."); // kill the database thread first so we don't lose any queries/data ((MySQLDatabase*)Database_Character)->SetThreadState(THREADSTATE_TERMINATE); ((MySQLDatabase*)Database_World)->SetThreadState(THREADSTATE_TERMINATE); CharacterDatabase.Execute("UPDATE characters SET online = 0"); WorldDatabase.Execute("UPDATE characters SET online = 0"); // wait for it to finish its work while(((MySQLDatabase*)Database_Character)->ThreadRunning || ((MySQLDatabase*)Database_World)->ThreadRunning) { Sleep(100); } sThreadMgr.RemoveThread(((MySQLDatabase*)Database_Character)); sThreadMgr.RemoveThread(((MySQLDatabase*)Database_World)); sLog.outString("All pending database operations cleared.\n"); sWorld.SaveAllPlayers(); sLog.outString(""); delete LogonCommHandler::getSingletonPtr(); sWorld.ShutdownClasses(); sLog.outString("\nDeleting World..."); delete World::getSingletonPtr(); sScriptMgr.UnloadScripts(); delete ScriptMgr::getSingletonPtr(); sLog.outString("Deleting Event Manager..."); delete EventMgr::getSingletonPtr(); sLog.outString("Terminating MySQL connections...\n"); _StopDB(); sLog.outString("Deleting Network Subsystem..."); delete SocketMgr::getSingletonPtr(); delete SocketGarbageCollector::getSingletonPtr(); sLog.outString("Deleting Script Engine..."); delete ScriptSystem; delete GMCommand_Log; delete Anticheat_Log; delete Player_Log; // remove pid remove("ascent.pid"); sLog.outString("\nServer shutdown completed successfully.\n"); #ifdef WIN32 WSACleanup(); // Terminate Entire Application //HANDLE pH = OpenProcess(PROCESS_TERMINATE, TRUE, GetCurrentProcessId()); //TerminateProcess(pH, 0); //CloseHandle(pH); #endif return true; } bool Master::_StartDB() { string hostname, username, password, database; int port = 0; int type = 1; //string lhostname, lusername, lpassword, ldatabase; //int lport = 0; //int ltype = 1; // Configure Main Database bool result = Config.MainConfig.GetString("WorldDatabase", "Username", &username); Config.MainConfig.GetString("WorldDatabase", "Password", &password); result = !result ? result : Config.MainConfig.GetString("WorldDatabase", "Hostname", &hostname); result = !result ? result : Config.MainConfig.GetString("WorldDatabase", "Name", &database); result = !result ? result : Config.MainConfig.GetInt("WorldDatabase", "Port", &port); result = !result ? result : Config.MainConfig.GetInt("WorldDatabase", "Type", &type); Database_World = CreateDatabaseInterface((DatabaseType)type); if(result == false) { sLog.outError("sql: One or more parameters were missing from WorldDatabase directive."); return false; } // Initialize it if(!WorldDatabase.Initialize(hostname.c_str(), (unsigned int)port, username.c_str(), password.c_str(), database.c_str(), Config.MainConfig.GetIntDefault("WorldDatabase", "ConnectionCount", 3), 16384)) { sLog.outError("sql: Main database initialization failed. Exiting."); return false; } result = Config.MainConfig.GetString("CharacterDatabase", "Username", &username); Config.MainConfig.GetString("CharacterDatabase", "Password", &password); result = !result ? result : Config.MainConfig.GetString("CharacterDatabase", "Hostname", &hostname); result = !result ? result : Config.MainConfig.GetString("CharacterDatabase", "Name", &database); result = !result ? result : Config.MainConfig.GetInt("CharacterDatabase", "Port", &port); result = !result ? result : Config.MainConfig.GetInt("CharacterDatabase", "Type", &type); Database_Character = CreateDatabaseInterface((DatabaseType)type); if(result == false) { sLog.outError("sql: One or more parameters were missing from Database directive."); return false; } // Initialize it if(!CharacterDatabase.Initialize(hostname.c_str(), (unsigned int)port, username.c_str(), password.c_str(), database.c_str(), Config.MainConfig.GetIntDefault("CharacterDatabase", "ConnectionCount", 3), 16384)) { sLog.outError("sql: Main database initialization failed. Exiting."); return false; } return true; } void Master::_StopDB() { CharacterDatabase.Shutdown(); WorldDatabase.Shutdown(); DestroyDatabaseInterface(Database_World); DestroyDatabaseInterface(Database_Character); } void Master::_HookSignals() { signal(SIGINT, _OnSignal); signal(SIGTERM, _OnSignal); signal(SIGABRT, _OnSignal); #ifdef _WIN32 signal(SIGBREAK, _OnSignal); #else signal(SIGHUP, _OnSignal); #endif } void Master::_UnhookSignals() { signal(SIGINT, 0); signal(SIGTERM, 0); signal(SIGABRT, 0); #ifdef _WIN32 signal(SIGBREAK, 0); #else signal(SIGHUP, 0); #endif } #ifdef WIN32 Mutex m_crashedMutex; // Crash Handler void OnCrash(bool Terminate) { sLog.outString("Advanced crash handler initialized."); if(!m_crashedMutex.AttemptAcquire()) TerminateThread(GetCurrentThread(), 0); try { if(World::getSingletonPtr() != 0 && ThreadMgr::getSingletonPtr() != 0) { sLog.outString("Waiting for all database queries to finish..."); MySQLDatabase* dbThread = (MySQLDatabase*)sThreadMgr.GetThreadByType(THREADTYPE_DATABASE); if(dbThread != 0) { // end it MySQLDatabase * dbThread2 = (MySQLDatabase*)Database_World; dbThread = (MySQLDatabase*)Database_Character; dbThread->SetThreadState(THREADSTATE_TERMINATE); dbThread2->SetThreadState(THREADSTATE_TERMINATE); const char * query = "UPDATE characters SET online = 0 WHERE guid = 0"; uint32 next_query_time = getMSTime() + 10000; // wait for it to finish its work CharacterDatabase.Execute(query); WorldDatabase.Execute(query); while(dbThread->ThreadRunning || dbThread2->ThreadRunning) { if(getMSTime() >= next_query_time) { next_query_time = getMSTime() + 10000; /* send some bullshit queries */ if(dbThread->ThreadRunning) CharacterDatabase.Execute(query); if(dbThread2->ThreadRunning) WorldDatabase.Execute(query); } Sleep(100); } } sLog.outString("All pending database operations cleared.\n"); //sWorld.SaveAllPlayers(); sLog.outString("Data saved."); } } catch(...) { sLog.outString("Threw an exception while attempting to save all data."); } sLog.outString("Closing."); // beep //printf("\x7"); // Terminate Entire Application if(Terminate) { HANDLE pH = OpenProcess(PROCESS_TERMINATE, TRUE, GetCurrentProcessId()); TerminateProcess(pH, 1); CloseHandle(pH); } } #endif