aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_quick/src/installerquick.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/installer_quick/src/installerquick.cpp
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_quick/src/installerquick.cpp')
-rw-r--r--libs/installer_quick/src/installerquick.cpp176
1 files changed, 176 insertions, 0 deletions
diff --git a/libs/installer_quick/src/installerquick.cpp b/libs/installer_quick/src/installerquick.cpp
new file mode 100644
index 0000000..20b4b76
--- /dev/null
+++ b/libs/installer_quick/src/installerquick.cpp
@@ -0,0 +1,176 @@
+#include "installerquick.h"
+
+#include <QDialog>
+#include <QtPlugin>
+
+#include <uibase/game_features/igamefeatures.h>
+#include <uibase/iplugingame.h>
+#include <uibase/log.h>
+
+#include "simpleinstalldialog.h"
+
+using namespace MOBase;
+
+InstallerQuick::InstallerQuick() : m_MOInfo(nullptr) {}
+
+bool InstallerQuick::init(IOrganizer* moInfo)
+{
+ m_MOInfo = moInfo;
+ // Note: Cannot retrieve the checker here because the game might
+ // not be initialized yet.
+ return true;
+}
+
+QString InstallerQuick::name() const
+{
+ return "Simple Installer";
+}
+
+QString InstallerQuick::localizedName() const
+{
+ return tr("Simple Installer");
+}
+
+QString InstallerQuick::author() const
+{
+ return "Tannin";
+}
+
+QString InstallerQuick::description() const
+{
+ return tr("Installer for very simple archives");
+}
+
+VersionInfo InstallerQuick::version() const
+{
+ return VersionInfo(1, 3, 0, VersionInfo::RELEASE_FINAL);
+}
+
+QList<PluginSetting> InstallerQuick::settings() const
+{
+ return {PluginSetting("silent",
+ "simple plugins will be installed without any user interaction",
+ QVariant(false))};
+}
+
+unsigned int InstallerQuick::priority() const
+{
+ return 50;
+}
+
+bool InstallerQuick::isManualInstaller() const
+{
+ return false;
+}
+
+bool InstallerQuick::isDataTextArchiveTopLayer(std::shared_ptr<const IFileTree> tree,
+ QString const& dataFolderName,
+ ModDataChecker*) const
+{
+ // A "DataText" archive is defined as having exactly one folder named like
+ // `dataFolderName` and one or more "useless" files (text files, pdf, or images).
+ static const std::set<QString, FileNameComparator> txtExtensions{
+ "txt", "pdf", "md", "jpg", "jpeg", "png", "bmp"};
+ bool dataFound = false;
+ bool txtFound = false;
+ for (auto entry : *tree) {
+ if (entry->isDir()) {
+ // If data was already found, or this is a directory not named "data", fail:
+ if (dataFound || entry->compare(dataFolderName) != 0) {
+ return false;
+ }
+ dataFound = true;
+ } else {
+ if (txtExtensions.count(entry->suffix()) == 0) {
+ return false;
+ }
+ txtFound = true;
+ }
+ }
+ return dataFound && txtFound;
+}
+
+std::shared_ptr<const IFileTree>
+InstallerQuick::getSimpleArchiveBase(std::shared_ptr<const IFileTree> dataTree,
+ QString const& dataFolderName,
+ ModDataChecker* checker) const
+{
+ if (!checker) {
+ return nullptr;
+ }
+ while (true) {
+ if (checker->dataLooksValid(dataTree) == ModDataChecker::CheckReturn::VALID ||
+ isDataTextArchiveTopLayer(dataTree, dataFolderName, checker)) {
+ return dataTree;
+ } else if (dataTree->size() == 1 && dataTree->at(0)->isDir()) {
+ dataTree = dataTree->at(0)->astree();
+ } else {
+ log::debug("Archive is not a simple archive.");
+ return nullptr;
+ }
+ }
+}
+
+bool InstallerQuick::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const
+{
+ auto checker = m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>();
+ if (!checker) {
+ return false;
+ }
+ if (getSimpleArchiveBase(tree, m_MOInfo->managedGame()->dataDirectory().dirName(),
+ checker.get()) != nullptr) {
+ return true;
+ }
+ return checker->dataLooksValid(tree) == ModDataChecker::CheckReturn::FIXABLE;
+}
+
+IPluginInstaller::EInstallResult
+InstallerQuick::install(GuessedValue<QString>& modName,
+ std::shared_ptr<IFileTree>& tree, QString&, int&)
+{
+ const QString dataFolderName = m_MOInfo->managedGame()->dataDirectory().dirName();
+ auto checker = m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>();
+
+ auto base = std::const_pointer_cast<IFileTree>(
+ getSimpleArchiveBase(tree, dataFolderName, checker.get()));
+ if (base == nullptr &&
+ checker->dataLooksValid(tree) == ModDataChecker::CheckReturn::FIXABLE) {
+ tree = checker->fix(tree);
+ } else {
+ tree = base;
+ }
+ if (tree != nullptr) {
+ SimpleInstallDialog dialog(modName, parentWidget());
+ if (m_MOInfo->pluginSetting(name(), "silent").toBool() ||
+ dialog.exec() == QDialog::Accepted) {
+ modName.update(dialog.getName(), GUESS_USER);
+
+ // If we have a data+txt archive, we move files to the data folder and
+ // switch to the data folder. We need to check that we actually have a
+ // checker here, otherwise it is anyway impossible that
+ // isDataTextArchiveTopLayer() returned true.
+ if (checker && isDataTextArchiveTopLayer(tree, dataFolderName, checker.get())) {
+ auto dataTree = tree->findDirectory(dataFolderName);
+ dataTree->detach();
+ dataTree->merge(tree);
+ tree = dataTree;
+ }
+ return RESULT_SUCCESS;
+ } else {
+ if (dialog.manualRequested()) {
+ modName.update(dialog.getName(), GUESS_USER);
+ return RESULT_MANUALREQUESTED;
+ } else {
+ return RESULT_CANCELED;
+ }
+ }
+ } else {
+ // install shouldn't even have even have been called
+ qCritical("unsupported archive for quick installer");
+ return RESULT_FAILED;
+ }
+}
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+Q_EXPORT_PLUGIN2(installerQuick, InstallerQuick)
+#endif