aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_plus/share
diff options
context:
space:
mode:
Diffstat (limited to 'libs/installer_fomod_plus/share')
-rw-r--r--libs/installer_fomod_plus/share/FOMODData/ArchiveExtractor.h160
-rw-r--r--libs/installer_fomod_plus/share/FOMODData/FomodDB.h144
-rw-r--r--libs/installer_fomod_plus/share/FOMODData/FomodDBEntry.h129
-rw-r--r--libs/installer_fomod_plus/share/FOMODData/FomodRescan.h277
-rw-r--r--libs/installer_fomod_plus/share/FOMODData/PluginReader.h119
-rw-r--r--libs/installer_fomod_plus/share/stringutil.h150
-rw-r--r--libs/installer_fomod_plus/share/xml/FomodInfoFile.cpp44
-rw-r--r--libs/installer_fomod_plus/share/xml/FomodInfoFile.h25
-rw-r--r--libs/installer_fomod_plus/share/xml/ModuleConfiguration.cpp352
-rw-r--r--libs/installer_fomod_plus/share/xml/ModuleConfiguration.h305
-rw-r--r--libs/installer_fomod_plus/share/xml/XmlHelper.h17
-rw-r--r--libs/installer_fomod_plus/share/xml/XmlParseException.h10
12 files changed, 0 insertions, 1732 deletions
diff --git a/libs/installer_fomod_plus/share/FOMODData/ArchiveExtractor.h b/libs/installer_fomod_plus/share/FOMODData/ArchiveExtractor.h
deleted file mode 100644
index 65cd0ae..0000000
--- a/libs/installer_fomod_plus/share/FOMODData/ArchiveExtractor.h
+++ /dev/null
@@ -1,160 +0,0 @@
-#pragma once
-
-#include "stringutil.h"
-
-#include <QDir>
-#include <QFileInfo>
-#include <QString>
-#include <QTemporaryDir>
-#include <archive.h>
-#include <filesystem>
-#include <functional>
-#include <iostream>
-#include <memory>
-#include <optional>
-
-struct ExtractionResult {
- bool success = false;
- QString moduleConfigPath;
- std::vector<QString> pluginPaths;
- QString errorMessage;
- std::unique_ptr<QTemporaryDir> tempDir; // Owns the temp directory lifetime
-};
-
-/**
- * Utility class to extract FOMOD data from archives without being in an installer context.
- * Used by the Patch Wizard's rescan functionality.
- */
-class ArchiveExtractor {
-public:
- using ProgressCallback = std::function<void(const QString& fileName)>;
-
- /**
- * Extract ModuleConfig.xml and plugin files from an archive.
- * @param archiveFilePath Full path to the archive file
- * @param progressCallback Optional callback for progress updates
- * @return ExtractionResult containing paths to extracted files
- */
- static ExtractionResult extractFomodData(
- const QString& archiveFilePath,
- const ProgressCallback& progressCallback = nullptr)
- {
- ExtractionResult result;
- result.tempDir = std::make_unique<QTemporaryDir>();
-
- if (!result.tempDir->isValid()) {
- result.errorMessage = "Failed to create temporary directory";
- return result;
- }
-
- const auto archive = CreateArchive();
- if (!archive->isValid()) {
- result.errorMessage = "Failed to load archive module";
- return result;
- }
-
- if (!archive->open(archiveFilePath.toStdWString(), nullptr)) {
- result.errorMessage = QString("Failed to open archive (error %1)")
- .arg(static_cast<int>(archive->getLastError()));
- return result;
- }
-
- // Get file list and mark files for extraction
- const auto& fileList = archive->getFileList();
- QString moduleConfigInArchive;
-
- for (auto* fileData : fileList) {
- const auto entryPath = QString::fromStdWString(fileData->getArchiveFilePath());
-
- // Check for ModuleConfig.xml
- if (entryPath.toLower().endsWith("fomod/moduleconfig.xml") ||
- entryPath.toLower().endsWith("fomod\\moduleconfig.xml")) {
- moduleConfigInArchive = entryPath;
- // Set output path relative to output directory for extract()
- fileData->addOutputFilePath(L"ModuleConfig.xml");
- result.moduleConfigPath = result.tempDir->filePath("ModuleConfig.xml");
- }
- // Check for plugin files
- else if (isPluginFile(entryPath)) {
- const auto fileName = QFileInfo(entryPath).fileName();
- const auto relativePath = QString("plugins/") + fileName;
- fileData->addOutputFilePath(relativePath.toStdWString());
- result.pluginPaths.push_back(result.tempDir->filePath(relativePath));
- }
- }
-
- if (moduleConfigInArchive.isEmpty()) {
- result.errorMessage = "No ModuleConfig.xml found in archive";
- return result;
- }
-
- // Create plugins subdirectory
- QDir(result.tempDir->path()).mkpath("plugins");
-
- // Extract the files
- Archive::FileChangeCallback fileChangeCallback = [&progressCallback](
- Archive::FileChangeType, const std::wstring& fileName) {
- if (progressCallback) {
- progressCallback(QString::fromStdWString(fileName));
- }
- };
-
- Archive::ErrorCallback errorCallback = [&result](const std::wstring& error) {
- result.errorMessage = QString::fromStdWString(error);
- };
-
- const bool extractSuccess = archive->extract(
- result.tempDir->path().toStdWString(),
- Archive::ProgressCallback{}, // progress callback
- fileChangeCallback,
- errorCallback
- );
-
- if (!extractSuccess) {
- if (result.errorMessage.isEmpty()) {
- result.errorMessage = "Extraction failed";
- }
- return result;
- }
-
- // Verify ModuleConfig.xml was extracted
- if (!QFile::exists(result.moduleConfigPath)) {
- result.errorMessage = "ModuleConfig.xml extraction failed";
- return result;
- }
-
- // Filter to only existing plugin files
- std::vector<QString> existingPlugins;
- for (const auto& path : result.pluginPaths) {
- if (QFile::exists(path)) {
- existingPlugins.push_back(path);
- }
- }
- result.pluginPaths = std::move(existingPlugins);
-
- result.success = true;
- return result;
- }
-
- /**
- * Check if an archive contains FOMOD files without extracting.
- * @param archiveFilePath Full path to the archive file
- * @return true if the archive contains fomod/ModuleConfig.xml
- */
- static bool hasFomodFiles(const QString& archiveFilePath)
- {
- const auto archive = CreateArchive();
- if (!archive->isValid() || !archive->open(archiveFilePath.toStdWString(), nullptr)) {
- return false;
- }
-
- for (const auto* fileData : archive->getFileList()) {
- const auto path = QString::fromStdWString(fileData->getArchiveFilePath());
- if (path.toLower().endsWith("fomod/moduleconfig.xml") ||
- path.toLower().endsWith("fomod\\moduleconfig.xml")) {
- return true;
- }
- }
- return false;
- }
-};
diff --git a/libs/installer_fomod_plus/share/FOMODData/FomodDB.h b/libs/installer_fomod_plus/share/FOMODData/FomodDB.h
deleted file mode 100644
index f5ea6f5..0000000
--- a/libs/installer_fomod_plus/share/FOMODData/FomodDB.h
+++ /dev/null
@@ -1,144 +0,0 @@
-#pragma once
-
-#include <fstream>
-#include <stringutil.h>
-
-#include "FomodDBEntry.h"
-
-#include <xml/ModuleConfiguration.h>
-
-#include "PluginReader.h"
-
-using FOMODDBEntries = std::vector<std::shared_ptr<FomodDbEntry> >;
-
-constexpr std::string FOMOD_DB_FILE = "fomod.db";
-
-class FomodDB {
-public:
- /**
- *
- * @param moBasePath The organizer instance's basePath() value
- * @param dbName The filename of the db. Only settable for testing purposes.
- */
- explicit FomodDB(const std::string &moBasePath, const std::string &dbName = FOMOD_DB_FILE) {
- dbFilePath = (std::filesystem::path(moBasePath) / dbName).string();
- loadFromFile();
- }
-
- // TODO: Also pull from non install steps (requiredInstallFiles or whatever, and optional);
- static std::shared_ptr<FomodDbEntry> getEntryFromFomod(ModuleConfiguration *fomod, std::vector<QString> pluginPaths,
- int modId) {
- std::vector<FomodOption> options;
- for (const auto &installStep: fomod->installSteps.installSteps) {
- for (const auto &group: installStep.optionalFileGroups.groups) {
- for (const auto &plugin: group.plugins.plugins) {
- // Create a DB entry for the given plugin if it has an ESP
- for (auto file: plugin.files.files) {
- if (file.isFolder || !isPluginFile(file.source)) {
- continue;
- }
-
- // Find the path in pluginPaths that ends with this path
- // PluginPaths is gathered from the archive contents.
- auto it = std::ranges::find_if(pluginPaths, [&file](const QString &path) {
- return path.endsWith(file.source.c_str());
- });
- if (it == pluginPaths.end()) {
- continue;
- }
- const auto &pluginPath = *it;
- const auto masters = PluginReader::readMasters(pluginPath.toStdString(), true);
- options.emplace_back(
- plugin.name,
- file.source,
- masters,
- installStep.name,
- group.name
- );
- }
- }
- }
- }
- return std::make_shared<FomodDbEntry>(modId, fomod->moduleName, options);
- }
-
- void addEntry(const std::shared_ptr<FomodDbEntry> &entry, const bool upsert = true) {
- // TODO: Test this upsert.
- if (upsert) {
- const auto it = std::ranges::find_if(entries, [&entry](const std::shared_ptr<FomodDbEntry> &e) {
- return e->getModId() == entry->getModId();
- });
- if (it != entries.end()) {
- *it = entry;
- } else {
- entries.emplace_back(entry);
- }
- } else {
- entries.emplace_back(entry);
- }
- }
-
- [[nodiscard]] const FOMODDBEntries &getEntries() { return entries; }
-
- void saveToFile() const {
- try {
- std::ofstream file(dbFilePath);
- if (!file.is_open()) {
- return;
- }
-
- file << toJson().dump(2); // Pretty-print with 2-space indentation
- file.close();
- } catch ([[maybe_unused]] const std::exception &e) {
- // Handle saving errors
- }
- }
-
- [[nodiscard]] nlohmann::json toJson() const {
- nlohmann::json jsonArray = nlohmann::json::array();
-
- for (const auto &entry: entries) {
- jsonArray.push_back(entry->toJson());
- }
-
- return jsonArray;
- }
-
-private:
- FOMODDBEntries entries;
- std::string dbFilePath;
-
- void loadFromFile() {
- entries.clear();
-
- // Create empty file if it doesn't exist
- if (!std::filesystem::exists(dbFilePath)) {
- std::ofstream file(dbFilePath);
- file << "[]"; // Empty JSON array
- file.close();
- return; // No entries to load
- }
-
- try {
- // Read and parse the JSON file
- std::ifstream file(dbFilePath);
- if (!file.is_open()) {
- return;
- }
-
- nlohmann::json jsonArray = nlohmann::json::parse(file);
-
- // Ensure it's an array
- if (!jsonArray.is_array()) {
- return;
- }
-
- // Process each entry in the array
- for (const auto &entryJson: jsonArray) {
- entries.push_back(std::make_unique<FomodDbEntry>(entryJson));
- }
- } catch ([[maybe_unused]] const std::exception &e) {
- // Handle parsing errors (leave entries empty)
- }
- }
-};
diff --git a/libs/installer_fomod_plus/share/FOMODData/FomodDBEntry.h b/libs/installer_fomod_plus/share/FOMODData/FomodDBEntry.h
deleted file mode 100644
index 5d39f95..0000000
--- a/libs/installer_fomod_plus/share/FOMODData/FomodDBEntry.h
+++ /dev/null
@@ -1,129 +0,0 @@
-#pragma once
-
-#include <string>
-#include <utility>
-#include <vector>
-#include <nlohmann/json.hpp>
-
-/*
-The following JSON will be part of an array of similar objects in the root level "JSON DB" for FOMOD Plus.
-It contains information to resolve the identity of a given mod (names can change), and then the options
-in the FOMOD with their respective masters.
-{
- modId: 12345,
- displayName: "Lux (Patch Hub)",
- options: [
- {
- "name" "JK's The Hag's Cure",
- "fileName": "Lux - JK's The Hag's Cure patch.esp",
- "masters": [
- "Skyrim.esm",
- "JK's The Hag's Cure.esp",
- "Lux - Resources.esp",
- "Lux.esp"
- ],
- "step": "Page One",
- "group": "Group One",
- "selectionState": "Available"
- }
- ]
-}
-*/
-
-enum class SelectionState {
- Unknown, // Not yet matched to choices
- Selected, // User selected this option
- Deselected, // User manually deselected
- Available // Present but user didn't interact (or choices not recorded)
-};
-
-inline std::string selectionStateToString(SelectionState state) {
- switch (state) {
- case SelectionState::Unknown: return "Unknown";
- case SelectionState::Selected: return "Selected";
- case SelectionState::Deselected: return "Deselected";
- case SelectionState::Available: return "Available";
- default: return "Unknown";
- }
-}
-
-inline SelectionState stringToSelectionState(const std::string& str) {
- if (str == "Selected") return SelectionState::Selected;
- if (str == "Deselected") return SelectionState::Deselected;
- if (str == "Available") return SelectionState::Available;
- return SelectionState::Unknown;
-}
-
-struct FomodOption {
- std::string name;
- std::string fileName;
- std::vector<std::string> masters;
- std::string step;
- std::string group;
- SelectionState selectionState = SelectionState::Unknown;
-
- FomodOption(std::string n, std::string fn, std::vector<std::string> m, std::string s, std::string g,
- SelectionState state = SelectionState::Unknown)
- : name(std::move(n)), fileName(std::move(fn)), masters(std::move(m)), step(std::move(s)), group(std::move(g)),
- selectionState(state) {}
-};
-
-class FomodDbEntry {
-public:
- explicit FomodDbEntry(nlohmann::json json) {
- modId = json["modId"];
- displayName = json["displayName"];
- for (auto &option: json["options"]) {
- // create an option from this object
- SelectionState state = SelectionState::Unknown;
- if (option.contains("selectionState")) {
- state = stringToSelectionState(option["selectionState"]);
- }
- FomodOption fomodOption(
- option["name"],
- option["fileName"],
- option["masters"],
- option["step"],
- option["group"],
- state
- );
- options.push_back(fomodOption);
- }
- }
-
- explicit FomodDbEntry(const int modId, std::string displayName, const std::vector<FomodOption> &options)
- : modId(modId), displayName(std::move(displayName)), options(options) {
- }
-
-
- [[nodiscard]] int getModId() const { return modId; }
- [[nodiscard]] std::string getDisplayName() const { return displayName; }
- [[nodiscard]] const std::vector<FomodOption>& getOptions() const { return options; }
- [[nodiscard]] std::vector<FomodOption>& getOptionsMutable() { return options; }
-
- [[nodiscard]] nlohmann::json toJson() const {
- nlohmann::json result;
- result["modId"] = modId;
- result["displayName"] = displayName;
-
- nlohmann::json optionsArray = nlohmann::json::array();
- for (const auto &[name, fileName, masters, step, group, selectionState]: options) {
- nlohmann::json optionJson;
- optionJson["name"] = name;
- optionJson["fileName"] = fileName;
- optionJson["masters"] = masters;
- optionJson["step"] = step;
- optionJson["group"] = group;
- optionJson["selectionState"] = selectionStateToString(selectionState);
- optionsArray.push_back(optionJson);
- }
-
- result["options"] = optionsArray;
- return result;
- }
-
-private:
- int modId;
- std::string displayName;
- std::vector<FomodOption> options;
-};
diff --git a/libs/installer_fomod_plus/share/FOMODData/FomodRescan.h b/libs/installer_fomod_plus/share/FOMODData/FomodRescan.h
deleted file mode 100644
index c7d53a2..0000000
--- a/libs/installer_fomod_plus/share/FOMODData/FomodRescan.h
+++ /dev/null
@@ -1,277 +0,0 @@
-#pragma once
-
-#include "ArchiveExtractor.h"
-#include "FomodDB.h"
-#include "stringutil.h"
-#include "xml/ModuleConfiguration.h"
-
-#include <QDir>
-#include <QString>
-#include <functional>
-#include <imodinterface.h>
-#include <imoinfo.h>
-#include <nlohmann/json.hpp>
-
-struct RescanResult {
- int totalModsProcessed = 0;
- int successfullyScanned = 0;
- int missingArchives = 0;
- int parseErrors = 0;
- std::vector<std::string> failedMods;
-};
-
-/**
- * Orchestrates rescanning of all mods with stored FOMOD choices to repopulate the database.
- * Used when fomod.db is missing or needs to be regenerated from existing installations.
- */
-class FomodRescan {
-public:
- using ProgressCallback = std::function<void(int current, int total, const QString& modName)>;
-
- FomodRescan(MOBase::IOrganizer* organizer, FomodDB* db)
- : mOrganizer(organizer), mFomodDb(db) {}
-
- /**
- * Scan all mods that have stored FOMOD Plus choices and repopulate the database.
- * @param progressCallback Optional callback for progress updates
- * @return RescanResult with statistics about the scan
- */
- RescanResult scanAllModsWithChoices(const ProgressCallback& progressCallback = nullptr)
- {
- RescanResult result;
-
- const auto modList = mOrganizer->modList();
- if (!modList) {
- return result;
- }
-
- // First pass: gather all mods with stored choices
- std::vector<MOBase::IModInterface*> modsWithChoices;
- for (const auto& modName : modList->allMods()) {
- auto* mod = modList->getMod(modName);
- if (mod && hasStoredChoices(mod)) {
- modsWithChoices.push_back(mod);
- }
- }
-
- result.totalModsProcessed = static_cast<int>(modsWithChoices.size());
-
- // Second pass: process each mod
- int current = 0;
- for (auto* mod : modsWithChoices) {
- current++;
- if (progressCallback) {
- progressCallback(current, result.totalModsProcessed, mod->name());
- }
-
- const auto scanResult = processMod(mod);
- switch (scanResult) {
- case ScanOutcome::Success:
- result.successfullyScanned++;
- break;
- case ScanOutcome::MissingArchive:
- result.missingArchives++;
- result.failedMods.push_back(mod->name().toStdString() + " (missing archive)");
- break;
- case ScanOutcome::ParseError:
- result.parseErrors++;
- result.failedMods.push_back(mod->name().toStdString() + " (parse error)");
- break;
- case ScanOutcome::NoFomod:
- result.failedMods.push_back(mod->name().toStdString() + " (no FOMOD)");
- break;
- }
- }
-
- // Save the database
- mFomodDb->saveToFile();
-
- return result;
- }
-
-private:
- MOBase::IOrganizer* mOrganizer;
- FomodDB* mFomodDb;
-
- enum class ScanOutcome {
- Success,
- MissingArchive,
- ParseError,
- NoFomod
- };
-
- /**
- * Check if a mod has stored FOMOD Plus choices (non-zero pluginSetting).
- */
- bool hasStoredChoices(MOBase::IModInterface* mod) const
- {
- const auto fomodData = mod->pluginSetting(
- StringConstants::Plugin::NAME.data(), "fomod", 0);
-
- if (!fomodData.isValid() || fomodData.isNull()) {
- return false;
- }
-
- // Check if it's actually valid JSON with steps
- try {
- const auto json = nlohmann::json::parse(fomodData.toString().toStdString());
- return json.contains("steps") && json["steps"].is_array() && !json["steps"].empty();
- } catch (...) {
- return false;
- }
- }
-
- /**
- * Get the stored choices JSON from a mod's pluginSetting.
- */
- nlohmann::json getStoredChoices(MOBase::IModInterface* mod) const
- {
- const auto fomodData = mod->pluginSetting(
- StringConstants::Plugin::NAME.data(), "fomod", 0);
-
- try {
- return nlohmann::json::parse(fomodData.toString().toStdString());
- } catch (...) {
- return nlohmann::json();
- }
- }
-
- /**
- * Process a single mod: extract archive, parse FOMOD, create DB entry with selection states.
- */
- ScanOutcome processMod(MOBase::IModInterface* mod)
- {
- // Get the archive path
- const auto installationFile = mod->installationFile();
- if (installationFile.isEmpty()) {
- return ScanOutcome::MissingArchive;
- }
-
- const auto downloadsPath = mOrganizer->downloadsPath();
- const auto archivePath = QDir(installationFile).isAbsolute()
- ? installationFile
- : downloadsPath + "/" + installationFile;
-
- if (!QFile::exists(archivePath)) {
- return ScanOutcome::MissingArchive;
- }
-
- // Extract FOMOD data from archive
- auto extractionResult = ArchiveExtractor::extractFomodData(archivePath);
- if (!extractionResult.success) {
- return ScanOutcome::ParseError;
- }
-
- // Parse ModuleConfiguration
- auto moduleConfig = std::make_unique<ModuleConfiguration>();
- try {
- if (!moduleConfig->deserialize(extractionResult.moduleConfigPath)) {
- return ScanOutcome::ParseError;
- }
- } catch (...) {
- return ScanOutcome::ParseError;
- }
-
- // Get the mod's Nexus ID
- const int modId = mod->nexusId();
-
- // Create FomodDbEntry using existing logic
- auto entry = FomodDB::getEntryFromFomod(
- moduleConfig.get(),
- extractionResult.pluginPaths,
- modId
- );
-
- if (!entry || entry->getOptions().empty()) {
- return ScanOutcome::NoFomod;
- }
-
- // Apply selection states from stored choices
- const auto choices = getStoredChoices(mod);
- applySelectionsToEntry(*entry, choices);
-
- // Add to database (upsert)
- mFomodDb->addEntry(entry, true);
-
- return ScanOutcome::Success;
- }
-
- /**
- * Apply user selection states to a FomodDbEntry based on stored choices JSON.
- *
- * Choices JSON format:
- * {
- * "steps": [{
- * "name": "Step Name",
- * "groups": [{
- * "name": "Group Name",
- * "plugins": ["Selected Plugin 1"],
- * "deselected": ["Manually Deselected Plugin"]
- * }]
- * }]
- * }
- */
- void applySelectionsToEntry(FomodDbEntry& entry, const nlohmann::json& choices)
- {
- if (!choices.contains("steps") || !choices["steps"].is_array()) {
- // No choices data - mark all as Available
- for (auto& option : entry.getOptionsMutable()) {
- option.selectionState = SelectionState::Available;
- }
- return;
- }
-
- // Build a lookup map for quick matching: stepName/groupName/pluginName -> state
- struct PluginState {
- bool selected = false;
- bool deselected = false;
- };
- std::map<std::string, PluginState> stateMap;
-
- for (const auto& step : choices["steps"]) {
- if (!step.contains("name") || !step.contains("groups")) continue;
- const std::string stepName = step["name"];
-
- for (const auto& group : step["groups"]) {
- if (!group.contains("name")) continue;
- const std::string groupName = group["name"];
-
- // Process selected plugins
- if (group.contains("plugins") && group["plugins"].is_array()) {
- for (const auto& plugin : group["plugins"]) {
- const std::string pluginName = plugin;
- const auto key = stepName + "/" + groupName + "/" + pluginName;
- stateMap[key].selected = true;
- }
- }
-
- // Process deselected plugins
- if (group.contains("deselected") && group["deselected"].is_array()) {
- for (const auto& plugin : group["deselected"]) {
- const std::string pluginName = plugin;
- const auto key = stepName + "/" + groupName + "/" + pluginName;
- stateMap[key].deselected = true;
- }
- }
- }
- }
-
- // Apply states to options
- for (auto& option : entry.getOptionsMutable()) {
- const auto key = option.step + "/" + option.group + "/" + option.name;
-
- if (auto it = stateMap.find(key); it != stateMap.end()) {
- if (it->second.selected) {
- option.selectionState = SelectionState::Selected;
- } else if (it->second.deselected) {
- option.selectionState = SelectionState::Deselected;
- } else {
- option.selectionState = SelectionState::Available;
- }
- } else {
- // Plugin not found in choices - mark as Available
- option.selectionState = SelectionState::Available;
- }
- }
- }
-};
diff --git a/libs/installer_fomod_plus/share/FOMODData/PluginReader.h b/libs/installer_fomod_plus/share/FOMODData/PluginReader.h
deleted file mode 100644
index dade7e1..0000000
--- a/libs/installer_fomod_plus/share/FOMODData/PluginReader.h
+++ /dev/null
@@ -1,119 +0,0 @@
-#pragma once
-
-#include <fstream>
-#include <string>
-#include <vector>
-#include <cstdint>
-#include <unordered_set>
-
-static const std::unordered_set<std::string> VANILLA_MASTERS = {
- "Skyrim.esm",
- "Update.esm",
- "Dawnguard.esm",
- "HearthFires.esm",
- "Dragonborn.esm"
-};
-
-class PluginReader {
-public:
-
- /**
- * Reads the master files from a Bethesda plugin file (ESP/ESM/ESL)
- * @param filePath Path to the plugin file
- * @param trimVanilla Exclude the vanilla game masters or not. Mostly to save DB space.
- * @return Vector of master filenames
- */
- static std::vector<std::string> readMasters(const std::string& filePath, const bool trimVanilla = false)
- {
- std::vector<std::string> masters;
- std::ifstream file(filePath, std::ios::binary);
-
- if (!file) {
- return masters;
- }
-
- // Check TES4 record signature
- char signature[4];
- file.read(signature, 4);
- if (strncmp(signature, "TES4", 4) != 0) {
- return masters;
- }
-
- // Read record size
- uint32_t recordSize;
- file.read(reinterpret_cast<char*>(&recordSize), 4);
-
- constexpr uint32_t skipSize = sizeof(uint32_t) // flags
- + sizeof(uint32_t) // formId
- + sizeof(uint16_t) // timestamp
- + sizeof(uint16_t) // version control
- + sizeof(uint16_t) // internal version
- + sizeof(uint16_t); // unknown
-
- // Skip header flags, formID, etc. (total 8 bytes)
- file.seekg(skipSize, std::ios::cur);
-
- // Calculate where the TES4 record ends
- std::streampos recordEnd = file.tellg() + static_cast<std::streampos>(recordSize);
-
- // Read subrecords until we reach the end of the TES4 record
- while (file && file.tellg() < recordEnd) {
- char subRecordType[4];
- uint16_t subRecordSize;
-
- // Read subrecord type and size
- file.read(subRecordType, 4);
- file.read(reinterpret_cast<char*>(&subRecordSize), 2);
-
- if (strncmp(subRecordType, "MAST", 4) == 0) {
- // Read master filename (null-terminated string)
- std::string masterName;
- masterName.resize(subRecordSize);
- file.read(masterName.data(), subRecordSize);
-
- // Remove null terminator if present
- if (!masterName.empty() && masterName.back() == '\0') {
- masterName.pop_back();
- }
-
- // Only add if it's not a vanilla master or if we're not trimming
- if (!trimVanilla || !VANILLA_MASTERS.contains(masterName)) {
- masters.push_back(masterName);
- }
-
- // Each MAST is followed by a DATA subrecord
- char dataType[4];
- uint16_t dataSize;
- file.read(dataType, 4);
- file.read(reinterpret_cast<char*>(&dataSize), 2);
-
- // Skip DATA content (usually an 8-byte value)
- file.seekg(dataSize, std::ios::cur);
- } else {
- // Skip other subrecord types
- file.seekg(subRecordSize, std::ios::cur);
- }
- }
-
- return masters;
- }
-
- /**
- * Checks if a file is a valid Bethesda plugin (ESP/ESM/ESL)
- * @param filePath Path to the file
- * @return True if the file is a valid plugin
- */
- static bool isValidPlugin(const std::string& filePath)
- {
- std::ifstream file(filePath, std::ios::binary);
-
- if (!file) {
- return false;
- }
-
- char signature[4];
- file.read(signature, 4);
-
- return strncmp(signature, "TES4", 4) == 0;
- }
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/share/stringutil.h b/libs/installer_fomod_plus/share/stringutil.h
deleted file mode 100644
index c5d2fb8..0000000
--- a/libs/installer_fomod_plus/share/stringutil.h
+++ /dev/null
@@ -1,150 +0,0 @@
-#ifndef STRINGCONSTANTS_H
-#define STRINGCONSTANTS_H
-#include <algorithm>
-#include <regex>
-#include <string>
-#include <vector>
-#include <QString>
-
-
-namespace StringConstants
-{
- namespace Plugin
- {
- constexpr std::string_view NAME = "FOMOD Plus";
- constexpr std::string_view AUTHOR = "clearing";
- constexpr std::string_view DESCRIPTION =
- "Extends the capabilities of the FOMOD installer for advanced users.\n\n"
- "Available colors (enter exactly): \n"
- "'Light0'\t'Light1'\t'Light2'\t'Light3'\n"
- "'Dark0'\t'Dark1'\t'Dark2'\t'Dark3'\n"
- "'Red'\t'Red Bright'\n"
- "'Green'\t'Green Bright'\n"
- "'Yellow'\t'Yellow Bright'\n"
- "'Blue'\t'Blue Bright'\n"
- "'Purple'\t'Purple Bright'\n"
- "'Aqua'\t'Aqua Bright'\n"
- "'Orange'\t'Orange Bright'\n";
- constexpr std::wstring_view W_NAME = L"FOMOD Plus";
- constexpr std::wstring_view W_AUTHOR = L"clearing";
- constexpr std::wstring_view W_DESCRIPTION =
- L"Extends the capabilities of the FOMOD installer for advanced users.";
- }
-
- namespace FomodFiles
- {
- constexpr std::string_view FOMOD_DIR = "fomod";
- constexpr std::string_view INFO_XML = "info.xml";
- constexpr std::string_view MODULE_CONFIG = "ModuleConfig.xml";
-
- // Wide string versions for archive API
- constexpr std::wstring_view W_FOMOD_DIR = L"fomod";
- constexpr std::wstring_view W_INFO_XML = L"fomod/info.xml";
- constexpr std::wstring_view W_MODULE_CONFIG = L"fomod/ModuleConfig.xml";
-
- constexpr std::string_view TYPE_REQUIRED = "Required";
- constexpr std::string_view TYPE_OPTIONAL = "Optional";
- constexpr std::string_view TYPE_RECOMMENDED = "Recommended";
- constexpr std::string_view TYPE_NOT_USABLE = "NotUsable";
- constexpr std::string_view TYPE_COULD_BE_USABLE = "CouldBeUsable";
- }
-}
-
-// Convert narrow string_view to wstring (for ASCII strings only)
-inline std::wstring toWide(std::string_view sv)
-{
- return std::wstring(sv.begin(), sv.end());
-}
-
-// trim from start (in place)
-inline void ltrim(std::string& s)
-{
- s.erase(s.begin(), std::ranges::find_if(s, [](const unsigned char ch)
- {
- return !std::isspace(ch);
- }));
-}
-
-// trim from end (in place)
-inline void rtrim(std::string& s)
-{
- s.erase(std::find_if(s.rbegin(), s.rend(), [](const unsigned char ch)
- {
- return !std::isspace(ch);
- }).base(), s.end());
-}
-
-// trim from both ends (in place)
-inline std::string& trim(std::string& s)
-{
- ltrim(s);
- rtrim(s);
- return s;
-}
-
-inline void trim(const std::vector<std::string>& strings)
-{
- for (auto s : strings) { trim(s); }
-}
-
-inline std::wstring toLower(const std::wstring& str)
-{
- std::wstring lowerStr = str;
- std::ranges::transform(lowerStr, lowerStr.begin(), towlower);
- return lowerStr;
-}
-
-inline std::string toLower(const std::string& str)
-{
- std::string lowerStr = str;
- std::ranges::transform(lowerStr, lowerStr.begin(), tolower);
- return lowerStr;
-}
-
-inline bool endsWithCaseInsensitive(const std::wstring& str, const std::wstring& suffix)
-{
- const std::wstring lowerStr = toLower(str);
- if (const std::wstring lowerSuffix = toLower(suffix); lowerStr.length() >= lowerSuffix.length())
- {
- return 0 == lowerStr.compare(lowerStr.length() - lowerSuffix.length(), lowerSuffix.length(), lowerSuffix);
- }
- return false;
-}
-
-inline QString formatPluginDescription(const QString& text)
-{
- std::string formattedText = text.toStdString();
- // Replace URLs with <a href> tags
- const std::regex
- urlRegex(R"((http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-]))");
- formattedText = std::regex_replace(formattedText, urlRegex, R"(<a href="$&">$&</a>)");
-
- // Replace line breaks
- formattedText = std::regex_replace(formattedText, std::regex("&#13;&#10;"), "<br>");
- formattedText = std::regex_replace(formattedText, std::regex("\\r\\n"), "<br>");
- formattedText = std::regex_replace(formattedText, std::regex("\\r"), "<br>");
- formattedText = std::regex_replace(formattedText, std::regex("\\n"), "<br>");
-
- return QString::fromStdString(formattedText);
-}
-
-// NOTE: This isn't perfect. Sometimes we have whole filenames, sometimes we're just passing
-// the suffix. It should be fine as long as no one names a file like..."Testesl". Idk what that
-// would do anyway.
-inline bool isPluginFile(const QString& file)
-{
- return file.toLower().endsWith("esl")
- || file.toLower().endsWith("esp")
- || file.toLower().endsWith("esm");
-}
-
-inline bool isPluginFile(const std::string& file)
-{
- const auto lower = toLower(file);
- return lower.ends_with("esl")
- || lower.ends_with("esp")
- || lower.ends_with("esm");
-}
-
-
-#endif
diff --git a/libs/installer_fomod_plus/share/xml/FomodInfoFile.cpp b/libs/installer_fomod_plus/share/xml/FomodInfoFile.cpp
deleted file mode 100644
index d4fed69..0000000
--- a/libs/installer_fomod_plus/share/xml/FomodInfoFile.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#include "FomodInfoFile.h"
-#include "XmlParseException.h"
-#include <format>
-#include <pugixml.hpp>
-
-#include "stringutil.h"
-
-#include <QFile>
-#include <QString>
-
-bool FomodInfoFile::deserialize(const QString& filePath)
-{
- QFile file(filePath);
- if (!file.open(QIODevice::ReadOnly)) {
- throw XmlParseException(std::format("Failed to open file: {}", filePath.toStdString()));
- }
- const QByteArray content = file.readAll();
- file.close();
-
- pugi::xml_document doc;
- if (const pugi::xml_parse_result result = doc.load_buffer(content.constData(), content.size()); !result) {
- throw XmlParseException(std::format("XML parsed with errors: {}", result.description()));
- }
-
- const pugi::xml_node fomodNode = doc.child("fomod");
- if (!fomodNode) {
- throw XmlParseException("No <config> node found");
- }
-
- name = fomodNode.child("Name").text().as_string();
- author = fomodNode.child("Author").text().as_string();
- version = fomodNode.child("Version").text().as_string();
- website = fomodNode.child("Website").text().as_string();
- description = fomodNode.child("Description").text().as_string();
-
- trim({ name, author, version, website, description });
-
- for (pugi::xml_node groupNode : fomodNode.child("Groups").children("element")) {
- groups.emplace_back(groupNode.text().as_string());
- }
-
- return true;
-
-} \ No newline at end of file
diff --git a/libs/installer_fomod_plus/share/xml/FomodInfoFile.h b/libs/installer_fomod_plus/share/xml/FomodInfoFile.h
deleted file mode 100644
index d2c2a23..0000000
--- a/libs/installer_fomod_plus/share/xml/FomodInfoFile.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-
-#include <qstring.h>
-#include <string>
-#include <vector>
-
-class FomodInfoFile {
-public:
- bool deserialize(const QString &filePath);
-
- [[nodiscard]] const std::string& getName() const { return name; }
- [[nodiscard]] const std::string& getAuthor() const { return author; }
- [[nodiscard]] const std::string& getVersion() const { return version; }
- [[nodiscard]] const std::string& getWebsite() const { return website; }
- [[nodiscard]] const std::string& getDescription() const { return description; }
- [[nodiscard]] const std::vector<std::string>& getGroups() const { return groups; }
-
-private:
- std::string name;
- std::string author;
- std::string version;
- std::string website;
- std::string description;
- std::vector<std::string> groups;
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/share/xml/ModuleConfiguration.cpp b/libs/installer_fomod_plus/share/xml/ModuleConfiguration.cpp
deleted file mode 100644
index 64d6b0b..0000000
--- a/libs/installer_fomod_plus/share/xml/ModuleConfiguration.cpp
+++ /dev/null
@@ -1,352 +0,0 @@
-#include "ModuleConfiguration.h"
-
-#include <format>
-
-#include "XmlHelper.h"
-#include "XmlParseException.h"
-#include "stringutil.h"
-
-#include <QFile>
-#include <QString>
-
-using namespace StringConstants::FomodFiles;
-
-static GroupTypeEnum groupTypeFromString(const std::string& groupType)
-{
- if (groupType == "SelectAny")
- return SelectAny;
- if (groupType == "SelectAll")
- return SelectAll;
- if (groupType == "SelectExactlyOne")
- return SelectExactlyOne;
- if (groupType == "SelectAtMostOne")
- return SelectAtMostOne;
- if (groupType == "SelectAtLeastOne")
- return SelectAtLeastOne;
- return SelectAny; // is this a sane default? probably
-}
-
-PluginTypeEnum pluginTypeFromString(const std::string& typeStr)
-{
- if (typeStr == TYPE_REQUIRED)
- return PluginTypeEnum::Required;
- if (typeStr == TYPE_OPTIONAL)
- return PluginTypeEnum::Optional;
- if (typeStr == TYPE_RECOMMENDED)
- return PluginTypeEnum::Recommended;
- if (typeStr == TYPE_NOT_USABLE)
- return PluginTypeEnum::NotUsable;
- if (typeStr == TYPE_COULD_BE_USABLE)
- return PluginTypeEnum::CouldBeUsable;
- return PluginTypeEnum::Optional;
-}
-
-template <typename T>
-bool deserializeList(pugi::xml_node& node, const char* childName, std::vector<T>& list)
-{
- for (pugi::xml_node childNode : node.children(childName)) {
- T item;
- item.deserialize(childNode);
- list.push_back(item);
- }
- return true;
-}
-
-/*
- * NOTE: We call 'trim()' on all error-prone fields that rely on user-input. I'm assuming these FOMODs are created with
- * the FOMOD creation tool, so I won't trim things like flags that the tool sets for the user.
- */
-
-bool FileDependency::deserialize(pugi::xml_node& node)
-{
- file = node.attribute("file").as_string();
- trim(file);
- // ReSharper disable once CppTooWideScopeInitStatement
- // Do not use 'auto' for these. it will break equality checks
- const std::string stateStr = node.attribute("state").as_string();
-
- if (stateStr == "Missing")
- state = FileDependencyTypeEnum::Missing;
- else if (stateStr == "Inactive")
- state = FileDependencyTypeEnum::Inactive;
- else if (stateStr == "Active")
- state = FileDependencyTypeEnum::Active;
- return true;
-}
-
-bool FlagDependency::deserialize(pugi::xml_node& node)
-{
- flag = node.attribute("flag").as_string();
- value = node.attribute("value").as_string();
- trim({ flag, value });
- return true;
-}
-
-bool GameDependency::deserialize(pugi::xml_node& node)
-{
- version = node.attribute("version").as_string();
- trim(version);
- return true;
-}
-
-bool CompositeDependency::deserialize(pugi::xml_node& node)
-{
-
- // this could EITHER have a dependencies child or the dependencies are here.
- // turns out they could have both.
- pugi::xml_node possibleNode = node;
-
- // If the dependencies are all right inside, just use the root node as the dependency base.
- // This looks hacky but accommodates both _nested_ dependencies for plugins, and extremely simple ones for step visibility.
- if (node.child("dependencies") && !node.child("fileDependency") && !node.child("flagDependency") &&!node.child("gameDependency")) {
- possibleNode = node.child("dependencies");
- }
-
- deserializeList(possibleNode, "fileDependency", fileDependencies);
- deserializeList(possibleNode, "flagDependency", flagDependencies);
- deserializeList(possibleNode, "gameDependency", gameDependencies);
- deserializeList(possibleNode, "dependencies", nestedDependencies);
-
- operatorType = OperatorTypeEnum::AND; // safest default.
-
- if (const std::string operatorStr = possibleNode.attribute("operator").as_string(); operatorStr == "Or") {
- operatorType = OperatorTypeEnum::OR;
- }
-
- return true;
-}
-
-bool DependencyPattern::deserialize(pugi::xml_node& node)
-{
- if (!node)
- return false;
- pugi::xml_node dependenciesNode = node.child("dependencies");
- dependencies.deserialize(dependenciesNode);
-
- const pugi::xml_node typeNode = node.child("type");
- type = pluginTypeFromString(typeNode.attribute("name").as_string());
- return true;
-}
-
-bool DependencyPatternList::deserialize(pugi::xml_node& node)
-{
- return deserializeList(node, "pattern", patterns);
-}
-
-bool DependencyPluginType::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node patternsNode = node.child("patterns");
- const pugi::xml_node defaultTypeNode = node.child("defaultType");
- defaultType = pluginTypeFromString(defaultTypeNode.attribute("name").as_string());
- patterns.deserialize(patternsNode);
- return true;
-}
-
-bool TypeDescriptor::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node dependencyTypeNode = node.child("dependencyType");
- dependencyType.deserialize(dependencyTypeNode);
- const pugi::xml_node typeNode = node.child("type");
- type = pluginTypeFromString(typeNode.attribute("name").as_string());
- return true;
-}
-
-bool Image::deserialize(pugi::xml_node& node)
-{
- path = node.attribute("path").as_string();
- return true;
-}
-
-bool HeaderImage::deserialize(pugi::xml_node& node)
-{
- path = node.attribute("path").as_string();
- showImage = node.attribute("showImage").as_bool();
- showFade = node.attribute("showFade").as_bool();
- height = node.attribute("height").as_int();
- return true;
-}
-
-bool FileList::deserialize(pugi::xml_node& node)
-{
- for (pugi::xml_node childNode : node.children()) {
- if (std::string(childNode.name()) == "folder"
- || std::string(childNode.name()) == "file") {
- File file;
- file.deserialize(childNode);
- files.emplace_back(file);
- }
- }
- return true;
-}
-
-bool ConditionalFileInstallPattern::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node dependenciesNode = node.child("dependencies");
- pugi::xml_node filesNode = node.child("files");
-
- dependencies.deserialize(dependenciesNode);
- files.deserialize(filesNode);
-
- return true;
-}
-
-// <flag name="2">On</flag>
-bool ConditionFlag::deserialize(pugi::xml_node& node)
-{
- name = node.attribute("name").as_string();
- value = node.child_value(); //
- return true;
-}
-
-bool ConditionFlagList::deserialize(pugi::xml_node& node)
-{
- return deserializeList(node, "flag", flags);
-}
-
-bool File::deserialize(pugi::xml_node& node)
-{
- source = node.attribute("source").as_string();
- // destination = node.attribute("destination").as_string();
- priority = node.attribute("priority").as_int();
- isFolder = strcmp(node.name(), "folder") == 0;
- if (auto attr = node.attribute("destination"); attr) {
- destination = attr.as_string();
- } else {
- destination = std::nullopt;
- }
- return true;
-}
-
-
-bool Plugin::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node imageNode = node.child("image");
- pugi::xml_node typeDescriptorNode = node.child("typeDescriptor");
- pugi::xml_node conditionFlagsNode = node.child("conditionFlags");
- pugi::xml_node filesNode = node.child("files");
-
- // Description is optional in the schema; guard against null C strings from pugixml.
- if (const pugi::xml_node descNode = node.child("description")) {
- if (const char* rawDesc = descNode.text().as_string()) {
- description = rawDesc;
- }
- }
- description = trim(description); // Find a better way to do this eventually.
- image.deserialize(imageNode);
- typeDescriptor.deserialize(typeDescriptorNode);
- name = node.attribute("name").as_string();
- name = trim(name);
- conditionFlags.deserialize(conditionFlagsNode);
- files.deserialize(filesNode);
- return true;
-}
-
-bool PluginList::deserialize(pugi::xml_node& node)
-{
- deserializeList(node, "plugin", plugins);
- order = XmlHelper::getOrderType(node.attribute("order").as_string(), OrderTypeEnum::Ascending);
-
- // Sort the plugins based on the specified order
- std::ranges::sort(plugins, [this](const Plugin& a, const Plugin& b) {
- if (order == OrderTypeEnum::Ascending) {
- return a.name < b.name;
- }
- if (order == OrderTypeEnum::Descending) {
- return a.name > b.name;
- }
- return false; // Default case, no sorting
- });
-
- return true;
-}
-
-bool Group::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node pluginsNode = node.child("plugins");
- plugins.deserialize(pluginsNode);
- name = node.attribute("name").as_string();
- type = groupTypeFromString(node.attribute("type").as_string());
- return true;
-}
-
-bool GroupList::deserialize(pugi::xml_node& node)
-{
- deserializeList(node, "group", groups);
- order = XmlHelper::getOrderType(node.attribute("order").as_string());
-
- // Sort the groups based on the specified order
- std::ranges::sort(groups, [this](const Group& a, const Group& b) {
- if (order == OrderTypeEnum::Ascending) {
- return a.name < b.name;
- }
- if (order == OrderTypeEnum::Descending) {
- return a.name > b.name;
- }
- return false; // Default case, no sorting
- });
- return true;
-}
-
-bool InstallStep::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node visibleNode = node.child("visible");
- pugi::xml_node optionalFileGroupsNode = node.child("optionalFileGroups");
- visible.deserialize(visibleNode);
- optionalFileGroups.deserialize(optionalFileGroupsNode);
- name = node.attribute("name").as_string();
- return true;
-}
-
-bool ConditionalFileInstall::deserialize(pugi::xml_node& node)
-{
- pugi::xml_node patternsNode = node.child("patterns");
- deserializeList(patternsNode, "pattern", patterns);
- return true;
-}
-
-bool StepList::deserialize(pugi::xml_node& node)
-{
- deserializeList(node, "installStep", installSteps);
- order = XmlHelper::getOrderType(node.attribute("order").as_string());
- return true;
-}
-
-bool ModuleConfiguration::deserialize(const QString& filePath)
-{
- QFile file(filePath);
- if (!file.open(QIODevice::ReadOnly)) {
- throw XmlParseException(std::format("Failed to open file: {}", filePath.toStdString()));
- }
- const QByteArray content = file.readAll();
- file.close();
-
- pugi::xml_document doc;
- if (const pugi::xml_parse_result result = doc.load_buffer(content.constData(), content.size()); !result) {
- throw XmlParseException(std::format("XML parsed with errors: {}", result.description()));
- }
-
- const pugi::xml_node configNode = doc.child("config");
- if (!configNode) {
- throw XmlParseException("No <config> node found");
- }
-
- moduleName = configNode.child("moduleName").text().as_string();
-
- moduleImage = HeaderImage();
- pugi::xml_node moduleImageNode = configNode.child("moduleImage");
- moduleImage.deserialize(moduleImageNode);
-
- pugi::xml_node moduleDependenciesNode = configNode.child("moduleDependencies");
- moduleDependencies.deserialize(moduleDependenciesNode);
-
- pugi::xml_node requiredInstallFilesNode = configNode.child("requiredInstallFiles");
- requiredInstallFiles.deserialize(requiredInstallFilesNode);
-
- pugi::xml_node installStepsNode = configNode.child("installSteps");
- installSteps.deserialize(installStepsNode);
-
- pugi::xml_node conditionalFileInstallsNode = configNode.child("conditionalFileInstalls");
- conditionalFileInstalls.deserialize(conditionalFileInstallsNode);
-
- return true;
-}
diff --git a/libs/installer_fomod_plus/share/xml/ModuleConfiguration.h b/libs/installer_fomod_plus/share/xml/ModuleConfiguration.h
deleted file mode 100644
index a0a017b..0000000
--- a/libs/installer_fomod_plus/share/xml/ModuleConfiguration.h
+++ /dev/null
@@ -1,305 +0,0 @@
-#pragma once
-
-#include <iostream>
-#include <optional>
-#include <pugixml.hpp>
-#include <qstring.h>
-#include <string>
-#include <vector>
-
-class XmlDeserializable {
-public:
- virtual ~XmlDeserializable() = default;
-
- virtual bool deserialize(pugi::xml_node& node) = 0;
-
-protected:
- XmlDeserializable() = default;
-};
-
-enum GroupTypeEnum {
- SelectAny,
- SelectAll,
- SelectExactlyOne,
- SelectAtMostOne,
- SelectAtLeastOne
-};
-
-enum class OperatorTypeEnum {
- AND,
- OR
-};
-
-enum class OrderTypeEnum {
- Explicit,
- Ascending,
- Descending
-};
-
-enum class FileDependencyTypeEnum {
- Missing,
- Inactive,
- Active,
- UNKNOWN_STATE
-};
-
-template <typename T>
-class OrderedContents {
-public:
- OrderTypeEnum order;
-
- OrderedContents() : order(OrderTypeEnum::Ascending) {}
- explicit OrderedContents(const OrderTypeEnum orderType): order(orderType) {}
-
- template <typename Accessor>
- bool compare(const T& a, const T& b, Accessor accessor) const
- {
- switch (order) {
- case OrderTypeEnum::Ascending:
- return accessor(a) < accessor(b);
- case OrderTypeEnum::Descending:
- return accessor(a) > accessor(b);
- case OrderTypeEnum::Explicit:
- default:
- return false; // No sorting for explicit order
- }
- }
-};
-
-enum class PluginTypeEnum {
- Recommended,
- Required,
- Optional,
- NotUsable,
- CouldBeUsable,
- UNKNOWN
-};
-
-
-inline std::ostream& operator<<(std::ostream& os, const PluginTypeEnum& type)
-{
- switch (type) {
- case PluginTypeEnum::Recommended:
- os << "Recommended";
- break;
- case PluginTypeEnum::Required:
- os << "Required";
- break;
- case PluginTypeEnum::Optional:
- os << "Optional";
- break;
- case PluginTypeEnum::NotUsable:
- os << "NotUsable";
- break;
- case PluginTypeEnum::CouldBeUsable:
- os << "CouldBeUsable";
- break;
- default: ;
- }
- return os;
-}
-
-class PluginType final : public XmlDeserializable {
-public:
- PluginTypeEnum name = PluginTypeEnum::Optional; // sane default
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class FileDependency final : public XmlDeserializable {
-public:
- std::string file;
- FileDependencyTypeEnum state = FileDependencyTypeEnum::UNKNOWN_STATE;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class FlagDependency final : public XmlDeserializable {
-public:
- std::string flag;
- std::string value;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class GameDependency final : public XmlDeserializable {
-public:
- std::string version;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class CompositeDependency final : public XmlDeserializable {
-public:
- std::vector<FileDependency> fileDependencies;
- std::vector<FlagDependency> flagDependencies;
- std::vector<GameDependency> gameDependencies;
- std::vector<CompositeDependency> nestedDependencies;
- OperatorTypeEnum operatorType = OperatorTypeEnum::AND; // safest default.
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class DependencyPattern final : public XmlDeserializable {
-public:
- CompositeDependency dependencies;
- PluginTypeEnum type;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-
-class DependencyPatternList final : public XmlDeserializable {
-public:
- std::vector<DependencyPattern> patterns;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class DependencyPluginType final : public XmlDeserializable {
-public:
- std::optional<PluginTypeEnum> defaultType;
- DependencyPatternList patterns;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class TypeDescriptor final : public XmlDeserializable {
-public:
- DependencyPluginType dependencyType;
- PluginTypeEnum type;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class Image final : public XmlDeserializable {
-public:
- std::string path;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class HeaderImage final : public XmlDeserializable {
-public:
- std::string path;
- bool showImage;
- bool showFade;
- int height;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class File final : public XmlDeserializable {
-public:
- std::string source;
- std::optional<std::string> destination;
- int priority{ 0 };
- bool isFolder;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-
-class FileList final : public XmlDeserializable {
-public:
- std::vector<File> files;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class ConditionalFileInstallPattern final : public XmlDeserializable {
-public:
- CompositeDependency dependencies;
- FileList files;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-// <flag name="2">On</flag>
-class ConditionFlag final : public XmlDeserializable {
-public:
- std::string name;
- std::string value;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class ConditionFlagList final : public XmlDeserializable {
-public:
- std::vector<ConditionFlag> flags;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class Plugin final : public XmlDeserializable {
-public:
- std::string description;
- Image image;
- TypeDescriptor typeDescriptor;
- std::string name;
- ConditionFlagList conditionFlags;
- FileList files;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class PluginList final : public XmlDeserializable, public OrderedContents<Plugin> {
-public:
- std::vector<Plugin> plugins;
- OrderTypeEnum order;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class Group final : public XmlDeserializable {
-public:
- PluginList plugins;
- std::string name;
- GroupTypeEnum type;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class GroupList final : public XmlDeserializable, public OrderedContents<Group> {
-public:
- std::vector<Group> groups;
- OrderTypeEnum order;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class InstallStep final : public XmlDeserializable {
-public:
- CompositeDependency visible;
- GroupList optionalFileGroups;
- std::string name;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class ConditionalFileInstall final : public XmlDeserializable {
-public:
- std::vector<ConditionalFileInstallPattern> patterns;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class StepList final : public XmlDeserializable, public OrderedContents<InstallStep> {
-public:
- std::vector<InstallStep> installSteps;
- OrderTypeEnum order;
-
- bool deserialize(pugi::xml_node& node) override;
-};
-
-class ModuleConfiguration {
-public:
- std::string moduleName;
- HeaderImage moduleImage;
- CompositeDependency moduleDependencies;
- FileList requiredInstallFiles;
- StepList installSteps;
- ConditionalFileInstall conditionalFileInstalls;
-
- bool deserialize(const QString& filePath);
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/share/xml/XmlHelper.h b/libs/installer_fomod_plus/share/xml/XmlHelper.h
deleted file mode 100644
index 6934a86..0000000
--- a/libs/installer_fomod_plus/share/xml/XmlHelper.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "ModuleConfiguration.h"
-
-class XmlHelper {
-public:
- static OrderTypeEnum getOrderType(const std::string& orderType, OrderTypeEnum defaultOrder = OrderTypeEnum::Explicit)
- {
- if (orderType == "Explicit")
- return OrderTypeEnum::Explicit;
- if (orderType == "Ascending")
- return OrderTypeEnum::Ascending;
- if (orderType == "Descending")
- return OrderTypeEnum::Descending;
- return defaultOrder; // Ascending for plugins, Explicit for groups
- }
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/share/xml/XmlParseException.h b/libs/installer_fomod_plus/share/xml/XmlParseException.h
deleted file mode 100644
index 6406f3b..0000000
--- a/libs/installer_fomod_plus/share/xml/XmlParseException.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-#include <stdexcept>
-#include <string>
-
-class XmlParseException final : public std::runtime_error {
-public:
- explicit XmlParseException(const std::string& message)
- : std::runtime_error(message) {}
-};