aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp')
-rw-r--r--libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp510
1 files changed, 0 insertions, 510 deletions
diff --git a/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp b/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
deleted file mode 100644
index c748495..0000000
--- a/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
+++ /dev/null
@@ -1,510 +0,0 @@
-#include "FomodPlusInstaller.h"
-
-#include <igamefeatures.h>
-#include <iinstallationmanager.h>
-#include <iplugingame.h>
-#include <QEventLoop>
-#include <QTreeWidget>
-#include <xml/FomodInfoFile.h>
-#include <xml/ModuleConfiguration.h>
-#include <xml/XmlParseException.h>
-
-#include "FomodInstallerWindow.h"
-#include "stringutil.h"
-#include "integration/FomodDataContent.h"
-#include "ui/Colors.h"
-#include "ui/FomodViewModel.h"
-#include "lib/CrashHandler.h"
-
-#include <QMessageBox>
-#include <QSettings>
-
-using namespace Qt::Literals::StringLiterals;
-
-/*
---------------------------------------------------------------------------------
- Init
---------------------------------------------------------------------------------
-*/
-#pragma region Initialization
-
-bool FomodPlusInstaller::init(IOrganizer* organizer)
-{
- CrashHandler::initialize();
- mOrganizer = organizer;
- mFomodContent = make_shared<FomodDataContent>(organizer);
- log.setLogFilePath(QDir::currentPath().toStdString() + "/logs/fomodplus.log");
-
- // REMEMBER: This mFomodDB persists beyond the scope of an individual install. Do not do anything nasty to it.
- mFomodDb = std::make_unique<FomodDB>(mOrganizer->basePath().toStdString());
- setupUiInjection();
- return true;
-}
-
-void FomodPlusInstaller::setupUiInjection() const
-{
- if (shouldShowSidebarFilter()) {
- mOrganizer->gameFeatures()->registerFeature(mFomodContent, 9999, false);
- }
-
- mOrganizer->onPluginSettingChanged([this](const QString& pluginName, const QString& key, const QVariant& oldValue, const QVariant& newValue) {
- if (pluginName == name() && key == "show_fomod_filter") {
- toggleFeature(newValue.toBool());
- }
- });
-}
-
-void FomodPlusInstaller::toggleFeature(const bool enabled) const
-{
- if (enabled) {
- mOrganizer->gameFeatures()->registerFeature(mFomodContent, 9999, false);
- } else {
- mOrganizer->gameFeatures()->unregisterFeature(mFomodContent);
- }
- mOrganizer->refresh();
-}
-
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Settings
---------------------------------------------------------------------------------
-*/
-#pragma region Settings
-bool FomodPlusInstaller::shouldFallbackToLegacyInstaller() const
-{
- return mOrganizer->pluginSetting(name(), "fallback_to_legacy").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldShowImages() const
-{
- return mOrganizer->pluginSetting(name(), "show_images").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldShowNotifications() const
-{
- return mOrganizer->pluginSetting(name(), "show_notifications").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldShowSidebarFilter() const
-{
- return mOrganizer->pluginSetting(name(), "show_fomod_filter").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldAutoRestoreChoices() const
-{
- return mOrganizer->pluginSetting(name(), "always_restore_choices").value<bool>();
-}
-
-bool FomodPlusInstaller::isWizardIntegrated() const
-{
- return mOrganizer->pluginSetting(name(), "wizard_integration").value<bool>();
-}
-
-void FomodPlusInstaller::toggleShouldShowImages() const
-{
- const bool showImages = shouldShowImages();
- mOrganizer->setPluginSetting(name(), "show_images", !showImages);
-}
-
-QString FomodPlusInstaller::getSelectedColor() const
-{
- const auto colorName = mOrganizer->pluginSetting(name(), "color_theme").toString();
- const auto it = UiColors::colorStyles.find(colorName);
- return it != UiColors::colorStyles.end() ? it->first : "Blue";
-}
-
-std::vector<std::shared_ptr<const IPluginRequirement> > FomodPlusInstaller::requirements() const
-{
- return { Requirements::gameDependency(
- { u"Morrowind"_s, u"Oblivion"_s, u"Fallout 3"_s, u"New Vegas"_s, u"Skyrim"_s, u"Enderal"_s,
- u"Fallout 4"_s, u"Skyrim Special Edition"_s, u"Enderal Special Edition"_s,
- u"Skyrim VR"_s, u"Fallout 4 VR"_s, u"Starfield"_s }) };
-}
-
-QList<PluginSetting> FomodPlusInstaller::settings() const
-{
- return {
- { u"fallback_to_legacy"_s, u"When hitting cancel, fall back to the legacy FOMOD installer."_s, false },
- { u"always_restore_choices"_s, u"Restore previous choices without clicking the magic button"_s, false },
- { u"show_images"_s, u"Show image previews and the image carousel in installer windows."_s, true },
- { u"color_theme"_s, u"Select the color theme for the installer"_s, QString("Blue") }, // Default color name
- { u"show_notifications"_s, u"Show the notifications panel"_s, false }, //WIP
- { u"wizard_integration"_s, u"Integrate the installer with patch wizard."_s, true }, //WIP
- { u"show_fomod_filter"_s, u"Show the filter in the sidebar (may break other content filters)"_s, true }
- };
-}
-
-#pragma endregion
-
-bool FomodPlusInstaller::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const
-{
- tree = findFomodDirectory(tree);
- if (tree != nullptr) {
- return tree->exists(StringConstants::FomodFiles::MODULE_CONFIG.data(), FileTreeEntry::FILE);
- }
- return false;
-}
-
-
-std::pair<nlohmann::json, IModInterface*> FomodPlusInstaller::getExistingFomodJson(const GuessedValue<QString>& modName,
- const int& nexusId,
- const int& stepsInCurrentFomod) const
-{
- logMessage(DEBUG, "FomodPlusInstaller::getExistingFomodJson - modName: " + modName->toStdString() +
- " nexusId: " + std::to_string(nexusId));
-
- // Need to have better mod matching based on presence of FOMOD plugin data.
- struct ModMatch {
- MOBase::IModInterface* mod;
- bool hasFomodData;
- int stepCount;
- nlohmann::json fomodJson;
- };
- std::vector<ModMatch> matches;
-
- auto parseStepCount = [](const QVariant& fomodData) -> std::pair<bool, int> {
- try {
- if (fomodData.isValid() && !fomodData.isNull()) {
- if (auto json = nlohmann::json::parse(fomodData.toString().toStdString()); json.contains("steps") && json["steps"].is_array()) {
- return { true, static_cast<int>(json["steps"].size()) };
- }
- }
- } catch (...) {
- // Ignore parsing errors
- }
- return { false, 0 };
- };
-
- // Check exact match first
- const auto modList = mOrganizer->modList();
- if (modList == nullptr) {
- return {};
- }
-
- if (const auto exactMod = modList->getMod(modName)) {
- const auto fomodData = exactMod->pluginSetting(name(), "fomod", 0);
- if (auto [valid, stepCount] = parseStepCount(fomodData); valid) {
- matches.push_back({
- exactMod,
- true,
- stepCount,
- nlohmann::json::parse(fomodData.toString().toStdString())
- });
- } else {
- matches.push_back({
- exactMod,
- false,
- 0,
- nlohmann::json()
- });
- }
- }
-
- // Check all variants
- for (const auto& variant : modName.variants()) {
- if (const auto variantMod = modList->getMod(variant)) {
- const auto fomodData = variantMod->pluginSetting(name(), "fomod", 0);
- if (auto [valid, stepCount] = parseStepCount(fomodData); valid) {
- matches.push_back({
- variantMod,
- true,
- stepCount,
- nlohmann::json::parse(fomodData.toString().toStdString())
- });
- } else {
- matches.push_back({
- variantMod,
- false,
- 0,
- nlohmann::json()
- });
- }
- }
- }
-
- // No matches found
- if (matches.empty()) {
- return {};
- }
-
- // First try to find exact step count match
- const auto exactMatch = ranges::find_if(matches,
- [stepsInCurrentFomod](const ModMatch& match) {
- return match.hasFomodData && match.stepCount == stepsInCurrentFomod;
- });
-
- if (exactMatch != matches.end()) {
- logMessage(DEBUG, "Found exact step count match in mod: " +
- exactMatch->mod->name().toStdString() +
- " with " + std::to_string(exactMatch->stepCount) + " steps");
- return std::make_pair(exactMatch->fomodJson, exactMatch->mod);
- }
-
- // Find the closest step count among mods with FOMOD data
- const auto closestMatch = ranges::min_element(matches,
- [stepsInCurrentFomod](const ModMatch& a, const ModMatch& b) {
- if (!a.hasFomodData || !b.hasFomodData)
- return false;
- // The min difference between the step counts
- return std::abs(a.stepCount - stepsInCurrentFomod) <
- std::abs(b.stepCount - stepsInCurrentFomod);
- });
-
- if (closestMatch != matches.end() && closestMatch->hasFomodData) {
- logMessage(DEBUG, "Using closest step count match from mod: " +
- closestMatch->mod->name().toStdString() +
- " with " + std::to_string(closestMatch->stepCount) + " steps");
- return std::make_pair(closestMatch->fomodJson, closestMatch->mod);
- }
-
- // Fallback to first mod with any FOMOD data
- const auto anyFomod = ranges::find_if(matches,
- [](const ModMatch& match) { return match.hasFomodData; });
-
- if (anyFomod != matches.end()) {
- logMessage(DEBUG, "Using first available FOMOD data from mod: " +
- anyFomod->mod->name().toStdString());
- return std::make_pair(anyFomod->fomodJson, anyFomod->mod);
- }
-
- logMessage(DEBUG, "No matching FOMOD data found");
- return {};
-}
-
-void FomodPlusInstaller::clearPriorInstallData()
-{
- mInstallerUsed = false;
- mFomodJson = nullptr;
- mFomodPath = "";
-}
-
-/**
- *
- * @param modName
- * @param tree
- * @param version
- * @param nexusID
- * @return
- */
-IPluginInstaller::EInstallResult FomodPlusInstaller::install(GuessedValue<QString>& modName,
- std::shared_ptr<IFileTree>& tree, QString& version,
- int& nexusID)
-{
- clearPriorInstallData();
-
- logMessage(INFO, std::format("FomodPlusInstaller::install - modName: {}, version: {}, nexusID: {}",
- modName->toStdString(),
- version.toStdString(),
- nexusID
- ));
- logMessage(INFO, std::format("FomodPlusInstaller::install - tree size: {}", tree->size()));
-
- auto [infoFile, moduleConfigFile, filePaths] = parseFomodFiles(tree);
-
- if (infoFile == nullptr || moduleConfigFile == nullptr) {
- return RESULT_FAILED;
- }
-
- std::vector<QString> pluginPaths = {};
- for (const auto& filePath : filePaths) {
- if (isPluginFile(filePath)) {
- pluginPaths.emplace_back(filePath);
- }
- }
-
- const auto dbEntry = mFomodDb->getEntryFromFomod(moduleConfigFile.get(), pluginPaths, nexusID);
-
- // create ui & pass xml classes to ui
- auto [json, matchMod] = getExistingFomodJson(modName, nexusID, static_cast<int>(moduleConfigFile->installSteps.installSteps.size()));
- if (matchMod != nullptr) {
- modName.update(matchMod->name(), GUESS_USER);
- }
- auto fomodViewModel = FomodViewModel::create(mOrganizer, std::move(moduleConfigFile), std::move(infoFile));
- const auto window = std::make_shared<FomodInstallerWindow>(this, modName, tree, mFomodPath, fomodViewModel, json);
-
- // ReSharper disable once CppTooWideScopeInitStatement
- const QDialog::DialogCode result = showInstallerWindow(window);
- if (result == QDialog::Accepted) {
- // modname was updated in window
- mInstallerUsed = true;
- const std::shared_ptr<IFileTree> installTree = window->getFileInstaller()->install();
- tree = installTree;
- mFomodJson = std::make_shared<nlohmann::json>(window->getFileInstaller()->generateFomodJson());
-
- try {
- mFomodDb->addEntry(dbEntry);
- mFomodDb->saveToFile();
- } catch ([[maybe_unused]] Exception& e) {
- logMessage(ERR, "Failed to add FomodDB entries.");
- logMessage(ERR, e.what());
- }
- return RESULT_SUCCESS;
- }
- if (window->isManualInstall()) {
- return RESULT_MANUALREQUESTED;
- }
- if (shouldFallbackToLegacyInstaller()) {
- return RESULT_NOTATTEMPTED;
- }
- return RESULT_CANCELED;
-}
-
-
-/**
- *
- * @param tree
- * @return
- */
-ParsedFilesTuple FomodPlusInstaller::parseFomodFiles(const std::shared_ptr<IFileTree>& tree)
-{
- const auto emptyResult = std::make_tuple(nullptr, nullptr, QStringList());
-
- const auto fomodDir = findFomodDirectory(tree);
- if (fomodDir == nullptr) {
- logMessage(ERR, "FomodPlusInstaller::install - fomod directory not found");
- return emptyResult;
- }
-
- // This is a strange place to set this value but okay for now.
- mFomodPath = fomodDir->parent()->path();
-
- const auto infoXML = fomodDir->find(
- StringConstants::FomodFiles::INFO_XML.data(),
- FileTreeEntry::FILE
- );
- const auto moduleConfig = fomodDir->find(
- StringConstants::FomodFiles::MODULE_CONFIG.data(),
- FileTreeEntry::FILE
- );
-
- // Extract files first.
- vector<std::shared_ptr<const FileTreeEntry> > toExtract = {};
- if (moduleConfig) {
- toExtract.push_back(moduleConfig);
- } else {
- logMessage(ERR, "FomodPlusInstaller::install - error parsing moduleConfig.xml: Not Present");
- return emptyResult;
- }
- if (infoXML) {
- toExtract.push_back(infoXML);
- }
- appendImageFiles(toExtract, tree);
- appendPluginFiles(toExtract, tree); // For patch wizard data collection
- auto paths = manager()->extractFiles(toExtract);
- // Normalize backslash separators from MO2's internal file tree to forward slashes for Linux
- for (auto& p : paths) {
- p.replace('\\', '/');
- }
-
- auto moduleConfiguration = std::make_unique<ModuleConfiguration>();
- try {
- moduleConfiguration->deserialize(paths.at(0));
- } catch (XmlParseException& e) {
- logMessage(ERR, std::format("FomodPlusInstaller::install - error parsing moduleConfig.xml: {}", e.what()));
- return emptyResult;
- }
-
- auto infoFile = std::make_unique<FomodInfoFile>();
- if (infoXML) {
- try {
- infoFile->deserialize(paths.at(1));
- } catch (XmlParseException& e) {
- logMessage(ERR, std::format("FomodPlusInstaller::install - error parsing info.xml: {}", e.what()));
- }
- }
-
- return std::make_tuple(std::move(infoFile), std::move(moduleConfiguration), paths);
-}
-
-// Taken from https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/master/src/installerfomod.cpp#L123
-void FomodPlusInstaller::appendImageFiles(vector<shared_ptr<const FileTreeEntry> >& entries,
- const shared_ptr<const IFileTree>& tree)
-{
- static std::set<QString, FileNameComparator> imageSuffixes{ "png", "jpg", "jpeg", "gif", "bmp" };
- for (auto entry : *tree) {
- if (entry->isDir()) {
- appendImageFiles(entries, entry->astree());
- } else if (imageSuffixes.contains(entry->suffix())) {
- entries.push_back(entry);
- }
- }
-}
-
-void FomodPlusInstaller::appendPluginFiles(vector<shared_ptr<const FileTreeEntry> >& entries,
- const shared_ptr<const IFileTree>& tree)
-{
- // Don't bother with this stuff if the user doesn't care about the wizard.
- // It may slightly bloat the temp file directory if not needed.
- if (isWizardIntegrated()) {
- for (auto entry : *tree) {
- if (entry->isDir()) {
- appendPluginFiles(entries, entry->astree());
- } else if (isPluginFile(entry->suffix())) {
- entries.push_back(entry);
- }
- }
- }
-}
-
-void FomodPlusInstaller::onInstallationStart(QString const& archive, const bool reinstallation,
- IModInterface* currentMod)
-{
- IPluginInstallerSimple::onInstallationStart(archive, reinstallation, currentMod);
-}
-
-void FomodPlusInstaller::onInstallationEnd(const EInstallResult result, IModInterface* newMod)
-{
- IPluginInstallerSimple::onInstallationEnd(result, newMod);
-
- // Update the meta.ini file with the fomod information
- if (mFomodJson != nullptr && result == RESULT_SUCCESS && newMod != nullptr && mInstallerUsed) {
- newMod->setPluginSetting(this->name(), "fomod", mFomodJson->dump().c_str());
- mOrganizer->refresh();
- }
- clearPriorInstallData();
-}
-
-// Borrowed from https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/master/src/installerfomod.cpp
-std::shared_ptr<const IFileTree> FomodPlusInstaller::findFomodDirectory(const std::shared_ptr<const IFileTree>& tree)
-{
- // ReSharper disable once CppTooWideScopeInitStatement
- const auto entry = tree->find(StringConstants::FomodFiles::FOMOD_DIR.data(), FileTreeEntry::DIRECTORY);
-
- if (entry != nullptr) {
- return entry->astree();
- }
-
- if (tree->size() == 1 && tree->at(0)->isDir()) {
- return findFomodDirectory(tree->at(0)->astree());
- }
- return nullptr;
-}
-
-QDialog::DialogCode FomodPlusInstaller::showInstallerWindow(const std::shared_ptr<FomodInstallerWindow>& window)
-{
- QEventLoop loop;
- connect(window.get(), SIGNAL(accepted()), &loop, SLOT(quit()));
- connect(window.get(), SIGNAL(rejected()), &loop, SLOT(quit()));
- window->show();
- loop.exec();
- return static_cast<QDialog::DialogCode>(window->result());
-}
-
-void showError(const Exception& e)
-{
- const QString errorText = "Mod Name: \n" "Nexus ID: " "\n" "Exception: " + QString(e.what()) + "\n";
-
- QMessageBox msgBox;
- msgBox.setWindowTitle("FOMOD Plus Error :(");
- msgBox.setTextFormat(Qt::RichText);
- msgBox.setText("Sorry this happened. Please copy the following error and report it to me on Nexus or GitHub.\n"
- "<pre style='background-color: #f0f0f0; padding: 10px;'>"
- + errorText +
- "</pre>"
- );
- msgBox.setIcon(QMessageBox::Critical);
- msgBox.exec();
-
-}