diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 02:45:12 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 02:45:25 -0600 |
| commit | 817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (patch) | |
| tree | 52aed11d6751bb7d20b06acf197d56fac113203d /libs/installer_fomod_plus/scanner | |
| parent | 51a9f8f197727f00896e5de44569b098923527dd (diff) | |
Add FUSE external mapping support, BG3/Oblivion Remastered fixes, fomod-plus and NaK integration
FUSE VFS now deploys non-data-dir mod mappings (Paks, OBSE, UE4SS, etc.)
via real symlinks and injects file-level data-dir mappings (plugins.txt,
loadorder.txt) into the VFS tree. Fixes game launches for Oblivion
Remastered (Root Builder path resolution, script extender support) and
BG3 (Wine prefix documents directory, file mapper symlinks on Linux).
Vendors mo2-fomod-plus plugin and NaK crate for FOMOD installer and
game finder/runtime support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_fomod_plus/scanner')
| -rw-r--r-- | libs/installer_fomod_plus/scanner/CMakeLists.txt | 54 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/FomodPlusScanner.cpp | 128 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/FomodPlusScanner.h | 64 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/archiveparser.h | 98 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/fomod_plus_scanner_de.ts | 57 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/fomod_plus_scanner_en.ts | 57 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/fomod_plus_scanner_zh_CN.ts | 57 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/fomodplusscanner.json | 8 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/resources.qrc | 6 | ||||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/resources/fomod_icon.png | bin | 0 -> 1782 bytes | |||
| -rw-r--r-- | libs/installer_fomod_plus/scanner/resources/wizard.gif | bin | 0 -> 5263 bytes |
11 files changed, 529 insertions, 0 deletions
diff --git a/libs/installer_fomod_plus/scanner/CMakeLists.txt b/libs/installer_fomod_plus/scanner/CMakeLists.txt new file mode 100644 index 0000000..daea477 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.16) +project(fomod_plus_scanner) + +include(FetchContent) +set(project_type plugin) + +file(GLOB_RECURSE SCANNER_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(fomod_plus_scanner SHARED ${SCANNER_SOURCES}) + +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) +FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml GIT_TAG v1.14) +FetchContent_MakeAvailable(pugixml json) + +target_include_directories( + fomod_plus_scanner + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../share + ${CMAKE_CURRENT_SOURCE_DIR}/../share/FOMODData + ${MO2_UIBASE_INCLUDE_DIRS} + ${MO2_ARCHIVE_INCLUDE_DIRS} +) + +if (MSVC) + target_compile_options( + fomod_plus_scanner + PRIVATE + /bigobj + /W4 + /WX + /wd4201 + /wd4458 + ) +endif () + +target_link_libraries(fomod_plus_scanner PRIVATE mo2::uibase mo2::archive pugixml nlohmann_json::nlohmann_json) +mo2_configure_plugin(fomod_plus_scanner NO_SOURCES WARNINGS OFF PRIVATE_DEPENDS archive) + +if (MSVC) + target_compile_options(fomod_plus_scanner PRIVATE $<$<CONFIG:RelWithDebInfo>:/Zi>) + target_link_options(fomod_plus_scanner PRIVATE $<$<CONFIG:RelWithDebInfo>:/DEBUG>) + install(FILES $<TARGET_PDB_FILE:fomod_plus_scanner> + DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO} + CONFIGURATIONS RelWithDebInfo + OPTIONAL) +endif () + +mo2_install_target(fomod_plus_scanner) diff --git a/libs/installer_fomod_plus/scanner/FomodPlusScanner.cpp b/libs/installer_fomod_plus/scanner/FomodPlusScanner.cpp new file mode 100644 index 0000000..4770178 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/FomodPlusScanner.cpp @@ -0,0 +1,128 @@ +#include "FomodPlusScanner.h" + +#include "archiveparser.h" +#include "FomodDBEntry.h" + +#include <QDialog> +#include <QPushButton> +#include <QProgressBar> +#include <QVBoxLayout> +#include <QMessageBox> +#include <archive.h> + +#include <QLabel> +#include <QMovie> +#include <iostream> + +#include "stringutil.h" + +using ScanCallbackFn = std::function<bool(IModInterface*, ScanResult result)>; + +bool FomodPlusScanner::init(IOrganizer* organizer) +{ + mOrganizer = organizer; + + mDialog = new QDialog(); + mDialog->setWindowTitle(tr("FOMOD Scanner")); + + const auto layout = new QVBoxLayout(mDialog); + + const QString description = tr("Greetings, traveler.\n" + "This tool will scan your load order for mods installed via FOMOD.\n" + "It will also fix up any erroneous FOMOD flags from previous versions of FOMOD Plus :) \n\n" + "Safe travels, and may your load order be free of conflicts."); + + const auto descriptionLabel = new QLabel(description, mDialog); + descriptionLabel->setWordWrap(true); // Enable word wrap for large text + descriptionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + descriptionLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); // Align text to the top-left corner + + const auto gifLabel = new QLabel(mDialog); + const auto movie = new QMovie(":/fomod/wizard"); + gifLabel->setMovie(movie); + movie->start(); + + mProgressBar = new QProgressBar(mDialog); + mProgressBar->setRange(0, mOrganizer->modList()->allMods().size()); + mProgressBar->setVisible(false); + + const auto scanButton = new QPushButton(tr("Scan"), mDialog); + const auto cancelButton = new QPushButton(tr("Cancel"), mDialog); + + layout->addWidget(descriptionLabel, 1); + layout->addWidget(gifLabel, 1); + layout->addWidget(mProgressBar, 1); + layout->addWidget(scanButton, 1); + layout->addWidget(cancelButton, 1); + + connect(cancelButton, &QPushButton::clicked, mDialog, &QDialog::reject); + connect(scanButton, &QPushButton::clicked, this, &FomodPlusScanner::onScanClicked); + connect(mDialog, &QDialog::finished, this, &FomodPlusScanner::cleanup); + + mDialog->setLayout(layout); + mDialog->setMinimumSize(400, 300); + mDialog->adjustSize(); + descriptionLabel->adjustSize(); + return true; +} + +void FomodPlusScanner::onScanClicked() const +{ + mProgressBar->setVisible(true); + const int added = scanLoadOrder(setFomodInfoForMod); + mDialog->accept(); + QMessageBox::information(mDialog, tr("Scan Complete"), + tr("The load order scan is complete. Updated filter info for ") + QString::number(added) + tr(" mods.")); + mOrganizer->refresh(); +} + +void FomodPlusScanner::cleanup() const +{ + mProgressBar->reset(); + mProgressBar->setVisible(false); +} + +void FomodPlusScanner::display() const +{ + mDialog->exec(); +} + +int FomodPlusScanner::scanLoadOrder(const ScanCallbackFn& callback) const +{ + int progress = 0; + int modified = 0; + for (const auto& modName : mOrganizer->modList()->allMods()) { + const auto mod = mOrganizer->modList()->getMod(modName); + if (const ScanResult result = openInstallationArchive(mod); callback(mod, result)) { + modified++; + } + mProgressBar->setValue(++progress); + } + return modified; +} + +ScanResult FomodPlusScanner::openInstallationArchive(const IModInterface* mod) const +{ + const auto downloadsDir = mOrganizer->downloadsPath(); + const auto installationFilePath = mod->installationFile(); + return ArchiveParser::scanForFomodFiles(downloadsDir, installationFilePath, mod->name()); +} + +bool FomodPlusScanner::setFomodInfoForMod(IModInterface* mod, const ScanResult result) +{ + const auto pluginName = "FOMOD Plus"; + const auto setting = mod->pluginSetting(pluginName, "fomod", 0); + if (setting == 0 && ScanResult::HAS_FOMOD == result) { + return mod->setPluginSetting(pluginName, "fomod", "{}"); + } + if (setting != 0 && ScanResult::NO_FOMOD == result) { + return mod->setPluginSetting(pluginName, "fomod", 0); + } + return false; +} + +bool FomodPlusScanner::removeFomodInfoFromMod(IModInterface* mod, ScanResult) +{ + const auto pluginName = QString::fromStdString("FOMOD Plus"); + return mod->setPluginSetting(pluginName, "fomod", 0); +} diff --git a/libs/installer_fomod_plus/scanner/FomodPlusScanner.h b/libs/installer_fomod_plus/scanner/FomodPlusScanner.h new file mode 100644 index 0000000..b7893f7 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/FomodPlusScanner.h @@ -0,0 +1,64 @@ +#ifndef FOMODPLUSSCANNER_H +#define FOMODPLUSSCANNER_H + +#include "archiveparser.h" + +#include <iplugin.h> +#include <iplugintool.h> + +#include <QDialog> +#include <QProgressBar> + +using namespace MOBase; + +class FomodPlusScanner final : public IPluginTool { + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool) +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "io.clearing.FomodPlusScanner" FILE "fomodplusscanner.json") +#endif + +public: + ~FomodPlusScanner() override + { + delete mDialog; + mDialog = nullptr; + mProgressBar = nullptr; + } + + bool init(IOrganizer* organizer) override; + + void onScanClicked() const; + + void cleanup() const; + + [[nodiscard]] QString name() const override { return "FOMOD Scanner"; } // This should not be translated + [[nodiscard]] QString author() const override { return "clearing"; } + [[nodiscard]] QString description() const override { return tr("Scans modlist for files installed via FOMOD"); } + [[nodiscard]] VersionInfo version() const override { return {1, 0, 0, VersionInfo::RELEASE_FINAL}; } + + [[nodiscard]] QList<PluginSetting> settings() const override { return {}; } + + [[nodiscard]] QString displayName() const override { return tr("FOMOD Scanner"); } + + [[nodiscard]] QString tooltip() const override { return tr("Scan modlist for files installed via FOMOD"); } + + [[nodiscard]] QIcon icon() const override { return QIcon(":/fomod/hat"); } + + void display() const override; + + int scanLoadOrder(const std::function<bool(IModInterface*, ScanResult result)> &callback) const; + + ScanResult openInstallationArchive(const IModInterface* mod) const; + + static bool setFomodInfoForMod(IModInterface *mod, ScanResult result); + + static bool removeFomodInfoFromMod(IModInterface *mod, ScanResult); + +private: + QDialog* mDialog{ nullptr }; + QProgressBar* mProgressBar{ nullptr }; + IOrganizer* mOrganizer{ nullptr }; +}; + +#endif // FOMODPLUSSCANNER_H
\ No newline at end of file diff --git a/libs/installer_fomod_plus/scanner/archiveparser.h b/libs/installer_fomod_plus/scanner/archiveparser.h new file mode 100644 index 0000000..4058d43 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/archiveparser.h @@ -0,0 +1,98 @@ +#pragma once +#include "stringutil.h" + +#include <QDir> +#include <QString> +#include <archive.h> +#include <iostream> +#include <ostream> + +inline std::ostream& operator<<(std::ostream& os, const Archive::Error& error) +{ + switch (error) { + case Archive::Error::ERROR_NONE: + os << "No error"; + break; + case Archive::Error::ERROR_ARCHIVE_NOT_FOUND: + os << "File not found"; + break; + case Archive::Error::ERROR_FAILED_TO_OPEN_ARCHIVE: + os << "Failed to open file"; + break; + case Archive::Error::ERROR_INVALID_ARCHIVE_FORMAT: + os << "Invalid archive format"; + break; + default: + os << "Unknown error??"; + } + return os; +} + +inline bool hasFomodFiles(const std::vector<FileData*>& files) +{ + bool hasModuleXml = false; + bool hasInfoXml = false; + + for (const auto* file : files) { + if (endsWithCaseInsensitive(file->getArchiveFilePath(), StringConstants::FomodFiles::W_MODULE_CONFIG.data())) { + hasModuleXml = true; + } + if (endsWithCaseInsensitive(file->getArchiveFilePath(), StringConstants::FomodFiles::W_INFO_XML.data())) { + hasInfoXml = true; + } + } + return hasModuleXml && hasInfoXml; +} + +/* This class can do the following: + * 1.) Detects if an archive has FOMOD files in it (without extracting) + * - This is used by the FOMOD scanner to set content flags + * + * 2.) It may support extracting ESPs and FOMODs, but this might be better served + * by a separate class. Maybe it could return a ModuleConfiguration to use like + * the installer. + */ + +enum class ScanResult { + HAS_FOMOD, + NO_FOMOD, + NO_ARCHIVE +}; + +class ArchiveParser { +public: + static ScanResult scanForFomodFiles(const QString& downloadsPath, const QString& installationFilePath, + const QString& modName) + { + if (installationFilePath.isEmpty()) { + return ScanResult::NO_ARCHIVE; + } + const auto qualifiedInstallerPath = QDir(installationFilePath).isAbsolute() + ? installationFilePath + : downloadsPath + "/" + installationFilePath; + + const auto archive = CreateArchive(); + + if (!archive->isValid()) { + logErrorForMod(modName, "Failed to load the archive module ", archive); + return ScanResult::NO_ARCHIVE; + } + if (!archive->open(qualifiedInstallerPath.toStdWString(), nullptr)) { + logErrorForMod(modName, "Failed to open archive [" + qualifiedInstallerPath + "]", archive); + return ScanResult::NO_ARCHIVE; + } + + if (hasFomodFiles(archive->getFileList())) { + std::cout << "Found FOMOD files in " << qualifiedInstallerPath.toStdString() << std::endl; + return ScanResult::HAS_FOMOD; + } + return ScanResult::NO_FOMOD; + } + +private: + static void logErrorForMod(const QString& modName, const QString& message, const std::unique_ptr<Archive>& archive) + { + std::cerr << "[" << modName.toStdString() << "] " << message.toStdString() << " (" << archive->getLastError() << + ")" << std::endl; + } +};
\ No newline at end of file diff --git a/libs/installer_fomod_plus/scanner/fomod_plus_scanner_de.ts b/libs/installer_fomod_plus/scanner/fomod_plus_scanner_de.ts new file mode 100644 index 0000000..759824f --- /dev/null +++ b/libs/installer_fomod_plus/scanner/fomod_plus_scanner_de.ts @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +<context> + <name>FomodPlusScanner</name> + <message> + <location filename="FomodPlusScanner.cpp" line="23"/> + <location filename="FomodPlusScanner.h" line="47"/> + <source>FOMOD Scanner</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="27"/> + <source>Sei gegrüßt, Reisender. +Dieses Tool scannt deine Ladereihenfolge nach Mods, die über einen FOMOD installiert wurden. +Es wird außerdem fehlerhafte FOMOD-Selektierungen aus früheren Versionen von FOMOD Plus beheben. :) + +Gute Reise, und möge deine Ladereihenfolge frei von Konflikten sein.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="46"/> + <source>Scannen</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="47"/> + <source>Abbrechen</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="71"/> + <source>Scan Abgeschlossen</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="72"/> + <source>Der Scan der Ladereihenfolge ist abgeschlossen. Aktualisierte Filterinformationen für </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="72"/> + <source> Mods.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.h" line="42"/> + <source>Scannt die Modliste nach Dateien, die über einen FOMOD installiert wurden.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.h" line="49"/> + <source>Modliste nach Dateien durchsuchen, die über einen FOMOD installiert wurden.</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/installer_fomod_plus/scanner/fomod_plus_scanner_en.ts b/libs/installer_fomod_plus/scanner/fomod_plus_scanner_en.ts new file mode 100644 index 0000000..83a310b --- /dev/null +++ b/libs/installer_fomod_plus/scanner/fomod_plus_scanner_en.ts @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="en_US"> +<context> + <name>FomodPlusScanner</name> + <message> + <location filename="FomodPlusScanner.cpp" line="26"/> + <location filename="FomodPlusScanner.h" line="42"/> + <source>FOMOD Scanner</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="30"/> + <source>Greetings, traveler. +This tool will scan your load order for mods installed via FOMOD. +It will also fix up any erroneous FOMOD flags from previous versions of FOMOD Plus :) + +Safe travels, and may your load order be free of conflicts.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="49"/> + <source>Scan</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="50"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="74"/> + <source>Scan Complete</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="75"/> + <source>The load order scan is complete. Updated filter info for </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="75"/> + <source> mods.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.h" line="37"/> + <source>Scans modlist for files installed via FOMOD</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.h" line="44"/> + <source>Scan modlist for files installed via FOMOD</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/installer_fomod_plus/scanner/fomod_plus_scanner_zh_CN.ts b/libs/installer_fomod_plus/scanner/fomod_plus_scanner_zh_CN.ts new file mode 100644 index 0000000..01f84c8 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/fomod_plus_scanner_zh_CN.ts @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>FomodPlusScanner</name> + <message> + <location filename="FomodPlusScanner.cpp" line="23"/> + <location filename="FomodPlusScanner.h" line="47"/> + <source>FOMOD Scanner</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="27"/> + <source>你好,旅行者。 +该工具将扫描模组列表来查找通过 FOMOD 安装的模组, +并修正旧版本错误的 FOMOD 标志(需保留安装包才能被检测到):) + +一路平安,祝您的 load order 没有冲突。</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="46"/> + <source>扫描</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="47"/> + <source>取消</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="71"/> + <source>扫描完成</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="72"/> + <source>模组扫描已完成。更新了 </source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.cpp" line="72"/> + <source> 个模组筛选框信息。</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.h" line="42"/> + <source>扫描模组列表查找通过 FOMOD 安装的文件</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="FomodPlusScanner.h" line="49"/> + <source>扫描模组列表查找通过 FOMOD 安装的文件</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/installer_fomod_plus/scanner/fomodplusscanner.json b/libs/installer_fomod_plus/scanner/fomodplusscanner.json new file mode 100644 index 0000000..f6643ea --- /dev/null +++ b/libs/installer_fomod_plus/scanner/fomodplusscanner.json @@ -0,0 +1,8 @@ +{ + "author" : "clearing", + "date" : "2025/02/06", + "name" : "FOMOD Plus - Scanner", + "version" : "1.0.0", + "des" : "Scans load order for FOMOD installations", + "dependencies" : [] +} diff --git a/libs/installer_fomod_plus/scanner/resources.qrc b/libs/installer_fomod_plus/scanner/resources.qrc new file mode 100644 index 0000000..df7c48c --- /dev/null +++ b/libs/installer_fomod_plus/scanner/resources.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/fomod"> + <file alias="hat">resources/fomod_icon.png</file> + <file alias="wizard">resources/wizard.gif</file> + </qresource> +</RCC>
\ No newline at end of file diff --git a/libs/installer_fomod_plus/scanner/resources/fomod_icon.png b/libs/installer_fomod_plus/scanner/resources/fomod_icon.png Binary files differnew file mode 100644 index 0000000..e044052 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/resources/fomod_icon.png diff --git a/libs/installer_fomod_plus/scanner/resources/wizard.gif b/libs/installer_fomod_plus/scanner/resources/wizard.gif Binary files differnew file mode 100644 index 0000000..c1f75d3 --- /dev/null +++ b/libs/installer_fomod_plus/scanner/resources/wizard.gif |
