From 0d3788fb7ab0ca7555a07e4d5dcf9bb471c9c552 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 27 Apr 2025 02:08:53 -0500 Subject: [PATCH 1/3] Updates for libloot 0.26.0 --- src/game_settings.cpp | 59 ++++++++++++++++++++++++++++++++++++++++--- src/game_settings.h | 6 +++-- src/lootthread.cpp | 21 ++++++--------- src/lootthread.h | 3 ++- 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/src/game_settings.cpp b/src/game_settings.cpp index bdee2c7..2162931 100644 --- a/src/game_settings.cpp +++ b/src/game_settings.cpp @@ -11,10 +11,47 @@ static constexpr float FONV_MINIMUM_HEADER_VERSION = 1.32f; static constexpr float FO4_MINIMUM_HEADER_VERSION = 0.95f; static constexpr float STARFIELD_MINIMUM_HEADER_VERSION = 0.96f; +std::filesystem::path GetOpenMWDataPath(const std::filesystem::path& gamePath) +{ +#ifndef _WIN32 + if (gamePath == "/usr/games") { + // Ubuntu, Debian + return "/usr/share/games/openmw/resources/vfs"; + } else if (gamePath == "/run/host/usr/games") { + // Ubuntu, Debian from inside a Flatpak sandbox + return "/run/host/usr/share/games/openmw/resources/vfs"; + } else if (gamePath == "/usr/bin") { + const auto path = "/usr/share/games/openmw/resources/vfs"; + if (std::filesystem::exists(path)) { + // Arch + return path; + } + + // OpenSUSE + return "/usr/share/openmw/resources/vfs"; + } else if (gamePath == "/run/host/usr/bin") { + const auto path = "/run/host/usr/share/games/openmw/resources/vfs"; + if (std::filesystem::exists(path)) { + // Arch from inside a Flatpak sandbox + return path; + } + + // OpenSUSE from inside a Flatpak sandbox + return "/run/host/usr/share/openmw/resources/vfs"; + } else if (boost::ends_with(gamePath.u8string(), + "/app/org.openmw.OpenMW/current/active/files/bin")) { + // Flatpak + return gamePath / "../share/games/openmw/resources/vfs"; + } +#endif + return gamePath / "resources" / "vfs"; +} + GameType GetGameType(const GameId gameId) { switch (gameId) { case GameId::tes3: + case GameId::openmw: return GameType::tes3; case GameId::tes4: case GameId::nehrim: @@ -46,6 +83,7 @@ float GetMinimumHeaderVersion(const GameId gameId) { switch (gameId) { case GameId::tes3: + case GameId::openmw: return MORROWIND_MINIMUM_HEADER_VERSION; case GameId::tes4: case GameId::nehrim: @@ -71,11 +109,12 @@ float GetMinimumHeaderVersion(const GameId gameId) } } -std::string GetPluginsFolderName(GameId gameId) +std::filesystem::path GetDataPath(const GameId gameId, + const std::filesystem::path& gamePath) { switch (gameId) { case GameId::tes3: - return "Data Files"; + return gamePath / "Data Files"; case GameId::tes4: case GameId::nehrim: case GameId::tes5: @@ -88,7 +127,9 @@ std::string GetPluginsFolderName(GameId gameId) case GameId::fo4: case GameId::fo4vr: case GameId::starfield: - return "Data"; + return gamePath / "Data"; + case GameId::openmw: + return GetOpenMWDataPath(gamePath); default: throw std::logic_error("Unrecognised game ID"); } @@ -123,6 +164,8 @@ std::string ToString(const GameId gameId) return "Fallout4VR"; case GameId::starfield: return "Starfield"; + case GameId::openmw: + return "OpenMW"; default: throw std::logic_error("Unrecognised game ID"); } @@ -158,6 +201,12 @@ std::string GetMasterFilename(const GameId gameId) return "Fallout4.esm"; case GameId::starfield: return "Starfield.esm"; + case GameId::openmw: + // This isn't actually a master file, but it's hardcoded to load first, + // and the value is only used to check the game is installed and to + // skip fully loading this file before sorting - and omwscripts files + // don't get loaded anyway. + return "builtin.omwscripts"; default: throw std::logic_error("Unrecognised game ID"); } @@ -192,6 +241,8 @@ std::string GetGameName(const GameId gameId) return "Fallout 4 VR"; case GameId::starfield: return "Starfield"; + case GameId::openmw: + return "OpenMW"; default: throw std::logic_error("Unrecognised game ID"); } @@ -301,7 +352,7 @@ std::filesystem::path GameSettings::GameLocalPath() const std::filesystem::path GameSettings::DataPath() const { - return gamePath_ / GetPluginsFolderName(id_); + return GetDataPath(id_, gamePath_); } GameSettings& GameSettings::SetName(const std::string& name) diff --git a/src/game_settings.h b/src/game_settings.h index 054fc54..998f64c 100644 --- a/src/game_settings.h +++ b/src/game_settings.h @@ -30,14 +30,16 @@ enum struct GameId : uint8_t fonv, fo4, fo4vr, - starfield + starfield, + openmw }; GameType GetGameType(const GameId gameId); float GetMinimumHeaderVersion(const GameId gameId); -std::string GetPluginsFolderName(GameId gamiId); +std::filesystem::path GetDataPath(const GameId gamiId, + const std::filesystem::path& gamePath); std::string ToString(const GameId gameId); diff --git a/src/lootthread.cpp b/src/lootthread.cpp index 9066093..c08571f 100644 --- a/src/lootthread.cpp +++ b/src/lootthread.cpp @@ -667,7 +667,7 @@ int LOOTWorker::run() std::locale::global(gen("en.UTF-8")); } - loot::SetLoggingCallback([&](loot::LogLevel level, const char* message) { + loot::SetLoggingCallback([&](loot::LogLevel level, std::string_view message) { log(level, message); }); @@ -785,20 +785,22 @@ int LOOTWorker::run() progress(Progress::LoadingLists); fs::path userlist = userlistPath(); - gameHandle->GetDatabase().LoadLists(masterlistPath().string(), - fs::exists(userlist) ? userlistPath().string() + gameHandle->GetDatabase().LoadMasterlist(masterlistPath().string()); + gameHandle->GetDatabase().LoadUserlist(fs::exists(userlist) ? userlistPath().string() : fs::path()); progress(Progress::ReadingPlugins); gameHandle->LoadCurrentLoadOrderState(); + auto loadOrder = gameHandle->GetLoadOrder(); std::vector pluginsList; for (auto plugin : gameHandle->GetLoadOrder()) { std::filesystem::path pluginPath(plugin); pluginsList.push_back(pluginPath); } + gameHandle->LoadPlugins(pluginsList, false); progress(Progress::SortingPlugins); - std::vector sortedPlugins = gameHandle->SortPlugins(pluginsList); + std::vector sortedPlugins = gameHandle->SortPlugins(loadOrder); progress(Progress::WritingLoadorder); @@ -1040,14 +1042,7 @@ void LOOTWorker::progress(Progress p) std::cout.flush(); } -std::string escapeNewlines(const std::string& s) -{ - auto ss = boost::replace_all_copy(s, "\n", "\\n"); - boost::replace_all(ss, "\r", "\\r"); - return ss; -} - -void LOOTWorker::log(loot::LogLevel level, const std::string& message) const +void LOOTWorker::log(loot::LogLevel level, const std::string_view message) const { if (level < m_LogLevel) { return; @@ -1056,7 +1051,7 @@ void LOOTWorker::log(loot::LogLevel level, const std::string& message) const const auto ll = fromLootLogLevel(level); const auto levelName = logLevelToString(ll); - std::cout << "[" << levelName << "] " << escapeNewlines(message) << "\n"; + std::cout << "[" << levelName << "] " << message << "\n"; std::cout.flush(); } diff --git a/src/lootthread.h b/src/lootthread.h index 0bf1e68..647f4a9 100644 --- a/src/lootthread.h +++ b/src/lootthread.h @@ -2,6 +2,7 @@ #define LOOTTHREAD_H #include "game_settings.h" +#include "loot/database_interface.h" #include namespace loot @@ -35,7 +36,7 @@ class LOOTWorker private: void progress(Progress p); - void log(loot::LogLevel level, const std::string& message) const; + void log(loot::LogLevel level, const std::string_view message) const; DWORD GetFile(const WCHAR* szUrl, const CHAR* szFileName); void getSettings(const std::filesystem::path& file); From 0b2d558b0182f3407ca4144c3d91664b5138ca5d Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 3 May 2025 02:48:49 -0500 Subject: [PATCH 2/3] Update for 0.26.1 and OR support --- src/game_settings.cpp | 11 +++++++++++ src/game_settings.h | 5 +++-- src/lootthread.cpp | 39 ++++++++++++++++++++++++++------------- src/version.rc | 2 +- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/game_settings.cpp b/src/game_settings.cpp index 2162931..20af34b 100644 --- a/src/game_settings.cpp +++ b/src/game_settings.cpp @@ -74,6 +74,8 @@ GameType GetGameType(const GameId gameId) return GameType::fo4vr; case GameId::starfield: return GameType::starfield; + case GameId::oblivionRemastered: + return GameType::oblivionRemastered; default: throw std::logic_error("Unrecognised game ID"); } @@ -87,6 +89,7 @@ float GetMinimumHeaderVersion(const GameId gameId) return MORROWIND_MINIMUM_HEADER_VERSION; case GameId::tes4: case GameId::nehrim: + case GameId::oblivionRemastered: return OBLIVION_MINIMUM_HEADER_VERSION; case GameId::tes5: case GameId::enderal: @@ -130,6 +133,8 @@ std::filesystem::path GetDataPath(const GameId gameId, return gamePath / "Data"; case GameId::openmw: return GetOpenMWDataPath(gamePath); + case GameId::oblivionRemastered: + return gamePath / "OblivionRemastered" / "Content" / "Dev" / "ObvData" / "Data"; default: throw std::logic_error("Unrecognised game ID"); } @@ -166,6 +171,8 @@ std::string ToString(const GameId gameId) return "Starfield"; case GameId::openmw: return "OpenMW"; + case GameId::oblivionRemastered: + return "Oblivion Remastered"; default: throw std::logic_error("Unrecognised game ID"); } @@ -183,6 +190,7 @@ std::string GetMasterFilename(const GameId gameId) case GameId::tes3: return "Morrowind.esm"; case GameId::tes4: + case GameId::oblivionRemastered: return "Oblivion.esm"; case GameId::nehrim: return "Nehrim.esm"; @@ -243,6 +251,8 @@ std::string GetGameName(const GameId gameId) return "Starfield"; case GameId::openmw: return "OpenMW"; + case GameId::oblivionRemastered: + return "TES IV: Oblivion Remastered"; default: throw std::logic_error("Unrecognised game ID"); } @@ -255,6 +265,7 @@ std::string GetDefaultMasterlistRepositoryName(const GameId gameId) return "morrowind"; case GameId::tes4: case GameId::nehrim: + case GameId::oblivionRemastered: return "oblivion"; case GameId::tes5: return "skyrim"; diff --git a/src/game_settings.h b/src/game_settings.h index 998f64c..bc775c9 100644 --- a/src/game_settings.h +++ b/src/game_settings.h @@ -14,7 +14,7 @@ namespace loot constexpr inline std::string_view NEHRIM_STEAM_REGISTRY_KEY = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App " "1014940\\InstallLocation"; -static constexpr const char* DEFAULT_MASTERLIST_BRANCH = "v0.21"; +static constexpr const char* DEFAULT_MASTERLIST_BRANCH = "v0.26"; enum struct GameId : uint8_t { @@ -31,7 +31,8 @@ enum struct GameId : uint8_t fo4, fo4vr, starfield, - openmw + openmw, + oblivionRemastered }; GameType GetGameType(const GameId gameId); diff --git a/src/lootthread.cpp b/src/lootthread.cpp index c08571f..a06cd5f 100644 --- a/src/lootthread.cpp +++ b/src/lootthread.cpp @@ -17,9 +17,9 @@ using std::recursive_mutex; namespace lootcli { -static const std::set oldDefaultBranches({"master", "v0.7", "v0.8", - "v0.10", "v0.13", "v0.14", - "v0.15", "v0.17", "v0.18"}); +static const std::set + oldDefaultBranches({"master", "v0.7", "v0.8", "v0.10", "v0.13", "v0.14", "v0.15", + "v0.17", "v0.18", "v0.21"}); static const std::regex GITHUB_REPO_URL_REGEX = std::regex(R"(^https://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$)", std::regex::ECMAScript | std::regex::icase); @@ -55,13 +55,20 @@ std::string ToLower(std::string text) void LOOTWorker::setGame(const std::string& gameName) { static std::map gameMap = { - {"morrowind", loot::GameId::tes3}, {"oblivion", loot::GameId::tes4}, - {"fallout3", loot::GameId::fo3}, {"fallout4", loot::GameId::fo4}, - {"fallout4vr", loot::GameId::fo4vr}, {"falloutnv", loot::GameId::fonv}, - {"skyrim", loot::GameId::tes5}, {"skyrimse", loot::GameId::tes5se}, - {"skyrimvr", loot::GameId::tes5vr}, {"nehrim", loot::GameId::nehrim}, - {"enderal", loot::GameId::enderal}, {"enderalse", loot::GameId::enderalse}, - {"starfield", loot::GameId::starfield}}; + {"morrowind", loot::GameId::tes3}, + {"oblivion", loot::GameId::tes4}, + {"fallout3", loot::GameId::fo3}, + {"fallout4", loot::GameId::fo4}, + {"fallout4vr", loot::GameId::fo4vr}, + {"falloutnv", loot::GameId::fonv}, + {"skyrim", loot::GameId::tes5}, + {"skyrimse", loot::GameId::tes5se}, + {"skyrimvr", loot::GameId::tes5vr}, + {"nehrim", loot::GameId::nehrim}, + {"enderal", loot::GameId::enderal}, + {"enderalse", loot::GameId::enderalse}, + {"starfield", loot::GameId::starfield}, + {"oblivionremastered", loot::GameId::oblivionRemastered}}; auto iter = gameMap.find(ToLower(gameName)); @@ -197,6 +204,12 @@ void LOOTWorker::getSettings(const fs::path& file) gameId = GameId::fo4; } else if (gameType == "Fallout4VR") { gameId = GameId::fo4vr; + } else if (gameType == "Starfield") { + gameId = GameId::starfield; + } else if (gameType == "OpenMW") { + gameId = GameId::openmw; + } else if (gameType == "Oblivion Remastered") { + gameId = GameId::oblivionRemastered; } else { throw std::runtime_error( "invalid value for 'type' key in game settings table"); @@ -784,10 +797,10 @@ int LOOTWorker::run() progress(Progress::LoadingLists); - fs::path userlist = userlistPath(); gameHandle->GetDatabase().LoadMasterlist(masterlistPath().string()); - gameHandle->GetDatabase().LoadUserlist(fs::exists(userlist) ? userlistPath().string() - : fs::path()); + fs::path userlist = userlistPath(); + if (fs::exists(userlist)) + gameHandle->GetDatabase().LoadUserlist(userlist.string()); progress(Progress::ReadingPlugins); gameHandle->LoadCurrentLoadOrderState(); diff --git a/src/version.rc b/src/version.rc index 900d239..bf6ebb4 100644 --- a/src/version.rc +++ b/src/version.rc @@ -22,7 +22,7 @@ VALUE "CompanyName", "Mod Organizer 2 Team\0" VALUE "FileDescription", "LOOT Command line interface\0" VALUE "OriginalFilename", "lootcli.exe\0" VALUE "InternalName", "lootcli\0" -VALUE "LegalCopyright", "Copyright 2011-2016 Sebastian Herbord\r\nCopyright 2016-2023 Mod Organizer 2 contributors\0" +VALUE "LegalCopyright", "Copyright 2011-2016 Sebastian Herbord\r\nCopyright 2016-2025 Mod Organizer 2 contributors\0" VALUE "ProductName", "lootcli\0" VALUE "ProductVersion", VER_FILEVERSION_STR END From 50694c41bfa106ce6e721a8caf992da5dacd123c Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Wed, 7 May 2025 01:30:36 -0500 Subject: [PATCH 3/3] Formatting pass --- src/game_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_settings.cpp b/src/game_settings.cpp index 20af34b..89a2df8 100644 --- a/src/game_settings.cpp +++ b/src/game_settings.cpp @@ -113,7 +113,7 @@ float GetMinimumHeaderVersion(const GameId gameId) } std::filesystem::path GetDataPath(const GameId gameId, - const std::filesystem::path& gamePath) + const std::filesystem::path& gamePath) { switch (gameId) { case GameId::tes3: