diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/categories.h | 2 | ||||
| -rw-r--r-- | src/downloadmanager.cpp | 10 | ||||
| -rw-r--r-- | src/envsecurity.cpp | 7 | ||||
| -rw-r--r-- | src/filerenamer.cpp | 42 | ||||
| -rw-r--r-- | src/filerenamer.h | 8 | ||||
| -rw-r--r-- | src/installationmanager.cpp | 8 | ||||
| -rw-r--r-- | src/main.cpp | 46 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 38 | ||||
| -rw-r--r-- | src/modinfodialogconflicts.cpp | 2 | ||||
| -rw-r--r-- | src/modinfodialogfiletree.cpp | 8 | ||||
| -rw-r--r-- | src/modinfodialogimages.cpp | 2 | ||||
| -rw-r--r-- | src/modinfodialognexus.cpp | 6 | ||||
| -rw-r--r-- | src/modlist.cpp | 2 | ||||
| -rw-r--r-- | src/modlistsortproxy.cpp | 18 | ||||
| -rw-r--r-- | src/motddialog.cpp | 2 | ||||
| -rw-r--r-- | src/nxmaccessmanager.cpp | 2 | ||||
| -rw-r--r-- | src/overwriteinfodialog.cpp | 4 | ||||
| -rw-r--r-- | src/pluginlist.cpp | 7 | ||||
| -rw-r--r-- | src/problemsdialog.cpp | 2 | ||||
| -rw-r--r-- | src/profile.cpp | 5 | ||||
| -rw-r--r-- | src/profile.h | 7 | ||||
| -rw-r--r-- | src/sanitychecks.cpp | 256 | ||||
| -rw-r--r-- | src/selfupdater.cpp | 7 | ||||
| -rw-r--r-- | src/settings.cpp | 6 | ||||
| -rw-r--r-- | src/settingsdialognexus.cpp | 2 | ||||
| -rw-r--r-- | src/settingsutilities.h | 4 | ||||
| -rw-r--r-- | src/texteditor.cpp | 2 |
28 files changed, 391 insertions, 116 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b21d1a8b..180422ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ SET(organizer_SRCS envshortcut.cpp envwindows.cpp colortable.cpp + sanitychecks.cpp shared/windows_error.cpp shared/error_report.cpp @@ -324,6 +325,7 @@ set(application mainwindow moapplication moshortcut + sanitychecks selfupdater singleinstance statusbar diff --git a/src/categories.h b/src/categories.h index 48e0b44b..67fee3e7 100644 --- a/src/categories.h +++ b/src/categories.h @@ -50,6 +50,8 @@ public: static const int CATEGORY_SPECIAL_BACKUP = 10006;
static const int CATEGORY_SPECIAL_MANAGED = 10007;
static const int CATEGORY_SPECIAL_UNMANAGED = 10008;
+ static const int CATEGORY_SPECIAL_NOGAMEDATA = 10009;
+ static const int CATEGORY_SPECIAL_NONEXUSID = 10010;
public:
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 35f60d7a..b4a7b57d 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1059,11 +1059,11 @@ void DownloadManager::openFile(int index) QDir path = QDir(m_OutputDirectory); if (path.exists(getFileName(index))) { - shell::OpenFile(getFilePath(index)); + shell::Open(getFilePath(index)); return; } - shell::ExploreFile(m_OutputDirectory); + shell::Explore(m_OutputDirectory); return; } @@ -1077,18 +1077,18 @@ void DownloadManager::openInDownloadsFolder(int index) const auto path = getFilePath(index); if (QFile::exists(path)) { - shell::ExploreFile(path); + shell::Explore(path); return; } else { const auto unfinished = path + ".unfinished"; if (QFile::exists(unfinished)) { - shell::ExploreFile(unfinished); + shell::Explore(unfinished); return; } } - shell::ExploreFile(m_OutputDirectory); + shell::Explore(m_OutputDirectory); } diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index ffb17c42..786291c6 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -206,9 +206,10 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - if (m_guid.isNull()) { - s += ", (no guid)"; - } else { + // all products have a guid, but the windows firewall is not actually a real + // one from wmi, it's queried independently in getWindowsFirewall() and has a + // null guid, so just don't log it + if (!m_guid.isNull()) { s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); } diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index a97d7742..7fc90eb2 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,4 +1,5 @@ #include "filerenamer.h" +#include <utility.h> #include <log.h> #include <QMessageBox> #include <QFileInfo> @@ -37,10 +38,13 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt log::debug("removing {}", newName); // user wants to replace the file, so remove it - if (!QFile(newName).remove()) { - log::warn("failed to remove '{}'", newName); + const auto r = shell::Delete(newName); + + if (!r.success()) { + log::error("failed to remove '{}': {}", newName, r.toString()); + // removal failed, warn the user and allow canceling - if (!removeFailed(newName)) { + if (!removeFailed(newName, r)) { log::debug("canceling {}", oldName); // user wants to cancel return RESULT_CANCEL; @@ -64,12 +68,15 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt } // target either didn't exist or was removed correctly + const auto r = shell::Rename(oldName, newName); - if (!QFile::rename(oldName, newName)) { - log::warn("failed to rename '{}' to '{}'", oldName, newName); + if (!r.success()) { + log::error( + "failed to rename '{}' to '{}': {}", + oldName, newName, r.toString()); // renaming failed, warn the user and allow canceling - if (!renameFailed(oldName, newName)) { + if (!renameFailed(oldName, newName, r)) { // user wants to cancel log::debug("canceling"); return RESULT_CANCEL; @@ -144,7 +151,7 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) } } -bool FileRenamer::removeFailed(const QString& name) +bool FileRenamer::removeFailed(const QString& name, const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -153,8 +160,9 @@ bool FileRenamer::removeFailed(const QString& name) } const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), + m_parent, + QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\": %2").arg(name).arg(r.toString()), buttons); if (answer == QMessageBox::Cancel) { @@ -168,7 +176,8 @@ bool FileRenamer::removeFailed(const QString& name) return true; } -bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) +bool FileRenamer::renameFailed( + const QString& oldName, const QString& newName, const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -177,9 +186,16 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) } const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), - buttons); + m_parent, + QObject::tr("File operation failed"), + QObject::tr( + "Failed to rename file: %1.\r\n\r\n" + "Source:\r\n\"%2\"\r\n\r\n" + "Destination:\r\n\"%3\"") + .arg(r.toString()) + .arg(QDir::toNativeSeparators(oldName)) + .arg(QDir::toNativeSeparators(newName)), + buttons); if (answer == QMessageBox::Cancel) { // user wants to stop diff --git a/src/filerenamer.h b/src/filerenamer.h index cd57244c..5583ecbd 100644 --- a/src/filerenamer.h +++ b/src/filerenamer.h @@ -3,6 +3,8 @@ #include <QWidget> +namespace MOBase::shell { class Result; } + /** * Renames individual files and handles dialog boxes to confirm replacements and * failures with the user @@ -126,7 +128,7 @@ private: * @param name The name of the file that failed to be removed * @return true to continue, false to stop **/ - bool removeFailed(const QString& name); + bool removeFailed(const QString& name, const MOBase::shell::Result& r); /** * renaming a file failed, ask the user to continue or cancel @@ -134,7 +136,9 @@ private: * @param newName new filename * @return true to continue, false to stop **/ - bool renameFailed(const QString& oldName, const QString& newName); + bool renameFailed( + const QString& oldName, const QString& newName, + const MOBase::shell::Result& r); }; #endif // FILERENAMER_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 522489e4..dd5cfb55 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -307,7 +307,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool QCoreApplication::processEvents(); } while (!future.isFinished() || m_InstallationProgress->isVisible()); if (!future.result()) { - throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); } return result; @@ -491,7 +491,7 @@ bool InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *me if (overwriteDialog.backup()) { QString backupDirectory = generateBackupName(targetDirectory); if (!copyDir(targetDirectory, backupDirectory, false)) { - reportError(tr("failed to create backup")); + reportError(tr("Failed to create backup")); return false; } } @@ -616,12 +616,12 @@ bool InstallationManager::doInstall(GuessedValue<QString> &modName, QString game if (!future.result()) { if (m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { if (!m_ErrorMessage.isEmpty()) { - throw MyException(QString("extracting failed (%1)").arg(m_ErrorMessage)); + throw MyException(tr("Extraction failed: %1").arg(m_ErrorMessage)); } else { return false; } } else { - throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); } } diff --git a/src/main.cpp b/src/main.cpp index ba988ae3..fe5fd87a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -91,6 +91,9 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using namespace MOBase; using namespace MOShared; + +void sanityChecks(const env::Environment& env); + bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); @@ -496,49 +499,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void checkMissingFiles() -{ - // files that are likely to be eaten - static const QStringList files({ - "helper.exe", "nxmhandler.exe", - "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", - "usvfs_x64.dll", "usvfs_x86.dll" - }); - - const auto dir = QCoreApplication::applicationDirPath(); - - for (const auto& name : files) { - const QFileInfo file(dir + QDir::separator() + name); - if (!file.exists()) { - log::warn( - "'{}' seems to be missing, an antivirus may have deleted it", - file.absoluteFilePath()); - } - } -} - -void checkNahimic(const env::Environment& e) -{ - for (auto&& m : e.loadedModules()) { - const QFileInfo file(m.path()); - - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { - log::warn( - "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " - "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it."); - - break; - } - } -} - -void sanityChecks(const env::Environment& e) -{ - checkMissingFiles(); - checkNahimic(e); -} - int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0a82fcaf..3b977dd6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2691,6 +2691,8 @@ void MainWindow::refreshFilters() addFilterItem(nullptr, tr("<No category>"), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); addFilterItem(nullptr, tr("<Conflicted>"), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); addFilterItem(nullptr, tr("<Not Endorsed>"), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr("<No Nexus ID>"), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr("<No valid game data>"), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); addContentFilters(); std::set<int> categoriesUsed; @@ -3313,12 +3315,12 @@ void MainWindow::openExplorer_clicked() if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - shell::ExploreFile(info->absolutePath()); + shell::Explore(info->absolutePath()); } } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3333,14 +3335,14 @@ void MainWindow::openPluginOriginExplorer_clicked() continue; } ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } else { QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3355,7 +3357,7 @@ void MainWindow::openExplorer_activated() std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3376,7 +3378,7 @@ void MainWindow::openExplorer_activated() std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } } @@ -4344,61 +4346,61 @@ void MainWindow::disableVisibleMods() void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); - shell::ExploreFile(dataPath); + shell::Explore(dataPath); } void MainWindow::openLogsFolder() { QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - shell::ExploreFile(logsPath); + shell::Explore(logsPath); } void MainWindow::openInstallFolder() { - shell::ExploreFile(qApp->applicationDirPath()); + shell::Explore(qApp->applicationDirPath()); } void MainWindow::openPluginsFolder() { QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - shell::ExploreFile(pluginsPath); + shell::Explore(pluginsPath); } void MainWindow::openProfileFolder() { - shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); } void MainWindow::openIniFolder() { if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) { - shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); } else { - shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } } void MainWindow::openDownloadsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().paths().downloads()); + shell::Explore(m_OrganizerCore.settings().paths().downloads()); } void MainWindow::openModsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().paths().mods()); + shell::Explore(m_OrganizerCore.settings().paths().mods()); } void MainWindow::openGameFolder() { - shell::ExploreFile(m_OrganizerCore.managedGame()->gameDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->gameDirectory()); } void MainWindow::openMyGamesFolder() { - shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } @@ -5391,7 +5393,7 @@ void MainWindow::openDataOriginExplorer_clicked() const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); log::debug("opening in explorer: {}", fullPath); - shell::ExploreFile(fullPath); + shell::Explore(fullPath); } void MainWindow::updateAvailable() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 3a71b405..36559a75 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -547,7 +547,7 @@ void ConflictsTab::exploreItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - shell::ExploreFile(item->fileName()); + shell::Explore(item->fileName()); return true; }); } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 207c792d..71ea9210 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -128,7 +128,7 @@ void FileTreeTab::onOpen() return; } - shell::OpenFile(m_fs->filePath(selection)); + shell::Open(m_fs->filePath(selection)); } void FileTreeTab::onPreview() @@ -146,9 +146,9 @@ void FileTreeTab::onExplore() auto selection = singleSelection(); if (selection.isValid()) { - shell::ExploreFile(m_fs->filePath(selection)); + shell::Explore(m_fs->filePath(selection)); } else { - shell::ExploreFile(mod().absolutePath()); + shell::Explore(mod().absolutePath()); } } @@ -204,7 +204,7 @@ void FileTreeTab::onUnhide() void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(mod().absolutePath()); + shell::Explore(mod().absolutePath()); } bool FileTreeTab::deleteFile(const QModelIndex& index) diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9d347f57..c5b04538 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -547,7 +547,7 @@ void ImagesTab::showTooltip(QHelpEvent* e) void ImagesTab::onExplore() { if (auto* f=m_files.selectedFile()) { - MOBase::shell::ExploreFile(f->path()); + shell::Explore(f->path()); } } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 95e62328..59bfe930 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -95,7 +95,7 @@ void NexusTab::update() connect( page, &NexusTabWebpage::linkClicked, - [&](const QUrl& url){ shell::OpenLink(url); }); + [&](const QUrl& url){ shell::Open(url); }); ui->endorse->setEnabled( (mod().endorsedState() == ModInfo::ENDORSED_FALSE) || @@ -363,7 +363,7 @@ void NexusTab::onVisitNexus() const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod().getGameName()); - shell::OpenLink(QUrl(nexusLink)); + shell::Open(QUrl(nexusLink)); } } @@ -412,6 +412,6 @@ void NexusTab::onVisitCustomURL() { const auto url = mod().parseCustomURL(); if (url.isValid()) { - shell::OpenLink(url); + shell::Open(url); } } diff --git a/src/modlist.cpp b/src/modlist.cpp index 6018d3d4..c5bc37e9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1272,7 +1272,7 @@ QString ModList::getColumnToolTip(int column) case COL_CATEGORY: return tr("Category of the mod."); case COL_GAME: return tr("The source game which was the origin of this mod."); case COL_MODID: return tr("Id of the mod as used on Nexus."); - case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); case COL_CONTENT: return tr("Depicts the content of the mod:<br>" "<table cellspacing=7>" "<tr><td><img src=\":/MO/gui/content/plugin\" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr>" diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 77ffad96..a9ff6463 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -308,6 +308,15 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: {
if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false;
} break;
+ case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: {
+ if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false;
+ } break;
+ case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: {
+ if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) &&
+ !info->hasFlag(ModInfo::FLAG_BACKUP) &&
+ !info->hasFlag(ModInfo::FLAG_SEPARATOR) &&
+ !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false;
+ } break;
default: {
if (!info->categorySet(*iter)) return false;
} break;
@@ -353,6 +362,15 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: {
if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true;
} break;
+ case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: {
+ if (info->hasFlag(ModInfo::FLAG_INVALID)) return true;
+ } break;
+ case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: {
+ if ((info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) &&
+ !info->hasFlag(ModInfo::FLAG_BACKUP) &&
+ !info->hasFlag(ModInfo::FLAG_SEPARATOR) &&
+ !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return true;
+ } break;
default: {
if (info->categorySet(*iter)) return true;
} break;
diff --git a/src/motddialog.cpp b/src/motddialog.cpp index ca1e60ad..eee80205 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -47,5 +47,5 @@ void MotDDialog::on_okButton_clicked() void MotDDialog::linkClicked(const QUrl &url)
{
- shell::OpenLink(url);
+ shell::Open(url);
}
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c6ef7bc7..3cc1b7d9 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -286,7 +286,7 @@ void NexusSSOLogin::onMessage(const QString& s) // open browser const auto url = NexusSSOPage.arg(m_guid); - shell::OpenLink(url); + shell::Open(url); m_timeout.stop(); setState(WaitingForBrowser); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index fe1d8825..078bcfc9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -229,7 +229,7 @@ void OverwriteInfoDialog::renameTriggered() void OverwriteInfoDialog::openFile(const QModelIndex &index)
{
- shell::OpenFile(m_FileSystemModel->filePath(index));
+ shell::Open(m_FileSystemModel->filePath(index));
}
@@ -270,7 +270,7 @@ void OverwriteInfoDialog::createDirectoryTriggered() void OverwriteInfoDialog::on_explorerButton_clicked()
{
- shell::ExploreFile(m_ModInfo->absolutePath());
+ shell::Explore(m_ModInfo->absolutePath());
}
void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos)
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index c6c61da3..5f1ae347 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -113,10 +113,11 @@ QString PluginList::getColumnName(int column) QString PluginList::getColumnToolTip(int column)
{
switch (column) {
- case COL_NAME: return tr("Name of your mods");
- case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus "
+ case COL_NAME: return tr("Name of the plugin");
+ case COL_FLAGS: return tr("Emblems to highlight things that might require attention.");
+ case COL_PRIORITY: return tr("Load priority of plugins. The higher, the more \"important\" it is and thus "
"overwrites data from plugins with lower priority.");
- case COL_MODINDEX: return tr("The modindex determines the formids of objects originating from this mods.");
+ case COL_MODINDEX: return tr("Determines the formids of objects originating from this mods.");
default: return tr("unknown");
}
}
diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 63d58295..ea23beec 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -112,5 +112,5 @@ void ProblemsDialog::startFix() void ProblemsDialog::urlClicked(const QUrl &url)
{
- shell::OpenLink(url);
+ shell::Open(url);
}
diff --git a/src/profile.cpp b/src/profile.cpp index e76060b9..f2360674 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -917,6 +917,11 @@ QVariant Profile::setting(const QString §ion, const QString &name, return m_Settings->value(section + "/" + name, fallback); } +QVariant Profile::setting(const QString &name, const QVariant &fallback) const +{ + return m_Settings->value(name, fallback); +} + void Profile::storeSetting(const QString §ion, const QString &name, const QVariant &value) { diff --git a/src/profile.h b/src/profile.h index bc7964f8..85d929ac 100644 --- a/src/profile.h +++ b/src/profile.h @@ -313,8 +313,11 @@ public: void dumpModStatus() const; - QVariant setting(const QString §ion, const QString &name = QString(), - const QVariant &fallback = QVariant()) const; + QVariant setting( + const QString §ion, const QString &name, + const QVariant &fallback) const; + + QVariant setting(const QString &name, const QVariant &fallback={}) const; void storeSetting(const QString §ion, const QString &name, const QVariant &value); diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp new file mode 100644 index 00000000..3b4185a7 --- /dev/null +++ b/src/sanitychecks.cpp @@ -0,0 +1,256 @@ +#include "env.h" +#include "envmodule.h" +#include <log.h> + +using namespace MOBase; + +enum class SecurityZone +{ + NoZone = -1, + MyComputer = 0, + Intranet = 1, + Trusted = 2, + Internet = 3, + Untrusted = 4, +}; + +QString toCodeName(SecurityZone z) +{ + switch (z) + { + case SecurityZone::NoZone: return "NoZone"; + case SecurityZone::MyComputer: return "MyComputer"; + case SecurityZone::Intranet: return "Intranet"; + case SecurityZone::Trusted: return "Trusted"; + case SecurityZone::Internet: return "Internet"; + case SecurityZone::Untrusted: return "Untrusted"; + default: return "Unknown zone"; + } +} + +QString toString(SecurityZone z) +{ + return QString("%1 (%2)") + .arg(toCodeName(z)) + .arg(static_cast<int>(z)); +} + +// whether the given zone is considered blocked +// +bool isZoneBlocked(SecurityZone z) +{ + switch (z) + { + case SecurityZone::Internet: + case SecurityZone::Untrusted: + return true; + + case SecurityZone::NoZone: + case SecurityZone::MyComputer: + case SecurityZone::Intranet: + case SecurityZone::Trusted: + default: + return false; + } +} + +// whether the given file is blocked +// +bool isFileBlocked(const QFileInfo& fi) +{ + // name of the alternate data stream containing the zone identifier ini + const QString ads = "Zone.Identifier"; + + // key in the ini + const auto key = "ZoneTransfer/ZoneId"; + + // the path to the ADS is always `filename:Zone.Identifier` + const auto path = fi.absoluteFilePath(); + const auto adsPath = path + ":" + ads; + + QFile f(adsPath); + if (!f.exists()) { + // no ADS for this file + return false; + } + + log::debug("'{}' has an ADS for {}", path, adsPath); + + const QSettings qs(adsPath, QSettings::IniFormat); + + // looking for key + if (!qs.contains(key)) { + log::debug("'{}': key '{}' not found", adsPath, key); + return false; + } + + // getting value + const auto v = qs.value(key); + if (v.isNull()) { + log::debug("'{}': key '{}' is null", adsPath, key); + return false; + } + + // should be an int + bool ok = false; + const auto z = static_cast<SecurityZone>(v.toInt(&ok)); + + if (!ok) { + log::debug("'{}': key '{}' is not an int (value is '{}')", adsPath, key, v); + return false; + } + + if (!isZoneBlocked(z)) { + // that zone is not a blocked zone + log::debug("'{}': zone id is {}, which is fine", adsPath, toString(z)); + return false; + } + + // file is blocked + log::warn("'{}': file is blocked, zone id is {}", path, toString(z)); + return true; +} + +int checkBlockedFiles(const QDir& dir) +{ + // executables file types + const QStringList FileTypes = {"*.dll", "*.exe"}; + + if (!dir.exists()) { + // shouldn't happen + log::error( + "while checking for blocked files, directory '{}' not found", + dir.absolutePath()); + + return 1; + } + + const auto files = dir.entryInfoList(FileTypes, QDir::Files); + if (files.empty()) { + // shouldn't happen + log::error( + "while checking for blocked files, directory '{}' is empty", + dir.absolutePath()); + + return 1; + } + + int n = 0; + + // checking each file in this directory + for (auto&& fi : files) { + if (isFileBlocked(fi)) { + ++n; + } + } + + return n; +} + +int checkBlocked() +{ + // directories that contain executables; these need to be explicit because + // portable instances might add billions of files in MO's directory + const QString dirs[] = { + ".", + "/dlls", + "/loot", + "/NCC", + "/platforms", + "/plugins" + }; + + log::debug(" . blocked files"); + const QString appDir = QCoreApplication::applicationDirPath(); + + int n = 0; + + for (const auto& d : dirs) { + const auto path = QDir(appDir + "/" + d).canonicalPath(); + n += checkBlockedFiles(path); + } + + return n; +} + +int checkMissingFiles() +{ + // files that are likely to be eaten + static const QStringList files({ + "helper.exe", "nxmhandler.exe", + "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll" + }); + + log::debug(" . missing files"); + const auto dir = QCoreApplication::applicationDirPath(); + + int n = 0; + + for (const auto& name : files) { + const QFileInfo file(dir + "/" + name); + + if (!file.exists()) { + log::warn( + "'{}' seems to be missing, an antivirus may have deleted it", + file.absoluteFilePath()); + + ++n; + } + } + + return n; +} + +bool checkNahimic(const env::Environment& e) +{ + // Nahimic seems to interfere mostly with dialogs, like the mod info dialog: + // it renders dialogs fully white and makes it impossible to interact with + // them + // + // NahimicOSD.dll is usually loaded on startup, but there has been some + // reports where it got loaded later, so this check is not entirely accurate + + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); + + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + log::warn( + "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it."); + + return true; + } + } + + return false; +} + +int checkIncompatibilities(const env::Environment& e) +{ + log::debug(" . incompatibilities"); + + int n = 0; + + if (checkNahimic(e)) { + ++n; + } + + return n; +} + +void sanityChecks(const env::Environment& e) +{ + log::debug("running sanity checks..."); + + int n = 0; + + n += checkBlocked(); + n += checkMissingFiles(); + n += checkIncompatibilities(e); + + log::debug( + "sanity checks done, {}", + (n > 0 ? "problems were found" : "everything looks okay")); +} diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 8887927a..5a70568e 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -341,11 +341,14 @@ void SelfUpdater::downloadCancel() void SelfUpdater::installUpdate()
{
const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" ";
+ const auto r = shell::Execute(m_UpdateFile.fileName(), parameters);
- if (shell::Execute(m_UpdateFile.fileName(), parameters)) {
+ if (r.success()) {
QCoreApplication::quit();
} else {
- reportError(tr("Failed to start %1").arg(m_UpdateFile.fileName()));
+ reportError(tr("Failed to start %1: %2")
+ .arg(m_UpdateFile.fileName())
+ .arg(r.toString()));
}
m_UpdateFile.remove();
diff --git a/src/settings.cpp b/src/settings.cpp index 462cd92a..15bc801a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1808,10 +1808,12 @@ void NexusSettings::registerAsNXMHandler(bool force) } parameters += " \"" + executable + "\""; - if (!shell::Execute(nxmPath, parameters)) { + const auto r = shell::Execute(nxmPath, parameters); + + if (!r.success()) { QMessageBox::critical( nullptr, QObject::tr("Failed"), - QObject::tr("Failed to start the helper application")); + QObject::tr("Failed to start the helper application: %1").arg(r.toString())); } } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 826075c0..2021bdc1 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -49,7 +49,7 @@ public: void openBrowser() { - shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); + shell::Open(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); } void paste() diff --git a/src/settingsutilities.h b/src/settingsutilities.h index a6737144..ac6aeb29 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -39,11 +39,11 @@ void logChange( using VC = ValueConverter<T>; if (oldValue) { - log::debug( + MOBase::log::debug( "setting '{}' changed from '{}' to '{}'", displayName, VC::convert(*oldValue), VC::convert(newValue)); } else { - log::debug( + MOBase::log::debug( "setting '{}' set to '{}'", displayName, VC::convert(newValue)); } diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 0c0eb1cc..4a8080f4 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -199,7 +199,7 @@ void TextEditor::explore() return; } - MOBase::shell::ExploreFile(m_filename); + shell::Explore(m_filename); } void TextEditor::onModified(bool b) |
