Merge branch 'main' into cdclient-rework

This commit is contained in:
David Markowitz
2023-07-27 20:03:12 -07:00
1177 changed files with 21192 additions and 13777 deletions

View File

@@ -1,9 +1,13 @@
set(DMASTERSERVER_SOURCES "InstanceManager.cpp"
"MasterServer.cpp"
"ObjectIDManager.cpp")
add_executable(MasterServer ${DMASTERSERVER_SOURCES})
target_link_libraries(MasterServer ${COMMON_LIBRARIES})
set(DMASTERSERVER_SOURCES
"InstanceManager.cpp"
"ObjectIDManager.cpp"
)
add_library(dMasterServer ${DMASTERSERVER_SOURCES})
add_executable(MasterServer "MasterServer.cpp")
target_link_libraries(dMasterServer ${COMMON_LIBRARIES})
target_link_libraries(MasterServer ${COMMON_LIBRARIES} dMasterServer)
if(WIN32)
add_dependencies(MasterServer WorldServer AuthServer ChatServer)

View File

@@ -7,9 +7,11 @@
#include "CDClientDatabase.h"
#include "CDClientManager.h"
#include "CDZoneTableTable.h"
#include "dMessageIdentifiers.h"
#include "MasterPackets.h"
#include "PacketUtils.h"
#include "BinaryPathFinder.h"
#include "eConnectionType.h"
#include "eMasterMessageType.h"
InstanceManager::InstanceManager(dLogger* logger, const std::string& externalIP) {
mLogger = logger;
@@ -30,6 +32,15 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW
Instance* instance = FindInstance(mapID, isFriendTransfer, cloneID);
if (instance) return instance;
// If we are shutting down, return a nullptr so a new instance is not created.
if (m_IsShuttingDown) {
Game::logger->Log("InstanceManager",
"Tried to create a new instance map/instance/clone %i/%i/%i, but Master is shutting down.",
mapID,
m_LastInstanceID + 1,
cloneID);
return nullptr;
}
//TODO: Update this so that the IP is read from a configuration file instead
int softCap = 8;
@@ -48,24 +59,13 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW
//Start the actual process:
#ifdef _WIN32
std::string cmd = "start ./WorldServer.exe";
std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone ";
#else
std::string cmd;
Game::logger->Log("InstanceManager", "world_command: %s\n", Game::config->GetValue("world_command").c_str());
if (!Game::config->GetValue("world_command").empty()) {
cmd = Game::config->GetValue("world_command");
// Replace %map%_%instance%_%time% with the correct values
cmd = cmd.replace(cmd.find("%map%"), 5, std::to_string(mapID));
cmd = cmd.replace(cmd.find("%instance%"), 10, std::to_string(instance->GetInstanceID()));
cmd = cmd.replace(cmd.find("%time%"), 5, std::to_string(time(nullptr)));
// Remove any other %
cmd = cmd.replace(cmd.find("%"), 1, "");
} else if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) {
cmd = "sudo ./WorldServer";
if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) {
cmd = "sudo " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone ";
} else {
cmd = "./WorldServer";
cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone ";
}
#endif
@@ -85,9 +85,7 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW
cmd.append("&"); //Sends our next process to the background on Linux
#endif
Game::logger->Log("InstanceManager", "Starting instance %i with command: %s\n", instance->GetInstanceID(), cmd.c_str());
system(cmd.c_str());
auto ret = system(cmd.c_str());
m_Instances.push_back(instance);
@@ -163,7 +161,7 @@ void InstanceManager::RemoveInstance(Instance* instance) {
if (m_Instances[i] == instance) {
instance->SetShutdownComplete(true);
RedirectPendingRequests(instance);
if (!Game::shouldShutdown) RedirectPendingRequests(instance);
delete m_Instances[i];
@@ -205,7 +203,7 @@ void InstanceManager::RequestAffirmation(Instance* instance, const PendingInstan
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_AFFIRM_TRANSFER_REQUEST);
PacketUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::AFFIRM_TRANSFER_REQUEST);
bitStream.Write(request.id);
@@ -251,7 +249,7 @@ void InstanceManager::RedirectPendingRequests(Instance* instance) {
for (const auto& request : instance->GetPendingAffirmations()) {
auto* in = Game::im->GetInstance(zoneId.GetMapID(), false, zoneId.GetCloneID());
if (!in->GetIsReady()) // Instance not ready, make a pending request
if (in && !in->GetIsReady()) // Instance not ready, make a pending request
{
in->GetPendingRequests().push_back(request);
@@ -308,16 +306,25 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon
return instance;
}
if (m_IsShuttingDown) {
Game::logger->Log("InstanceManager",
"Tried to create a new private instance map/instance/clone %i/%i/%i, but Master is shutting down.",
mapID,
m_LastInstanceID + 1,
cloneID);
return nullptr;
}
int maxPlayers = 999;
uint32_t port = GetFreePort();
instance = new Instance(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, maxPlayers, maxPlayers, true, password);
//Start the actual process:
std::string cmd = "start ./WorldServer.exe -zone ";
std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone ";
#ifndef _WIN32
cmd = "./WorldServer -zone ";
cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone ";
#endif
cmd.append(std::to_string(mapID));
@@ -335,7 +342,7 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon
cmd.append("&"); //Sends our next process to the background on Linux
#endif
system(cmd.c_str());
auto ret = system(cmd.c_str());
m_Instances.push_back(instance);
@@ -364,7 +371,7 @@ Instance* InstanceManager::FindPrivateInstance(const std::string& password) {
}
int InstanceManager::GetSoftCap(LWOMAPID mapID) {
CDZoneTableTable* zoneTable = CDClientManager::Instance()->GetTable<CDZoneTableTable>("ZoneTable");
CDZoneTableTable* zoneTable = CDClientManager::Instance().GetTable<CDZoneTableTable>();
if (zoneTable) {
const CDZoneTable* zone = zoneTable->Query(mapID);
@@ -377,7 +384,7 @@ int InstanceManager::GetSoftCap(LWOMAPID mapID) {
}
int InstanceManager::GetHardCap(LWOMAPID mapID) {
CDZoneTableTable* zoneTable = CDClientManager::Instance()->GetTable<CDZoneTableTable>("ZoneTable");
CDZoneTableTable* zoneTable = CDClientManager::Instance().GetTable<CDZoneTableTable>();
if (zoneTable) {
const CDZoneTable* zone = zoneTable->Query(mapID);
@@ -400,9 +407,9 @@ bool Instance::GetShutdownComplete() const {
void Instance::Shutdown() {
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_SHUTDOWN);
PacketUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN);
Game::server->Send(&bitStream, this->m_SysAddr, false);
Game::logger->Log("Instance", "Triggered world shutdown");
Game::logger->Log("Instance", "Triggered world shutdown for zone/clone/instance %i/%i/%i", GetMapID(), GetCloneID(), GetInstanceID());
}

View File

@@ -128,6 +128,7 @@ public:
Instance* CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password);
Instance* FindPrivateInstance(const std::string& password);
void SetIsShuttingDown(bool value) { this->m_IsShuttingDown = value; };
private:
dLogger* mLogger;
@@ -136,6 +137,11 @@ private:
unsigned short m_LastPort;
LWOINSTANCEID m_LastInstanceID;
/**
* Whether or not the master server is currently shutting down.
*/
bool m_IsShuttingDown = false;
//Private functions:
bool IsInstanceFull(Instance* instance, bool isFriendTransfer);
int GetSoftCap(LWOMAPID mapID);

View File

@@ -25,6 +25,10 @@
#include "dConfig.h"
#include "dLogger.h"
#include "dServer.h"
#include "AssetManager.h"
#include "BinaryPathFinder.h"
#include "eConnectionType.h"
#include "eMasterMessageType.h"
//RakNet includes:
#include "RakNetDefines.h"
@@ -37,27 +41,32 @@
#include "MasterPackets.h"
#include "ObjectIDManager.h"
#include "PacketUtils.h"
#include "dMessageIdentifiers.h"
#include "FdbToSqlite.h"
namespace Game {
dLogger* logger;
dServer* server;
InstanceManager* im;
dConfig* config;
dLogger* logger = nullptr;
dServer* server = nullptr;
InstanceManager* im = nullptr;
dConfig* config = nullptr;
AssetManager* assetManager = nullptr;
bool shouldShutdown = false;
std::mt19937 randomEngine;
} //namespace Game
bool shutdownSequenceStarted = false;
void ShutdownSequence();
int FinalizeShutdown();
void ShutdownSequence(int32_t signal = -1);
int32_t FinalizeShutdown(int32_t signal = -1);
dLogger* SetupLogger();
void StartAuthServer();
void StartChatServer();
void HandlePacket(Packet* packet);
std::map<uint32_t, std::string> activeSessions;
bool shouldShutdown = false;
SystemAddress authServerMasterPeerSysAddr;
SystemAddress chatServerMasterPeerSysAddr;
int main(int argc, char** argv) {
constexpr uint32_t masterFramerate = mediumFramerate;
constexpr uint32_t masterFrameDelta = mediumFrameDelta;
Diagnostics::SetProcessName("Master");
Diagnostics::SetProcessFileName(argv[0]);
Diagnostics::Initialize();
@@ -67,29 +76,52 @@ int main(int argc, char** argv) {
#endif
//Triggers the shutdown sequence at application exit
std::atexit(ShutdownSequence);
signal(SIGINT, [](int) { ShutdownSequence(); });
signal(SIGTERM, [](int) { ShutdownSequence(); });
std::atexit([]() { ShutdownSequence(); });
signal(SIGINT, [](int32_t signal) { ShutdownSequence(EXIT_FAILURE); });
signal(SIGTERM, [](int32_t signal) { ShutdownSequence(EXIT_FAILURE); });
//Create all the objects we need to run our service:
Game::logger = SetupLogger();
if (!Game::logger) return EXIT_FAILURE;
if (!std::filesystem::exists(BinaryPathFinder::GetBinaryDir() / "authconfig.ini")) {
Game::logger->Log("MasterServer", "Couldnt find authconfig.ini");
return EXIT_FAILURE;
}
if (!std::filesystem::exists(BinaryPathFinder::GetBinaryDir() / "chatconfig.ini")) {
Game::logger->Log("MasterServer", "Couldnt find chatconfig.ini");
return EXIT_FAILURE;
}
if (!std::filesystem::exists(BinaryPathFinder::GetBinaryDir() / "masterconfig.ini")) {
Game::logger->Log("MasterServer", "Couldnt find masterconfig.ini");
return EXIT_FAILURE;
}
if (!std::filesystem::exists(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini")) {
Game::logger->Log("MasterServer", "Couldnt find sharedconfig.ini");
return EXIT_FAILURE;
}
if (!std::filesystem::exists(BinaryPathFinder::GetBinaryDir() / "worldconfig.ini")) {
Game::logger->Log("MasterServer", "Couldnt find worldconfig.ini");
return EXIT_FAILURE;
}
Game::config = new dConfig((BinaryPathFinder::GetBinaryDir() / "masterconfig.ini").string());
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
Game::logger->Log("MasterServer", "Starting Master server...");
Game::logger->Log("MasterServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
Game::logger->Log("MasterServer", "Compiled on: %s", __TIMESTAMP__);
//Read our config:
dConfig config("masterconfig.ini");
Game::config = &config;
Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console"))));
Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1");
//Connect to the MySQL Database
std::string mysql_host = config.GetValue("mysql_host");
std::string mysql_database = config.GetValue("mysql_database");
std::string mysql_username = config.GetValue("mysql_username");
std::string mysql_password = config.GetValue("mysql_password");
std::string mysql_host = Game::config->GetValue("mysql_host");
std::string mysql_database = Game::config->GetValue("mysql_database");
std::string mysql_username = Game::config->GetValue("mysql_username");
std::string mysql_password = Game::config->GetValue("mysql_password");
try {
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
@@ -99,20 +131,58 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
MigrationRunner::RunMigrations();
try {
std::string clientPathStr = Game::config->GetValue("client_location");
if (clientPathStr.empty()) clientPathStr = "./res";
std::filesystem::path clientPath = std::filesystem::path(clientPathStr);
if (clientPath.is_relative()) {
clientPath = BinaryPathFinder::GetBinaryDir() / clientPath;
}
Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) {
Game::logger->Log("MasterServer", "Got an error while setting up assets: %s", ex.what());
//Check CDClient exists
const std::string cdclient_path = "./res/CDServer.sqlite";
std::ifstream cdclient_fd(cdclient_path);
if (!cdclient_fd.good()) {
Game::logger->Log("WorldServer", "%s could not be opened", cdclient_path.c_str());
return EXIT_FAILURE;
}
cdclient_fd.close();
MigrationRunner::RunMigrations();
const bool cdServerExists = std::filesystem::exists(BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite");
const bool oldCDServerExists = std::filesystem::exists(Game::assetManager->GetResPath() / "CDServer.sqlite");
const bool fdbExists = std::filesystem::exists(Game::assetManager->GetResPath() / "cdclient.fdb");
if (!cdServerExists) {
if (oldCDServerExists) {
// If the file doesn't exist in the new CDServer location, copy it there. We copy because we may not have write permissions from the previous directory.
Game::logger->Log("MasterServer", "CDServer.sqlite is not located at resServer, but is located at res path. Copying file...");
std::filesystem::copy_file(Game::assetManager->GetResPath() / "CDServer.sqlite", BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite");
} else {
Game::logger->Log("MasterServer",
"%s could not be found in resServer or res. Looking for %s to convert to sqlite.",
(BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").c_str(),
(Game::assetManager->GetResPath() / "cdclient.fdb").c_str());
AssetMemoryBuffer cdClientBuffer = Game::assetManager->GetFileAsBuffer("cdclient.fdb");
if (!cdClientBuffer.m_Success) {
Game::logger->Log("MasterServer", "Failed to load %s", (Game::assetManager->GetResPath() / "cdclient.fdb").c_str());
throw std::runtime_error("Aborting initialization due to missing cdclient.fdb.");
}
Game::logger->Log("MasterServer", "Found %s. Converting to SQLite", (Game::assetManager->GetResPath() / "cdclient.fdb").c_str());
Game::logger->Flush();
if (FdbToSqlite::Convert((BinaryPathFinder::GetBinaryDir() / "resServer").string()).ConvertDatabase(cdClientBuffer) == false) {
Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite.");
return EXIT_FAILURE;
}
cdClientBuffer.close();
}
}
//Connect to CDClient
try {
CDClientDatabase::Connect(cdclient_path);
CDClientDatabase::Connect((BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").string());
} catch (CppSQLite3Exception& e) {
Game::logger->Log("WorldServer", "Unable to connect to CDServer SQLite Database");
Game::logger->Log("WorldServer", "Error: %s", e.errorMessage());
@@ -120,12 +190,15 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
// Run migrations should any need to be run.
MigrationRunner::RunSQLiteMigrations();
//Get CDClient initial information
try {
CDClientManager::Instance()->Initialize();
CDClientManager::Instance();
} catch (CppSQLite3Exception& e) {
Game::logger->Log("WorldServer", "Failed to initialize CDServer SQLite Database");
Game::logger->Log("WorldServer", "May be caused by corrupted file: %s", cdclient_path.c_str());
Game::logger->Log("WorldServer", "May be caused by corrupted file: %s", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str());
Game::logger->Log("WorldServer", "Error: %s", e.errorMessage());
Game::logger->Log("WorldServer", "Error Code: %i", e.errorCode());
return EXIT_FAILURE;
@@ -141,60 +214,97 @@ int main(int argc, char** argv) {
std::cout << "Enter a username: ";
std::cin >> username;
std::unique_ptr<sql::PreparedStatement> userLookupStatement(Database::CreatePreppedStmt("SELECT id FROM accounts WHERE name=? LIMIT 1;"));
userLookupStatement->setString(1, username.c_str());
std::unique_ptr<sql::ResultSet> res(userLookupStatement->executeQuery());
if (res->rowsCount() > 0) {
Game::logger->Log("MasterServer", "Account with name \"%s\" already exists", username.c_str());
std::cout << "Do you want to change the password of that account? [y/n]?";
std::string prompt = "";
std::cin >> prompt;
if (prompt == "y" || prompt == "yes"){
uint32_t accountId = 0;
res->next();
accountId = res->getUInt(1);
if (accountId == 0) return EXIT_FAILURE;
//Read the password from the console without echoing it.
#ifdef __linux__
//This function is obsolete, but it only meant to be used by the
//sysadmin to create their first account.
password = getpass("Enter a password: ");
#else
std::cout << "Enter a password: ";
std::cin >> password;
#endif
// Regenerate hash based on new password
char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE];
int32_t bcryptState = ::bcrypt_gensalt(12, salt);
assert(bcryptState == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
std::unique_ptr<sql::PreparedStatement> userUpdateStatement(Database::CreatePreppedStmt("UPDATE accounts SET password = ? WHERE id = ?;"));
userUpdateStatement->setString(1, std::string(hash, BCRYPT_HASHSIZE).c_str());
userUpdateStatement->setUInt(2, accountId);
userUpdateStatement->execute();
Game::logger->Log("MasterServer", "Account \"%s\" password updated successfully!", username.c_str());
} else {
Game::logger->Log("MasterServer", "Account \"%s\" was not updated.", username.c_str());
}
return EXIT_SUCCESS;
}
//Read the password from the console without echoing it.
#ifdef __linux__
//This function is obsolete, but it only meant to be used by the
//sysadmin to create their first account.
password = getpass("Enter a password: ");
#else
std::cout << "Enter a password: ";
std::cin >> password;
#endif
#ifdef __linux__
//This function is obsolete, but it only meant to be used by the
//sysadmin to create their first account.
password = getpass("Enter a password: ");
#else
std::cout << "Enter a password: ";
std::cin >> password;
#endif
//Generate new hash for bcrypt
char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE];
int32_t bcryptState = ::bcrypt_gensalt(12, salt);
assert(bcryptState == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
//Create account
try {
std::unique_ptr<sql::PreparedStatement> statement(Database::CreatePreppedStmt("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);"));
statement->setString(1, username.c_str());
statement->setString(2, std::string(hash, BCRYPT_HASHSIZE).c_str());
statement->setInt(3, 9);
statement->execute();
} catch(sql::SQLException& e) {
Game::logger->Log("MasterServer", "A SQL error occurred!:\n %s", e.what());
return EXIT_FAILURE;
}
auto* statement = Database::CreatePreppedStmt("INSERT INTO accounts (name, password, ""gm_level) VALUES (?, ?, ?);");
statement->setString(1, username);
statement->setString(2, std::string(hash, BCRYPT_HASHSIZE).c_str());
statement->setInt(3, 9);
statement->execute();
delete statement;
std::cout << "Account created successfully!\n";
Database::Destroy("MasterServer");
delete Game::logger;
Game::logger->Log("MasterServer", "Account created successfully!");
return EXIT_SUCCESS;
}
int maxClients = 999;
int ourPort = 1000;
if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients"));
if (config.GetValue("port") != "") ourPort = std::stoi(config.GetValue("port"));
Game::randomEngine = std::mt19937(time(0));
uint32_t maxClients = 999;
uint32_t ourPort = 1000;
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
if (Game::config->GetValue("port") != "") ourPort = std::stoi(Game::config->GetValue("port"));
Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master);
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config, &Game::shouldShutdown);
//Query for the database for a server labeled "master"
auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'");
auto* result = masterLookupStatement->executeQuery();
auto master_server_ip = config.GetValue("master_ip");
auto master_server_ip = Game::config->GetValue("master_ip");
if (master_server_ip == "") {
master_server_ip = Game::server->GetIP();
@@ -203,7 +313,7 @@ int main(int argc, char** argv) {
//If we found a server, update it's IP and port to the current one.
if (result->next()) {
auto* updateStatement = Database::CreatePreppedStmt("UPDATE `servers` SET `ip` = ?, `port` = ? WHERE `id` = ?");
updateStatement->setString(1, master_server_ip);
updateStatement->setString(1, master_server_ip.c_str());
updateStatement->setInt(2, Game::server->GetPort());
updateStatement->setInt(3, result->getInt("id"));
updateStatement->execute();
@@ -211,7 +321,7 @@ int main(int argc, char** argv) {
} else {
//If we didn't find a server, create one.
auto* insertStatement = Database::CreatePreppedStmt("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171023)");
insertStatement->setString(1, master_server_ip);
insertStatement->setString(1, master_server_ip.c_str());
insertStatement->setInt(2, Game::server->GetPort());
insertStatement->execute();
delete insertStatement;
@@ -224,20 +334,23 @@ int main(int argc, char** argv) {
CDClientManager::Instance()->LoadHost();
//Depending on the config, start up servers:
if (config.GetValue("prestart_servers") != "" && config.GetValue("prestart_servers") == "1") {
if (Game::config->GetValue("prestart_servers") != "" && Game::config->GetValue("prestart_servers") == "1") {
StartChatServer();
Game::im->GetInstance(0, false, 0)->SetIsReady(true);
Game::im->GetInstance(1000, false, 0)->SetIsReady(true);
Game::im->GetInstance(0, false, 0);
Game::im->GetInstance(1000, false, 0);
StartAuthServer();
}
auto t = std::chrono::high_resolution_clock::now();
Packet* packet = nullptr;
int framesSinceLastFlush = 0;
int framesSinceLastSQLPing = 0;
int framesSinceKillUniverseCommand = 0;
constexpr uint32_t logFlushTime = 15 * masterFramerate;
constexpr uint32_t sqlPingTime = 10 * 60 * masterFramerate;
constexpr uint32_t shutdownUniverseTime = 10 * 60 * masterFramerate;
constexpr uint32_t instanceReadyTimeout = 30 * masterFramerate;
uint32_t framesSinceLastFlush = 0;
uint32_t framesSinceLastSQLPing = 0;
uint32_t framesSinceKillUniverseCommand = 0;
while (true) {
//In world we'd update our other systems here.
@@ -251,17 +364,17 @@ int main(int argc, char** argv) {
}
//Push our log every 15s:
if (framesSinceLastFlush >= 900) {
if (framesSinceLastFlush >= logFlushTime) {
Game::logger->Flush();
framesSinceLastFlush = 0;
} else
framesSinceLastFlush++;
//Every 10 min we ping our sql server to keep it alive hopefully:
if (framesSinceLastSQLPing >= 40000) {
if (framesSinceLastSQLPing >= sqlPingTime) {
//Find out the master's IP for absolutely no reason:
std::string masterIP;
int masterPort;
uint32_t masterPort;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
@@ -277,8 +390,8 @@ int main(int argc, char** argv) {
framesSinceLastSQLPing++;
//10m shutdown for universe kill command
if (shouldShutdown) {
if (framesSinceKillUniverseCommand >= 40000) {
if (Game::shouldShutdown) {
if (framesSinceKillUniverseCommand >= shutdownUniverseTime) {
//Break main loop and exit
break;
} else
@@ -302,7 +415,7 @@ int main(int argc, char** argv) {
instance->SetAffirmationTimeout(affirmTimeout);
if (affirmTimeout == 1000) {
if (affirmTimeout == instanceReadyTimeout) {
instance->Shutdown();
instance->SetIsShuttingDown(true);
@@ -321,17 +434,15 @@ int main(int argc, char** argv) {
}
}
t += std::chrono::milliseconds(highFrameRate);
t += std::chrono::milliseconds(masterFrameDelta);
std::this_thread::sleep_until(t);
}
FinalizeShutdown();
exit(EXIT_SUCCESS);
return EXIT_SUCCESS;
return FinalizeShutdown(EXIT_SUCCESS);
}
dLogger* SetupLogger() {
std::string logPath =
"./logs/MasterServer_" + std::to_string(time(nullptr)) + ".log";
(BinaryPathFinder::GetBinaryDir() / ("logs/MasterServer_" + std::to_string(time(nullptr)) + ".log")).string();
bool logToConsole = false;
bool logDebugStatements = false;
#ifdef _DEBUG
@@ -355,9 +466,15 @@ void HandlePacket(Packet* packet) {
Game::im->RemoveInstance(instance); //Delete the old
}
if (packet->systemAddress == chatServerMasterPeerSysAddr && !shouldShutdown) {
if (packet->systemAddress == chatServerMasterPeerSysAddr) {
chatServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
StartChatServer();
}
if (packet->systemAddress == authServerMasterPeerSysAddr) {
authServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
StartAuthServer();
}
}
if (packet->data[0] == ID_CONNECTION_LOST) {
@@ -368,17 +485,24 @@ void HandlePacket(Packet* packet) {
if (instance) {
LWOZONEID zoneID = instance->GetZoneID(); //Get the zoneID so we can recreate a server
Game::im->RemoveInstance(instance); //Delete the old
//Game::im->GetInstance(zoneID.GetMapID(), false, 0); //Create the new
}
if (packet->systemAddress == chatServerMasterPeerSysAddr && !shouldShutdown) {
if (packet->systemAddress == chatServerMasterPeerSysAddr) {
chatServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
StartChatServer();
}
if (packet->systemAddress == authServerMasterPeerSysAddr) {
authServerMasterPeerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
StartAuthServer();
}
}
if (packet->data[1] == MASTER) {
switch (packet->data[3]) {
case MSG_MASTER_REQUEST_PERSISTENT_ID: {
if (packet->length < 4) return;
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::MASTER) {
switch (static_cast<eMasterMessageType>(packet->data[3])) {
case eMasterMessageType::REQUEST_PERSISTENT_ID: {
Game::logger->Log("MasterServer", "A persistent ID req");
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -390,7 +514,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_REQUEST_ZONE_TRANSFER: {
case eMasterMessageType::REQUEST_ZONE_TRANSFER: {
Game::logger->Log("MasterServer", "Received zone transfer req");
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -403,14 +527,17 @@ void HandlePacket(Packet* packet) {
inStream.Read(mythranShift);
inStream.Read(zoneID);
inStream.Read(zoneClone);
if (shutdownSequenceStarted) {
Game::logger->Log("MasterServer", "Shutdown sequence has been started. Not creating a new zone.");
break;
}
Instance* in = Game::im->GetInstance(zoneID, false, zoneClone);
for (auto* instance : Game::im->GetInstances()) {
Game::logger->Log("MasterServer", "Instance: %i/%i/%i -> %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in);
}
if (!in->GetIsReady()) //Instance not ready, make a pending request
if (in && !in->GetIsReady()) //Instance not ready, make a pending request
{
in->GetPendingRequests().push_back({ requestID, static_cast<bool>(mythranShift), packet->systemAddress });
Game::logger->Log("MasterServer", "Server not ready, adding pending request %llu %i %i", requestID, zoneID, zoneClone);
@@ -423,7 +550,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_SERVER_INFO: {
case eMasterMessageType::SERVER_INFO: {
//MasterPackets::HandleServerInfo(packet);
//This is here because otherwise we'd have to include IM in
@@ -469,12 +596,20 @@ void HandlePacket(Packet* packet) {
chatServerMasterPeerSysAddr = copy;
}
if (theirServerType == ServerType::Auth) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
authServerMasterPeerSysAddr = copy;
}
Game::logger->Log("MasterServer", "Received server info, instance: %i port: %i", theirInstanceID, theirPort);
break;
}
case MSG_MASTER_SET_SESSION_KEY: {
case eMasterMessageType::SET_SESSION_KEY: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
uint32_t sessionKey = 0;
@@ -488,7 +623,7 @@ void HandlePacket(Packet* packet) {
activeSessions.erase(it.first);
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_NEW_SESSION_ALERT);
PacketUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::NEW_SESSION_ALERT);
bitStream.Write(sessionKey);
bitStream.Write<uint32_t>(username.size());
for (auto character : username) {
@@ -505,7 +640,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_REQUEST_SESSION_KEY: {
case eMasterMessageType::REQUEST_SESSION_KEY: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
std::string username = PacketUtils::ReadString(8, packet, false);
@@ -513,7 +648,7 @@ void HandlePacket(Packet* packet) {
for (auto key : activeSessions) {
if (key.second == username) {
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_SESSION_KEY_RESPONSE);
PacketUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SESSION_KEY_RESPONSE);
bitStream.Write(key.first);
PacketUtils::WriteString(bitStream, key.second, 64);
Game::server->Send(&bitStream, packet->systemAddress, false);
@@ -523,7 +658,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_PLAYER_ADDED: {
case eMasterMessageType::PLAYER_ADDED: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -543,7 +678,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_PLAYER_REMOVED: {
case eMasterMessageType::PLAYER_REMOVED: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -561,7 +696,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_CREATE_PRIVATE_ZONE: {
case eMasterMessageType::CREATE_PRIVATE_ZONE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -574,7 +709,7 @@ void HandlePacket(Packet* packet) {
uint32_t len;
inStream.Read<uint32_t>(len);
for (int i = 0; len > i; i++) {
for (uint32_t i = 0; len > i; i++) {
char character;
inStream.Read<char>(character);
password += character;
@@ -585,7 +720,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_REQUEST_PRIVATE_ZONE: {
case eMasterMessageType::REQUEST_PRIVATE_ZONE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -600,7 +735,7 @@ void HandlePacket(Packet* packet) {
uint32_t len;
inStream.Read<uint32_t>(len);
for (int i = 0; i < len; i++) {
for (uint32_t i = 0; i < len; i++) {
char character; inStream.Read<char>(character);
password += character;
}
@@ -620,7 +755,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_WORLD_READY: {
case eMasterMessageType::WORLD_READY: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -644,19 +779,23 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_PREP_ZONE: {
case eMasterMessageType::PREP_ZONE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
int zoneID;
int32_t zoneID;
inStream.Read(zoneID);
Game::logger->Log("MasterServer", "Prepping zone %i", zoneID);
Game::im->GetInstance(zoneID, false, 0);
if (shutdownSequenceStarted) {
Game::logger->Log("MasterServer", "Shutdown sequence has been started. Not prepping a new zone.");
break;
} else {
Game::logger->Log("MasterServer", "Prepping zone %i", zoneID);
Game::im->GetInstance(zoneID, false, 0);
}
break;
}
case MSG_MASTER_AFFIRM_TRANSFER_RESPONSE: {
case eMasterMessageType::AFFIRM_TRANSFER_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -676,7 +815,7 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_SHUTDOWN_RESPONSE: {
case eMasterMessageType::SHUTDOWN_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
@@ -691,9 +830,9 @@ void HandlePacket(Packet* packet) {
break;
}
case MSG_MASTER_SHUTDOWN_UNIVERSE: {
case eMasterMessageType::SHUTDOWN_UNIVERSE: {
Game::logger->Log("MasterServer", "Received shutdown universe command, shutting down in 10 minutes.");
shouldShutdown = true;
Game::shouldShutdown = true;
break;
}
@@ -704,76 +843,95 @@ void HandlePacket(Packet* packet) {
}
void StartChatServer() {
if (Game::shouldShutdown) {
Game::logger->Log("MasterServer", "Currently shutting down. Chat will not be restarted.");
return;
}
#ifdef __APPLE__
//macOS doesn't need sudo to run on ports < 1024
system("./ChatServer&");
auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
#elif _WIN32
system("start ./ChatServer.exe");
auto result = system(("start " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str());
#else
if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) {
system("sudo ./ChatServer&");
auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
} else {
system("./ChatServer&");
}
auto result = system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str());
}
#endif
}
void StartAuthServer() {
if (Game::shouldShutdown) {
Game::logger->Log("MasterServer", "Currently shutting down. Auth will not be restarted.");
return;
}
#ifdef __APPLE__
system("./AuthServer&");
auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str());
#elif _WIN32
system("start ./AuthServer.exe");
auto result = system(("start " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str());
#else
if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) {
system("sudo ./AuthServer&");
auto result = system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str());
} else {
system("./AuthServer&");
}
auto result = system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str());
}
#endif
}
void ShutdownSequence() {
void ShutdownSequence(int32_t signal) {
if (shutdownSequenceStarted) {
return;
}
if (!Game::im) {
FinalizeShutdown(EXIT_FAILURE);
}
Game::im->SetIsShuttingDown(true);
shutdownSequenceStarted = true;
Game::shouldShutdown = true;
if (Game::im) {
for (auto* instance : Game::im->GetInstances()) {
if (instance == nullptr) {
continue;
}
instance->Shutdown();
}
{
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN);
Game::server->Send(&bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
Game::logger->Log("MasterServer", "Triggered master shutdown");
}
auto* objIdManager = ObjectIDManager::TryInstance();
if (objIdManager != nullptr) {
if (objIdManager) {
objIdManager->SaveToDatabase();
Game::logger->Log("MasterServer", "Saved ObjectIDTracker to DB");
}
auto t = std::chrono::high_resolution_clock::now();
auto ticks = 0;
// A server might not be finished spinning up yet, remove all of those here.
for (auto* instance : Game::im->GetInstances()) {
if (!instance->GetIsReady()) {
Game::im->RemoveInstance(instance);
}
}
if (!Game::im) {
exit(EXIT_SUCCESS);
for (auto* instance : Game::im->GetInstances()) {
instance->SetIsShuttingDown(true);
}
Game::logger->Log("MasterServer", "Attempting to shutdown instances, max 60 seconds...");
auto t = std::chrono::high_resolution_clock::now();
uint32_t framesSinceShutdownStart = 0;
constexpr uint32_t maxShutdownTime = 60 * mediumFramerate;
bool allInstancesShutdown = false;
Packet* packet = nullptr;
while (true) {
auto packet = Game::server->Receive();
packet = Game::server->Receive();
if (packet) {
HandlePacket(packet);
Game::server->DeallocatePacket(packet);
packet = nullptr;
}
auto done = true;
allInstancesShutdown = true;
for (auto* instance : Game::im->GetInstances()) {
if (instance == nullptr) {
@@ -781,36 +939,37 @@ void ShutdownSequence() {
}
if (!instance->GetShutdownComplete()) {
done = false;
allInstancesShutdown = false;
}
}
if (done) {
if (allInstancesShutdown && authServerMasterPeerSysAddr == UNASSIGNED_SYSTEM_ADDRESS && chatServerMasterPeerSysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
Game::logger->Log("MasterServer", "Finished shutting down MasterServer!");
break;
}
t += std::chrono::milliseconds(highFrameRate);
t += std::chrono::milliseconds(mediumFrameDelta);
std::this_thread::sleep_until(t);
ticks++;
framesSinceShutdownStart++;
if (ticks == 600 * 6) {
if (framesSinceShutdownStart == maxShutdownTime) {
Game::logger->Log("MasterServer", "Finished shutting down by timeout!");
break;
}
}
FinalizeShutdown();
FinalizeShutdown(signal);
}
int FinalizeShutdown() {
int32_t FinalizeShutdown(int32_t signal) {
//Delete our objects here:
Database::Destroy("MasterServer");
if (Game::config) delete Game::config;
if (Game::im) delete Game::im;
if (Game::server) delete Game::server;
if (Game::logger) delete Game::logger;
exit(EXIT_SUCCESS);
return EXIT_SUCCESS;
if (signal != EXIT_SUCCESS) exit(signal);
return signal;
}

View File

@@ -51,13 +51,13 @@ uint32_t ObjectIDManager::GeneratePersistentID(void) {
uint32_t toReturn = ++this->currentPersistentID;
// So we peroidically save our ObjID to the database:
if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT!
// if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT!
sql::PreparedStatement* stmt = Database::CreatePreppedStmt(
"UPDATE object_id_tracker SET last_object_id=?");
stmt->setUInt(1, toReturn);
stmt->execute();
delete stmt;
}
// }
return toReturn;
}