Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 66 additions & 4 deletions src/game_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -37,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");
}
Expand All @@ -46,9 +85,11 @@ float GetMinimumHeaderVersion(const GameId gameId)
{
switch (gameId) {
case GameId::tes3:
case GameId::openmw:
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:
Expand All @@ -71,11 +112,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:
Expand All @@ -88,7 +130,11 @@ 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);
case GameId::oblivionRemastered:
return gamePath / "OblivionRemastered" / "Content" / "Dev" / "ObvData" / "Data";
default:
throw std::logic_error("Unrecognised game ID");
}
Expand Down Expand Up @@ -123,6 +169,10 @@ std::string ToString(const GameId gameId)
return "Fallout4VR";
case GameId::starfield:
return "Starfield";
case GameId::openmw:
return "OpenMW";
case GameId::oblivionRemastered:
return "Oblivion Remastered";
default:
throw std::logic_error("Unrecognised game ID");
}
Expand All @@ -140,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";
Expand All @@ -158,6 +209,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");
}
Expand Down Expand Up @@ -192,6 +249,10 @@ std::string GetGameName(const GameId gameId)
return "Fallout 4 VR";
case GameId::starfield:
return "Starfield";
case GameId::openmw:
return "OpenMW";
case GameId::oblivionRemastered:
return "TES IV: Oblivion Remastered";
default:
throw std::logic_error("Unrecognised game ID");
}
Expand All @@ -204,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";
Expand Down Expand Up @@ -301,7 +363,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)
Expand Down
9 changes: 6 additions & 3 deletions src/game_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -30,14 +30,17 @@ enum struct GameId : uint8_t
fonv,
fo4,
fo4vr,
starfield
starfield,
openmw,
oblivionRemastered
};

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

Expand Down
56 changes: 32 additions & 24 deletions src/lootthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ using std::recursive_mutex;

namespace lootcli
{
static const std::set<std::string> oldDefaultBranches({"master", "v0.7", "v0.8",
"v0.10", "v0.13", "v0.14",
"v0.15", "v0.17", "v0.18"});
static const std::set<std::string>
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);
Expand Down Expand Up @@ -55,13 +55,20 @@ std::string ToLower(std::string text)
void LOOTWorker::setGame(const std::string& gameName)
{
static std::map<std::string, loot::GameId> 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));

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -667,7 +680,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);
});

Expand Down Expand Up @@ -784,21 +797,23 @@ int LOOTWorker::run()

progress(Progress::LoadingLists);

gameHandle->GetDatabase().LoadMasterlist(masterlistPath().string());
fs::path userlist = userlistPath();
gameHandle->GetDatabase().LoadLists(masterlistPath().string(),
fs::exists(userlist) ? userlistPath().string()
: fs::path());
if (fs::exists(userlist))
gameHandle->GetDatabase().LoadUserlist(userlist.string());

progress(Progress::ReadingPlugins);
gameHandle->LoadCurrentLoadOrderState();
auto loadOrder = gameHandle->GetLoadOrder();
std::vector<std::filesystem::path> pluginsList;
for (auto plugin : gameHandle->GetLoadOrder()) {
std::filesystem::path pluginPath(plugin);
pluginsList.push_back(pluginPath);
}
gameHandle->LoadPlugins(pluginsList, false);

progress(Progress::SortingPlugins);
std::vector<std::string> sortedPlugins = gameHandle->SortPlugins(pluginsList);
std::vector<std::string> sortedPlugins = gameHandle->SortPlugins(loadOrder);

progress(Progress::WritingLoadorder);

Expand Down Expand Up @@ -1040,14 +1055,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;
Expand All @@ -1056,7 +1064,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();
}

Expand Down
3 changes: 2 additions & 1 deletion src/lootthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define LOOTTHREAD_H

#include "game_settings.h"
#include "loot/database_interface.h"
#include <lootcli/lootcli.h>

namespace loot
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/version.rc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading