aboutsummaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 03:00:11 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 03:00:11 -0500
commitb54e479a3f9e973a083c643905f21c59fadd63bb (patch)
treeff920cc2614c5d014e7de910cbad75875257776e /libs
parentbf7a57fc55493247a624ecfd65e4d73964e7d8d2 (diff)
Remove LOOT integration entirely
The sort button was already hidden on Linux (setVisible(false)) and the LOOT feedback path (LootDialog -> addLootReport) has always been Windows-only, meaning the dirty/incompatibilities/missingMasters/messages tooltip bullets and the dirty-plugin icon could never fire on Linux. MO2 shipped lootcli + libloot for a load-order sort mechanism that was unreachable from the UI. Users who want LOOT can download it from https://github.com/loot/loot and run it against their instance directly. - Drop libs/lootcli/, libloot Dockerfile build step, and lootcli staging/patchelf in build-inner.sh - Drop src/src/loot.{cpp,h}, src/src/lootdialog.{cpp,h,ui} - Extract MarkdownDocument / MarkdownPage (used by UpdateDialog for its changelog view) into src/src/markdowndocument.{h,cpp} - Strip addLootReport, makeLootTooltip, Loot::Plugin field, LOOT icon checks, and LOOT tooltip/isProblematic/hasInfo paths from pluginlist - Delete sortButton widget, updateSortButton(), onSortButtonClicked(), and all m_didUpdateMasterList wiring - Delete lootLogLevel setting, combo box, and "Integrated LOOT" group from the diagnostics settings tab
Diffstat (limited to 'libs')
-rw-r--r--libs/lootcli/.clang-format41
-rw-r--r--libs/lootcli/.git-blame-ignore-revs2
-rw-r--r--libs/lootcli/.gitattributes7
-rw-r--r--libs/lootcli/.github/workflows/build.yml18
-rw-r--r--libs/lootcli/.github/workflows/linting.yml15
-rw-r--r--libs/lootcli/.gitignore5
-rw-r--r--libs/lootcli/.pre-commit-config.yaml20
-rw-r--r--libs/lootcli/CMakeLists.txt32
-rw-r--r--libs/lootcli/CMakePresets.json52
-rw-r--r--libs/lootcli/README.md3
-rw-r--r--libs/lootcli/cmake/config.cmake.in3
-rw-r--r--libs/lootcli/include/lootcli/lootcli.h116
-rw-r--r--libs/lootcli/src/CMakeLists.txt138
-rw-r--r--libs/lootcli/src/game_settings.cpp435
-rw-r--r--libs/lootcli/src/game_settings.h101
-rw-r--r--libs/lootcli/src/lootthread.cpp1197
-rw-r--r--libs/lootcli/src/lootthread.h107
-rw-r--r--libs/lootcli/src/main.cpp102
-rw-r--r--libs/lootcli/src/pch.cpp1
-rw-r--r--libs/lootcli/src/pch.h84
-rw-r--r--libs/lootcli/src/version.h4
-rw-r--r--libs/lootcli/src/version.rc35
-rw-r--r--libs/lootcli/vcpkg.json30
23 files changed, 0 insertions, 2548 deletions
diff --git a/libs/lootcli/.clang-format b/libs/lootcli/.clang-format
deleted file mode 100644
index 6098e1f..0000000
--- a/libs/lootcli/.clang-format
+++ /dev/null
@@ -1,41 +0,0 @@
----
-# We'll use defaults from the LLVM style, but with 4 columns indentation.
-BasedOnStyle: LLVM
-IndentWidth: 2
----
-Language: Cpp
-DeriveLineEnding: false
-UseCRLF: true
-DerivePointerAlignment: false
-PointerAlignment: Left
-AlignConsecutiveAssignments: true
-AllowShortFunctionsOnASingleLine: Inline
-AllowShortIfStatementsOnASingleLine: Never
-AllowShortLambdasOnASingleLine: Empty
-AlwaysBreakTemplateDeclarations: Yes
-AccessModifierOffset: -2
-AlignTrailingComments: true
-SpacesBeforeTrailingComments: 2
-NamespaceIndentation: Inner
-MaxEmptyLinesToKeep: 1
-BreakBeforeBraces: Custom
-BraceWrapping:
- AfterCaseLabel: false
- AfterClass: true
- AfterControlStatement: false
- AfterEnum: true
- AfterFunction: true
- AfterNamespace: true
- AfterStruct: true
- AfterUnion: true
- AfterExternBlock: true
- BeforeCatch: false
- BeforeElse: false
- BeforeLambdaBody: false
- BeforeWhile: false
- IndentBraces: false
- SplitEmptyFunction: false
- SplitEmptyRecord: false
- SplitEmptyNamespace: true
-ColumnLimit: 88
-ForEachMacros: ['Q_FOREACH', 'foreach']
diff --git a/libs/lootcli/.git-blame-ignore-revs b/libs/lootcli/.git-blame-ignore-revs
deleted file mode 100644
index 28904f1..0000000
--- a/libs/lootcli/.git-blame-ignore-revs
+++ /dev/null
@@ -1,2 +0,0 @@
-dfac8a52f654858beacb3f908234388e70fd88a2
-95a7b8b4226e401db8b7c1a13b1bbbbd8cb5c2cf
diff --git a/libs/lootcli/.gitattributes b/libs/lootcli/.gitattributes
deleted file mode 100644
index f869712..0000000
--- a/libs/lootcli/.gitattributes
+++ /dev/null
@@ -1,7 +0,0 @@
-# Set the default behavior, in case people don't have core.autocrlf set.
-* text=auto
-
-# Explicitly declare text files you want to always be normalized and converted
-# to native line endings on checkout.
-*.cpp text eol=crlf
-*.h text eol=crlf
diff --git a/libs/lootcli/.github/workflows/build.yml b/libs/lootcli/.github/workflows/build.yml
deleted file mode 100644
index d9ca97e..0000000
--- a/libs/lootcli/.github/workflows/build.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Build LootCLI
-on:
- push:
- branches: [master]
- pull_request:
- types: [opened, synchronize, reopened]
-
-env:
- VCPKG_BINARY_SOURCES: clear;x-azblob,${{ vars.AZ_BLOB_VCPKG_URL }},${{ secrets.AZ_BLOB_SAS }},readwrite
-
-jobs:
- build:
- runs-on: windows-2022
- steps:
- - name: Build LootCLI
- uses: ModOrganizer2/build-with-mob-action@master
- with:
- mo2-dependencies: cmake_common
diff --git a/libs/lootcli/.github/workflows/linting.yml b/libs/lootcli/.github/workflows/linting.yml
deleted file mode 100644
index b457e70..0000000
--- a/libs/lootcli/.github/workflows/linting.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-name: Lint LootCLI
-on:
- push:
- pull_request:
- types: [opened, synchronize, reopened]
-jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Run clang-format
- uses: jidicula/clang-format-action@v4.11.0
- with:
- clang-format-version: "15"
- check-path: "."
diff --git a/libs/lootcli/.gitignore b/libs/lootcli/.gitignore
deleted file mode 100644
index cf71be7..0000000
--- a/libs/lootcli/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-edit
-CMakeLists.txt.user
-/msbuild.log
-/*std*.log
-/*build
diff --git a/libs/lootcli/.pre-commit-config.yaml b/libs/lootcli/.pre-commit-config.yaml
deleted file mode 100644
index 3103a1f..0000000
--- a/libs/lootcli/.pre-commit-config.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-repos:
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
- hooks:
- - id: trailing-whitespace
- - id: end-of-file-fixer
- - id: check-merge-conflict
- - id: check-case-conflict
- - repo: https://github.com/pre-commit/mirrors-clang-format
- rev: v19.1.5
- hooks:
- - id: clang-format
- 'types_or': [c++, c]
-
-ci:
- autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks."
- autofix_prs: true
- autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate."
- autoupdate_schedule: quarterly
- submodules: false
diff --git a/libs/lootcli/CMakeLists.txt b/libs/lootcli/CMakeLists.txt
deleted file mode 100644
index 35e9d98..0000000
--- a/libs/lootcli/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-
-include(CMakePackageConfigHelpers)
-
-project(lootcli)
-
-add_subdirectory(src)
-
-# install the header helper
-configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.cmake.in
- "${CMAKE_CURRENT_BINARY_DIR}/mo2-lootcli-header-config.cmake"
- INSTALL_DESTINATION "lib/cmake/mo2-lootcli-header"
- NO_SET_AND_CHECK_MACRO
- NO_CHECK_REQUIRED_COMPONENTS_MACRO
-)
-
-file(READ "${CMAKE_CURRENT_SOURCE_DIR}/src/version.h" lootcli_version)
-string(REGEX MATCH "#define LOOTCLI_VERSION_STRING[ \t]*\"([0-9.]+)\"" _ ${lootcli_version})
-set(lootcli_version ${CMAKE_MATCH_1})
-
-write_basic_package_version_file(
- "${CMAKE_CURRENT_BINARY_DIR}/mo2-lootcli-header-config-version.cmake"
- VERSION "${lootcli_version}"
- COMPATIBILITY AnyNewerVersion
- ARCH_INDEPENDENT
-)
-
-install(FILES
- ${CMAKE_CURRENT_BINARY_DIR}/mo2-lootcli-header-config.cmake
- ${CMAKE_CURRENT_BINARY_DIR}/mo2-lootcli-header-config-version.cmake
- DESTINATION lib/cmake/mo2-lootcli-header
-)
diff --git a/libs/lootcli/CMakePresets.json b/libs/lootcli/CMakePresets.json
deleted file mode 100644
index 023baf5..0000000
--- a/libs/lootcli/CMakePresets.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "configurePresets": [
- {
- "errors": {
- "deprecated": true
- },
- "hidden": true,
- "name": "cmake-dev",
- "warnings": {
- "deprecated": true,
- "dev": true
- }
- },
- {
- "cacheVariables": {
- "VCPKG_MANIFEST_NO_DEFAULT_FEATURES": {
- "type": "BOOL",
- "value": "ON"
- }
- },
- "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
- "hidden": true,
- "name": "vcpkg"
- },
- {
- "binaryDir": "${sourceDir}/vsbuild",
- "architecture": {
- "strategy": "set",
- "value": "x64"
- },
- "cacheVariables": {
- "CMAKE_CXX_FLAGS": "/EHsc /MP /W4",
- "VCPKG_TARGET_TRIPLET": {
- "type": "STRING",
- "value": "x64-windows-static-md"
- }
- },
- "generator": "Visual Studio 17 2022",
- "inherits": ["cmake-dev", "vcpkg"],
- "name": "vs2022-windows",
- "toolset": "v143"
- }
- ],
- "buildPresets": [
- {
- "name": "vs2022-windows",
- "resolvePackageReferences": "on",
- "configurePreset": "vs2022-windows"
- }
- ],
- "version": 4
-}
diff --git a/libs/lootcli/README.md b/libs/lootcli/README.md
deleted file mode 100644
index 1dc0ded..0000000
--- a/libs/lootcli/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-[![Build status](https://ci.appveyor.com/api/projects/status/e9k28w726ucmd0cu?svg=true)](https://ci.appveyor.com/project/Modorganizer2/modorganizer-lootcli)
-
-# modorganizer-lootcli
diff --git a/libs/lootcli/cmake/config.cmake.in b/libs/lootcli/cmake/config.cmake.in
deleted file mode 100644
index 4c845a2..0000000
--- a/libs/lootcli/cmake/config.cmake.in
+++ /dev/null
@@ -1,3 +0,0 @@
-@PACKAGE_INIT@
-
-include ( "${CMAKE_CURRENT_LIST_DIR}/mo2-lootcli-header-targets.cmake" )
diff --git a/libs/lootcli/include/lootcli/lootcli.h b/libs/lootcli/include/lootcli/lootcli.h
deleted file mode 100644
index 90305a6..0000000
--- a/libs/lootcli/include/lootcli/lootcli.h
+++ /dev/null
@@ -1,116 +0,0 @@
-#ifndef MODORGANIZER_LOOTCLI_INCLUDED
-#define MODORGANIZER_LOOTCLI_INCLUDED
-
-#include <regex>
-
-namespace lootcli
-{
-
-enum class LogLevels
-{
- Trace = 0,
- Debug,
- Info,
- Warning,
- Error
-};
-
-inline LogLevels logLevelFromString(const std::string& s)
-{
- if (s == "trace") {
- return LogLevels::Trace;
- } else if (s == "debug") {
- return LogLevels::Debug;
- } else if (s == "info") {
- return LogLevels::Info;
- } else if (s == "warning") {
- return LogLevels::Warning;
- } else if (s == "error") {
- return LogLevels::Error;
- } else {
- return LogLevels::Info;
- }
-}
-
-inline std::string logLevelToString(LogLevels level)
-{
- switch (level) {
- case LogLevels::Trace:
- return "trace";
- case LogLevels::Debug:
- return "debug";
- case LogLevels::Info:
- return "info";
- case LogLevels::Warning:
- return "warning";
- case LogLevels::Error:
- return "error";
- default:
- return "info";
- }
-}
-
-enum class Progress
-{
- None = 0,
- CheckingMasterlistExistence,
- UpdatingMasterlist,
- LoadingLists,
- ReadingPlugins,
- SortingPlugins,
- WritingLoadorder,
- ParsingLootMessages,
- Done
-};
-
-enum class MessageType
-{
- None = 0,
- Progress,
- Log
-};
-
-struct Message
-{
- MessageType type = MessageType::None;
- Progress progress = Progress::None;
- LogLevels logLevel = LogLevels::Info;
- std::string log;
-
- static Message fromProgress(Progress p)
- {
- return {MessageType::Progress, p, LogLevels::Info, ""};
- }
-
- static Message fromLog(LogLevels level, std::string log)
- {
- return {MessageType::Log, Progress::None, level, std::move(log)};
- }
-};
-
-inline Message parseMessage(const std::string_view& line)
-{
- static std::regex e(R"(^\[([a-z]+)\] (.+)$)");
-
- std::match_results<std::string_view::const_iterator> m;
- if (!std::regex_match(line.begin(), line.end(), m, e)) {
- return {};
- }
-
- const auto type = m[1];
-
- if (type == "progress") {
- try {
- const auto p = std::stoi(m[2]);
- return Message::fromProgress(static_cast<Progress>(p));
- } catch (std::exception&) {
- return {};
- }
- } else {
- return Message::fromLog(logLevelFromString(type), m[2]);
- }
-}
-
-} // namespace lootcli
-
-#endif // MODORGANIZER_LOOTCLI_INCLUDED
diff --git a/libs/lootcli/src/CMakeLists.txt b/libs/lootcli/src/CMakeLists.txt
deleted file mode 100644
index fdc292e..0000000
--- a/libs/lootcli/src/CMakeLists.txt
+++ /dev/null
@@ -1,138 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-
-# Qt is not required, this allows us to skip building the executable and only create
-# the single-header interface when using this as a VCPKG dependency
-
-find_package(Qt6 CONFIG COMPONENTS Core)
-
-if (Qt6_FOUND)
-
-message(STATUS "Qt6 found, building lootcli executable")
-
-find_package(tomlplusplus CONFIG REQUIRED)
-find_package(Boost REQUIRED CONFIG COMPONENTS locale)
-
-# Try to find libloot via cmake config, then fall back to pkg-config
-find_package(libloot CONFIG QUIET)
-if(NOT libloot_FOUND)
- # On Linux with AUR package, libloot installs a .pc file
- find_package(PkgConfig)
- if(PkgConfig_FOUND)
- pkg_check_modules(LIBLOOT REQUIRED IMPORTED_TARGET libloot)
- endif()
-endif()
-
-if(TARGET libloot::loot)
- # avoid CMake error/warning
- set_target_properties(libloot::loot PROPERTIES
- MAP_IMPORTED_CONFIG_RELEASE RelWithDebInfo
- MAP_IMPORTED_CONFIG_MINSIZEREL RelWithDebInfo
- )
-endif()
-
-if(WIN32)
- add_executable(lootcli WIN32)
- set_target_properties(lootcli PROPERTIES
- CXX_STANDARD 20
- WIN32_EXECUTABLE TRUE)
-else()
- add_executable(lootcli)
- set_target_properties(lootcli PROPERTIES
- CXX_STANDARD 20)
-endif()
-
-set(LOOTCLI_SOURCES
- game_settings.cpp
- game_settings.h
- lootthread.cpp
- lootthread.h
- main.cpp
- pch.h
- version.h
- ${CMAKE_CURRENT_SOURCE_DIR}/../include/lootcli/lootcli.h
-)
-
-if(WIN32)
- list(APPEND LOOTCLI_SOURCES version.rc)
-endif()
-
-target_sources(lootcli PRIVATE ${LOOTCLI_SOURCES})
-
-if(WIN32)
- target_compile_definitions(lootcli
- PRIVATE
- _UNICODE UNICODE
- _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
-endif()
-
-target_include_directories(lootcli PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
-target_precompile_headers(lootcli PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/pch.h)
-
-# Link libraries
-if(TARGET libloot::loot)
- target_link_libraries(lootcli PRIVATE libloot::loot)
-elseif(TARGET PkgConfig::LIBLOOT)
- target_link_libraries(lootcli PRIVATE PkgConfig::LIBLOOT)
-else()
- # Fallback: try to find the library manually
- find_library(LIBLOOT_LIBRARY NAMES loot libloot)
- find_path(LIBLOOT_INCLUDE_DIR NAMES loot/api.h)
- if(LIBLOOT_LIBRARY AND LIBLOOT_INCLUDE_DIR)
- target_link_libraries(lootcli PRIVATE ${LIBLOOT_LIBRARY})
- target_include_directories(lootcli PRIVATE ${LIBLOOT_INCLUDE_DIR})
- else()
- message(FATAL_ERROR "libloot not found. Install libloot or set CMAKE_PREFIX_PATH.")
- endif()
-endif()
-
-target_link_libraries(lootcli
- PRIVATE Boost::headers Boost::locale
- tomlplusplus::tomlplusplus Qt6::Core)
-
-if(NOT WIN32)
- find_package(CURL REQUIRED)
- target_link_libraries(lootcli PRIVATE CURL::libcurl)
-endif()
-
-if (MSVC)
- target_compile_options(lootcli
- PRIVATE
- "/MP"
- "/W4"
- "/external:anglebrackets"
- "/external:W0"
- )
- target_link_options(lootcli
- PRIVATE
- $<$<CONFIG:RelWithDebInfo>:/LTCG /INCREMENTAL:NO /OPT:REF /OPT:ICF>
- )
- target_compile_definitions(lootcli PRIVATE _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
-
- set_target_properties(lootcli PROPERTIES VS_STARTUP_PROJECT lootcli)
-endif()
-
-if(WIN32)
- install(FILES
- $<TARGET_FILE:lootcli>
- $<TARGET_FILE:libloot::loot>
- DESTINATION bin/loot)
-else()
- install(TARGETS lootcli DESTINATION bin/loot)
-endif()
-
-endif()
-
-# library to make the header available
-add_library(lootcli-header INTERFACE)
-target_sources(lootcli-header INTERFACE
- FILE_SET HEADERS
- BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/../include
- FILES ${CMAKE_CURRENT_LIST_DIR}/../include/lootcli/lootcli.h)
-add_library(mo2::lootcli-header ALIAS lootcli-header)
-
-install(TARGETS lootcli-header EXPORT lootcliHeaderTargets FILE_SET HEADERS)
-install(EXPORT lootcliHeaderTargets
- FILE mo2-lootcli-header-targets.cmake
- NAMESPACE mo2::
- DESTINATION lib/cmake/mo2-lootcli-header
-)
diff --git a/libs/lootcli/src/game_settings.cpp b/libs/lootcli/src/game_settings.cpp
deleted file mode 100644
index 00d74c0..0000000
--- a/libs/lootcli/src/game_settings.cpp
+++ /dev/null
@@ -1,435 +0,0 @@
-#include "game_settings.h"
-#include <cstdlib>
-
-namespace fs = std::filesystem;
-namespace loot
-{
-static constexpr float MORROWIND_MINIMUM_HEADER_VERSION = 1.2f;
-static constexpr float OBLIVION_MINIMUM_HEADER_VERSION = 0.8f;
-static constexpr float SKYRIM_FO3_MINIMUM_HEADER_VERSION = 0.94f;
-static constexpr float SKYRIM_SE_MINIMUM_HEADER_VERSION = 1.7f;
-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:
- return GameType::tes4;
- case GameId::tes5:
- case GameId::enderal:
- return GameType::tes5;
- case GameId::tes5se:
- case GameId::enderalse:
- return GameType::tes5se;
- case GameId::tes5vr:
- return GameType::tes5vr;
- case GameId::fo3:
- return GameType::fo3;
- case GameId::fonv:
- return GameType::fonv;
- case GameId::fo4:
- return GameType::fo4;
- case GameId::fo4vr:
- return GameType::fo4vr;
- case GameId::starfield:
- return GameType::starfield;
- case GameId::oblivionRemastered:
- return GameType::oblivionRemastered;
- default:
- throw std::logic_error("Unrecognised game ID");
- }
-}
-
-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:
- return SKYRIM_FO3_MINIMUM_HEADER_VERSION;
- case GameId::tes5se:
- case GameId::tes5vr:
- case GameId::enderalse:
- return SKYRIM_SE_MINIMUM_HEADER_VERSION;
- case GameId::fo3:
- return SKYRIM_FO3_MINIMUM_HEADER_VERSION;
- case GameId::fonv:
- return FONV_MINIMUM_HEADER_VERSION;
- case GameId::fo4:
- case GameId::fo4vr:
- return FO4_MINIMUM_HEADER_VERSION;
- case GameId::starfield:
- return STARFIELD_MINIMUM_HEADER_VERSION;
- default:
- throw std::logic_error("Unrecognised game ID");
- }
-}
-
-std::filesystem::path GetDataPath(const GameId gameId,
- const std::filesystem::path& gamePath)
-{
- switch (gameId) {
- case GameId::tes3:
- return gamePath / "Data Files";
- case GameId::tes4:
- case GameId::nehrim:
- case GameId::tes5:
- case GameId::enderal:
- case GameId::tes5se:
- case GameId::enderalse:
- case GameId::tes5vr:
- case GameId::fo3:
- case GameId::fonv:
- case GameId::fo4:
- case GameId::fo4vr:
- case GameId::starfield:
- 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");
- }
-}
-
-std::string ToString(const GameId gameId)
-{
- switch (gameId) {
- case GameId::tes3:
- return "Morrowind";
- case GameId::tes4:
- return "Oblivion";
- case GameId::nehrim:
- return "Nehrim";
- case GameId::tes5:
- return "Skyrim";
- case GameId::enderal:
- return "Enderal";
- case GameId::tes5se:
- return "Skyrim Special Edition";
- case GameId::enderalse:
- return "Enderal Special Edition";
- case GameId::tes5vr:
- return "Skyrim VR";
- case GameId::fo3:
- return "Fallout3";
- case GameId::fonv:
- return "FalloutNV";
- case GameId::fo4:
- return "Fallout4";
- case GameId::fo4vr:
- 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");
- }
-}
-
-bool SupportsLightPlugins(const GameType gameType)
-{
- return gameType == GameType::tes5se || gameType == GameType::tes5vr ||
- gameType == GameType::fo4 || gameType == GameType::fo4vr;
-}
-
-std::string GetMasterFilename(const GameId gameId)
-{
- switch (gameId) {
- case GameId::tes3:
- return "Morrowind.esm";
- case GameId::tes4:
- case GameId::oblivionRemastered:
- return "Oblivion.esm";
- case GameId::nehrim:
- return "Nehrim.esm";
- case GameId::tes5:
- case GameId::tes5se:
- case GameId::tes5vr:
- case GameId::enderal:
- case GameId::enderalse:
- return "Skyrim.esm";
- case GameId::fo3:
- return "Fallout3.esm";
- case GameId::fonv:
- return "FalloutNV.esm";
- case GameId::fo4:
- case GameId::fo4vr:
- 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");
- }
-}
-
-std::string GetGameName(const GameId gameId)
-{
- switch (gameId) {
- case GameId::tes3:
- return "TES III: Morrowind";
- case GameId::tes4:
- return "TES IV: Oblivion";
- case GameId::nehrim:
- return "Nehrim - At Fate's Edge";
- case GameId::tes5:
- return "TES V: Skyrim";
- case GameId::enderal:
- return "Enderal: Forgotten Stories";
- case GameId::tes5se:
- return "TES V: Skyrim Special Edition";
- case GameId::enderalse:
- return "Enderal: Forgotten Stories (Special Edition)";
- case GameId::tes5vr:
- return "TES V: Skyrim VR";
- case GameId::fo3:
- return "Fallout 3";
- case GameId::fonv:
- return "Fallout: New Vegas";
- case GameId::fo4:
- return "Fallout 4";
- case GameId::fo4vr:
- 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");
- }
-}
-
-std::string GetDefaultMasterlistRepositoryName(const GameId gameId)
-{
- switch (gameId) {
- case GameId::tes3:
- return "morrowind";
- case GameId::tes4:
- case GameId::nehrim:
- case GameId::oblivionRemastered:
- return "oblivion";
- case GameId::tes5:
- return "skyrim";
- case GameId::enderal:
- case GameId::enderalse:
- return "enderal";
- case GameId::tes5se:
- return "skyrimse";
- case GameId::tes5vr:
- return "skyrimvr";
- case GameId::fo3:
- return "fallout3";
- case GameId::fonv:
- return "falloutnv";
- case GameId::fo4:
- return "fallout4";
- case GameId::fo4vr:
- return "fallout4vr";
- case GameId::starfield:
- return "starfield";
- default:
- throw std::logic_error("Unrecognised game type");
- }
-}
-
-std::string GetDefaultMasterlistUrl(const std::string& repositoryName)
-{
- return std::string("https://raw.githubusercontent.com/loot/") + repositoryName + "/" +
- DEFAULT_MASTERLIST_BRANCH + "/masterlist.yaml";
-}
-
-std::string GetDefaultMasterlistUrl(const GameId gameId)
-{
- const auto repoName = GetDefaultMasterlistRepositoryName(gameId);
-
- return GetDefaultMasterlistUrl(repoName);
-}
-
-GameSettings::GameSettings(const GameId gameId, const std::string& lootFolder)
- : id_(gameId), type_(GetGameType(gameId)), name_(GetGameName(gameId)),
- masterFile_(GetMasterFilename(gameId)),
- minimumHeaderVersion_(GetMinimumHeaderVersion(gameId)),
- lootFolderName_(lootFolder), masterlistSource_(GetDefaultMasterlistUrl(gameId))
-{}
-
-bool GameSettings::operator==(const GameSettings& rhs) const
-{
- return name_ == rhs.Name() || lootFolderName_ == rhs.FolderName();
-}
-
-GameId GameSettings::Id() const
-{
- return id_;
-}
-
-GameType GameSettings::Type() const
-{
- return type_;
-}
-
-std::string GameSettings::Name() const
-{
- return name_;
-}
-
-std::string GameSettings::FolderName() const
-{
- return lootFolderName_;
-}
-
-std::string GameSettings::Master() const
-{
- return masterFile_;
-}
-
-float GameSettings::MinimumHeaderVersion() const
-{
- return minimumHeaderVersion_;
-}
-
-std::string GameSettings::MasterlistSource() const
-{
- return masterlistSource_;
-}
-
-std::filesystem::path GameSettings::GamePath() const
-{
- return gamePath_;
-}
-
-std::filesystem::path GameSettings::GameLocalPath() const
-{
- return gameLocalPath_;
-}
-
-std::filesystem::path GameSettings::DataPath() const
-{
- return GetDataPath(id_, gamePath_);
-}
-
-GameSettings& GameSettings::SetName(const std::string& name)
-{
- name_ = name;
- return *this;
-}
-
-GameSettings& GameSettings::SetMaster(const std::string& masterFile)
-{
- masterFile_ = masterFile;
- return *this;
-}
-
-GameSettings& GameSettings::SetMinimumHeaderVersion(float mininumHeaderVersion)
-{
- minimumHeaderVersion_ = mininumHeaderVersion;
- return *this;
-}
-
-GameSettings& GameSettings::SetMasterlistSource(const std::string& source)
-{
- masterlistSource_ = source;
- return *this;
-}
-
-GameSettings& GameSettings::SetGamePath(const std::filesystem::path& path)
-{
- gamePath_ = path;
- return *this;
-}
-
-GameSettings& GameSettings::SetGameLocalPath(const std::filesystem::path& path)
-{
- gameLocalPath_ = path;
- return *this;
-}
-
-GameSettings& GameSettings::SetGameLocalFolder(const std::string& folderName)
-{
- fs::path appData;
-#ifdef _WIN32
- TCHAR path[MAX_PATH];
-
- HRESULT res = ::SHGetFolderPath(nullptr, CSIDL_LOCAL_APPDATA, nullptr,
- SHGFP_TYPE_CURRENT, path);
- if (res == S_OK) {
- appData = fs::path(path);
- } else {
- appData = fs::path("");
- }
-#else
- const char* xdgData = std::getenv("XDG_DATA_HOME");
- if (xdgData && xdgData[0] != '\0') {
- appData = fs::path(xdgData);
- } else {
- const char* home = std::getenv("HOME");
- if (home && home[0] != '\0') {
- appData = fs::path(home) / ".local" / "share";
- } else {
- appData = fs::path("");
- }
- }
-#endif
- gameLocalPath_ = appData / fs::path(folderName);
- return *this;
-}
-} // namespace loot
diff --git a/libs/lootcli/src/game_settings.h b/libs/lootcli/src/game_settings.h
deleted file mode 100644
index bc775c9..0000000
--- a/libs/lootcli/src/game_settings.h
+++ /dev/null
@@ -1,101 +0,0 @@
-#ifndef LOOT_GUI_STATE_GAME_GAME_SETTINGS
-#define LOOT_GUI_STATE_GAME_GAME_SETTINGS
-
-#include <filesystem>
-#include <optional>
-#include <set>
-#include <string>
-#include <vector>
-
-#include "loot/enum/game_type.h"
-
-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.26";
-
-enum struct GameId : uint8_t
-{
- tes3,
- tes4,
- nehrim,
- tes5,
- enderal,
- tes5se,
- enderalse,
- tes5vr,
- fo3,
- fonv,
- fo4,
- fo4vr,
- starfield,
- openmw,
- oblivionRemastered
-};
-
-GameType GetGameType(const GameId gameId);
-
-float GetMinimumHeaderVersion(const GameId gameId);
-
-std::filesystem::path GetDataPath(const GameId gamiId,
- const std::filesystem::path& gamePath);
-
-std::string ToString(const GameId gameId);
-
-bool SupportsLightPlugins(const GameType gameType);
-
-std::string GetMasterFilename(const GameId gameId);
-
-std::string GetGameName(const GameId gameId);
-
-std::string GetDefaultMasterlistRepositoryName(GameId gameId);
-
-std::string GetDefaultMasterlistUrl(const std::string& repositoryName);
-std::string GetDefaultMasterlistUrl(const GameId gameId);
-
-class GameSettings
-{
-public:
- GameSettings() = default;
- explicit GameSettings(const GameId gameId, const std::string& lootFolder = "");
-
- bool operator==(const GameSettings& rhs) const; // Compares names and folder names.
-
- GameId Id() const;
- GameType Type() const;
- std::string Name() const; // Returns the game's name, eg. "TES IV: Oblivion".
- std::string FolderName() const;
- std::string Master() const;
- float MinimumHeaderVersion() const;
- std::string MasterlistSource() const;
- std::filesystem::path GamePath() const;
- std::filesystem::path GameLocalPath() const;
- std::filesystem::path DataPath() const;
-
- GameSettings& SetName(const std::string& name);
- GameSettings& SetMaster(const std::string& masterFile);
- GameSettings& SetMinimumHeaderVersion(float minimumHeaderVersion);
- GameSettings& SetMasterlistSource(const std::string& source);
- GameSettings& SetGamePath(const std::filesystem::path& path);
- GameSettings& SetGameLocalPath(const std::filesystem::path& GameLocalPath);
- GameSettings& SetGameLocalFolder(const std::string& folderName);
-
-private:
- GameId id_{GameId::tes4};
- GameType type_{GameType::tes4};
- std::string name_;
- std::string masterFile_;
- float minimumHeaderVersion_{0.0f};
-
- std::string lootFolderName_;
-
- std::string masterlistSource_;
-
- std::filesystem::path gamePath_; // Path to the game's folder.
- std::filesystem::path gameLocalPath_;
-};
-} // namespace loot
-
-#endif
diff --git a/libs/lootcli/src/lootthread.cpp b/libs/lootcli/src/lootthread.cpp
deleted file mode 100644
index 9f7f60c..0000000
--- a/libs/lootcli/src/lootthread.cpp
+++ /dev/null
@@ -1,1197 +0,0 @@
-#ifdef _WIN32
-#pragma comment(lib, "winhttp.lib")
-#endif
-
-#include "lootthread.h"
-#include "game_settings.h"
-#include "version.h"
-#include <codecvt>
-#include <fstream>
-
-#ifdef _WIN32
-#include <strsafe.h>
-#include <windows.h>
-#include <winhttp.h>
-#else
-#include <curl/curl.h>
-#include <cstring>
-#endif
-
-// using namespace loot;
-namespace fs = std::filesystem;
-
-using std::lock_guard;
-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", "v0.21"});
-static const std::regex GITHUB_REPO_URL_REGEX =
- std::regex(R"(^https://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$)",
- std::regex::ECMAScript | std::regex::icase);
-
-std::string toString(loot::MessageType type)
-{
- switch (type) {
- case loot::MessageType::say:
- return "info";
- case loot::MessageType::warn:
- return "warn";
- case loot::MessageType::error:
- return "error";
- default:
- return "unknown";
- }
-}
-
-LOOTWorker::LOOTWorker()
- : m_GameId(loot::GameId::tes5), m_GameName("Skyrim"),
- m_LogLevel(loot::LogLevel::info)
-{}
-
-std::string ToLower(std::string text)
-{
- std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) {
- return static_cast<char>(std::tolower(c));
- });
-
- return 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},
- {"oblivionremastered", loot::GameId::oblivionRemastered}};
-
- auto iter = gameMap.find(ToLower(gameName));
-
- if (iter != gameMap.end()) {
- m_GameId = iter->second;
- m_GameName = loot::ToString(m_GameId);
- } else {
- throw std::runtime_error("invalid game name \"" + gameName + "\"");
- }
-}
-
-void LOOTWorker::setGamePath(const std::string& gamePath)
-{
- m_GamePath = gamePath;
-}
-
-void LOOTWorker::setOutput(const std::string& outputPath)
-{
- m_OutputPath = outputPath;
-}
-
-void LOOTWorker::setUpdateMasterlist(bool update)
-{
- m_UpdateMasterlist = update;
-}
-
-void LOOTWorker::setPluginListPath(const std::string& pluginListPath)
-{
- m_PluginListPath = pluginListPath;
-}
-
-void LOOTWorker::setLanguageCode(const std::string& languageCode)
-{
- m_Language = languageCode;
-}
-
-void LOOTWorker::setLogLevel(loot::LogLevel level)
-{
- m_LogLevel = level;
-}
-
-fs::path GetLOOTAppData()
-{
-#ifdef _WIN32
- TCHAR path[MAX_PATH];
-
- HRESULT res = ::SHGetFolderPath(nullptr, CSIDL_LOCAL_APPDATA, nullptr,
- SHGFP_TYPE_CURRENT, path);
-
- if (res == S_OK) {
- return fs::path(path) / "LOOT";
- } else {
- return fs::path("");
- }
-#else
- // On Linux, use XDG_DATA_HOME or fallback to ~/.local/share
- const char* xdgData = std::getenv("XDG_DATA_HOME");
- if (xdgData && xdgData[0] != '\0') {
- return fs::path(xdgData) / "LOOT";
- }
- const char* home = std::getenv("HOME");
- if (home && home[0] != '\0') {
- return fs::path(home) / ".local" / "share" / "LOOT";
- }
- return fs::path("");
-#endif
-}
-
-fs::path LOOTWorker::gamePath() const
-{
- return GetLOOTAppData() / "games" / m_GameSettings.FolderName();
-}
-
-fs::path LOOTWorker::masterlistPath() const
-{
- return gamePath() / "masterlist.yaml";
-}
-
-fs::path LOOTWorker::userlistPath() const
-{
- return gamePath() / "userlist.yaml";
-}
-fs::path LOOTWorker::settingsPath() const
-{
- return GetLOOTAppData() / "settings.toml";
-}
-
-fs::path LOOTWorker::l10nPath() const
-{
- return GetLOOTAppData() / "resources" / "l10n";
-}
-
-fs::path LOOTWorker::dataPath() const
-{
- return m_GameSettings.DataPath();
-}
-
-void LOOTWorker::getSettings(const fs::path& file)
-{
- lock_guard<recursive_mutex> guard(mutex_);
- // Don't use cpptoml::parse_file() as it just uses a std stream,
- // which don't support UTF-8 paths on Windows.
- std::ifstream in(file);
- if (!in.is_open())
- throw std::runtime_error(file.string() + " could not be opened for parsing");
-
- const auto settings = toml::parse(in, file.string());
- const auto games = settings["games"];
- if (games.is_array_of_tables()) {
- for (const auto& game : *games.as_array()) {
- try {
- if (!game.is_table()) {
- throw std::runtime_error("games array element is not a table");
- }
- auto gameTable = *game.as_table();
-
- using loot::GameId;
- using loot::GameSettings;
-
- auto id = gameTable["gameId"].value<std::string>();
- if (!id) {
- throw std::runtime_error(
- "'gameId' and 'type' keys both missing from game settings table");
- }
- const auto gameType = *id;
- GameId gameId;
-
- if (gameType == "Morrowind") {
- gameId = GameId::tes3;
- } else if (gameType == "Oblivion") {
- // The Oblivion game type is shared between Oblivon and Nehrim.
- gameId = IsNehrim(gameTable) ? GameId::nehrim : GameId::tes4;
- } else if (gameType == "Skyrim") {
- // The Skyrim game type is shared between Skyrim and Enderal.
- gameId = IsEnderal(gameTable) ? GameId::enderal : GameId::tes5;
- } else if (gameType == "SkyrimSE" || gameType == "Skyrim Special Edition") {
- // The Skyrim SE game type is shared between Skyrim SE and Enderal SE.
- gameId = IsEnderalSE(gameTable) ? GameId::enderalse : GameId::tes5se;
- } else if (gameType == "Skyrim VR") {
- gameId = GameId::tes5vr;
- } else if (gameType == "Fallout3") {
- gameId = GameId::fo3;
- } else if (gameType == "FalloutNV") {
- gameId = GameId::fonv;
- } else if (gameType == "Fallout4") {
- 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");
- }
-
- auto folder = gameTable["folder"].value<std::string>();
- if (!folder) {
- throw std::runtime_error("'folder' key missing from game settings table");
- }
-
- const auto type = gameTable["type"].value<std::string>();
-
- // SkyrimSE was a previous serialised value for GameType::tes5se,
- // and the game folder name LOOT created for that game type.
- if (type && *type == "SkyrimSE" && *folder == *type) {
- folder = "Skyrim Special Edition";
- }
-
- GameSettings newSettings(gameId, folder.value());
-
- if (newSettings.Type() == m_GameSettings.Type()) {
-
- auto name = gameTable["name"].value<std::string>();
- if (name) {
- newSettings.SetName(*name);
- }
-
- auto master = gameTable["master"].value<std::string>();
- if (master) {
- newSettings.SetMaster(*master);
- }
-
- const auto minimumHeaderVersion =
- gameTable["minimumHeaderVersion"].value<double>();
- if (minimumHeaderVersion) {
- newSettings.SetMinimumHeaderVersion((float)*minimumHeaderVersion);
- }
-
- auto source = gameTable["masterlistSource"].value<std::string>();
- if (source) {
- newSettings.SetMasterlistSource(migrateMasterlistSource(*source));
- } else {
- auto url = gameTable["repo"].value<std::string>();
- auto branch = gameTable["branch"].value<std::string>();
- auto migratedSource =
- migrateMasterlistRepoSettings(newSettings.Id(), *url, *branch);
- if (migratedSource.has_value()) {
- newSettings.SetMasterlistSource(migratedSource.value());
- }
- }
-
- auto path = gameTable["path"].value<std::string>();
- if (path) {
- newSettings.SetGamePath(std::filesystem::path(*path));
- }
-
- auto localPath = gameTable["local_path"].value<std::string>();
- auto localFolder = gameTable["local_folder"].value<std::string>();
- if (localPath && localFolder) {
- throw std::runtime_error(
- "Game settings have local_path and local_folder set, use only one.");
- } else if (localPath) {
- newSettings.SetGameLocalPath(std::filesystem::path(*localPath));
- } else if (localFolder) {
- newSettings.SetGameLocalFolder(*localFolder);
- }
-
- m_GameSettings = newSettings;
- break;
- }
- } catch (...) {
- // Skip invalid games.
- }
- }
- }
-
- if (m_Language.empty()) {
- m_Language = settings["language"].value_or(loot::MessageContent::DEFAULT_LANGUAGE);
- }
-}
-
-std::optional<std::string> LOOTWorker::GetLocalFolder(const toml::table& table)
-{
- const auto localPath = table["local_path"].value<std::string>();
- const auto localFolder = table["local_folder"].value<std::string>();
-
- if (localFolder.has_value()) {
- return localFolder;
- }
-
- if (localPath.has_value()) {
- return std::filesystem::path(*localPath).filename().string();
- }
-
- return std::nullopt;
-}
-
-bool LOOTWorker::IsNehrim(const toml::table& table)
-{
- const auto installPath = table["path"].value<std::string>();
-
- if (installPath.has_value() && !installPath.value().empty()) {
- const auto path = std::filesystem::path(installPath.value());
- if (std::filesystem::exists(path)) {
- return std::filesystem::exists(path / "NehrimLauncher.exe");
- }
- }
-
- // Fall back to using heuristics based on the existing settings.
- // Return true if any of these heuristics return a positive match.
- const auto gameName = table["name"].value<std::string>();
- const auto masterFilename = table["master"].value<std::string>();
- const auto isBaseGameInstance = table["isBaseGameInstance"].value<bool>();
- const auto folder = table["folder"].value<std::string>();
-
- return
- // Nehrim uses a different main master file from Oblivion.
- (masterFilename.has_value() &&
- masterFilename.value() == loot::GetMasterFilename(loot::GameId::nehrim)) ||
- // Game name probably includes "nehrim".
- (gameName.has_value() && boost::icontains(gameName.value(), "nehrim")) ||
- // LOOT folder name probably includes "nehrim".
- (folder.has_value() && boost::icontains(folder.value(), "nehrim")) ||
- // Between 0.18.1 and 0.19.0 inclusive, LOOT had an isBaseGameInstance
- // game setting that was false for Nehrim, Enderal and Enderal SE.
- (isBaseGameInstance.has_value() && !isBaseGameInstance.value());
-}
-
-bool LOOTWorker::IsEnderal(const toml::table& table,
- const std::string& expectedLocalFolder)
-{
- const auto installPath = table["path"].value<std::string>();
-
- if (installPath.has_value() && !installPath.value().empty()) {
- const auto path = std::filesystem::path(installPath.value());
- if (std::filesystem::exists(path)) {
- return std::filesystem::exists(path / "Enderal Launcher.exe");
- }
- }
-
- // Fall back to using heuristics based on the existing settings.
- // Return true if any of these heuristics return a positive match.
- const auto gameName = table["name"].value<std::string>();
- const auto isBaseGameInstance = table["isBaseGameInstance"].value<bool>();
- const auto localFolder = GetLocalFolder(table);
- const auto folder = table["folder"].value<std::string>();
-
- return
- // Enderal and Enderal SE use different local folders than their base
- // games.
- (localFolder.has_value() && localFolder.value() == expectedLocalFolder) ||
- // Game name probably includes "enderal".
- (gameName.has_value() && boost::icontains(gameName.value(), "enderal")) ||
- // LOOT folder name probably includes "enderal".
- (folder.has_value() && boost::icontains(folder.value(), "enderal")) ||
- // Between 0.18.1 and 0.19.0 inclusive, LOOT had an isBaseGameInstance
- // game setting that was false for Nehrim, Enderal and Enderal SE.
- (isBaseGameInstance.has_value() && !isBaseGameInstance.value());
-}
-
-bool LOOTWorker::IsEnderal(const toml::table& table)
-{
- return IsEnderal(table, "enderal");
-}
-
-bool LOOTWorker::IsEnderalSE(const toml::table& table)
-{
- return IsEnderal(table, "Enderal Special Edition");
-}
-
-std::string LOOTWorker::getOldDefaultRepoUrl(loot::GameId GameId)
-{
- switch (GameId) {
- case loot::GameId::tes3:
- return "https://github.com/loot/morrowind.git";
- case loot::GameId::tes4:
- return "https://github.com/loot/oblivion.git";
- case loot::GameId::tes5:
- return "https://github.com/loot/skyrim.git";
- case loot::GameId::tes5se:
- return "https://github.com/loot/skyrimse.git";
- case loot::GameId::tes5vr:
- return "https://github.com/loot/skyrimvr.git";
- case loot::GameId::fo3:
- return "https://github.com/loot/fallout3.git";
- case loot::GameId::fonv:
- return "https://github.com/loot/falloutnv.git";
- case loot::GameId::fo4:
- return "https://github.com/loot/fallout4.git";
- case loot::GameId::fo4vr:
- return "https://github.com/loot/fallout4vr.git";
- default:
- throw std::runtime_error(
- "Unrecognised game type: " +
- std::to_string(static_cast<std::underlying_type_t<loot::GameId>>(GameId)));
- }
-}
-
-bool LOOTWorker::isLocalPath(const std::string& location, const std::string& filename)
-{
- if (boost::starts_with(location, "http://") ||
- boost::starts_with(location, "https://")) {
- return false;
- }
-
- // Could be a local path. Only return true if it points to a non-bare
- // Git repository that currently has the given branch checked out and
- // the given filename exists in the repo root.
- auto locationPath = std::filesystem::path(location);
-
- auto filePath = locationPath / std::filesystem::path(filename);
-
- if (!std::filesystem::is_regular_file(filePath)) {
- return false;
- }
-
- auto headFilePath = locationPath / ".git" / "HEAD";
-
- return std::filesystem::is_regular_file(headFilePath);
-}
-
-bool LOOTWorker::isBranchCheckedOut(const std::filesystem::path& localGitRepo,
- const std::string& branch)
-{
- auto headFilePath = localGitRepo / ".git" / "HEAD";
-
- std::ifstream in(headFilePath);
- if (!in.is_open()) {
- return false;
- }
-
- std::string line;
- std::getline(in, line);
- in.close();
-
- return line == "ref: refs/heads/" + branch;
-}
-
-std::optional<std::string>
-LOOTWorker::migrateMasterlistRepoSettings(loot::GameId GameId, std::string url,
- std::string branch)
-{
-
- if (oldDefaultBranches.count(branch) == 1) {
- // Update to the latest masterlist branch.
- log(loot::LogLevel::info, "Updating masterlist repository branch from " + branch +
- " to " + loot::DEFAULT_MASTERLIST_BRANCH);
- branch = loot::DEFAULT_MASTERLIST_BRANCH;
- }
-
- if (GameId == loot::GameId::tes5vr && url == "https://github.com/loot/skyrimse.git") {
- // Switch to the VR-specific repository (introduced for LOOT v0.17.0).
- auto newUrl = "https://github.com/loot/skyrimvr.git";
- log(loot::LogLevel::info,
- "Updating masterlist repository URL from" + url + " to " + newUrl);
- url = newUrl;
- }
-
- if (GameId == loot::GameId::fo4vr && url == "https://github.com/loot/fallout4.git") {
- // Switch to the VR-specific repository (introduced for LOOT v0.17.0).
- auto newUrl = "https://github.com/loot/fallout4vr.git";
- log(loot::LogLevel::info,
- "Updating masterlist repository URL from " + url + " to " + newUrl);
- url = newUrl;
- }
-
- auto filename = "masterlist.yaml";
- if (isLocalPath(url, filename)) {
- auto localRepoPath = std::filesystem::path(url);
- if (!isBranchCheckedOut(localRepoPath, branch)) {
- log(loot::LogLevel::warning,
- "The URL " + url +
- " is a local Git repository path but the configured branch " + branch +
- " is not checked out. LOOT will use the path as the masterlist "
- "source, but there may be unexpected differences in the loaded "
- "metadata if the " +
- branch +
- " branch is not manually checked out before the "
- "next time the masterlist is updated.");
- }
-
- return (localRepoPath / filename).string();
- }
-
- std::smatch regexMatches;
- std::regex_match(url, regexMatches, GITHUB_REPO_URL_REGEX);
- if (regexMatches.size() != 3) {
- log(loot::LogLevel::warning,
- "Cannot migrate masterlist repository settings as the URL does not "
- "point to a repository on GitHub.");
- return std::nullopt;
- }
-
- auto githubOwner = regexMatches.str(1);
- auto githubRepo = regexMatches.str(2);
-
- return "https://raw.githubusercontent.com/" + githubOwner + "/" + githubRepo + "/" +
- branch + "/masterlist.yaml";
-}
-
-std::string LOOTWorker::migrateMasterlistSource(const std::string& source)
-{
- static const std::vector<std::string> officialMasterlistRepos = {
- "morrowind", "oblivion", "skyrim", "skyrimse", "skyrimvr",
- "fallout3", "falloutnv", "fallout4", "fallout4vr", "enderal"};
-
- for (const auto& repo : officialMasterlistRepos) {
- for (const auto& branch : oldDefaultBranches) {
- const auto url = "https://raw.githubusercontent.com/loot/" + repo + "/" + branch +
- "/masterlist.yaml";
-
- if (source == url) {
- const auto newSource = loot::GetDefaultMasterlistUrl(repo);
-
- log(loot::LogLevel::info,
- "Migrating masterlist source from " + source + " to " + newSource);
-
- return newSource;
- }
- }
- }
-
- return source;
-}
-
-#ifdef _WIN32
-DWORD LOOTWorker::GetFile(const WCHAR* szUrl, // Full URL
- const CHAR* szFileName) // Local file name
-{
- BYTE szTemp[25];
- DWORD dwSize = 0;
- DWORD dwDownloaded = 0;
- LPSTR pszOutBuffer;
- BOOL bResults = FALSE;
- HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
- FILE* pFile;
- std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
-
- URL_COMPONENTS urlComp;
- DWORD dwUrlLen = 0;
-
- DWORD result = ERROR_SUCCESS;
-
- // Initialize the URL_COMPONENTS structure.
- ZeroMemory(&urlComp, sizeof(urlComp));
- urlComp.dwStructSize = sizeof(urlComp);
-
- // Set required component lengths to non-zero
- // so that they are cracked.
- wchar_t szHostName[MAX_PATH] = L"";
- wchar_t szURLPath[MAX_PATH * 4] = L"";
- urlComp.lpszHostName = szHostName;
- urlComp.lpszUrlPath = szURLPath;
- urlComp.dwSchemeLength = (DWORD)-1;
- urlComp.dwHostNameLength = (DWORD)-1;
- urlComp.dwUrlPathLength = (DWORD)-1;
- urlComp.dwExtraInfoLength = (DWORD)-1;
- if (WinHttpCrackUrl(szUrl, (DWORD)wcslen(szUrl), 0, &urlComp)) {
- // Use WinHttpOpen to obtain a session handle.
- hSession = WinHttpOpen(L"lootcli/1.5.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
- WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
-
- // Specify an HTTP server.
- if (hSession)
- hConnect = WinHttpConnect(hSession, szHostName, urlComp.nPort, 0);
-
- // Create an HTTP request handle.
- if (hConnect)
- hRequest =
- WinHttpOpenRequest(hConnect, L"GET", szURLPath, NULL, WINHTTP_NO_REFERER,
- WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
-
- // Send a request.
- if (hRequest)
- bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
-
- // End the request.
- if (bResults)
- bResults = WinHttpReceiveResponse(hRequest, NULL);
-
- // Keep checking for data until there is nothing left.
- if (bResults) {
- if (!(pFile = fopen(szFileName, "wb"))) {
- log(loot::LogLevel::debug, "File open failure");
- result = GetLastError();
- }
- do {
- // Check for available data.
- dwSize = 0;
- if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
- log(loot::LogLevel::debug, "No data");
- result = GetLastError();
- break;
- }
-
- // No more available data.
- if (!dwSize) {
- log(loot::LogLevel::debug, "No data");
- result = GetLastError();
- break;
- }
-
- // Allocate space for the buffer.
- pszOutBuffer = new char[dwSize + 1];
- if (!pszOutBuffer) {
- log(loot::LogLevel::debug, "Bad buffer");
- result = GetLastError();
- }
-
- // Read the Data.
- ZeroMemory(pszOutBuffer, dwSize + 1);
-
- if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) {
- log(loot::LogLevel::debug, "Read data failure");
- result = GetLastError();
- } else {
- fwrite(pszOutBuffer, sizeof(char), dwSize, pFile);
- }
-
- // Free the memory allocated to the buffer.
- delete[] pszOutBuffer;
-
- // This condition should never be reached since WinHttpQueryDataAvailable
- // reported that there are bits to read.
- if (!dwDownloaded)
- break;
-
- } while (dwSize > 0);
- } else {
- log(loot::LogLevel::debug, "Response failure");
- result = GetLastError();
- }
-
- // Close any open handles.
- if (hRequest)
- WinHttpCloseHandle(hRequest);
- if (hConnect)
- WinHttpCloseHandle(hConnect);
- if (hSession)
- WinHttpCloseHandle(hSession);
- fflush(pFile);
- fclose(pFile);
- } else {
- log(loot::LogLevel::debug, "URL parse failure: " + converter.to_bytes(szUrl));
- result = GetLastError();
- }
- return result;
-}
-#else
-// Linux implementation using libcurl
-static size_t curlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp)
-{
- FILE* fp = static_cast<FILE*>(userp);
- return fwrite(contents, size, nmemb, fp);
-}
-
-int LOOTWorker::GetFile(const std::string& url, const std::string& fileName)
-{
- CURL* curl = curl_easy_init();
- if (!curl) {
- log(loot::LogLevel::error, "Failed to initialize curl");
- return 1;
- }
-
- FILE* fp = fopen(fileName.c_str(), "wb");
- if (!fp) {
- log(loot::LogLevel::debug, "File open failure: " + fileName);
- curl_easy_cleanup(curl);
- return 1;
- }
-
- curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
- curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
- curl_easy_setopt(curl, CURLOPT_USERAGENT, "lootcli/" LOOTCLI_VERSION_STRING);
-
- CURLcode res = curl_easy_perform(curl);
-
- fflush(fp);
- fclose(fp);
- curl_easy_cleanup(curl);
-
- if (res != CURLE_OK) {
- log(loot::LogLevel::error,
- std::string("curl download failed: ") + curl_easy_strerror(res));
- return 1;
- }
-
- return 0;
-}
-#endif
-
-std::string escape(const std::string& s)
-{
- return boost::replace_all_copy(s, "\"", "\\\"");
-}
-
-int LOOTWorker::run()
-{
- m_startTime = std::chrono::high_resolution_clock::now();
-
- {
- // Do some preliminary locale / UTF-8 support setup here, in case the settings file
- // reading requires it.
- // Boost.Locale initialisation: Specify location of language dictionaries.
- boost::locale::generator gen;
- gen.add_messages_path(l10nPath().string());
- gen.add_messages_domain("loot");
-
- // Boost.Locale initialisation: Generate and imbue locales.
- std::locale::global(gen("en.UTF-8"));
- }
-
- loot::SetLoggingCallback([&](loot::LogLevel level, std::string_view message) {
- log(level, message);
- });
-
- try {
- fs::path profile(m_PluginListPath);
- profile = profile.parent_path();
-
- m_GameSettings = loot::GameSettings(m_GameId, loot::ToString(m_GameId));
-
- fs::path settings = settingsPath();
-
- if (fs::exists(settings))
- getSettings(settings);
-
- m_GameSettings.SetGamePath(m_GamePath);
-
- std::unique_ptr<loot::GameInterface> gameHandle = CreateGameHandle(
- m_GameSettings.Type(), m_GameSettings.GamePath(), profile.string());
-
- if (!GetLOOTAppData().empty()) {
- // Make sure that the LOOT game path exists.
- auto lootGamePath = gamePath();
- if (!fs::is_directory(lootGamePath)) {
- if (fs::exists(lootGamePath)) {
- throw std::runtime_error(
- "Could not create LOOT folder for game, the path exists but is not "
- "a directory");
- }
-
- std::vector<fs::path> legacyGamePaths{GetLOOTAppData() /
- fs::path(m_GameSettings.FolderName())};
-
- if (m_GameSettings.Id() == loot::GameId::tes5se) {
- // LOOT v0.10.0 used SkyrimSE as its folder name for Skyrim SE, so
- // migrate from that if it's present.
- legacyGamePaths.insert(legacyGamePaths.begin(),
- GetLOOTAppData() / "SkyrimSE");
- }
-
- for (const auto& legacyGamePath : legacyGamePaths) {
- if (fs::is_directory(legacyGamePath)) {
- log(loot::LogLevel::info,
- "Found a folder for this game in the LOOT data folder, "
- "assuming "
- "that it's a legacy game folder and moving into the correct "
- "subdirectory...");
-
- fs::create_directories(lootGamePath.parent_path());
- fs::rename(legacyGamePath, lootGamePath);
- break;
- }
- }
-
- fs::create_directories(lootGamePath);
- }
- }
-
- if (m_Language != loot::MessageContent::DEFAULT_LANGUAGE) {
- log(loot::LogLevel::debug, "initialising language settings");
- log(loot::LogLevel::debug, "selected language: " + m_Language);
-
- // Boost.Locale initialisation: Generate and imbue locales.
- boost::locale::generator gen;
- std::locale::global(gen(m_Language + ".UTF-8"));
- }
-
- progress(Progress::CheckingMasterlistExistence);
- if (!fs::exists(masterlistPath())) {
- if (!m_UpdateMasterlist) {
- log(loot::LogLevel::error,
- "Masterlist not found at: " + masterlistPath().string());
- return 0; // was FALSE on Windows
- }
- fs::create_directories(masterlistPath().parent_path());
- }
-
- if (m_UpdateMasterlist) {
- progress(Progress::UpdatingMasterlist);
-
-#ifdef _WIN32
- std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
- std::wstring masterlistSource =
- converter.from_bytes(m_GameSettings.MasterlistSource());
-
- log(loot::LogLevel::info, "Downloading latest masterlist file from " +
- m_GameSettings.MasterlistSource() + " to " +
- masterlistPath().string());
- DWORD result =
- GetFile(masterlistSource.c_str(), masterlistPath().string().c_str());
- if (result != ERROR_SUCCESS) {
- LPVOID lpMsgBuf;
- LPVOID lpDisplayBuf;
- LPCWSTR lpszFunction = TEXT("GetFile");
- DWORD dw = result;
-
- FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPTSTR)&lpMsgBuf, 0, NULL);
-
- lpDisplayBuf =
- (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) +
- lstrlen((LPCTSTR)lpszFunction) + 40) *
- sizeof(TCHAR));
- StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR),
- TEXT("%s failed with error %d: %s"), lpszFunction, dw,
- lpMsgBuf);
-
- std::wstring errorMessage = (LPTSTR)lpDisplayBuf;
-
- log(loot::LogLevel::error,
- "Error downloading masterlist: " + converter.to_bytes(errorMessage));
- return 0; // was FALSE
- }
-#else
- log(loot::LogLevel::info, "Downloading latest masterlist file from " +
- m_GameSettings.MasterlistSource() + " to " +
- masterlistPath().string());
- int result = GetFile(m_GameSettings.MasterlistSource(),
- masterlistPath().string());
- if (result != 0) {
- log(loot::LogLevel::error, "Error downloading masterlist");
- return 0;
- }
-#endif
- }
-
- progress(Progress::LoadingLists);
-
- gameHandle->GetDatabase().LoadMasterlist(masterlistPath().string());
- fs::path userlist = userlistPath();
- 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(loadOrder);
-
- progress(Progress::WritingLoadorder);
-
- std::ofstream outf(m_PluginListPath);
- if (!outf) {
- log(loot::LogLevel::error,
- "failed to open " + m_PluginListPath + " to rewrite it");
- return 1;
- }
- outf << "# This file was automatically generated by Mod Organizer." << std::endl;
- for (const std::string& plugin : sortedPlugins) {
- outf << plugin << std::endl;
- }
- outf.close();
-
- progress(Progress::ParsingLootMessages);
- std::ofstream(m_OutputPath) << createJsonReport(*gameHandle, sortedPlugins);
- } catch (std::system_error& e) {
- log(loot::LogLevel::error, e.what());
- return 1;
- } catch (const std::exception& e) {
- log(loot::LogLevel::error, e.what());
- return 1;
- }
-
- progress(Progress::Done);
-
- return 0;
-}
-
-void set(QJsonObject& o, const char* e, const QJsonValue& v)
-{
- if (v.isObject() && v.toObject().isEmpty()) {
- return;
- }
-
- if (v.isArray() && v.toArray().isEmpty()) {
- return;
- }
-
- if (v.isString() && v.toString().isEmpty()) {
- return;
- }
-
- o[e] = v;
-}
-
-std::string
-LOOTWorker::createJsonReport(loot::GameInterface& game,
- const std::vector<std::string>& sortedPlugins) const
-{
- QJsonObject root;
-
- set(root, "messages", createMessages(game.GetDatabase().GetGeneralMessages(true)));
- set(root, "plugins", createPlugins(game, sortedPlugins));
-
- const auto end = std::chrono::high_resolution_clock::now();
-
- set(root, "stats",
- QJsonObject{{"time", static_cast<qint64>(std::chrono::duration_cast<std::chrono::milliseconds>(
- end - m_startTime)
- .count())},
- {"lootcliVersion", LOOTCLI_VERSION_STRING},
- {"lootVersion", QString::fromStdString(loot::GetLiblootVersion())}});
-
- QJsonDocument doc(root);
- return doc.toJson(QJsonDocument::Indented).toStdString();
-}
-
-template <class Container>
-QJsonArray createStringArray(const Container& c)
-{
- QJsonArray array;
-
- for (auto&& e : c) {
- array.push_back(QString::fromStdString(e));
- }
-
- return array;
-}
-
-QJsonArray
-LOOTWorker::createPlugins(loot::GameInterface& game,
- const std::vector<std::string>& sortedPlugins) const
-{
- QJsonArray plugins;
-
- for (auto&& pluginName : sortedPlugins) {
-
- auto plugin = game.GetPlugin(pluginName);
-
- QJsonObject o;
- o["name"] = QString::fromStdString(pluginName);
-
- if (auto metaData = game.GetDatabase().GetPluginMetadata(pluginName, true, true)) {
- set(o, "incompatibilities",
- createIncompatibilities(game, metaData->GetIncompatibilities()));
- set(o, "messages", createMessages(metaData->GetMessages()));
- set(o, "dirty", createDirty(metaData->GetDirtyInfo()));
- set(o, "clean", createClean(metaData->GetCleanInfo()));
- }
-
- set(o, "missingMasters", createMissingMasters(game, pluginName));
-
- if (plugin->LoadsArchive()) {
- o["loadsArchive"] = true;
- }
-
- if (plugin->IsMaster()) {
- o["isMaster"] = true;
- }
-
- if (plugin->IsLightPlugin()) {
- o["isLightMaster"] = true;
- }
-
- // don't add if the name is the only thing in there
- if (o.size() > 1) {
- plugins.push_back(o);
- }
- }
-
- return plugins;
-}
-
-QJsonValue LOOTWorker::createMessages(const std::vector<loot::Message>& list) const
-{
- QJsonArray messages;
-
- for (loot::Message m : list) {
- auto simpleMessage = loot::SelectMessageContent(m.GetContent(), m_Language);
- if (simpleMessage.has_value()) {
- messages.push_back(QJsonObject{
- {"type", QString::fromStdString(toString(m.GetType()))},
- {"text", QString::fromStdString(simpleMessage.value().GetText())}});
- }
- }
-
- return messages;
-}
-
-QJsonValue
-LOOTWorker::createDirty(const std::vector<loot::PluginCleaningData>& data) const
-{
- QJsonArray array;
-
- for (const auto& d : data) {
- QJsonObject o{
- {"crc", static_cast<qint64>(d.GetCRC())},
- {"itm", static_cast<qint64>(d.GetITMCount())},
- {"deletedReferences", static_cast<qint64>(d.GetDeletedReferenceCount())},
- {"deletedNavmesh", static_cast<qint64>(d.GetDeletedNavmeshCount())},
- };
-
- set(o, "cleaningUtility", QString::fromStdString(d.GetCleaningUtility()));
- auto simpleMessage = loot::SelectMessageContent(
- loot::Message(loot::MessageType::say, d.GetDetail()).GetContent(), m_Language);
- if (simpleMessage.has_value()) {
- set(o, "info", QString::fromStdString(simpleMessage.value().GetText()));
- } else {
- set(o, "info", QString::fromStdString(""));
- }
-
- array.push_back(o);
- }
-
- return array;
-}
-
-QJsonValue
-LOOTWorker::createClean(const std::vector<loot::PluginCleaningData>& data) const
-{
- QJsonArray array;
-
- for (const auto& d : data) {
- QJsonObject o{
- {"crc", static_cast<qint64>(d.GetCRC())},
- };
-
- set(o, "cleaningUtility", QString::fromStdString(d.GetCleaningUtility()));
- auto simpleMessage = loot::SelectMessageContent(
- loot::Message(loot::MessageType::say, d.GetDetail()).GetContent(), m_Language);
- if (simpleMessage.has_value()) {
- set(o, "info", QString::fromStdString(simpleMessage.value().GetText()));
- } else {
- set(o, "info", QString::fromStdString(""));
- }
-
- array.push_back(o);
- }
-
- return array;
-}
-
-QJsonValue
-LOOTWorker::createIncompatibilities(loot::GameInterface& game,
- const std::vector<loot::File>& data) const
-{
- QJsonArray array;
-
- for (auto&& f : data) {
- const auto n = static_cast<std::string>(f.GetName());
- if (!game.GetPlugin(n)) {
- continue;
- }
-
- const auto name = QString::fromStdString(n);
- const auto displayName = QString::fromStdString(f.GetDisplayName());
-
- QJsonObject o{{"name", name}};
-
- if (displayName != name) {
- set(o, "displayName", displayName);
- }
-
- array.push_back(std::move(o));
- }
-
- return array;
-}
-
-QJsonValue LOOTWorker::createMissingMasters(loot::GameInterface& game,
- const std::string& pluginName) const
-{
- QJsonArray array;
-
- for (auto&& master : game.GetPlugin(pluginName)->GetMasters()) {
- if (!game.GetPlugin(master)) {
- array.push_back(QString::fromStdString(master));
- }
- }
-
- return array;
-}
-
-void LOOTWorker::progress(Progress p)
-{
- std::cout << "[progress] " << static_cast<int>(p) << "\n";
- std::cout.flush();
-}
-
-void LOOTWorker::log(loot::LogLevel level, const std::string_view message) const
-{
- if (level < m_LogLevel) {
- return;
- }
-
- const auto ll = fromLootLogLevel(level);
- const auto levelName = logLevelToString(ll);
-
- std::cout << "[" << levelName << "] " << message << "\n";
- std::cout.flush();
-}
-
-loot::LogLevel toLootLogLevel(lootcli::LogLevels level)
-{
- using L = loot::LogLevel;
- using LC = lootcli::LogLevels;
-
- switch (level) {
- case LC::Trace:
- return L::trace;
- case LC::Debug:
- return L::debug;
- case LC::Info:
- return L::info;
- case LC::Warning:
- return L::warning;
- case LC::Error:
- return L::error;
- default:
- return L::info;
- }
-}
-
-lootcli::LogLevels fromLootLogLevel(loot::LogLevel level)
-{
- using L = loot::LogLevel;
- using LC = lootcli::LogLevels;
-
- switch (level) {
- case L::trace:
- return LC::Trace;
-
- case L::debug:
- return LC::Debug;
-
- case L::info:
- return LC::Info;
-
- case L::warning:
- return LC::Warning;
-
- case L::error:
- return LC::Error;
-
- default:
- return LC::Info;
- }
-}
-
-} // namespace lootcli
diff --git a/libs/lootcli/src/lootthread.h b/libs/lootcli/src/lootthread.h
deleted file mode 100644
index d4f4e65..0000000
--- a/libs/lootcli/src/lootthread.h
+++ /dev/null
@@ -1,107 +0,0 @@
-#ifndef LOOTTHREAD_H
-#define LOOTTHREAD_H
-
-#include "game_settings.h"
-#include "loot/database_interface.h"
-#include <lootcli/lootcli.h>
-
-namespace loot
-{
-class Game;
-}
-
-namespace lootcli
-{
-
-loot::LogLevel toLootLogLevel(lootcli::LogLevels level);
-lootcli::LogLevels fromLootLogLevel(loot::LogLevel level);
-
-class LOOTWorker
-{
-public:
- explicit LOOTWorker();
-
- void setGame(const std::string& gameName);
- void setGamePath(const std::string& gamePath);
- void setOutput(const std::string& outputPath);
- void setPluginListPath(const std::string& pluginListPath);
- void
- setLanguageCode(const std::string& language_code); // Will add this when I figure out
- // how languages work on MO
- void setLogLevel(loot::LogLevel level);
-
- void setUpdateMasterlist(bool update);
-
- int run();
-
-private:
- void progress(Progress p);
- void log(loot::LogLevel level, const std::string_view message) const;
-
-#ifdef _WIN32
- DWORD GetFile(const WCHAR* szUrl, const CHAR* szFileName);
-#else
- int GetFile(const std::string& url, const std::string& fileName);
-#endif
- void getSettings(const std::filesystem::path& file);
- std::string getOldDefaultRepoUrl(loot::GameId gameType);
- std::optional<std::string> GetLocalFolder(const toml::table& table);
- bool IsNehrim(const toml::table& table);
- bool IsEnderal(const toml::table& table, const std::string& expectedLocalFolder);
- bool IsEnderal(const toml::table& table);
- bool IsEnderalSE(const toml::table& table);
- bool isLocalPath(const std::string& location, const std::string& filename);
- bool isBranchCheckedOut(const std::filesystem::path& localGitRepo,
- const std::string& branch);
- std::optional<std::string> migrateMasterlistRepoSettings(loot::GameId gameType,
- std::string url,
- std::string branch);
- std::string migrateMasterlistSource(const std::string& source);
-
- std::filesystem::path gamePath() const;
- std::filesystem::path masterlistPath() const;
- std::filesystem::path settingsPath() const;
- std::filesystem::path userlistPath() const;
- std::filesystem::path l10nPath() const;
- std::filesystem::path dataPath() const;
-
-private:
- // void handleErr(unsigned int resultCode, const char *description);
- bool sort(loot::Game& game);
- // const char *lootErrorString(unsigned int errorCode);
- // template <typename T> T resolveVariable(HMODULE lib, const char *name);
- // template <typename T> T resolveFunction(HMODULE lib, const char *name);
-
-private:
- loot::GameId m_GameId;
- std::string m_Language;
- std::string m_GameName;
- std::string m_GamePath;
- std::string m_OutputPath;
- std::string m_PluginListPath;
- loot::LogLevel m_LogLevel;
- bool m_UpdateMasterlist;
- mutable std::recursive_mutex mutex_;
- loot::GameSettings m_GameSettings;
- std::chrono::high_resolution_clock::time_point m_startTime;
-
- std::string createJsonReport(loot::GameInterface& game,
- const std::vector<std::string>& sortedPlugins) const;
-
- QJsonArray createPlugins(loot::GameInterface& game,
- const std::vector<std::string>& sortedPlugins) const;
-
- QJsonValue createMessages(const std::vector<loot::Message>& list) const;
- QJsonValue createDirty(const std::vector<loot::PluginCleaningData>& data) const;
- QJsonValue createClean(const std::vector<loot::PluginCleaningData>& data) const;
-
- QJsonValue createIncompatibilities(loot::GameInterface& game,
- const std::vector<loot::File>& data) const;
-
- QJsonValue createMissingMasters(loot::GameInterface& game,
- const std::string& pluginName) const;
-};
-
-} // namespace lootcli
-
-#endif // LOOTTHREAD_H
diff --git a/libs/lootcli/src/main.cpp b/libs/lootcli/src/main.cpp
deleted file mode 100644
index d06e0ef..0000000
--- a/libs/lootcli/src/main.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-#include "lootthread.h"
-#include <lootcli/lootcli.h>
-
-using namespace std;
-
-template <typename T>
-T getParameter(const std::vector<std::string>& arguments, const std::string& key)
-{
- auto iter = std::find(arguments.begin(), arguments.end(), std::string("--") + key);
- if ((iter != arguments.end()) && ((iter + 1) != arguments.end())) {
- return boost::lexical_cast<T>(*(iter + 1));
- } else {
- throw std::runtime_error(std::string("argument missing " + key));
- }
-}
-
-template <>
-bool getParameter<bool>(const std::vector<std::string>& arguments,
- const std::string& key)
-{
- auto iter = std::find(arguments.begin(), arguments.end(), std::string("--") + key);
- if (iter != arguments.end()) {
- return true;
- } else {
- return false;
- }
-}
-
-template <typename T>
-T getOptionalParameter(const std::vector<std::string>& arguments,
- const std::string& key, T def)
-{
- try {
- return getParameter<T>(arguments, key);
- } catch (std::runtime_error&) {
- return def;
- }
-}
-
-loot::LogLevel getLogLevel(const std::vector<std::string>& arguments)
-{
- const auto s = getOptionalParameter<std::string>(arguments, "logLevel", "");
- const auto level = lootcli::logLevelFromString(s);
-
- return lootcli::toLootLogLevel(level);
-}
-
-#ifdef _WIN32
-int wWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
-{
- _setmode(_fileno(stdout), _O_BINARY);
- setlocale(LC_ALL, "en.UTF-8");
-
- std::vector<std::string> arguments;
- int argc;
- LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
-
- if (argv) {
- for (int i = 0; i < argc; ++i) {
- size_t num_converted;
- std::vector<char> arg(wcslen(argv[i]) * sizeof(wchar_t) + 1);
-
- wcstombs_s(&num_converted, &(arg[0]), arg.size(), argv[i], arg.size() - 1);
-
- arguments.push_back(&(arg[0]));
- }
- }
-#else
-int main(int argc, char* argv[])
-{
- setlocale(LC_ALL, "en_US.UTF-8");
-
- std::vector<std::string> arguments;
- for (int i = 0; i < argc; ++i) {
- arguments.push_back(argv[i]);
- }
-#endif
-
- // design rationale: this was designed to have the actual loot stuff run in a separate
- // thread. That turned out to be unnecessary atm.
-
- try {
- lootcli::LOOTWorker worker;
-
- worker.setUpdateMasterlist(!getParameter<bool>(arguments, "skipUpdateMasterlist"));
- worker.setGame(getParameter<std::string>(arguments, "game"));
- worker.setGamePath(getParameter<std::string>(arguments, "gamePath"));
- worker.setPluginListPath(getParameter<std::string>(arguments, "pluginListPath"));
- worker.setOutput(getParameter<std::string>(arguments, "out"));
- worker.setLogLevel(getLogLevel(arguments));
-
- const auto lang = getOptionalParameter<std::string>(arguments, "language", "");
- if (!lang.empty()) {
- worker.setLanguageCode(lang);
- }
-
- return worker.run();
- } catch (const std::exception& e) {
- std::cerr << "Error: " << e.what();
- return 1;
- }
-}
diff --git a/libs/lootcli/src/pch.cpp b/libs/lootcli/src/pch.cpp
deleted file mode 100644
index 1d9f38c..0000000
--- a/libs/lootcli/src/pch.cpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "pch.h"
diff --git a/libs/lootcli/src/pch.h b/libs/lootcli/src/pch.h
deleted file mode 100644
index cf460fd..0000000
--- a/libs/lootcli/src/pch.h
+++ /dev/null
@@ -1,84 +0,0 @@
-#ifdef _MSC_VER
-#pragma warning(disable : 4251) // neds to have dll-interface
-#pragma warning(disable : 4355) // this used in initializer list
-#pragma warning(disable : 4371) // layout may have changed
-#pragma warning(disable : 4514) // unreferenced inline function removed
-#pragma warning(disable : 4571) // catch semantics changed
-#pragma warning(disable : 4619) // no warning X
-#pragma warning(disable : 4623) // default constructor deleted
-#pragma warning(disable : 4625) // copy constructor deleted
-#pragma warning(disable : 4626) // copy assignment operator deleted
-#pragma warning(disable : 4710) // function not inlined
-#pragma warning(disable : 4820) // padding
-#pragma warning(disable : 4866) // left-to-right evaluation order
-#pragma warning(disable : 4868) // left-to-right evaluation order
-#pragma warning(disable : 5026) // move constructor deleted
-#pragma warning(disable : 5027) // move assignment operator deleted
-#pragma warning(disable : 5045) // spectre mitigation
-
-#pragma warning(push, 3)
-#pragma warning(disable : 4365) // signed/unsigned mismatch
-#pragma warning(disable : 4774) // bad format string
-#pragma warning(disable : 4946) // reinterpret_cast used between related classes
-#pragma warning(disable : 4800) // implicit conversion
-#endif
-
-// std
-#include <algorithm>
-#include <cstdint>
-#include <cstring>
-#include <exception>
-#include <filesystem>
-#include <fstream>
-#include <iostream>
-#include <locale>
-#include <map>
-#include <memory>
-#include <mutex>
-#include <optional>
-#include <regex>
-#include <set>
-#include <sstream>
-#include <stdexcept>
-#include <string>
-#include <thread>
-#include <utility>
-#include <vector>
-
-// qt
-#include <QDir>
-#include <QFileInfo>
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-
-// boost
-#include <boost/algorithm/string.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/locale.hpp>
-
-// loot
-#include <loot/api.h>
-#include <loot/enum/game_type.h>
-
-// third-party
-#include <toml++/toml.h>
-
-#ifdef _WIN32
-// windows
-#define WIN32_LEAN_AND_MEAN
-#include <Shlobj.h>
-#include <Windows.h>
-#include <fcntl.h>
-#include <io.h>
-#include <shellapi.h>
-#else
-// Linux equivalents
-#include <cstdlib>
-#include <unistd.h>
-#endif
-
-#ifdef _MSC_VER
-#undef _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
-#pragma warning(pop)
-#endif
diff --git a/libs/lootcli/src/version.h b/libs/lootcli/src/version.h
deleted file mode 100644
index d496a56..0000000
--- a/libs/lootcli/src/version.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#define LOOTCLI_VERSION_MAJOR 1
-#define LOOTCLI_VERSION_MINOR 7
-#define LOOTCLI_VERSION_MAINTENANCE 0
-#define LOOTCLI_VERSION_STRING "1.7.0"
diff --git a/libs/lootcli/src/version.rc b/libs/lootcli/src/version.rc
deleted file mode 100644
index bf6ebb4..0000000
--- a/libs/lootcli/src/version.rc
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "version.h"
-#include "Winver.h"
-
-#define VER_FILEVERSION LOOTCLI_VERSION_MAJOR,LOOTCLI_VERSION_MINOR,LOOTCLI_VERSION_MAINTENANCE,0
-#define VER_FILEVERSION_STR LOOTCLI_VERSION_STRING
-
-VS_VERSION_INFO VERSIONINFO
-FILEVERSION VER_FILEVERSION
-PRODUCTVERSION VER_FILEVERSION
-FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
-FILEFLAGS (0)
-FILEOS VOS__WINDOWS32
-FILETYPE VFT_APP
-FILESUBTYPE (0)
-BEGIN
-BLOCK "StringFileInfo"
-BEGIN
-BLOCK "040904B0"
-BEGIN
-VALUE "FileVersion", VER_FILEVERSION_STR
-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-2025 Mod Organizer 2 contributors\0"
-VALUE "ProductName", "lootcli\0"
-VALUE "ProductVersion", VER_FILEVERSION_STR
-END
-END
-
-BLOCK "VarFileInfo"
-BEGIN
-VALUE "Translation", 0x0409, 1200
-END
-END
diff --git a/libs/lootcli/vcpkg.json b/libs/lootcli/vcpkg.json
deleted file mode 100644
index d890106..0000000
--- a/libs/lootcli/vcpkg.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "dependencies": ["boost-locale", "tomlplusplus", "libloot"],
- "overrides": [
- {
- "name": "tomlplusplus",
- "version": "3.1.0"
- }
- ],
- "vcpkg-configuration": {
- "default-registry": {
- "kind": "git",
- "repository": "https://github.com/Microsoft/vcpkg",
- "baseline": "294f76666c3000630d828703e675814c05a4fd43"
- },
- "registries": [
- {
- "kind": "git",
- "repository": "https://github.com/Microsoft/vcpkg",
- "baseline": "294f76666c3000630d828703e675814c05a4fd43",
- "packages": ["boost*", "boost-*"]
- },
- {
- "kind": "git",
- "repository": "https://github.com/ModOrganizer2/vcpkg-registry",
- "baseline": "8beb2e0efa9c17dd6d17bb05288dd1e40727f673",
- "packages": ["libloot"]
- }
- ]
- }
-}