diff options
Diffstat (limited to 'libs/installer_fomod_plus/share')
| -rw-r--r-- | libs/installer_fomod_plus/share/FOMODData/ArchiveExtractor.h | 160 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/FOMODData/FomodDB.h | 144 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/FOMODData/FomodDBEntry.h | 129 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/FOMODData/FomodRescan.h | 277 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/FOMODData/PluginReader.h | 119 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/stringutil.h | 150 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/xml/FomodInfoFile.cpp | 44 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/xml/FomodInfoFile.h | 25 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/xml/ModuleConfiguration.cpp | 352 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/xml/ModuleConfiguration.h | 305 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/xml/XmlHelper.h | 17 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/share/xml/XmlParseException.h | 10 |
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(" "), "<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) {} -}; |
