summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/categories.h2
-rw-r--r--src/downloadmanager.cpp10
-rw-r--r--src/envsecurity.cpp7
-rw-r--r--src/filerenamer.cpp42
-rw-r--r--src/filerenamer.h8
-rw-r--r--src/installationmanager.cpp8
-rw-r--r--src/main.cpp46
-rw-r--r--src/mainwindow.cpp38
-rw-r--r--src/modinfodialogconflicts.cpp2
-rw-r--r--src/modinfodialogfiletree.cpp8
-rw-r--r--src/modinfodialogimages.cpp2
-rw-r--r--src/modinfodialognexus.cpp6
-rw-r--r--src/modlist.cpp2
-rw-r--r--src/modlistsortproxy.cpp18
-rw-r--r--src/motddialog.cpp2
-rw-r--r--src/nxmaccessmanager.cpp2
-rw-r--r--src/overwriteinfodialog.cpp4
-rw-r--r--src/pluginlist.cpp7
-rw-r--r--src/problemsdialog.cpp2
-rw-r--r--src/profile.cpp5
-rw-r--r--src/profile.h7
-rw-r--r--src/sanitychecks.cpp256
-rw-r--r--src/selfupdater.cpp7
-rw-r--r--src/settings.cpp6
-rw-r--r--src/settingsdialognexus.cpp2
-rw-r--r--src/settingsutilities.h4
-rw-r--r--src/texteditor.cpp2
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 &section, 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 &section, 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 &section, const QString &name = QString(),
- const QVariant &fallback = QVariant()) const;
+ QVariant setting(
+ const QString &section, const QString &name,
+ const QVariant &fallback) const;
+
+ QVariant setting(const QString &name, const QVariant &fallback={}) const;
void storeSetting(const QString &section, 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)