From f8f5b731f189a1a31ceecbb5256bfc4a3da08ed7 Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Sun, 27 Nov 2022 06:59:59 -0500 Subject: [PATCH] Allow servers to be run from directories other than `build`. Read/write files relative to binary instead of cwd (#834) Allows the server to be run from a non-build directory. Also only read or write files relative to the build directory, regardless of where the server is run from --- dAuthServer/AuthServer.cpp | 3 +- dChatServer/ChatServer.cpp | 14 +++-- dCommon/BinaryPathFinder.cpp | 71 ++++++++++++++++++++++++ dCommon/BinaryPathFinder.h | 15 +++++ dCommon/CMakeLists.txt | 1 + dCommon/dClient/AssetManager.cpp | 8 ++- dCommon/dClient/AssetManager.h | 2 +- dCommon/dConfig.cpp | 5 +- dDatabase/MigrationRunner.cpp | 7 ++- dGame/dUtilities/SlashCommandHandler.cpp | 3 +- dGame/dUtilities/VanityUtilities.cpp | 5 +- dGame/dUtilities/dLocale.cpp | 3 +- dMasterServer/InstanceManager.cpp | 11 ++-- dMasterServer/MasterServer.cpp | 40 +++++++------ dNavigation/dNavMesh.cpp | 3 +- dWorldServer/WorldServer.cpp | 13 +++-- 16 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 dCommon/BinaryPathFinder.cpp create mode 100644 dCommon/BinaryPathFinder.h diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 73cb4212..9621f683 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -11,6 +11,7 @@ #include "Database.h" #include "dConfig.h" #include "Diagnostics.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -150,7 +151,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger() { - std::string logPath = "./logs/AuthServer_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/AuthServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index fc8bdd5b..e0073747 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -13,6 +13,7 @@ #include "dChatFilter.h" #include "Diagnostics.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" #include "PlayerContainer.h" #include "ChatPacketHandler.h" @@ -53,9 +54,14 @@ int main(int argc, char** argv) { Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = 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("ChatServer", "Got an error while setting up assets: %s", ex.what()); @@ -167,7 +173,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger() { - std::string logPath = "./logs/ChatServer_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/ChatServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG diff --git a/dCommon/BinaryPathFinder.cpp b/dCommon/BinaryPathFinder.cpp new file mode 100644 index 00000000..b1c2afef --- /dev/null +++ b/dCommon/BinaryPathFinder.cpp @@ -0,0 +1,71 @@ +#include +#include +#include "BinaryPathFinder.h" +#include "dPlatforms.h" + +#if defined(DARKFLAME_PLATFORM_WIN32) +#include +#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS) +#include +#elif defined(DARKFLAME_PLATFORM_FREEBSD) +#include +#include +#include +#endif + +std::filesystem::path BinaryPathFinder::binaryDir; + +std::filesystem::path BinaryPathFinder::GetBinaryDir() { + if (!binaryDir.empty()) { + return binaryDir; + } + + std::string pathStr; + + // Derived from boost::dll::program_location, licensed under the Boost Software License: http://www.boost.org/LICENSE_1_0.txt +#if defined(DARKFLAME_PLATFORM_WIN32) + char path[MAX_PATH]; + GetModuleFileName(NULL, path, MAX_PATH); + pathStr = std::string(path); +#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS) + char path[1024]; + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) == 0) { + pathStr = std::string(path); + } else { + // The filepath size is greater than our initial buffer size, so try again with the size + // that _NSGetExecutablePath told us it actually is + char *p = new char[size]; + if (_NSGetExecutablePath(p, &size) != 0) { + throw std::runtime_error("Failed to get binary path from _NSGetExecutablePath"); + } + + pathStr = std::string(p); + delete[] p; + } +#elif defined(DARKFLAME_PLATFORM_FREEBSD) + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + char buf[10240]; + size_t cb = sizeof(buf); + sysctl(mib, 4, buf, &cb, NULL, 0); + pathStr = std::string(buf); +#else // DARKFLAME_PLATFORM_LINUX || DARKFLAME_PLATFORM_UNIX || DARKFLAME_PLATFORM_ANDROID + pathStr = std::filesystem::read_symlink("/proc/self/exe"); +#endif + + // Some methods like _NSGetExecutablePath could return a symlink + // Either way, we need to get the parent path because we want the directory, not the binary itself + // We also ensure that it is an absolute path so that it is valid if we need to construct a path + // to exucute on unix systems (eg sudo BinaryPathFinder::GetBinaryDir() / WorldServer) + if (std::filesystem::is_symlink(pathStr)) { + binaryDir = std::filesystem::absolute(std::filesystem::read_symlink(pathStr).parent_path()); + } else { + binaryDir = std::filesystem::absolute(std::filesystem::path(pathStr).parent_path()); + } + + return binaryDir; +} diff --git a/dCommon/BinaryPathFinder.h b/dCommon/BinaryPathFinder.h new file mode 100644 index 00000000..c4ca6da2 --- /dev/null +++ b/dCommon/BinaryPathFinder.h @@ -0,0 +1,15 @@ +#pragma once + +#ifndef __BINARYPATHFINDER__H__ +#define __BINARYPATHFINDER__H__ + +#include + +class BinaryPathFinder { +private: + static std::filesystem::path binaryDir; +public: + static std::filesystem::path GetBinaryDir(); +}; + +#endif //!__BINARYPATHFINDER__H__ diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 46102b74..8d02186b 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -15,6 +15,7 @@ set(DCOMMON_SOURCES "AMFFormat.cpp" "Type.cpp" "ZCompression.cpp" "BrickByBrickFix.cpp" + "BinaryPathFinder.cpp" ) add_subdirectory(dClient) diff --git a/dCommon/dClient/AssetManager.cpp b/dCommon/dClient/AssetManager.cpp index 349b5271..dc3db0a1 100644 --- a/dCommon/dClient/AssetManager.cpp +++ b/dCommon/dClient/AssetManager.cpp @@ -1,15 +1,17 @@ +#include + #include "AssetManager.h" #include "Game.h" #include "dLogger.h" #include -AssetManager::AssetManager(const std::string& path) { +AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::is_directory(path)) { - throw std::runtime_error("Attempted to load asset bundle (" + path + ") however it is not a valid directory."); + throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory."); } - m_Path = std::filesystem::path(path); + m_Path = path; if (std::filesystem::exists(m_Path / "client") && std::filesystem::exists(m_Path / "versions")) { m_AssetBundleType = eAssetBundleType::Packed; diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index 87653845..73b24f18 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -48,7 +48,7 @@ struct AssetMemoryBuffer : std::streambuf { class AssetManager { public: - AssetManager(const std::string& path); + AssetManager(const std::filesystem::path& path); ~AssetManager(); std::filesystem::path GetResPath(); diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index c22c91cc..1bc940e8 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,9 +1,10 @@ #include "dConfig.h" #include +#include "BinaryPathFinder.h" dConfig::dConfig(const std::string& filepath) { m_EmptyString = ""; - std::ifstream in(filepath); + std::ifstream in(BinaryPathFinder::GetBinaryDir() / filepath); if (!in.good()) return; std::string line; @@ -13,7 +14,7 @@ dConfig::dConfig(const std::string& filepath) { } } - std::ifstream sharedConfig("sharedconfig.ini", std::ios::in); + std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in); if (!sharedConfig.good()) return; while (std::getline(sharedConfig, line)) { diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 25312608..fe0f933a 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -6,12 +6,13 @@ #include "Game.h" #include "GeneralUtils.h" #include "dLogger.h" +#include "BinaryPathFinder.h" #include Migration LoadMigration(std::string path) { Migration migration{}; - std::ifstream file("./migrations/" + path); + std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path); if (file.is_open()) { std::string line; @@ -37,7 +38,7 @@ void MigrationRunner::RunMigrations() { sql::SQLString finalSQL = ""; bool runSd0Migrations = false; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/dlu/")) { + for (const auto& entry : GeneralUtils::GetFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { auto migration = LoadMigration("dlu/" + entry); if (migration.data.empty()) { @@ -97,7 +98,7 @@ void MigrationRunner::RunSQLiteMigrations() { stmt->execute(); delete stmt; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/cdserver/")) { + for (const auto& entry : GeneralUtils::GetFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { auto migration = LoadMigration("cdserver/" + entry); if (migration.data.empty()) continue; diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 8f4ac1a8..2f4d9211 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -64,6 +64,7 @@ #include "ScriptedActivityComponent.h" #include "LevelProgressionComponent.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { std::string chatCommand; @@ -248,7 +249,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown("./vanity/CREDITS.md") : VanityUtilities::ParseMarkdown("./vanity/INFO.md"); + const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); { AMFArrayValue args; diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 733d94c4..3a1259a2 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -13,6 +13,7 @@ #include "tinyxml2.h" #include "Game.h" #include "dLogger.h" +#include "BinaryPathFinder.h" #include @@ -27,7 +28,7 @@ void VanityUtilities::SpawnVanity() { const uint32_t zoneID = Game::server->GetZoneID(); - ParseXML("./vanity/NPC.xml"); + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); // Loop through all parties for (const auto& party : m_Parties) { @@ -131,7 +132,7 @@ void VanityUtilities::SpawnVanity() { info.spawnerID = EntityManager::Instance()->GetZoneControlEntity()->GetObjectID(); info.settings = { new LDFData(u"hasCustomText", true), - new LDFData(u"customText", ParseMarkdown("./vanity/TESTAMENT.md")) }; + new LDFData(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string())) }; auto* entity = EntityManager::Instance()->CreateEntity(info); diff --git a/dGame/dUtilities/dLocale.cpp b/dGame/dUtilities/dLocale.cpp index 65ef3a58..de65511b 100644 --- a/dGame/dUtilities/dLocale.cpp +++ b/dGame/dUtilities/dLocale.cpp @@ -9,13 +9,14 @@ #include "tinyxml2.h" #include "Game.h" #include "dConfig.h" +#include "BinaryPathFinder.h" dLocale::dLocale() { if (Game::config->GetValue("locale_enabled") != "1") { return; } - std::ifstream file(m_LocalePath); + std::ifstream file(BinaryPathFinder::GetBinaryDir() / m_LocalePath); if (!file.good()) { return; diff --git a/dMasterServer/InstanceManager.cpp b/dMasterServer/InstanceManager.cpp index 0c605ed6..83378dbb 100644 --- a/dMasterServer/InstanceManager.cpp +++ b/dMasterServer/InstanceManager.cpp @@ -10,6 +10,7 @@ #include "dMessageIdentifiers.h" #include "MasterPackets.h" #include "PacketUtils.h" +#include "BinaryPathFinder.h" InstanceManager::InstanceManager(dLogger* logger, const std::string& externalIP) { mLogger = logger; @@ -48,13 +49,13 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW //Start the actual process: #ifdef _WIN32 - std::string cmd = "start ./WorldServer.exe -zone "; + std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone "; #else std::string cmd; if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) { - cmd = "sudo ./WorldServer -zone "; + cmd = "sudo " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; } else { - cmd = "./WorldServer -zone "; + cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; } #endif @@ -300,10 +301,10 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon 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)); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 061a91c7..34e2f71a 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -26,6 +26,7 @@ #include "dLogger.h" #include "dServer.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -102,9 +103,14 @@ int main(int argc, char** argv) { } try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = 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()); @@ -127,18 +133,16 @@ int main(int argc, char** argv) { stmt->executeUpdate(); delete stmt; - std::string res = "python3 ../thirdparty/docker-utils/utils/fdb_to_sqlite.py " + (Game::assetManager->GetResPath() / "cdclient.fdb").string(); + std::string res = "python3 " + + (BinaryPathFinder::GetBinaryDir() / "../thirdparty/docker-utils/utils/fdb_to_sqlite.py").string() + + " --sqlite_path " + (Game::assetManager->GetResPath() / "CDServer.sqlite").string() + + " " + (Game::assetManager->GetResPath() / "cdclient.fdb").string(); int result = system(res.c_str()); if (result != 0) { Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite"); return EXIT_FAILURE; } - - if (std::rename("./cdclient.sqlite", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()) != 0) { - Game::logger->Log("MasterServer", "Failed to move cdclient file."); - return EXIT_FAILURE; - } } //Connect to CDClient @@ -363,7 +367,7 @@ int main(int argc, char** argv) { 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 @@ -738,28 +742,28 @@ void HandlePacket(Packet* packet) { void StartChatServer() { #ifdef __APPLE__ //macOS doesn't need sudo to run on ports < 1024 - system("./ChatServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); #elif _WIN32 - system("start ./ChatServer.exe"); + system(("start " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) { - system("sudo ./ChatServer&"); + system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); } else { - system("./ChatServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); } #endif } void StartAuthServer() { #ifdef __APPLE__ - system("./AuthServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); #elif _WIN32 - system("start ./AuthServer.exe"); + system(("start " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) { - system("sudo ./AuthServer&"); + system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); } else { - system("./AuthServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); } #endif } diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index e5ba0129..fdba4a2d 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -7,6 +7,7 @@ #include "dPlatforms.h" #include "NiPoint3.h" #include "BinaryIO.h" +#include "BinaryPathFinder.h" #include "dZoneManager.h" @@ -43,7 +44,7 @@ dNavMesh::~dNavMesh() { void dNavMesh::LoadNavmesh() { - std::string path = "./navmeshes/" + std::to_string(m_ZoneId) + ".bin"; + std::string path = (BinaryPathFinder::GetBinaryDir() / "navmeshes/" / (std::to_string(m_ZoneId) + ".bin")).string(); if (!BinaryIO::DoesFileExist(path)) { return; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index d4e55cb5..b7d7cc14 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -17,6 +17,7 @@ #include "Metrics.hpp" #include "PerformanceManager.h" #include "Diagnostics.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -146,9 +147,13 @@ int main(int argc, char** argv) { if (config.GetValue("disable_chat") == "1") chatDisabled = true; try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = 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("WorldServer", "Got an error while setting up assets: %s", ex.what()); @@ -489,7 +494,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger(int zoneID, int instanceID) { - std::string logPath = "./logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG