diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-06-17 01:46:09 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-06-17 01:46:09 -0500 |
| commit | 05aa96d20ca726908a7d8b1943f86f0c4e11bf18 (patch) | |
| tree | bd45913d1c7a033088f3456e40c8b44c72818ace /libs/installer_fomod_plus/share/FOMODData | |
| parent | 30d2c801a01eeb140ddd5adf8676f2626ecff3fa (diff) | |
Prepare Nexus-safe release cleanup
Diffstat (limited to 'libs/installer_fomod_plus/share/FOMODData')
5 files changed, 0 insertions, 829 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 |
