aboutsummaryrefslogtreecommitdiff
path: root/libs/rootbuilder_native/src
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 02:40:00 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-12 02:40:00 -0500
commite0fb66428a9b78b4c326df8d7e30429a5b271d74 (patch)
tree0fd83fe790ecb7d5cd3cfb36535e45d1b7118098 /libs/rootbuilder_native/src
parente985dc340097831d4e5625941bded217abaa57b8 (diff)
Remove dead plugin scaffolds and duplicate lib dirs
The *_native C++ ports were replaced by bundled Python + .py plugins in de4db79, but the source dirs were left behind without any CMake wiring — causing confusion when editing them appears to do nothing. Same story for libs/dds_header (stale duplicate of libs/preview_dds, unrelated to the actively-used libs/dds-header) and libs/game_gamebryo (superseded by libs/game_bethesda/src/gamebryo). Removes: - libs/basic_games_native - libs/form43_checker_native - libs/installer_omod_native - libs/preview_dds_native - libs/rootbuilder_native - libs/script_extender_checker_native - libs/dds_header - libs/game_gamebryo
Diffstat (limited to 'libs/rootbuilder_native/src')
-rw-r--r--libs/rootbuilder_native/src/CMakeLists.txt18
-rw-r--r--libs/rootbuilder_native/src/rootbuilder.cpp674
-rw-r--r--libs/rootbuilder_native/src/rootbuilder.h79
3 files changed, 0 insertions, 771 deletions
diff --git a/libs/rootbuilder_native/src/CMakeLists.txt b/libs/rootbuilder_native/src/CMakeLists.txt
deleted file mode 100644
index 438c068..0000000
--- a/libs/rootbuilder_native/src/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-
-find_package(Qt6 REQUIRED COMPONENTS Widgets)
-qt_standard_project_setup()
-
-file(GLOB SOURCES *.cpp *.h)
-
-add_library(rootbuilder_native SHARED ${SOURCES})
-
-set_target_properties(rootbuilder_native PROPERTIES
- AUTOMOC ON
- CXX_STANDARD 23
- CXX_STANDARD_REQUIRED ON
- PREFIX "lib"
-)
-
-target_link_libraries(rootbuilder_native PRIVATE mo2::uibase Qt6::Widgets)
-install(TARGETS rootbuilder_native LIBRARY DESTINATION bin/plugins)
diff --git a/libs/rootbuilder_native/src/rootbuilder.cpp b/libs/rootbuilder_native/src/rootbuilder.cpp
deleted file mode 100644
index 6ee2e1c..0000000
--- a/libs/rootbuilder_native/src/rootbuilder.cpp
+++ /dev/null
@@ -1,674 +0,0 @@
-#include "rootbuilder.h"
-
-#include <uibase/imodinterface.h>
-#include <uibase/imodlist.h>
-#include <uibase/iplugingame.h>
-#include <uibase/versioninfo.h>
-
-#include <QCoreApplication>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QDialog>
-#include <QDir>
-#include <QDirIterator>
-#include <QFile>
-#include <QFileInfo>
-#include <QHBoxLayout>
-#include <QJsonArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QLabel>
-#include <QProcess>
-#include <QPushButton>
-#include <QVBoxLayout>
-
-#include <algorithm>
-#include <set>
-
-// Storage constants
-static const QString STORAGE_SUBDIR = QStringLiteral("rootbuilder");
-static const QString MANIFEST_NAME = QStringLiteral("manifest.json");
-static const QString SETTINGS_NAME = QStringLiteral("settings.json");
-static const QString BACKUP_SUBDIR = QStringLiteral("backup");
-static const QString LEGACY_MANIFEST = QStringLiteral(".rootbuilder_manifest.json");
-static const QString LEGACY_BACKUP = QStringLiteral(".rootbuilder_backup");
-
-// ============================================================================
-// Construction / IPlugin
-// ============================================================================
-
-RootBuilderNative::RootBuilderNative() = default;
-
-bool RootBuilderNative::init(MOBase::IOrganizer* organizer)
-{
- m_organizer = organizer;
- migrateLegacy();
- checkThirdPartyRootBuilder();
-
- organizer->onAboutToRun([this](const QString& exe) {
- return onAboutToRun(exe);
- });
- organizer->onFinishedRun([this](const QString& exe, unsigned int code) {
- onFinishedRun(exe, code);
- });
-
- return true;
-}
-
-QString RootBuilderNative::name() const
-{
- return QStringLiteral("Root Builder (Native)");
-}
-
-QString RootBuilderNative::localizedName() const
-{
- return QStringLiteral("Root Builder (Native)");
-}
-
-QString RootBuilderNative::author() const
-{
- return QStringLiteral("Fluorine Manager");
-}
-
-QString RootBuilderNative::description() const
-{
- return QStringLiteral(
- "Deploys mod files from Root/ subdirectories to the game's root directory. "
- "Supports copy and symlink modes with auto-deploy on launch.");
-}
-
-MOBase::VersionInfo RootBuilderNative::version() const
-{
- return MOBase::VersionInfo(1, 0, 0);
-}
-
-QList<MOBase::PluginSetting> RootBuilderNative::settings() const
-{
- return {};
-}
-
-bool RootBuilderNative::enabledByDefault() const
-{
- return true;
-}
-
-// ============================================================================
-// IPluginTool
-// ============================================================================
-
-QString RootBuilderNative::displayName() const
-{
- return QStringLiteral("Root Builder");
-}
-
-QString RootBuilderNative::tooltip() const
-{
- return QStringLiteral("Deploy mod Root/ files to the game directory");
-}
-
-QIcon RootBuilderNative::icon() const
-{
- return QIcon();
-}
-
-void RootBuilderNative::display() const
-{
- QJsonObject settings = loadSettings();
-
- // We need non-const this for build/clear — display() is const in the
- // interface, but build/clear mutate the filesystem (not object state).
- auto* self = const_cast<RootBuilderNative*>(this);
-
- auto* dialog = new QDialog(parentWidget());
- dialog->setAttribute(Qt::WA_DeleteOnClose);
- dialog->setWindowTitle(QStringLiteral("Root Builder"));
- dialog->resize(350, 220);
-
- auto* layout = new QVBoxLayout(dialog);
-
- auto* desc = new QLabel(
- QStringLiteral("Deploys files from mod Root/ folders to the game directory."));
- desc->setWordWrap(true);
- layout->addWidget(desc);
-
- // Enable checkbox
- auto* enableCheck = new QCheckBox(QStringLiteral("Auto-deploy on game launch"));
- enableCheck->setChecked(settings.value(QStringLiteral("enabled")).toBool(false));
- layout->addWidget(enableCheck);
-
- // Mode selector
- auto* modeLayout = new QHBoxLayout();
- modeLayout->addWidget(new QLabel(QStringLiteral("Deploy mode:")));
- auto* modeCombo = new QComboBox();
- modeCombo->addItems({QStringLiteral("copy"), QStringLiteral("link")});
- modeCombo->setCurrentText(
- settings.value(QStringLiteral("mode")).toString(QStringLiteral("copy")));
- modeLayout->addWidget(modeCombo);
- layout->addLayout(modeLayout);
-
- // Manual build/clear buttons
- auto* btnLayout = new QHBoxLayout();
- auto* buildBtn = new QPushButton(QStringLiteral("Build Now"));
- auto* clearBtn = new QPushButton(QStringLiteral("Clear Now"));
- btnLayout->addWidget(buildBtn);
- btnLayout->addWidget(clearBtn);
- layout->addLayout(btnLayout);
-
- // Status label
- auto* statusLabel = new QLabel();
- layout->addWidget(statusLabel);
-
- // Close button
- auto* closeBtn = new QPushButton(QStringLiteral("Close"));
- layout->addWidget(closeBtn);
-
- // Save helper — captures enableCheck and modeCombo
- auto doSave = [this, enableCheck, modeCombo]() {
- QJsonObject s;
- s[QStringLiteral("enabled")] = enableCheck->isChecked();
- s[QStringLiteral("mode")] = modeCombo->currentText();
- saveSettings(s);
- };
-
- QObject::connect(enableCheck, &QCheckBox::stateChanged, dialog, [doSave](int) {
- doSave();
- });
- QObject::connect(modeCombo, &QComboBox::currentTextChanged, dialog,
- [doSave](const QString&) { doSave(); });
-
- QObject::connect(buildBtn, &QPushButton::clicked, dialog,
- [self, statusLabel]() {
- int count = self->build();
- statusLabel->setText(
- QStringLiteral("Deployed %1 file(s).").arg(count));
- });
-
- QObject::connect(clearBtn, &QPushButton::clicked, dialog,
- [self, statusLabel]() {
- int count = self->clear();
- statusLabel->setText(
- QStringLiteral("Cleared %1 file(s).").arg(count));
- });
-
- QObject::connect(closeBtn, &QPushButton::clicked, dialog, &QDialog::accept);
-
- dialog->exec();
-}
-
-// ============================================================================
-// Hooks
-// ============================================================================
-
-bool RootBuilderNative::onAboutToRun(const QString& /*executable*/)
-{
- if (isAutoEnabled())
- build();
- return true;
-}
-
-void RootBuilderNative::onFinishedRun(const QString& /*executable*/,
- unsigned int /*exitCode*/)
-{
- if (isAutoEnabled())
- clear();
-}
-
-// ============================================================================
-// Storage paths
-// ============================================================================
-
-QString RootBuilderNative::storageDir() const
-{
- QString d = m_organizer->basePath() + QStringLiteral("/") + STORAGE_SUBDIR;
- QDir().mkpath(d);
- return d;
-}
-
-QString RootBuilderNative::backupDir() const
-{
- return storageDir() + QStringLiteral("/") + BACKUP_SUBDIR;
-}
-
-QString RootBuilderNative::manifestPath() const
-{
- return storageDir() + QStringLiteral("/") + MANIFEST_NAME;
-}
-
-QString RootBuilderNative::settingsPath() const
-{
- return storageDir() + QStringLiteral("/") + SETTINGS_NAME;
-}
-
-// ============================================================================
-// Settings (own JSON)
-// ============================================================================
-
-QJsonObject RootBuilderNative::loadSettings() const
-{
- QFile f(settingsPath());
- if (!f.open(QIODevice::ReadOnly))
- return QJsonObject{
- {QStringLiteral("enabled"), false},
- {QStringLiteral("mode"), QStringLiteral("copy")}};
-
- QJsonParseError err;
- auto doc = QJsonDocument::fromJson(f.readAll(), &err);
- if (err.error != QJsonParseError::NoError || !doc.isObject())
- return QJsonObject{
- {QStringLiteral("enabled"), false},
- {QStringLiteral("mode"), QStringLiteral("copy")}};
-
- return doc.object();
-}
-
-void RootBuilderNative::saveSettings(const QJsonObject& settings) const
-{
- QDir().mkpath(storageDir());
- QFile f(settingsPath());
- if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
- f.write(QJsonDocument(settings).toJson(QJsonDocument::Indented));
- }
-}
-
-bool RootBuilderNative::isAutoEnabled() const
-{
- return loadSettings().value(QStringLiteral("enabled")).toBool(false);
-}
-
-// ============================================================================
-// Manifest
-// ============================================================================
-
-QJsonObject RootBuilderNative::loadManifest() const
-{
- QFile f(manifestPath());
- if (!f.open(QIODevice::ReadOnly))
- return QJsonObject();
-
- QJsonParseError err;
- auto doc = QJsonDocument::fromJson(f.readAll(), &err);
- if (err.error != QJsonParseError::NoError || !doc.isObject())
- return QJsonObject();
-
- return doc.object();
-}
-
-void RootBuilderNative::saveManifest(const QJsonObject& manifest) const
-{
- QDir().mkpath(storageDir());
- QFile f(manifestPath());
- if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
- f.write(QJsonDocument(manifest).toJson(QJsonDocument::Indented));
- }
-}
-
-void RootBuilderNative::removeManifest() const
-{
- QFile::remove(manifestPath());
-}
-
-// ============================================================================
-// Legacy migration
-// ============================================================================
-
-void RootBuilderNative::migrateLegacy() const
-{
- auto* game = m_organizer->managedGame();
- if (!game)
- return;
-
- QString gameDir = game->gameDirectory().absolutePath();
- QString storage = storageDir();
-
- // Migrate manifest
- QString oldManifest = gameDir + QStringLiteral("/") + LEGACY_MANIFEST;
- if (QFileInfo::exists(oldManifest)) {
- QString newPath = storage + QStringLiteral("/") + MANIFEST_NAME;
- if (!QFileInfo::exists(newPath))
- QFile::copy(oldManifest, newPath);
- forceRemove(oldManifest);
- }
-
- // Migrate backup directory
- QString oldBackup = gameDir + QStringLiteral("/") + LEGACY_BACKUP;
- QFileInfo oldBackupInfo(oldBackup);
- if (oldBackupInfo.isDir()) {
- QString newBackup = storage + QStringLiteral("/") + BACKUP_SUBDIR;
- if (!QFileInfo(newBackup).isDir()) {
- // Copy tree
- QDir().mkpath(newBackup);
- QDirIterator it(oldBackup, QDir::Files, QDirIterator::Subdirectories);
- while (it.hasNext()) {
- it.next();
- QString rel = QDir(oldBackup).relativeFilePath(it.filePath());
- QString dst = newBackup + QStringLiteral("/") + rel;
- QDir().mkpath(QFileInfo(dst).absolutePath());
- QFile::copy(it.filePath(), dst);
- }
- }
- // Remove old backup tree
- QDir(oldBackup).removeRecursively();
- }
-}
-
-// ============================================================================
-// Third-party conflict detection
-// ============================================================================
-
-void RootBuilderNative::checkThirdPartyRootBuilder() const
-{
- // Locate the plugins directory (where .py/.so plugins live, next to the binary)
- QString pluginsDir = QCoreApplication::applicationDirPath() + QStringLiteral("/plugins");
- if (!QDir(pluginsDir).exists())
- return;
-
- QString disabledDir =
- QCoreApplication::applicationDirPath() + QStringLiteral("/DisabledPlugins");
-
- QDir dir(pluginsDir);
- const auto entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
-
- for (const auto& entry : entries) {
- bool conflict = false;
- QString entryName = entry.fileName();
-
- if (entry.isDir() && entryName.compare(QStringLiteral("rootbuilder"),
- Qt::CaseInsensitive) == 0) {
- conflict = true;
- } else if (entry.isFile() &&
- entryName.toLower().startsWith(QStringLiteral("rootbuilder")) &&
- entryName.toLower().endsWith(QStringLiteral(".py"))) {
- // Don't move our own bundled rootbuilder.py
- if (entryName == QStringLiteral("rootbuilder.py"))
- continue;
- conflict = true;
- }
-
- if (conflict) {
- QDir().mkpath(disabledDir);
- QString dst = disabledDir + QStringLiteral("/") + entryName;
- if (QFile::rename(entry.absoluteFilePath(), dst)) {
- qInfo("Root Builder: moved incompatible third-party plugin "
- "'%s' to DisabledPlugins/.",
- qUtf8Printable(entryName));
- } else {
- qWarning("Root Builder: failed to move third-party plugin "
- "'%s' to DisabledPlugins/.",
- qUtf8Printable(entryName));
- }
- }
- }
-}
-
-// ============================================================================
-// File helpers
-// ============================================================================
-
-QString RootBuilderNative::findRootDir(const QString& modPath)
-{
- QDir dir(modPath);
- const auto entries =
- dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
- for (const auto& entry : entries) {
- if (entry.fileName().compare(QStringLiteral("root"),
- Qt::CaseInsensitive) == 0) {
- return entry.absoluteFilePath();
- }
- }
- return QString();
-}
-
-void RootBuilderNative::ensureReadable(const QString& path)
-{
- QFileInfo fi(path);
- if (fi.exists() && !fi.isReadable()) {
- QFile::setPermissions(path, fi.permissions() | QFileDevice::ReadOwner);
- }
-}
-
-void RootBuilderNative::ensureWritable(const QString& path)
-{
- QFileInfo fi(path);
- if (fi.exists()) {
- QFile::setPermissions(path, fi.permissions() | QFileDevice::WriteOwner);
- }
-}
-
-void RootBuilderNative::reflinkCopy(const QString& src, const QString& dst)
-{
- ensureReadable(src);
-
- // Try cp --reflink=auto first
- QProcess proc;
- proc.setProgram(QStringLiteral("cp"));
- proc.setArguments({QStringLiteral("--reflink=auto"), QStringLiteral("-f"),
- QStringLiteral("--"), src, dst});
- proc.start();
- if (proc.waitForFinished(10000) && proc.exitCode() == 0)
- return;
-
- // Fallback to QFile::copy (remove dst first since QFile::copy won't overwrite)
- QFile::remove(dst);
- if (QFile::copy(src, dst))
- return;
-
- qWarning("Root Builder: failed to copy %s -> %s",
- qUtf8Printable(src), qUtf8Printable(dst));
-}
-
-bool RootBuilderNative::forceRemove(const QString& path)
-{
- if (QFile::remove(path))
- return true;
-
- // Fix permissions and retry
- ensureWritable(QFileInfo(path).absolutePath());
- ensureWritable(path);
- if (QFile::remove(path))
- return true;
-
- qWarning("Root Builder: could not remove %s", qUtf8Printable(path));
- return false;
-}
-
-void RootBuilderNative::cleanupEmptyDirs(const QString& baseDir,
- const QStringList& paths)
-{
- std::set<QString> dirsToCheck;
- for (const auto& path : paths) {
- QString parent = QFileInfo(path).absolutePath();
- while (!parent.isEmpty() && parent != baseDir) {
- // Check samefile
- QFileInfo parentInfo(parent);
- QFileInfo baseInfo(baseDir);
- if (parentInfo.absoluteFilePath() == baseInfo.absoluteFilePath())
- break;
- dirsToCheck.insert(parent);
- parent = QFileInfo(parent).absolutePath();
- }
- }
-
- // Sort by length descending (deepest first)
- QStringList sorted(dirsToCheck.begin(), dirsToCheck.end());
- std::sort(sorted.begin(), sorted.end(),
- [](const QString& a, const QString& b) {
- return a.length() > b.length();
- });
-
- for (const auto& d : sorted) {
- QDir dir(d);
- if (dir.exists() && dir.isEmpty())
- dir.rmdir(QStringLiteral("."));
- }
-}
-
-// ============================================================================
-// Build
-// ============================================================================
-
-int RootBuilderNative::build() const
-{
- auto* game = m_organizer->managedGame();
- if (!game)
- return 0;
-
- QString gameDir = game->gameDirectory().absolutePath();
- QString storage = storageDir();
- auto* modList = m_organizer->modList();
- QString mode = loadSettings().value(QStringLiteral("mode"))
- .toString(QStringLiteral("copy"));
-
- // Clear any previous deployment first
- QJsonObject existingManifest = loadManifest();
- if (!existingManifest.isEmpty())
- const_cast<RootBuilderNative*>(this)->clear();
-
- QJsonArray deployed;
- QJsonObject backups;
- QSet<QString> deployedSet;
-
- QStringList mods = modList->allModsByProfilePriority();
- for (const auto& modName : mods) {
- if (!(modList->state(modName) & MOBase::IModList::STATE_ACTIVE))
- continue;
-
- auto* mod = modList->getMod(modName);
- if (!mod)
- continue;
- if (mod->isSeparator() || mod->isBackup() || mod->isForeign())
- continue;
-
- QString modPath = mod->absolutePath();
- QString rootDir = findRootDir(modPath);
- if (rootDir.isEmpty())
- continue;
-
- QDirIterator it(rootDir, QDir::Files, QDirIterator::Subdirectories);
- while (it.hasNext()) {
- it.next();
- QString srcFile = it.filePath();
- QString rel = QDir(rootDir).relativeFilePath(srcFile);
- QString dst = gameDir + QStringLiteral("/") + rel;
-
- // Backup existing file if not already deployed by us
- if (QFile::exists(dst) && !deployedSet.contains(dst)) {
- QString bak =
- backupDir() + QStringLiteral("/") + rel;
- QDir().mkpath(QFileInfo(bak).absolutePath());
- ensureWritable(dst);
- QFile::copy(dst, bak);
- backups[dst] = bak;
- }
-
- QDir().mkpath(QFileInfo(dst).absolutePath());
-
- // Remove existing file/symlink
- if (QFileInfo::exists(dst) || QFileInfo(dst).isSymLink())
- forceRemove(dst);
-
- // In link mode, .exe and .dll must be copied (Wine resolves
- // symlinked exe paths to the target, breaking sibling lookups)
- QString ext = QFileInfo(srcFile).suffix().toLower();
- if (mode == QStringLiteral("link") && ext != QStringLiteral("exe") &&
- ext != QStringLiteral("dll")) {
- QFile::link(srcFile, dst);
- } else {
- reflinkCopy(srcFile, dst);
- }
-
- if (!deployedSet.contains(dst)) {
- deployed.append(dst);
- deployedSet.insert(dst);
- }
- }
- }
-
- QJsonObject manifest;
- manifest[QStringLiteral("deployed")] = deployed;
- manifest[QStringLiteral("backups")] = backups;
- saveManifest(manifest);
-
- return deployed.count();
-}
-
-// ============================================================================
-// Clear
-// ============================================================================
-
-int RootBuilderNative::clear() const
-{
- auto* game = m_organizer->managedGame();
- if (!game)
- return 0;
-
- QString gameDir = game->gameDirectory().absolutePath();
- QString storage = storageDir();
-
- QJsonObject manifest = loadManifest();
- if (manifest.isEmpty())
- return 0;
-
- int count = 0;
- QJsonArray deployedArr = manifest.value(QStringLiteral("deployed")).toArray();
- QJsonObject backupsObj = manifest.value(QStringLiteral("backups")).toObject();
-
- QStringList failed;
- QStringList cleared;
-
- // Remove deployed files
- for (const auto& val : deployedArr) {
- QString path = val.toString();
- if (QFileInfo::exists(path) || QFileInfo(path).isSymLink()) {
- if (forceRemove(path)) {
- ++count;
- cleared.append(path);
- } else {
- failed.append(path);
- }
- } else {
- cleared.append(path);
- }
- }
-
- // Restore backups
- for (auto it = backupsObj.begin(); it != backupsObj.end(); ++it) {
- QString dst = it.key();
- QString bak = it.value().toString();
- if (QFile::exists(bak)) {
- QDir().mkpath(QFileInfo(dst).absolutePath());
- ensureWritable(QFileInfo(dst).absolutePath());
- if (QFileInfo::exists(dst) || QFileInfo(dst).isSymLink())
- forceRemove(dst);
- if (!QFile::rename(bak, dst)) {
- qWarning("Root Builder: could not restore backup %s -> %s",
- qUtf8Printable(bak), qUtf8Printable(dst));
- }
- }
- }
-
- // Clean up backup dir
- QString bDir = backupDir();
- if (QFileInfo(bDir).isDir())
- QDir(bDir).removeRecursively();
-
- if (!failed.isEmpty()) {
- // Update manifest to only contain files we couldn't remove
- QJsonArray failedArr;
- for (const auto& f : failed)
- failedArr.append(f);
-
- QJsonObject retryManifest;
- retryManifest[QStringLiteral("deployed")] = failedArr;
- retryManifest[QStringLiteral("backups")] = QJsonObject();
- saveManifest(retryManifest);
-
- qWarning("Root Builder: %d file(s) could not be removed. "
- "They will be retried on next clear.",
- static_cast<int>(failed.size()));
- } else {
- removeManifest();
- }
-
- cleanupEmptyDirs(gameDir, cleared);
- return count;
-}
diff --git a/libs/rootbuilder_native/src/rootbuilder.h b/libs/rootbuilder_native/src/rootbuilder.h
deleted file mode 100644
index 121bcc1..0000000
--- a/libs/rootbuilder_native/src/rootbuilder.h
+++ /dev/null
@@ -1,79 +0,0 @@
-#ifndef ROOTBUILDER_NATIVE_H
-#define ROOTBUILDER_NATIVE_H
-
-#include <uibase/iplugintool.h>
-
-#include <QJsonObject>
-#include <QString>
-
-class RootBuilderNative : public MOBase::IPluginTool
-{
- Q_OBJECT
- Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool)
- Q_PLUGIN_METADATA(IID "com.tannin.ModOrganizer.PluginTool/1.0")
-
-public:
- RootBuilderNative();
-
- // IPlugin
- bool init(MOBase::IOrganizer* organizer) override;
- QString name() const override;
- QString localizedName() const override;
- QString author() const override;
- QString description() const override;
- MOBase::VersionInfo version() const override;
- QList<MOBase::PluginSetting> settings() const override;
- bool enabledByDefault() const override;
-
- // IPluginTool
- QString displayName() const override;
- QString tooltip() const override;
- QIcon icon() const override;
-
-public slots:
- void display() const override;
-
-private:
- // Storage paths
- QString storageDir() const;
- QString backupDir() const;
- QString manifestPath() const;
- QString settingsPath() const;
-
- // Settings (own JSON, not pluginSetting)
- QJsonObject loadSettings() const;
- void saveSettings(const QJsonObject& settings) const;
- bool isAutoEnabled() const;
-
- // Manifest
- QJsonObject loadManifest() const;
- void saveManifest(const QJsonObject& manifest) const;
- void removeManifest() const;
-
- // Legacy migration
- void migrateLegacy() const;
-
- // Third-party conflict detection
- void checkThirdPartyRootBuilder() const;
-
- // Build / Clear
- int build() const;
- int clear() const;
-
- // File operations
- static QString findRootDir(const QString& modPath);
- static void reflinkCopy(const QString& src, const QString& dst);
- static void ensureReadable(const QString& path);
- static void ensureWritable(const QString& path);
- static bool forceRemove(const QString& path);
- static void cleanupEmptyDirs(const QString& baseDir,
- const QStringList& paths);
-
- // Hooks
- bool onAboutToRun(const QString& executable);
- void onFinishedRun(const QString& executable, unsigned int exitCode);
-
- MOBase::IOrganizer* m_organizer = nullptr;
-};
-
-#endif // ROOTBUILDER_NATIVE_H