diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /libs/installer_bain | |
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_bain')
| -rw-r--r-- | libs/installer_bain/.clang-format | 41 | ||||
| -rw-r--r-- | libs/installer_bain/.git-blame-ignore-revs | 1 | ||||
| -rw-r--r-- | libs/installer_bain/.gitattributes | 7 | ||||
| -rw-r--r-- | libs/installer_bain/.github/workflows/build.yml | 20 | ||||
| -rw-r--r-- | libs/installer_bain/.github/workflows/linting.yml | 16 | ||||
| -rw-r--r-- | libs/installer_bain/.gitignore | 5 | ||||
| -rw-r--r-- | libs/installer_bain/.pre-commit-config.yaml | 20 | ||||
| -rw-r--r-- | libs/installer_bain/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | libs/installer_bain/CMakePresets.json | 67 | ||||
| -rw-r--r-- | libs/installer_bain/src/CMakeLists.txt | 13 | ||||
| -rw-r--r-- | libs/installer_bain/src/baincomplexinstallerdialog.cpp | 121 | ||||
| -rw-r--r-- | libs/installer_bain/src/baincomplexinstallerdialog.h | 94 | ||||
| -rw-r--r-- | libs/installer_bain/src/baincomplexinstallerdialog.ui | 126 | ||||
| -rw-r--r-- | libs/installer_bain/src/installer_bain_en.ts | 83 | ||||
| -rw-r--r-- | libs/installer_bain/src/installerbain.cpp | 222 | ||||
| -rw-r--r-- | libs/installer_bain/src/installerbain.h | 80 | ||||
| -rw-r--r-- | libs/installer_bain/vcpkg.json | 15 |
17 files changed, 936 insertions, 0 deletions
diff --git a/libs/installer_bain/.clang-format b/libs/installer_bain/.clang-format new file mode 100644 index 0000000..6098e1f --- /dev/null +++ b/libs/installer_bain/.clang-format @@ -0,0 +1,41 @@ +--- +# We'll use defaults from the LLVM style, but with 4 columns indentation. +BasedOnStyle: LLVM +IndentWidth: 2 +--- +Language: Cpp +DeriveLineEnding: false +UseCRLF: true +DerivePointerAlignment: false +PointerAlignment: Left +AlignConsecutiveAssignments: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: Yes +AccessModifierOffset: -2 +AlignTrailingComments: true +SpacesBeforeTrailingComments: 2 +NamespaceIndentation: Inner +MaxEmptyLinesToKeep: 1 +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true +ColumnLimit: 88 +ForEachMacros: ['Q_FOREACH', 'foreach'] diff --git a/libs/installer_bain/.git-blame-ignore-revs b/libs/installer_bain/.git-blame-ignore-revs new file mode 100644 index 0000000..d0c3535 --- /dev/null +++ b/libs/installer_bain/.git-blame-ignore-revs @@ -0,0 +1 @@ +60e7642364b534d9cb405acfa750b7bbe73869a9 diff --git a/libs/installer_bain/.gitattributes b/libs/installer_bain/.gitattributes new file mode 100644 index 0000000..f869712 --- /dev/null +++ b/libs/installer_bain/.gitattributes @@ -0,0 +1,7 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.cpp text eol=crlf +*.h text eol=crlf diff --git a/libs/installer_bain/.github/workflows/build.yml b/libs/installer_bain/.github/workflows/build.yml new file mode 100644 index 0000000..e7f10a1 --- /dev/null +++ b/libs/installer_bain/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: Build Installer BAIN + +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened] + +env: + VCPKG_BINARY_SOURCES: clear;x-azblob,${{ vars.AZ_BLOB_VCPKG_URL }},${{ secrets.AZ_BLOB_SAS }},readwrite + +jobs: + build: + runs-on: windows-2022 + steps: + - name: Build Installer BAIN + id: build-installer-bain + uses: ModOrganizer2/build-with-mob-action@master + with: + mo2-dependencies: uibase diff --git a/libs/installer_bain/.github/workflows/linting.yml b/libs/installer_bain/.github/workflows/linting.yml new file mode 100644 index 0000000..75c294e --- /dev/null +++ b/libs/installer_bain/.github/workflows/linting.yml @@ -0,0 +1,16 @@ +name: Lint Installer BAIN Plugin + +on: + push: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check format + uses: ModOrganizer2/check-formatting-action@master + with: + check-path: "." diff --git a/libs/installer_bain/.gitignore b/libs/installer_bain/.gitignore new file mode 100644 index 0000000..cf71be7 --- /dev/null +++ b/libs/installer_bain/.gitignore @@ -0,0 +1,5 @@ +edit +CMakeLists.txt.user +/msbuild.log +/*std*.log +/*build diff --git a/libs/installer_bain/.pre-commit-config.yaml b/libs/installer_bain/.pre-commit-config.yaml new file mode 100644 index 0000000..eb8969c --- /dev/null +++ b/libs/installer_bain/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-merge-conflict + - id: check-case-conflict + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v21.1.8 + hooks: + - id: clang-format + 'types_or': [c++, c] + +ci: + autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks." + autofix_prs: true + autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate." + autoupdate_schedule: quarterly + submodules: false diff --git a/libs/installer_bain/CMakeLists.txt b/libs/installer_bain/CMakeLists.txt new file mode 100644 index 0000000..4834057 --- /dev/null +++ b/libs/installer_bain/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) + +project(installer_bain) + +add_subdirectory(src) diff --git a/libs/installer_bain/CMakePresets.json b/libs/installer_bain/CMakePresets.json new file mode 100644 index 0000000..5a63c77 --- /dev/null +++ b/libs/installer_bain/CMakePresets.json @@ -0,0 +1,67 @@ +{ + "configurePresets": [ + { + "errors": { + "deprecated": true + }, + "hidden": true, + "name": "cmake-dev", + "warnings": { + "deprecated": true, + "dev": true + } + }, + { + "cacheVariables": { + "VCPKG_MANIFEST_NO_DEFAULT_FEATURES": { + "type": "BOOL", + "value": "ON" + } + }, + "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "hidden": true, + "name": "vcpkg" + }, + { + "hidden": true, + "inherits": ["vcpkg"], + "name": "vcpkg-dev" + }, + { + "binaryDir": "${sourceDir}/vsbuild", + "architecture": { + "strategy": "set", + "value": "x64" + }, + "cacheVariables": { + "CMAKE_CXX_FLAGS": "/EHsc /MP /W4", + "VCPKG_TARGET_TRIPLET": { + "type": "STRING", + "value": "x64-windows-static-md" + } + }, + "generator": "Visual Studio 17 2022", + "inherits": ["cmake-dev", "vcpkg-dev"], + "name": "vs2022-windows", + "toolset": "v143" + }, + { + "cacheVariables": { + "VCPKG_MANIFEST_FEATURES": { + "type": "STRING", + "value": "standalone" + } + }, + "inherits": "vs2022-windows", + "name": "vs2022-windows-standalone" + } + ], + "buildPresets": [ + { + "name": "vs2022-windows", + "resolvePackageReferences": "on", + "configurePreset": "vs2022-windows" + } + ], + "version": 4 +} diff --git a/libs/installer_bain/src/CMakeLists.txt b/libs/installer_bain/src/CMakeLists.txt new file mode 100644 index 0000000..de57063 --- /dev/null +++ b/libs/installer_bain/src/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) + +file(GLOB installer_bain_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.ui + ${CMAKE_CURRENT_SOURCE_DIR}/*.qrc +) + +add_library(installer_bain SHARED ${installer_bain_SOURCES}) +mo2_configure_plugin(installer_bain NO_SOURCES WARNINGS OFF) +target_link_libraries(installer_bain PRIVATE mo2::uibase) +mo2_install_plugin(installer_bain) diff --git a/libs/installer_bain/src/baincomplexinstallerdialog.cpp b/libs/installer_bain/src/baincomplexinstallerdialog.cpp new file mode 100644 index 0000000..2c25d76 --- /dev/null +++ b/libs/installer_bain/src/baincomplexinstallerdialog.cpp @@ -0,0 +1,121 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ +#include "ui_baincomplexinstallerdialog.h" + +#include <QCompleter> + +#include <uibase/textviewer.h> + +#include "baincomplexinstallerdialog.h" + +using namespace MOBase; + +BainComplexInstallerDialog::BainComplexInstallerDialog( + const std::vector<std::shared_ptr<const MOBase::FileTreeEntry>>& subpackages, + const GuessedValue<QString>& modName, const QStringList& defaultOptions, + const QString& packageTXT, QWidget* parent) + : TutorableDialog("BainInstaller", parent), ui(new Ui::BainComplexInstallerDialog), + m_Manual(false), m_PackageTXT(packageTXT) +{ + ui->setupUi(this); + + for (auto iter = modName.variants().begin(); iter != modName.variants().end(); + ++iter) { + ui->nameCombo->addItem(*iter); + } + + ui->nameCombo->setCurrentIndex(ui->nameCombo->findText(modName)); + + for (const auto& subpackage : subpackages) { + + auto name = subpackage->name(); + + QListWidgetItem* item = new QListWidgetItem(name, ui->optionsList); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + + if (name.mid(0, 2) == "00" || defaultOptions.contains(name, Qt::CaseInsensitive)) { + item->setCheckState(Qt::Checked); + } else { + item->setCheckState(Qt::Unchecked); + } + + ui->optionsList->addItem(item); + } + + ui->packageBtn->setEnabled(!packageTXT.isEmpty()); + ui->nameCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); +} + +BainComplexInstallerDialog::~BainComplexInstallerDialog() +{ + delete ui; +} + +QString BainComplexInstallerDialog::getName() const +{ + return ui->nameCombo->currentText(); +} + +QStringList BainComplexInstallerDialog::updateTree(std::shared_ptr<IFileTree>& tree) +{ + // Retrieve the list of selected names: + std::set<QString, FileNameComparator> selectedNames; + for (int i = 0; i < ui->optionsList->count(); ++i) { + QListWidgetItem* item = ui->optionsList->item(i); + if (item->checkState() == Qt::Checked) { + selectedNames.insert(item->text()); + } + } + + // Create a new empty tree and merge all the selected folder in it: + auto newTree = tree->createOrphanTree(); + for (auto& entry : *tree) { + if (entry->isDir() && selectedNames.count(entry->name()) > 0) { + newTree->merge(entry->astree()); + } + } + + tree = newTree; + + return QStringList(selectedNames.begin(), selectedNames.end()); +} + +void BainComplexInstallerDialog::on_okBtn_clicked() +{ + this->accept(); +} + +void BainComplexInstallerDialog::on_cancelBtn_clicked() +{ + this->reject(); +} + +void BainComplexInstallerDialog::on_manualBtn_clicked() +{ + m_Manual = true; + this->reject(); +} + +void BainComplexInstallerDialog::on_packageBtn_clicked() +{ + TextViewer viewer(m_PackageTXT, this); + viewer.setDescription(""); + viewer.addFile(m_PackageTXT, false); + viewer.exec(); +} diff --git a/libs/installer_bain/src/baincomplexinstallerdialog.h b/libs/installer_bain/src/baincomplexinstallerdialog.h new file mode 100644 index 0000000..f657627 --- /dev/null +++ b/libs/installer_bain/src/baincomplexinstallerdialog.h @@ -0,0 +1,94 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef BAINCOMPLEXINSTALLERDIALOG_H +#define BAINCOMPLEXINSTALLERDIALOG_H + +#include <uibase/guessedvalue.h> +#include <uibase/ifiletree.h> +#include <uibase/tutorabledialog.h> + +namespace Ui +{ +class BainComplexInstallerDialog; +} + +/** + * @brief Dialog to choose from options offered by a (complex) bain package + **/ +class BainComplexInstallerDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief Constructor + * + * @param tree the directory tree of the archive. The caller is resonsible to verify + *this actually qualifies as a bain installer + * @param modName proposed name for the mod. The dialog allows the user to change this + * @param defaultOptions The default options to select. Can contains options not + *actually present. + * @param packageTXT path to the extracted package.txt file or an empty string if + *there is none + * @param parent parent widget + **/ + explicit BainComplexInstallerDialog( + const std::vector<std::shared_ptr<const MOBase::FileTreeEntry>>& subpackages, + const MOBase::GuessedValue<QString>& modName, const QStringList& defaultOptions, + const QString& packageTXT, QWidget* parent); + ~BainComplexInstallerDialog(); + + /** + * @return bool true if the user requested the manual dialog + **/ + bool manualRequested() const { return m_Manual; } + + /** + * @return QString the (user-modified) name to be used for the mod + **/ + QString getName() const; + + /** + * @brief Remove from the given tree the option not selected by the user. + * + * @param tree The input tree. + * + * @return the list of options selected by the user. + **/ + QStringList updateTree(std::shared_ptr<MOBase::IFileTree>& tree); + +private slots: + + void on_manualBtn_clicked(); + + void on_okBtn_clicked(); + + void on_cancelBtn_clicked(); + + void on_packageBtn_clicked(); + +private: + Ui::BainComplexInstallerDialog* ui; + + bool m_Manual; + QString m_PackageTXT; +}; + +#endif // BAINCOMPLEXINSTALLERDIALOG_H diff --git a/libs/installer_bain/src/baincomplexinstallerdialog.ui b/libs/installer_bain/src/baincomplexinstallerdialog.ui new file mode 100644 index 0000000..0ab5214 --- /dev/null +++ b/libs/installer_bain/src/baincomplexinstallerdialog.ui @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BainComplexInstallerDialog</class> + <widget class="QDialog" name="BainComplexInstallerDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>424</width> + <height>365</height> + </rect> + </property> + <property name="windowTitle"> + <string>BAIN Package Installer</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="nameCombo"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>This looks like a Package prepared for Installation through BAIN. You can select options from the list below. If there is a package.txt file, it should contain detailed information about the options.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="optionsList"> + <property name="toolTip"> + <string>Components of this package.</string> + </property> + <property name="whatsThis"> + <string>Components of this package. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author.</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="manualBtn"> + <property name="toolTip"> + <string>Opens a Dialog that allows custom modifications.</string> + </property> + <property name="whatsThis"> + <string>Opens a Dialog that allows custom modifications.</string> + </property> + <property name="text"> + <string>Manual</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="packageBtn"> + <property name="toolTip"> + <string>The package.txt is often part of BAIN packages and contains details about the options available.</string> + </property> + <property name="whatsThis"> + <string>The package.txt is often part of BAIN packages and contains details about the options available.</string> + </property> + <property name="text"> + <string notr="true">Package.txt</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="okBtn"> + <property name="text"> + <string>Ok</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelBtn"> + <property name="text"> + <string>Cancel</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/libs/installer_bain/src/installer_bain_en.ts b/libs/installer_bain/src/installer_bain_en.ts new file mode 100644 index 0000000..fe78e5d --- /dev/null +++ b/libs/installer_bain/src/installer_bain_en.ts @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="en_US"> +<context> + <name>BainComplexInstallerDialog</name> + <message> + <location filename="baincomplexinstallerdialog.ui" line="14"/> + <source>BAIN Package Installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="22"/> + <source>Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="38"/> + <source>This looks like a Package prepared for Installation through BAIN. You can select options from the list below. If there is a package.txt file, it should contain detailed information about the options.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="48"/> + <source>Components of this package.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="51"/> + <source>Components of this package. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="61"/> + <location filename="baincomplexinstallerdialog.ui" line="64"/> + <source>Opens a Dialog that allows custom modifications.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="67"/> + <source>Manual</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="77"/> + <location filename="baincomplexinstallerdialog.ui" line="80"/> + <source>The package.txt is often part of BAIN packages and contains details about the options available.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="106"/> + <source>Ok</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="113"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>InstallerBAIN</name> + <message> + <location filename="installerbain.cpp" line="51"/> + <source>BAIN Installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installerbain.cpp" line="61"/> + <source>Installer for BAIN archives (originally targeting Wrye Bash)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installerbain.cpp" line="173"/> + <source>May be BAIN installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installerbain.cpp" line="174"/> + <source>This installer looks like it may contain a BAIN installer but I'm not sure. Install as BAIN installer?</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/installer_bain/src/installerbain.cpp b/libs/installer_bain/src/installerbain.cpp new file mode 100644 index 0000000..d39590e --- /dev/null +++ b/libs/installer_bain/src/installerbain.cpp @@ -0,0 +1,222 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "installerbain.h" + +#include <uibase/game_features/igamefeatures.h> +#include <uibase/game_features/moddatachecker.h> +#include <uibase/iinstallationmanager.h> +#include <uibase/iplugingame.h> +#include <uibase/log.h> + +#include <QDir> +#include <QMessageBox> +#include <QtPlugin> + +#include "baincomplexinstallerdialog.h" + +using namespace MOBase; + +InstallerBAIN::InstallerBAIN() : m_MOInfo(nullptr) {} + +bool InstallerBAIN::init(IOrganizer* moInfo) +{ + m_MOInfo = moInfo; + return true; +} + +QString InstallerBAIN::name() const +{ + return "BAIN Installer"; +} + +QString InstallerBAIN::localizedName() const +{ + return tr("BAIN Installer"); +} + +QString InstallerBAIN::author() const +{ + return "Tannin"; +} + +QString InstallerBAIN::description() const +{ + return tr("Installer for BAIN archives (originally targeting Wrye Bash)"); +} + +VersionInfo InstallerBAIN::version() const +{ + return VersionInfo(1, 1, 0, VersionInfo::RELEASE_FINAL); +} + +QList<PluginSetting> InstallerBAIN::settings() const +{ + return QList<PluginSetting>(); +} + +unsigned int InstallerBAIN::priority() const +{ + return 40; +} + +bool InstallerBAIN::isManualInstaller() const +{ + return false; +} + +void InstallerBAIN::onInstallationStart(QString const&, bool, IModInterface* currentMod) +{ + // We reset some field and fetch the previously installed options: + m_InstallerUsed = false; + m_PreviousOptions.clear(); + m_SelectedOptions.clear(); + + if (currentMod) { + auto settings = currentMod->pluginSettings(name()); + for (auto& [name, value] : settings) { + if (name.startsWith("option")) { + m_PreviousOptions.append(value.toString()); + } + } + } +} + +void InstallerBAIN::onInstallationEnd(EInstallResult result, IModInterface* newMod) +{ + if (result == EInstallResult::RESULT_SUCCESS && m_InstallerUsed) { + newMod->clearPluginSettings(name()); + for (auto i = 0; i < m_SelectedOptions.size(); ++i) { + newMod->setPluginSetting(name(), QString("option%1").arg(i), + m_SelectedOptions[i]); + } + } +} + +std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> +InstallerBAIN::findSubpackages(std::shared_ptr<const MOBase::IFileTree> tree, + std::size_t* invalidFolders) const +{ + // Folders that can be present but do not impact the installation: + static const std::set<QString, FileNameComparator> IGNORED_FOLDERS{ + "fomod", "omod conversion data", "images", "screenshots", "docs"}; + + auto checker = m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>(); + + if (!checker) { + return {}; + } + + // each directory would have to serve as a top-level directory + std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> subpackages; + std::size_t ninvalids = 0; + for (auto entry : *tree) { + + if (!entry->isDir()) { + continue; + } + + // ignore fomod in case of combined fomod/bain packages. + // dirs starting with -- are supposed to be ignored + if (IGNORED_FOLDERS.contains(entry->name()) || entry->name().startsWith("--")) { + continue; + } + + if (checker->dataLooksValid(entry->astree()) == + ModDataChecker::CheckReturn::VALID) { + subpackages.push_back(entry); + } else { + ninvalids++; + } + } + + if (invalidFolders) { + *invalidFolders = ninvalids; + } + + return subpackages; +} + +bool InstallerBAIN::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const +{ + auto checker = m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>(); + + if (!checker) { + return false; + } + + std::size_t numInvalidDirs = 0; + auto subpackages = findSubpackages(tree, &numInvalidDirs); + + if (subpackages.size() < 2) { + // a complex bain package contains at least 2 directories to choose from + return false; + } else if (numInvalidDirs == 0) { + return true; + } else { + return QMessageBox::question(parentWidget(), tr("May be BAIN installer"), + tr("This installer looks like it may contain a BAIN " + "installer but I'm not sure. " + "Install as BAIN installer?"), + QMessageBox::Yes | QMessageBox::No) == + QMessageBox::Yes; + } + + return true; +} + +IPluginInstaller::EInstallResult +InstallerBAIN::install(GuessedValue<QString>& modName, std::shared_ptr<IFileTree>& tree, + QString&, int&) +{ + auto entry = tree->find("package.txt", FileTreeEntry::FILE); + + QString packageTXT; + if (entry != nullptr) { + packageTXT = manager()->extractFile(entry); + } + + auto subpackages = findSubpackages(tree); + + BainComplexInstallerDialog dialog(subpackages, modName, m_PreviousOptions, packageTXT, + parentWidget()); + + int res = dialog.exec(); + + if (res == QDialog::Accepted) { + modName.update(dialog.getName(), GUESS_USER); + + // Update the tree: + m_SelectedOptions = dialog.updateTree(tree); + m_InstallerUsed = true; + + return IPluginInstaller::RESULT_SUCCESS; + } else { + if (dialog.manualRequested()) { + modName.update(dialog.getName(), GUESS_USER); + return IPluginInstaller::RESULT_MANUALREQUESTED; + } else { + return IPluginInstaller::RESULT_CANCELED; + } + } +} + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +Q_EXPORT_PLUGIN2(installerBAIN, InstallerBAIN) +#endif diff --git a/libs/installer_bain/src/installerbain.h b/libs/installer_bain/src/installerbain.h new file mode 100644 index 0000000..535a31a --- /dev/null +++ b/libs/installer_bain/src/installerbain.h @@ -0,0 +1,80 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INSTALLERBAIN_H +#define INSTALLERBAIN_H + +#include <uibase/iplugininstallersimple.h> + +class InstallerBAIN : public MOBase::IPluginInstallerSimple +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstaller MOBase::IPluginInstallerSimple) +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "org.tannin.InstallerBAIN") +#endif + +public: + InstallerBAIN(); + + virtual bool init(MOBase::IOrganizer* moInfo) override; + virtual QString name() const override; + virtual QString localizedName() const override; + virtual QString author() const override; + virtual QString description() const override; + virtual MOBase::VersionInfo version() const override; + virtual QList<MOBase::PluginSetting> settings() const override; + + virtual unsigned int priority() const; + virtual bool isManualInstaller() const; + + virtual void onInstallationStart(QString const& archive, bool reinstallation, + MOBase::IModInterface* currentMod) override; + virtual void onInstallationEnd(EInstallResult result, + MOBase::IModInterface* newMod) override; + + virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const; + virtual EInstallResult install(MOBase::GuessedValue<QString>& modName, + std::shared_ptr<MOBase::IFileTree>& tree, + QString& version, int& modID); + +protected: + /** + * @brief Retrieve the entries corresponding to subpackages in the given tree. + * + * @param tree The tree to check for subpackages. + * @param invalidFolders If not null, will contain the number of invalid folders. + * + * @return the list of entries corresponding to subpackages. + */ + std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> + findSubpackages(std::shared_ptr<const MOBase::IFileTree> tree, + std::size_t* invalidFolders = nullptr) const; + +private: + const MOBase::IOrganizer* m_MOInfo; + + // Indicates if the installer was used: + bool m_InstallerUsed; + + // List of options currently installed and being installed: + QStringList m_PreviousOptions, m_SelectedOptions; +}; + +#endif // INSTALLERBAIN_H diff --git a/libs/installer_bain/vcpkg.json b/libs/installer_bain/vcpkg.json new file mode 100644 index 0000000..28051ad --- /dev/null +++ b/libs/installer_bain/vcpkg.json @@ -0,0 +1,15 @@ +{ + "features": { + "standalone": { + "description": "Build Standalone.", + "dependencies": ["mo2-cmake", "mo2-uibase"] + } + }, + "vcpkg-configuration": { + "default-registry": { + "kind": "git", + "repository": "https://github.com/ModOrganizer2/vcpkg-registry", + "baseline": "8beb2e0efa9c17dd6d17bb05288dd1e40727f673" + } + } +} |
