From 42da88302e06669a520ed26f428fbec6b3ff6f70 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 27 Apr 2025 00:19:50 -0500 Subject: [PATCH 01/10] Allow for mod directory maps - Set main data files based on game - Mapped mod directories to VFS - Update overwrite setup --- src/directoryrefresher.cpp | 11 ++++++---- src/moapplication.cpp | 1 + src/organizercore.cpp | 42 ++++++++++++++++++++++++++------------ src/organizercore.h | 1 + 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index e2e05b2cc..e22e1da70 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -179,10 +179,13 @@ void DirectoryRefresher::setMods( m_Mods.clear(); for (auto mod = mods.begin(); mod != mods.end(); ++mod) { - QString name = std::get<0>(*mod); - ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); - m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), - info->archives(), std::get<2>(*mod))); + QString name = std::get<0>(*mod); + ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); + QString path = std::get<1>(*mod); + QString modDataDir = m_Core.managedGame()->modDataDirectory(); + path = modDataDir.isEmpty() ? path : path + "/" + modDataDir; + m_Mods.push_back( + EntryInfo(name, path, info->stealFiles(), info->archives(), std::get<2>(*mod))); } m_EnabledArchives = managedArchives; diff --git a/src/moapplication.cpp b/src/moapplication.cpp index cac60f480..4640540f8 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -287,6 +287,7 @@ int MOApplication::setup(MOMultiProcess& multiProcess, bool forceSelect) // setting up organizer core m_core->setManagedGame(m_instance->gamePlugin()); m_core->createDefaultProfile(); + m_core->createOverwriteDirectories(); log::info("using game plugin '{}' ('{}', variant {}, steam id '{}') at {}", m_instance->gamePlugin()->gameName(), diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 19439d76a..0e41b7008 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -462,6 +462,16 @@ void OrganizerCore::createDefaultProfile() } } +void OrganizerCore::createOverwriteDirectories() +{ + QString overwritePath = settings().paths().overwrite(); + for (auto modDirectory : managedGame()->getModMappings().keys()) { + if (!modDirectory.isEmpty()) { + QDir(overwritePath).mkdir(modDirectory); + } + } +} + void OrganizerCore::prepareVFS() { m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); @@ -2034,12 +2044,7 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, MappingType result; - QStringList dataPaths; - dataPaths.append(QDir::toNativeSeparators(game->dataDirectory().absolutePath())); - - for (auto directory : game->secondaryDataDirectories()) { - dataPaths.append(directory.absolutePath()); - } + auto dataMaps = game->getModMappings(); bool overwriteActive = false; @@ -2052,13 +2057,19 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, ModInfo::Ptr modPtr = ModInfo::getByIndex(modIndex); bool createTarget = customOverwrite == std::get<0>(mod); + QDir modDir = QDir(std::get<1>(mod)); overwriteActive |= createTarget; if (modPtr->isRegular()) { - for (auto dataPath : dataPaths) { - result.insert(result.end(), {QDir::toNativeSeparators(std::get<1>(mod)), - dataPath, true, createTarget}); + for (auto dataMap : dataMaps.asKeyValueRange()) { + auto mapDir = QDir(modDir.absoluteFilePath(dataMap.first)); + if (mapDir.exists()) { + for (auto dir : dataMap.second) { + result.insert(result.end(), + {mapDir.absolutePath(), dir, true, createTarget}); + } + } } } } @@ -2080,10 +2091,15 @@ std::vector OrganizerCore::fileMapping(const QString& profileName, } } - for (auto dataPath : dataPaths) { - result.insert(result.end(), - {QDir::toNativeSeparators(m_Settings.paths().overwrite()), dataPath, - true, customOverwrite.isEmpty()}); + QDir overwriteDir(m_Settings.paths().overwrite()); + for (auto dataMap : dataMaps.asKeyValueRange()) { + auto overwriteSubpath = overwriteDir.absoluteFilePath(dataMap.first); + if (QDir(overwriteSubpath).exists()) { + for (auto dir : dataMap.second) { + result.insert(result.end(), + {overwriteSubpath, dir, true, customOverwrite.isEmpty()}); + } + } } for (MOBase::IPluginFileMapper* mapper : diff --git a/src/organizercore.h b/src/organizercore.h index 923adc12d..3e0cdbba5 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -332,6 +332,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose bool checkPathSymlinks(); bool bootstrap(); void createDefaultProfile(); + void createOverwriteDirectories(); MOBase::DelayedFileWriter& pluginsWriter() { return m_PluginListsWriter; } From 77ac18c9a89c55c9395ed3b56db9607b07d83237 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 27 Apr 2025 02:45:43 -0500 Subject: [PATCH 02/10] Skip if mod contains no 'data' dir --- src/shared/directoryentry.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 01c325ef1..acbefd7f9 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include #include +#include namespace MOShared { @@ -630,19 +631,22 @@ void DirectoryEntry::addFiles(env::DirectoryWalker& walker, FilesOrigin& origin, Context cx = {origin, stats}; cx.current.push(this); - walker.forEachEntry( - path, &cx, - [](void* pcx, std::wstring_view path) { - onDirectoryStart((Context*)pcx, path); - }, + if (std::filesystem::exists(path)) { + walker.forEachEntry( + path, &cx, + [](void* pcx, std::wstring_view path) { + onDirectoryStart((Context*)pcx, path); + }, - [](void* pcx, std::wstring_view path) { - onDirectoryEnd((Context*)pcx, path); - }, + [](void* pcx, std::wstring_view path) { + onDirectoryEnd((Context*)pcx, path); + }, + + [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t) { + onFile((Context*)pcx, path, ft); + }); + } - [](void* pcx, std::wstring_view path, FILETIME ft, uint64_t) { - onFile((Context*)pcx, path, ft); - }); } void DirectoryEntry::onDirectoryStart(Context* cx, std::wstring_view path) From 89d52b86663d5f97d526454cb4334cf63146784e Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Mon, 28 Apr 2025 02:40:46 -0500 Subject: [PATCH 03/10] More mod directory compatibility fixes --- src/modinfodialog.cpp | 29 +++++++++++++++++------------ src/organizercore.cpp | 5 ++++- src/pluginlist.cpp | 16 ++++++++++++---- src/savestab.cpp | 1 + 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1798e9870..04ef003a8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -571,25 +571,30 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) void ModInfoDialog::feedFiles(std::vector& interestedTabs) { - const auto rootPath = m_mod->absolutePath(); + const auto rootPath = + m_mod->absolutePath() + (m_core.managedGame()->modDataDirectory().isEmpty() + ? "" + : "/" + m_core.managedGame()->modDataDirectory()); if (rootPath.isEmpty()) { return; } - const fs::path fsPath(rootPath.toStdWString()); + if (fs::exists(rootPath.toStdWString())) { + const fs::path fsPath(rootPath.toStdWString()); - for (const auto& entry : fs::recursive_directory_iterator(fsPath)) { - if (!entry.is_regular_file()) { - // skip directories - continue; - } + for (const auto& entry : fs::recursive_directory_iterator(fsPath)) { + if (!entry.is_regular_file()) { + // skip directories + continue; + } - const auto filePath = QString::fromStdWString(entry.path().native()); + const auto filePath = QString::fromStdWString(entry.path().native()); - // for each tab - for (auto* tabInfo : interestedTabs) { - if (tabInfo->tab->feedFile(rootPath, filePath)) { - break; + // for each tab + for (auto* tabInfo : interestedTabs) { + if (tabInfo->tab->feedFile(rootPath, filePath)) { + break; + } } } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0e41b7008..947cfef1c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1406,8 +1406,11 @@ void OrganizerCore::updateModsInDirectoryStructure( std::vector entries; for (auto idx : modInfo.keys()) { + QString path = modInfo[idx]->absolutePath(); + QString modDataDir = managedGame()->modDataDirectory(); + path = modDataDir.isEmpty() ? path : path + "/" + modDataDir; entries.push_back({modInfo[idx]->name(), - modInfo[idx]->absolutePath(), + path, modInfo[idx]->stealFiles(), {}, m_CurrentProfile->getModPriority(idx)}); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index e90e4b562..566ed294d 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -147,10 +147,18 @@ void PluginList::highlightPlugins(const std::vector& modIndices, for (auto& modIndex : modIndices) { ModInfo::Ptr selectedMod = ModInfo::getByIndex(modIndex); if (!selectedMod.isNull() && profile->modEnabled(modIndex)) { - QDir dir(selectedMod->absolutePath()); - QStringList plugins = dir.entryList(QStringList() << "*.esp" - << "*.esm" - << "*.esl"); + QString modDataPath = selectedMod->absolutePath(); + modDataPath = + m_Organizer.managedGame()->modDataDirectory().isEmpty() + ? modDataPath + : modDataPath + "/" + m_Organizer.managedGame()->modDataDirectory(); + QDir dir(modDataPath); + QStringList plugins; + if (dir.exists()) { + plugins = dir.entryList(QStringList() << "*.esp" + << "*.esm" + << "*.esl"); + } const MOShared::FilesOrigin& origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); if (plugins.size() > 0) { diff --git a/src/savestab.cpp b/src/savestab.cpp index 2cbd17de9..7f634ab22 100644 --- a/src/savestab.cpp +++ b/src/savestab.cpp @@ -3,6 +3,7 @@ #include "organizercore.h" #include "ui_mainwindow.h" #include +#include #include using namespace MOBase; From 90bd0a9a0dd0d76e23c6ba6cb450b4ba1e8b61db Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Mon, 28 Apr 2025 02:43:13 -0500 Subject: [PATCH 04/10] Workaround for Obl:Rem save location - SLocalSavePath does nothing yet MO2 wants to use it to override the default save location - This only applies to BGS games anyway, we should move this logic --- src/savestab.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/savestab.cpp b/src/savestab.cpp index 7f634ab22..c88de8fc8 100644 --- a/src/savestab.cpp +++ b/src/savestab.cpp @@ -129,13 +129,14 @@ void SavesTab::refreshSavesIfOpen() QDir SavesTab::currentSavesDir() const { + // TODO: This code should probably be handled by the game plugins QDir savesDir; if (m_core.currentProfile()->localSavesEnabled()) { savesDir.setPath(m_core.currentProfile()->savePath()); } else { auto iniFiles = m_core.managedGame()->iniFiles(); - if (iniFiles.isEmpty()) { + if (iniFiles.isEmpty() || m_core.gameFeatures().gameFeature() == nullptr) { return m_core.managedGame()->savesDirectory(); } From eb2b050b8acddcb8a2b14d3446eac319342a4067 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 3 May 2025 02:50:06 -0500 Subject: [PATCH 05/10] First pass for overwrite mod directory support --- src/modinfooverwrite.cpp | 23 +++++++++++++--- src/modlistviewactions.cpp | 53 ++++++++++++++++++++++++++++++++----- src/overwriteinfodialog.cpp | 45 ++++++++++++++++++++++++++++--- src/overwriteinfodialog.h | 3 ++- 4 files changed, 109 insertions(+), 15 deletions(-) diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 9d0ed5f9d..c3531da6d 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -5,6 +5,7 @@ #include #include +#include "organizercore.h" ModInfoOverwrite::ModInfoOverwrite(OrganizerCore& core) : ModInfoWithConflictInfo(core) {} @@ -14,10 +15,24 @@ bool ModInfoOverwrite::isEmpty() const QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) - return true; - return false; + while (iter.hasNext()) { + iter.next(); + if (iter.fileInfo().isDir() && + !m_Core.managedGame()->getModMappings().keys().contains(iter.fileName(), + Qt::CaseInsensitive)) + return false; + if (iter.fileInfo().isDir() && + m_Core.managedGame()->getModMappings().keys().contains(iter.fileName(), + Qt::CaseInsensitive)) { + if (QDir(iter.filePath()).count() > 2) { + return false; + } + } + if (iter.fileInfo().isFile() && iter.fileName() != "meta.ini") + return false; + } + + return true; } QString ModInfoOverwrite::absolutePath() const diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index a38e876b1..e89c23c3a 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -548,7 +548,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, QDialog* dialog = m_parent->findChild("__overwriteDialog"); try { if (dialog == nullptr) { - dialog = new OverwriteInfoDialog(modInfo, m_parent); + dialog = new OverwriteInfoDialog(modInfo, m_core, m_parent); dialog->setObjectName("__overwriteDialog"); } else { qobject_cast(dialog)->setModInfo(modInfo); @@ -1299,9 +1299,40 @@ void ModListViewActions::restoreBackup(const QModelIndex& index) const void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) const { ModInfo::Ptr overwriteInfo = ModInfo::getOverwrite(); - bool successful = - shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - (QDir::toNativeSeparators(absolutePath)), false, m_parent); + bool successful = false; + if (m_core.managedGame()->getModMappings().count() > 1 || + m_core.managedGame()->getModMappings().keys().first() != "") { + QDirIterator iter(overwriteInfo->absolutePath(), + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + while (iter.hasNext()) { + auto entry = iter.nextFileInfo(); + if (entry.isDir() && m_core.managedGame()->getModMappings().keys().contains( + entry.fileName(), Qt::CaseInsensitive)) { + successful = shellCopy((QDir::toNativeSeparators(entry.absolutePath())), + (QDir::toNativeSeparators(absolutePath)), false, m_parent); + QDirIterator subDirIter(entry.absoluteFilePath(), + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + while (subDirIter.hasNext()) { + auto subDirEntry = subDirIter.nextFileInfo(); + if (subDirEntry.isDir()) { + QDir(subDirEntry.absoluteFilePath()).removeRecursively(); + } else { + QFile(subDirEntry.absoluteFilePath()).remove(); + } + } + } else { + successful = shellMove((QDir::toNativeSeparators(iter.filePath())), + (QDir::toNativeSeparators(absolutePath)), false, m_parent); + } + if (!successful) + break; + } + + } else { + successful = + shellMove((QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), + (QDir::toNativeSeparators(absolutePath)), false, m_parent); + } if (successful) { MessageDialog::showMessage(tr("Move successful."), m_parent); @@ -1397,8 +1428,18 @@ void ModListViewActions::clearOverwrite() const QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { QStringList delList; for (auto f : - overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) - delList.push_back(overwriteDir.absoluteFilePath(f)); + overwriteDir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) { + if (f.isDir() && m_core.managedGame()->getModMappings().keys().contains( + f.fileName(), Qt::CaseInsensitive)) { + for (auto sf : + QDir(f.absoluteFilePath()) + .entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) { + delList.push_back(sf.absoluteFilePath()); + } + } else { + delList.push_back(f.absoluteFilePath()); + } + } if (shellDelete(delList, true)) { emit overwriteCleared(); m_core.refresh(); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 366671e7c..a9243dbec 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -29,9 +29,11 @@ along with Mod Organizer. If not, see . using namespace MOBase; -OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget* parent) - : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(nullptr), - m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr) +OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore &organizer, + QWidget* parent) + : QDialog(parent), m_Organizer(organizer), ui(new Ui::OverwriteInfoDialog), + m_FileSystemModel(nullptr), m_DeleteAction(nullptr), m_RenameAction(nullptr), + m_OpenAction(nullptr) { ui->setupUi(this); @@ -137,10 +139,17 @@ void OverwriteInfoDialog::delete_activated() QItemSelectionModel* selection = ui->filesView->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() >= 1) { + auto root = m_FileSystemModel->rootDirectory(); if (selection->selectedRows().count() == 0) { return; } else if (selection->selectedRows().count() == 1) { + for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { + if (root.absoluteFilePath(modDir).compare(m_FileSystemModel->filePath(selection->selectedRows().at(0)), Qt::CaseInsensitive) == 0) { + return; + } + } + QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); if (QMessageBox::question( this, tr("Confirm"), @@ -158,6 +167,13 @@ void OverwriteInfoDialog::delete_activated() } foreach (QModelIndex index, selection->selectedRows()) { + for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { + if (root.absoluteFilePath(modDir).compare( + m_FileSystemModel->filePath(index), + Qt::CaseInsensitive) == 0) { + return; + } + } deleteFile(index); } } @@ -166,9 +182,17 @@ void OverwriteInfoDialog::delete_activated() void OverwriteInfoDialog::deleteTriggered() { + auto root = m_FileSystemModel->rootDirectory(); if (m_FileSelection.count() == 0) { return; } else if (m_FileSelection.count() == 1) { + for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { + if (root.absoluteFilePath(modDir).compare( + m_FileSystemModel->filePath(m_FileSelection.at(0)), + Qt::CaseInsensitive) == 0) { + return; + } + } QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); if (QMessageBox::question( this, tr("Confirm"), @@ -185,18 +209,31 @@ void OverwriteInfoDialog::deleteTriggered() } foreach (QModelIndex index, m_FileSelection) { + for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { + if (root.absoluteFilePath(modDir).compare( + m_FileSystemModel->filePath(index), + Qt::CaseInsensitive) == 0) { + return; + } + } deleteFile(index); } } void OverwriteInfoDialog::renameTriggered() { + auto root = m_FileSystemModel->rootDirectory(); QModelIndex selection = m_FileSelection.at(0); QModelIndex index = selection.sibling(selection.row(), 0); if (!index.isValid() || m_FileSystemModel->isReadOnly()) { return; } - + for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { + if (root.absoluteFilePath(modDir).compare(m_FileSystemModel->filePath(selection), + Qt::CaseInsensitive) == 0) { + return; + } + } ui->filesView->edit(index); } diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index e9fa34397..984578d66 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -79,7 +79,7 @@ class OverwriteInfoDialog : public QDialog Q_OBJECT public: - explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget* parent = 0); + explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore &organizer, QWidget* parent = 0); ~OverwriteInfoDialog(); ModInfo::Ptr modInfo() const { return m_ModInfo; } @@ -122,6 +122,7 @@ private slots: QAction* m_NewFolderAction; ModInfo::Ptr m_ModInfo; + OrganizerCore &m_Organizer; }; #endif // OVERWRITEINFODIALOG_H From 0fc49ef533538ee477926160a1b28f320d0ba8ac Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sat, 3 May 2025 14:34:39 -0500 Subject: [PATCH 06/10] More overwrite move / delete restrictions --- src/modlist.cpp | 45 +++++++++++++++++++++++++++++++------ src/overwriteinfodialog.cpp | 3 +-- src/overwriteinfodialog.h | 25 +++++++++++++++++++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/modlist.cpp b/src/modlist.cpp index 8945dea11..65744d59f 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1028,15 +1028,46 @@ bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, QList> relativePathList; for (auto localUrl : dropInfo.localUrls()) { - QFileInfo sourceInfo(localUrl.url.toLocalFile()); - QString sourceFile = sourceInfo.canonicalFilePath(); + if (localUrl.originName.compare("overwrite", Qt::CaseInsensitive) == 0) { + if (sourceInfo.isDir()) { + for (auto dir : m_Organizer->managedGame()->getModMappings().keys()) { + QDir overDir(m_Organizer->overwritePath()); + if (sourceInfo.canonicalFilePath().compare(overDir.absoluteFilePath(dir), + Qt::CaseInsensitive) == 0) { + + QDirIterator dirIter(overDir.absoluteFilePath(dir), + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + while (dirIter.hasNext()) { + auto entry = dirIter.nextFileInfo(); + QString sourceFile = entry.canonicalFilePath(); + + QFileInfo targetInfo(modDir.absoluteFilePath(overDir.relativeFilePath(entry.absoluteFilePath()))); + sourceList << sourceFile; + targetList << targetInfo.absoluteFilePath(); + relativePathList << QPair(localUrl.relativePath, + localUrl.originName); + } + } + } + } else { + QString sourceFile = sourceInfo.canonicalFilePath(); - QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); - sourceList << sourceFile; - targetList << targetInfo.absoluteFilePath(); - relativePathList << QPair(localUrl.relativePath, - localUrl.originName); + QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); + sourceList << sourceFile; + targetList << targetInfo.absoluteFilePath(); + relativePathList << QPair(localUrl.relativePath, + localUrl.originName); + } + } else { + QString sourceFile = sourceInfo.canonicalFilePath(); + + QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); + sourceList << sourceFile; + targetList << targetInfo.absoluteFilePath(); + relativePathList << QPair(localUrl.relativePath, + localUrl.originName); + } } if (sourceList.count()) { diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index a9243dbec..307ef521b 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -18,7 +18,6 @@ along with Mod Organizer. If not, see . */ #include "overwriteinfodialog.h" -#include "organizercore.h" #include "report.h" #include "ui_overwriteinfodialog.h" #include "utility.h" @@ -39,7 +38,7 @@ OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore &or this->setWindowModality(Qt::NonModal); - m_FileSystemModel = new OverwriteFileSystemModel(this); + m_FileSystemModel = new OverwriteFileSystemModel(this, organizer); m_FileSystemModel->setReadOnly(false); setModInfo(modInfo); ui->filesView->setModel(m_FileSystemModel); diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 984578d66..06f1212fc 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -21,6 +21,8 @@ along with Mod Organizer. If not, see . #define OVERWRITEINFODIALOG_H #include "modinfo.h" +#include "modlistdropinfo.h" +#include "organizercore.h" #include #include @@ -34,8 +36,8 @@ class OverwriteFileSystemModel : public QFileSystemModel Q_OBJECT; public: - OverwriteFileSystemModel(QObject* parent) - : QFileSystemModel(parent), m_RegularColumnCount(0) + OverwriteFileSystemModel(QObject* parent, OrganizerCore &organizer) + : QFileSystemModel(parent), m_Organizer(organizer), m_RegularColumnCount(0) {} virtual int columnCount(const QModelIndex& parent) const @@ -70,8 +72,27 @@ class OverwriteFileSystemModel : public QFileSystemModel } } + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, + int column, const QModelIndex& parent) + { + ModListDropInfo dropInfo(data, m_Organizer); + if (dropInfo.isLocalFileDrop()) { + for (auto entry : dropInfo.localUrls()) { + QFileInfo sourceInfo(entry.url.toLocalFile()); + if (sourceInfo.isDir() && + m_Organizer.managedGame()->getModMappings().keys().contains( + entry.relativePath, Qt::CaseInsensitive)) { + return false; + } + } + } + return QFileSystemModel::dropMimeData(data, action, row, column, parent); + } + private: mutable int m_RegularColumnCount; + + OrganizerCore &m_Organizer; }; class OverwriteInfoDialog : public QDialog From 98f62a456ea4f3fd21d44a9810b3256008fb4fbf Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Wed, 7 May 2025 00:55:19 -0500 Subject: [PATCH 07/10] Fix issue with moving directories that are not required --- src/modlist.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modlist.cpp b/src/modlist.cpp index 65744d59f..510d56ec9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1030,11 +1030,13 @@ bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, for (auto localUrl : dropInfo.localUrls()) { QFileInfo sourceInfo(localUrl.url.toLocalFile()); if (localUrl.originName.compare("overwrite", Qt::CaseInsensitive) == 0) { + bool needsMove = true; if (sourceInfo.isDir()) { for (auto dir : m_Organizer->managedGame()->getModMappings().keys()) { QDir overDir(m_Organizer->overwritePath()); if (sourceInfo.canonicalFilePath().compare(overDir.absoluteFilePath(dir), Qt::CaseInsensitive) == 0) { + needsMove = false; QDirIterator dirIter(overDir.absoluteFilePath(dir), QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); @@ -1050,7 +1052,8 @@ bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, } } } - } else { + } + if (needsMove) { QString sourceFile = sourceInfo.canonicalFilePath(); QFileInfo targetInfo(modDir.absoluteFilePath(localUrl.relativePath)); From 3e5daf4f7b5368cf0ec7916d7cf2cbdf85c5708f Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Wed, 7 May 2025 01:04:40 -0500 Subject: [PATCH 08/10] Formatting pass --- src/modinfooverwrite.cpp | 4 ++-- src/modlist.cpp | 3 ++- src/modlistviewactions.cpp | 14 ++++++++------ src/overwriteinfodialog.cpp | 16 ++++++++-------- src/overwriteinfodialog.h | 11 ++++++----- src/pluginlist.cpp | 4 ++-- src/savestab.cpp | 5 +++-- src/shared/directoryentry.cpp | 3 +-- 8 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index c3531da6d..c65cbace4 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -3,9 +3,9 @@ #include "settings.h" #include "shared/appconfig.h" +#include "organizercore.h" #include #include -#include "organizercore.h" ModInfoOverwrite::ModInfoOverwrite(OrganizerCore& core) : ModInfoWithConflictInfo(core) {} @@ -31,7 +31,7 @@ bool ModInfoOverwrite::isEmpty() const if (iter.fileInfo().isFile() && iter.fileName() != "meta.ini") return false; } - + return true; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 510d56ec9..4ef3afbc9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1044,7 +1044,8 @@ bool ModList::dropLocalFiles(const ModListDropInfo& dropInfo, int row, auto entry = dirIter.nextFileInfo(); QString sourceFile = entry.canonicalFilePath(); - QFileInfo targetInfo(modDir.absoluteFilePath(overDir.relativeFilePath(entry.absoluteFilePath()))); + QFileInfo targetInfo(modDir.absoluteFilePath( + overDir.relativeFilePath(entry.absoluteFilePath()))); sourceList << sourceFile; targetList << targetInfo.absoluteFilePath(); relativePathList << QPair(localUrl.relativePath, diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index e89c23c3a..e4f898901 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -1308,8 +1308,9 @@ void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) co auto entry = iter.nextFileInfo(); if (entry.isDir() && m_core.managedGame()->getModMappings().keys().contains( entry.fileName(), Qt::CaseInsensitive)) { - successful = shellCopy((QDir::toNativeSeparators(entry.absolutePath())), - (QDir::toNativeSeparators(absolutePath)), false, m_parent); + successful = + shellCopy((QDir::toNativeSeparators(entry.absolutePath())), + (QDir::toNativeSeparators(absolutePath)), false, m_parent); QDirIterator subDirIter(entry.absoluteFilePath(), QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); while (subDirIter.hasNext()) { @@ -1321,8 +1322,9 @@ void ModListViewActions::moveOverwriteContentsTo(const QString& absolutePath) co } } } else { - successful = shellMove((QDir::toNativeSeparators(iter.filePath())), - (QDir::toNativeSeparators(absolutePath)), false, m_parent); + successful = + shellMove((QDir::toNativeSeparators(iter.filePath())), + (QDir::toNativeSeparators(absolutePath)), false, m_parent); } if (!successful) break; @@ -1427,8 +1429,8 @@ void ModListViewActions::clearOverwrite() const tr("About to recursively delete:\n") + overwriteDir.absolutePath(), QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { QStringList delList; - for (auto f : - overwriteDir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot)) { + for (auto f : overwriteDir.entryInfoList(QDir::AllDirs | QDir::Files | + QDir::NoDotAndDotDot)) { if (f.isDir() && m_core.managedGame()->getModMappings().keys().contains( f.fileName(), Qt::CaseInsensitive)) { for (auto sf : diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 307ef521b..11e6fc324 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -28,7 +28,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; -OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore &organizer, +OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore& organizer, QWidget* parent) : QDialog(parent), m_Organizer(organizer), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(nullptr), m_DeleteAction(nullptr), m_RenameAction(nullptr), @@ -144,7 +144,9 @@ void OverwriteInfoDialog::delete_activated() return; } else if (selection->selectedRows().count() == 1) { for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { - if (root.absoluteFilePath(modDir).compare(m_FileSystemModel->filePath(selection->selectedRows().at(0)), Qt::CaseInsensitive) == 0) { + if (root.absoluteFilePath(modDir).compare( + m_FileSystemModel->filePath(selection->selectedRows().at(0)), + Qt::CaseInsensitive) == 0) { return; } } @@ -167,9 +169,8 @@ void OverwriteInfoDialog::delete_activated() foreach (QModelIndex index, selection->selectedRows()) { for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { - if (root.absoluteFilePath(modDir).compare( - m_FileSystemModel->filePath(index), - Qt::CaseInsensitive) == 0) { + if (root.absoluteFilePath(modDir).compare(m_FileSystemModel->filePath(index), + Qt::CaseInsensitive) == 0) { return; } } @@ -209,9 +210,8 @@ void OverwriteInfoDialog::deleteTriggered() foreach (QModelIndex index, m_FileSelection) { for (auto modDir : m_Organizer.managedGame()->getModMappings().keys()) { - if (root.absoluteFilePath(modDir).compare( - m_FileSystemModel->filePath(index), - Qt::CaseInsensitive) == 0) { + if (root.absoluteFilePath(modDir).compare(m_FileSystemModel->filePath(index), + Qt::CaseInsensitive) == 0) { return; } } diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 06f1212fc..59f6f7522 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -36,7 +36,7 @@ class OverwriteFileSystemModel : public QFileSystemModel Q_OBJECT; public: - OverwriteFileSystemModel(QObject* parent, OrganizerCore &organizer) + OverwriteFileSystemModel(QObject* parent, OrganizerCore& organizer) : QFileSystemModel(parent), m_Organizer(organizer), m_RegularColumnCount(0) {} @@ -73,7 +73,7 @@ class OverwriteFileSystemModel : public QFileSystemModel } virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, - int column, const QModelIndex& parent) + int column, const QModelIndex& parent) { ModListDropInfo dropInfo(data, m_Organizer); if (dropInfo.isLocalFileDrop()) { @@ -92,7 +92,7 @@ class OverwriteFileSystemModel : public QFileSystemModel private: mutable int m_RegularColumnCount; - OrganizerCore &m_Organizer; + OrganizerCore& m_Organizer; }; class OverwriteInfoDialog : public QDialog @@ -100,7 +100,8 @@ class OverwriteInfoDialog : public QDialog Q_OBJECT public: - explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore &organizer, QWidget* parent = 0); + explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, OrganizerCore& organizer, + QWidget* parent = 0); ~OverwriteInfoDialog(); ModInfo::Ptr modInfo() const { return m_ModInfo; } @@ -143,7 +144,7 @@ private slots: QAction* m_NewFolderAction; ModInfo::Ptr m_ModInfo; - OrganizerCore &m_Organizer; + OrganizerCore& m_Organizer; }; #endif // OVERWRITEINFODIALOG_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 566ed294d..c114465b0 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -156,8 +156,8 @@ void PluginList::highlightPlugins(const std::vector& modIndices, QStringList plugins; if (dir.exists()) { plugins = dir.entryList(QStringList() << "*.esp" - << "*.esm" - << "*.esl"); + << "*.esm" + << "*.esl"); } const MOShared::FilesOrigin& origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); diff --git a/src/savestab.cpp b/src/savestab.cpp index c88de8fc8..f59245cb0 100644 --- a/src/savestab.cpp +++ b/src/savestab.cpp @@ -3,8 +3,8 @@ #include "organizercore.h" #include "ui_mainwindow.h" #include -#include #include +#include using namespace MOBase; @@ -136,7 +136,8 @@ QDir SavesTab::currentSavesDir() const } else { auto iniFiles = m_core.managedGame()->iniFiles(); - if (iniFiles.isEmpty() || m_core.gameFeatures().gameFeature() == nullptr) { + if (iniFiles.isEmpty() || + m_core.gameFeatures().gameFeature() == nullptr) { return m_core.managedGame()->savesDirectory(); } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index acbefd7f9..6351083bc 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -24,9 +24,9 @@ along with Mod Organizer. If not, see . #include "originconnection.h" #include "util.h" #include "windows_error.h" +#include #include #include -#include namespace MOShared { @@ -646,7 +646,6 @@ void DirectoryEntry::addFiles(env::DirectoryWalker& walker, FilesOrigin& origin, onFile((Context*)pcx, path, ft); }); } - } void DirectoryEntry::onDirectoryStart(Context* cx, std::wstring_view path) From 25d6a6e41954bdee335916f8935ab1106986e382 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Tue, 13 May 2025 01:13:55 -0500 Subject: [PATCH 09/10] More modDataDirectory updates --- src/mainwindow.cpp | 5 ++++- src/modlistviewactions.cpp | 6 ++++-- src/organizercore.cpp | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bdc40f684..55a612128 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2910,8 +2910,11 @@ void MainWindow::originModified(int originID) origin.enable(false); DirectoryStats dummy; + QString path = QString::fromStdWString(origin.getPath()); + QString modDataDir = m_OrganizerCore.managedGame()->modDataDirectory(); + path = modDataDir.isEmpty() ? path : path + "/" + modDataDir; m_OrganizerCore.directoryStructure()->addFromOrigin( - origin.getName(), origin.getPath(), origin.getPriority(), dummy); + origin.getName(), path.toStdWString(), origin.getPriority(), dummy); DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index e4f898901..efe48c313 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -599,10 +599,12 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, FilesOrigin& origin = m_core.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); - + QString path = modInfo->absolutePath(); + QString modDataDir = m_core.managedGame()->modDataDirectory(); + path = modDataDir.isEmpty() ? path : path + "/" + modDataDir; m_core.directoryRefresher()->addModToStructure( m_core.directoryStructure(), modInfo->name(), - m_core.currentProfile()->getModPriority(modIndex), modInfo->absolutePath(), + m_core.currentProfile()->getModPriority(modIndex), path, modInfo->stealFiles(), modInfo->archives()); DirectoryRefresher::cleanStructure(m_core.directoryStructure()); m_core.directoryStructure()->getFileRegister()->sortOrigins(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 947cfef1c..169d7506f 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1439,9 +1439,12 @@ void OrganizerCore::updateModsInDirectoryStructure( // finally also add files from bsas to the directory structure for (auto idx : modInfo.keys()) { + QString path = modInfo[idx]->absolutePath(); + QString modDataDir = managedGame()->modDataDirectory(); + path = modDataDir.isEmpty() ? path : path + "/" + modDataDir; m_DirectoryRefresher->addModBSAToStructure( m_DirectoryStructure, modInfo[idx]->name(), - m_CurrentProfile->getModPriority(idx), modInfo[idx]->absolutePath(), + m_CurrentProfile->getModPriority(idx), path, modInfo[idx]->archives()); } } From bf8bb4d2c07ea9ac5e2a162f208cf2c8c2430511 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Thu, 22 May 2025 14:44:26 -0500 Subject: [PATCH 10/10] Formatting --- src/organizercore.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 169d7506f..4eac3d66d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1444,8 +1444,7 @@ void OrganizerCore::updateModsInDirectoryStructure( path = modDataDir.isEmpty() ? path : path + "/" + modDataDir; m_DirectoryRefresher->addModBSAToStructure( m_DirectoryStructure, modInfo[idx]->name(), - m_CurrentProfile->getModPriority(idx), path, - modInfo[idx]->archives()); + m_CurrentProfile->getModPriority(idx), path, modInfo[idx]->archives()); } }