summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/downloadmanager.cpp7
-rw-r--r--src/env.cpp221
-rw-r--r--src/env.h33
-rw-r--r--src/envmodule.cpp17
-rw-r--r--src/envsecurity.cpp20
-rw-r--r--src/instancemanager.cpp17
-rw-r--r--src/main.cpp20
-rw-r--r--src/mainwindow.cpp72
-rw-r--r--src/mainwindow.h1
-rw-r--r--src/modinfodialog.ui3
-rw-r--r--src/modinfodialogfiletree.cpp12
-rw-r--r--src/modinfodialogfiletree.h2
-rw-r--r--src/organizercore.cpp6
-rw-r--r--src/overwriteinfodialog.cpp15
-rw-r--r--src/sanitychecks.cpp48
-rw-r--r--src/settings.cpp10
-rw-r--r--src/settings.h5
-rw-r--r--src/settingsdialog.ui7
-rw-r--r--src/settingsdialoggeneral.cpp2
19 files changed, 461 insertions, 57 deletions
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index c84d4bd4..3143a22e 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -996,8 +996,8 @@ void DownloadManager::queryInfoMd5(int index)
QCryptographicHash hash(QCryptographicHash::Md5);
const qint64 progressStep = 10 * 1024 * 1024;
QProgressDialog progress(tr("Hashing download file '%1'").arg(info->m_FileName),
- tr("Cancel"),
- 0,
+ tr("Cancel"),
+ 0,
downloadFile.size() / progressStep);
progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(1000);
@@ -1606,7 +1606,7 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian
emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?"));
} else {
SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one."));
- std::sort(files.begin(), files.end(), [](const QVariant& lhs, const QVariant& rhs)
+ std::sort(files.begin(), files.end(), [](const QVariant& lhs, const QVariant& rhs)
{return lhs.toMap()["uploaded_timestamp"].toInt() > rhs.toMap()["uploaded_timestamp"].toInt();});
for (QVariant file : files) {
QVariantMap fileInfo = file.toMap();
@@ -1692,7 +1692,6 @@ static int evaluateFileInfoMap(
}
if (!found) {
- log::error("server '{}' not found while sorting by preference", name);
return 0;
}
diff --git a/src/env.cpp b/src/env.cpp
index 507607d1..f9507dc1 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -56,6 +56,71 @@ Console::~Console()
}
+ModuleNotification::ModuleNotification(QObject* o, std::function<void (Module)> f)
+ : m_cookie(nullptr), m_object(o), m_f(std::move(f))
+{
+}
+
+ModuleNotification::~ModuleNotification()
+{
+ if (!m_cookie) {
+ return;
+ }
+
+ typedef NTSTATUS NTAPI LdrUnregisterDllNotificationType(
+ PVOID Cookie
+ );
+
+ LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll"));
+
+ if (!ntdll) {
+ log::error("failed to load ntdll.dll while unregistering for module notifications");
+ return;
+ }
+
+ auto* LdrUnregisterDllNotification = reinterpret_cast<LdrUnregisterDllNotificationType*>(
+ GetProcAddress(ntdll.get(), "LdrUnregisterDllNotification"));
+
+ if (!LdrUnregisterDllNotification) {
+ log::error("LdrUnregisterDllNotification not found in ntdll.dll");
+ return;
+ }
+
+ const auto r = LdrUnregisterDllNotification(m_cookie);
+ if (r != 0) {
+ log::error("failed to unregister for module notifications, error {}", r);
+ }
+}
+
+void ModuleNotification::setCookie(void* c)
+{
+ m_cookie = c;
+}
+
+void ModuleNotification::fire(QString path, std::size_t fileSize)
+{
+ if (m_loaded.contains(path)) {
+ // don't notify if it's been loaded before
+ }
+
+ m_loaded.insert(path);
+
+ // constructing a Module will query the version info of the file, which seems
+ // to generate an access violation for at least plugin_python.dll on Windows 7
+ //
+ // it's not clear what the problem is, but making sure this is deferred until
+ // _after_ the dll is loaded seems to fix it
+ //
+ // so this queues the callback in the main thread
+
+ if (m_f) {
+ QMetaObject::invokeMethod(m_object, [path, fileSize, f=m_f] {
+ f(Module(path, fileSize));
+ }, Qt::QueuedConnection);
+ }
+}
+
+
Environment::Environment()
{
}
@@ -104,17 +169,169 @@ const Metrics& Environment::metrics() const
return *m_metrics;
}
+QString Environment::timezone() const
+{
+ TIME_ZONE_INFORMATION tz = {};
+
+ const auto r = GetTimeZoneInformation(&tz);
+ if (r == TIME_ZONE_ID_INVALID) {
+ const auto e = GetLastError();
+ log::error("failed to get timezone, {}", formatSystemMessage(e));
+ return "unknown";
+ }
+
+ auto offsetString = [](int o) {
+ return
+ QString("%1%2:%3")
+ .arg(o < 0 ? "" : "+")
+ .arg(QString::number(o / 60), 2, QChar::fromLatin1('0'))
+ .arg(QString::number(o % 60), 2, QChar::fromLatin1('0'));
+ };
+
+ const auto stdName = QString::fromWCharArray(tz.StandardName);
+ const auto stdOffset = -(tz.Bias + tz.StandardBias);
+ const auto std = QString("%1, %2")
+ .arg(stdName)
+ .arg(offsetString(stdOffset));
+
+ const auto dstName = QString::fromWCharArray(tz.DaylightName);
+ const auto dstOffset = -(tz.Bias + tz.DaylightBias);
+ const auto dst = QString("%1, %2")
+ .arg(dstName)
+ .arg(offsetString(dstOffset));
+
+ QString s;
+
+ if (r == TIME_ZONE_ID_DAYLIGHT) {
+ s = dst + " (dst is active, std is " + std + ")";
+ } else {
+ s = std + " (std is active, dst is " + dst + ")";
+ }
+
+ return s;
+}
+
+std::unique_ptr<ModuleNotification> Environment::onModuleLoaded(
+ QObject* o, std::function<void (Module)> f)
+{
+ typedef struct _UNICODE_STRING {
+ USHORT Length;
+ USHORT MaximumLength;
+ PWSTR Buffer;
+ } UNICODE_STRING, *PUNICODE_STRING;
+
+ typedef const PUNICODE_STRING PCUNICODE_STRING;
+
+ typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA {
+ ULONG Flags; //Reserved.
+ PCUNICODE_STRING FullDllName; //The full path name of the DLL module.
+ PCUNICODE_STRING BaseDllName; //The base file name of the DLL module.
+ PVOID DllBase; //A pointer to the base address for the DLL in memory.
+ ULONG SizeOfImage; //The size of the DLL image, in bytes.
+ } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
+
+ typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA {
+ ULONG Flags; //Reserved.
+ PCUNICODE_STRING FullDllName; //The full path name of the DLL module.
+ PCUNICODE_STRING BaseDllName; //The base file name of the DLL module.
+ PVOID DllBase; //A pointer to the base address for the DLL in memory.
+ ULONG SizeOfImage; //The size of the DLL image, in bytes.
+ } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
+
+ typedef union _LDR_DLL_NOTIFICATION_DATA {
+ LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
+ LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
+ } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
+
+ typedef VOID CALLBACK LDR_DLL_NOTIFICATION_FUNCTION(
+ ULONG NotificationReason,
+ const PLDR_DLL_NOTIFICATION_DATA NotificationData,
+ PVOID Context
+ );
+
+ typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION;
+
+ typedef NTSTATUS NTAPI LdrRegisterDllNotificationType(
+ ULONG Flags,
+ PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction,
+ PVOID Context,
+ PVOID *Cookie
+ );
+
+ const ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1;
+ const ULONG LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2;
+
+
+ // loading ntdll.dll, the function will be found with GetProcAddress()
+ LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll"));
+
+ if (!ntdll) {
+ log::error("failed to load ntdll.dll while registering for module notifications");
+ return {};
+ }
+
+ auto* LdrRegisterDllNotification = reinterpret_cast<LdrRegisterDllNotificationType*>(
+ GetProcAddress(ntdll.get(), "LdrRegisterDllNotification"));
+
+ if (!LdrRegisterDllNotification) {
+ log::error("LdrRegisterDllNotification not found in ntdll.dll");
+ return {};
+ }
+
+
+ auto context = std::make_unique<ModuleNotification>(o, f);
+ void* cookie = nullptr;
+
+ auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) {
+ if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) {
+ if (data && data->Loaded.FullDllName) {
+ if (context) {
+ static_cast<ModuleNotification*>(context)->fire(
+ QString::fromWCharArray(
+ data->Loaded.FullDllName->Buffer,
+ data->Loaded.FullDllName->Length / sizeof(wchar_t)),
+ data->Loaded.SizeOfImage);
+ }
+ }
+ }
+ };
+
+ const auto r = LdrRegisterDllNotification(
+ 0, OnDllLoaded, context.get(), &cookie);
+
+ if (r != 0) {
+ log::error("failed to register for module notifications, error {}", r);
+ return {};
+ }
+
+ context->setCookie(cookie);
+
+ return context;
+}
+
void Environment::dump(const Settings& s) const
{
log::debug("windows: {}", windowsInfo().toString());
+ log::debug("time zone: {}", timezone());
+
if (windowsInfo().compatibilityMode()) {
log::warn("MO seems to be running in compatibility mode");
}
log::debug("security products:");
- for (const auto& sp : securityProducts()) {
- log::debug(" . {}", sp.toString());
+
+ {
+ // ignore products with identical names, some AVs register themselves with
+ // the same names and provider, but different guids
+ std::set<QString> productNames;
+ for (const auto& sp : securityProducts()) {
+ productNames.insert(sp.toString());
+ }
+
+ for (auto&& name : productNames) {
+ log::debug(" . {}", name);
+ }
}
log::debug("modules loaded in process:");
diff --git a/src/env.h b/src/env.h
index f95d1013..dc0fd864 100644
--- a/src/env.h
+++ b/src/env.h
@@ -119,6 +119,29 @@ private:
};
+class ModuleNotification
+{
+public:
+ ModuleNotification(QObject* o, std::function<void (Module)> f);
+ ~ModuleNotification();
+
+ ModuleNotification(const ModuleNotification&) = delete;
+ ModuleNotification& operator=(const ModuleNotification&) = delete;
+
+ ModuleNotification(ModuleNotification&&) = default;
+ ModuleNotification& operator=(ModuleNotification&&) = default;
+
+ void setCookie(void* c);
+ void fire(QString path, std::size_t fileSize);
+
+private:
+ void* m_cookie;
+ QObject* m_object;
+ std::set<QString> m_loaded;
+ std::function<void (Module)> m_f;
+};
+
+
// represents the process's environment
//
class Environment
@@ -147,6 +170,16 @@ public:
//
const Metrics& metrics() const;
+ // timezone
+ //
+ QString timezone() const;
+
+ // will call `f` on the same thread `o` is running on every time a module
+ // is loaded in the process
+ //
+ std::unique_ptr<ModuleNotification> onModuleLoaded(
+ QObject* o, std::function<void (Module)> f);
+
// logs the environment
//
void dump(const Settings& s) const;
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index 13831631..8d348b5e 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -295,10 +295,19 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const
QString Module::getMD5() const
{
- if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) {
- // don't calculate md5 for system files, it's not really relevant and
- // it takes a while
- return {};
+ static const std::set<QString> ignore = {
+ "\\windows\\",
+ "\\program files\\",
+ "\\program files (x86)\\",
+ "\\programdata\\"
+ };
+
+ // don't calculate md5 for system files, it's not really relevant and
+ // it takes a while
+ for (auto&& i : ignore) {
+ if (m_path.contains(i, Qt::CaseInsensitive)) {
+ return {};
+ }
}
// opening the file
diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp
index 6d62728b..6f4826e7 100644
--- a/src/envsecurity.cpp
+++ b/src/envsecurity.cpp
@@ -207,13 +207,6 @@ QString SecurityProduct::toString() const
s += ", definitions outdated";
}
- // 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);
- }
-
return s;
}
@@ -390,7 +383,18 @@ std::optional<SecurityProduct> getWindowsFirewall()
hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant);
if (FAILED(hr))
{
- log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr));
+ // EPT_S_NOT_REGISTERED is "There are no more endpoints available from the
+ // endpoint mapper", which seems to happen sometimes on Windows 7 when the
+ // firewall has been disabled, so treat it as such and don't log it
+ //
+ // however the user reported the error was actually 0x800706d9, not just
+ // 0x6d9 (1753, what EPT_S_NOT_REGISTERED is defined to), so this is
+ // testing for both because it's not clear which it is and nobody can
+ // reproduce it
+ if (hr != EPT_S_NOT_REGISTERED && hr != 0x800706d9) {
+ log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr));
+ }
+
return {};
}
}
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index ec3c1add..a9da30d9 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -259,7 +259,22 @@ QString InstanceManager::instancePath() const
QStringList InstanceManager::instances() const
{
- return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ const std::set<QString> ignore = {
+ "cache", "qtwebengine",
+ };
+
+ const auto dirs = QDir(instancePath())
+ .entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+
+ QStringList list;
+
+ for (auto&& d : dirs) {
+ if (!ignore.contains(QFileInfo(d).fileName().toLower())) {
+ list.push_back(d);
+ }
+ }
+
+ return list;
}
diff --git a/src/main.cpp b/src/main.cpp
index 02347ee3..3cccf365 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -93,6 +93,7 @@ using namespace MOShared;
void sanityChecks(const env::Environment& env);
+int checkIncompatibleModule(const env::Module& m);
bool createAndMakeWritable(const std::wstring &subPath) {
QString const dataPath = qApp->property("dataPath").toString();
@@ -547,6 +548,11 @@ int runApplication(MOApplication &application, SingleInstance &instance,
settings.dump();
sanityChecks(env);
+ const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) {
+ log::debug("loaded module {}", m.toString());
+ checkIncompatibleModule(m);
+ });
+
log::debug("initializing core");
OrganizerCore organizer(settings);
if (!organizer.bootstrap()) {
@@ -554,6 +560,20 @@ int runApplication(MOApplication &application, SingleInstance &instance,
return 1;
}
+ {
+ // log if there are any dmp files
+ const auto hasCrashDumps =
+ !QDir(QString::fromStdWString(organizer.crashDumpsPath()))
+ .entryList({"*.dmp"}, QDir::Files)
+ .empty();
+
+ if (hasCrashDumps) {
+ log::debug(
+ "there are crash dumps in '{}'",
+ QString::fromStdWString(organizer.crashDumpsPath()));
+ }
+ }
+
log::debug("initializing plugins");
PluginContainer pluginContainer(&organizer);
pluginContainer.loadPlugins();
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 9ea554a2..e134d64a 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -190,6 +190,11 @@ const QSize SmallToolbarSize(24, 24);
const QSize MediumToolbarSize(32, 32);
const QSize LargeToolbarSize(42, 36);
+QString UnmanagedModName()
+{
+ return QObject::tr("<Unmanaged>");
+}
+
bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList);
@@ -277,6 +282,7 @@ MainWindow::MainWindow(Settings &settings
ui->espList->installEventFilter(m_OrganizerCore.pluginList());
ui->bsaList->setLocalMoveOnly(true);
+ ui->bsaList->setHeaderHidden(true);
initDownloadView();
@@ -1660,9 +1666,13 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director
QString fileName = ToQString(current->getName());
QStringList columns(fileName);
FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
- QString source("data");
- unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName()));
- if (modIndex != UINT_MAX) {
+
+ QString source;
+ const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName()));
+
+ if (modIndex == UINT_MAX) {
+ source = UnmanagedModName();
+ } else {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
source = modInfo->name();
}
@@ -2045,12 +2055,17 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString
int originID = iter->second->data(1, Qt::UserRole).toInt();
FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
- QString modName("data");
- unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName()));
- if (modIndex != UINT_MAX) {
+
+ QString modName;
+ const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName()));
+
+ if (modIndex == UINT_MAX) {
+ modName = UnmanagedModName();
+ } else {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
modName = modInfo->name();
}
+
QList<QTreeWidgetItem*> items = ui->bsaList->findItems(modName, Qt::MatchFixedString);
QTreeWidgetItem * subItem = nullptr;
if (items.length() > 0) {
@@ -5427,6 +5442,33 @@ void MainWindow::openDataOriginExplorer_clicked()
shell::Explore(fullPath);
}
+void MainWindow::openDataModInfo_clicked()
+{
+ if (m_ContextItem == nullptr) {
+ return;
+ }
+
+ const auto originID = m_ContextItem->data(1, Qt::UserRole + 1).toInt();
+ if (originID == 0) {
+ // unmanaged
+ return;
+ }
+
+ const auto& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID);
+ const auto& name = QString::fromStdWString(origin.getName());
+
+ unsigned int index = ModInfo::getIndex(name);
+ if (index == UINT_MAX) {
+ log::error("can't open mod info, mod '{}' not found", name);
+ return;
+ }
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+ if (modInfo) {
+ displayModInformation(modInfo, index, ModInfoTabIDs::None);
+ }
+}
+
void MainWindow::updateAvailable()
{
ui->actionUpdate->setEnabled(true);
@@ -5472,6 +5514,8 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos)
menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked()));
}
+ menu.addAction("Open Mod Info", this, SLOT(openDataModInfo_clicked()));
+
// offer to hide/unhide file, but not for files from archives
if (!isArchive) {
if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) {
@@ -6139,14 +6183,18 @@ void MainWindow::on_actionNotifications_triggered()
void MainWindow::on_actionChange_Game_triggered()
{
- const auto r = QMessageBox::question(
- this, tr("Are you sure?"), tr("This will restart MO, continue?"),
- QMessageBox::Yes | QMessageBox::Cancel);
+ if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) {
+ const auto r = QMessageBox::question(
+ this, tr("Are you sure?"), tr("This will restart MO, continue?"),
+ QMessageBox::Yes | QMessageBox::Cancel);
- if (r == QMessageBox::Yes) {
- InstanceManager::instance().clearCurrentInstance();
- ExitModOrganizer(Exit::Restart);
+ if (r != QMessageBox::Yes) {
+ return;
+ }
}
+
+ InstanceManager::instance().clearCurrentInstance();
+ ExitModOrganizer(Exit::Restart);
}
void MainWindow::setCategoryListVisible(bool visible)
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 7c2ee1eb..0c96c15d 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -448,6 +448,7 @@ private slots:
void hideFile();
void unhideFile();
void openDataOriginExplorer_clicked();
+ void openDataModInfo_clicked();
// pluginlist context menu
void enableSelectedPlugins_clicked();
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index 13a245f1..59263e1c 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -1258,6 +1258,9 @@ p, li { white-space: pre-wrap; }
<property name="uniformRowHeights">
<bool>true</bool>
</property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp
index 71ea9210..79ed4cba 100644
--- a/src/modinfodialogfiletree.cpp
+++ b/src/modinfodialogfiletree.cpp
@@ -55,6 +55,18 @@ void FileTreeTab::clear()
setHasData(true);
}
+void FileTreeTab::saveState(Settings& s)
+{
+ s.geometry().saveState(ui->filetree->header());
+}
+
+void FileTreeTab::restoreState(const Settings& s)
+{
+ if (!s.geometry().restoreState(ui->filetree->header())) {
+ ui->filetree->sortByColumn(0, Qt::AscendingOrder);
+ }
+}
+
void FileTreeTab::update()
{
const auto rootPath = mod().absolutePath();
diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h
index 42773899..494a7e14 100644
--- a/src/modinfodialogfiletree.h
+++ b/src/modinfodialogfiletree.h
@@ -12,6 +12,8 @@ public:
FileTreeTab(ModInfoDialogTabContext cx);
void clear() override;
+ void saveState(Settings& s);
+ void restoreState(const Settings& s);
void update() override;
bool deleteRequested() override;
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 9ceb149e..c585ba09 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -484,12 +484,6 @@ bool OrganizerCore::cycleDiagnostics()
removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
}
- // log if there are any files left
- const auto files = QDir(path).entryList({"*.dmp"}, QDir::Files);
- if (!files.isEmpty()) {
- log::debug("there are crash dumps in '{}'", path);
- }
-
return true;
}
diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp
index 078bcfc9..ab726fb3 100644
--- a/src/overwriteinfodialog.cpp
+++ b/src/overwriteinfodialog.cpp
@@ -106,13 +106,24 @@ OverwriteInfoDialog::~OverwriteInfoDialog()
void OverwriteInfoDialog::showEvent(QShowEvent* e)
{
- Settings::instance().geometry().restoreGeometry(this);
+ const auto& s = Settings::instance();
+
+ s.geometry().restoreGeometry(this);
+
+ if (!s.geometry().restoreState(ui->filesView->header())) {
+ ui->filesView->sortByColumn(0, Qt::AscendingOrder);
+ }
+
QDialog::showEvent(e);
}
void OverwriteInfoDialog::done(int r)
{
- Settings::instance().geometry().saveGeometry(this);
+ auto& s = Settings::instance();
+
+ s.geometry().saveGeometry(this);
+ s.geometry().saveState(ui->filesView->header());
+
QDialog::done(r);
}
diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp
index 3b4185a7..ef57a503 100644
--- a/src/sanitychecks.cpp
+++ b/src/sanitychecks.cpp
@@ -177,9 +177,14 @@ 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"
+ "helper.exe",
+ "nxmhandler.exe",
+ "usvfs_proxy_x64.exe",
+ "usvfs_proxy_x86.exe",
+ "usvfs_x64.dll",
+ "usvfs_x86.dll",
+ "loot/loot.dll",
+ "loot/lootcli.exe"
});
log::debug(" . missing files");
@@ -202,29 +207,36 @@ int checkMissingFiles()
return n;
}
-bool checkNahimic(const env::Environment& e)
+int checkIncompatibleModule(const env::Module& m)
{
- // 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
+ // these dlls 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
+ // the dlls is usually loaded on startup, but there has been some reports
+ // where it got loaded later, so this is also called every time a new module
+ // is loaded into this process
- for (auto&& m : e.loadedModules()) {
- const QFileInfo file(m.path());
+ static const std::map<QString, QString> names = {
+ {"NahimicOSD.dll", "Nahimic"},
+ {"RTSSHooks64.dll", "RivaTuner Statistics Server"}
+ };
+
+ const QFileInfo file(m.path());
+ int n = 0;
- if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) {
+ for (auto&& p : names) {
+ if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) {
log::warn(
- "NahimicOSD.dll is loaded. Nahimic is known to cause issues with "
+ "{} is loaded. This program is known to cause issues with "
"Mod Organizer, such as freezing or blank windows. Consider "
- "uninstalling it.");
+ "uninstalling it. ({})", p.second, file.absoluteFilePath());
- return true;
+ ++n;
}
}
- return false;
+ return n;
}
int checkIncompatibilities(const env::Environment& e)
@@ -233,8 +245,8 @@ int checkIncompatibilities(const env::Environment& e)
int n = 0;
- if (checkNahimic(e)) {
- ++n;
+ for (auto&& m : e.loadedModules()) {
+ n += checkIncompatibleModule(m);
}
return n;
diff --git a/src/settings.cpp b/src/settings.cpp
index b533b400..e1e5c2da 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -1961,6 +1961,16 @@ void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b)
set(m_Settings, "CompletedWindowTutorials", windowName, b);
}
+bool InterfaceSettings::showChangeGameConfirmation() const
+{
+ return get<bool>(m_Settings, "Settings", "show_change_game_confirmation", true);
+}
+
+void InterfaceSettings::setShowChangeGameConfirmation(bool b) const
+{
+ set(m_Settings, "Settings", "show_change_game_confirmation", b);
+}
+
DiagnosticsSettings::DiagnosticsSettings(QSettings& settings)
: m_Settings(settings)
diff --git a/src/settings.h b/src/settings.h
index d71fabf4..870e0fc4 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -605,6 +605,11 @@ public:
bool isTutorialCompleted(const QString& windowName) const;
void setTutorialCompleted(const QString& windowName, bool b=true);
+ // whether to show the confirmation when switching instances
+ //
+ bool showChangeGameConfirmation() const;
+ void setShowChangeGameConfirmation(bool b) const;
+
private:
QSettings& m_Settings;
};
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index b88c8b71..e63ca692 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -109,6 +109,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="changeGameConfirmation">
+ <property name="text">
+ <string>Show confirmation when changing instance</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="resetDialogsButton">
<property name="maximumSize">
<size>
diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp
index e21fc5d0..b0e64305 100644
--- a/src/settingsdialoggeneral.cpp
+++ b/src/settingsdialoggeneral.cpp
@@ -17,6 +17,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d)
ui->colorTable->load(s);
ui->centerDialogs->setChecked(settings().geometry().centerDialogs());
+ ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation());
ui->compactBox->setChecked(settings().interface().compactDownloads());
ui->showMetaBox->setChecked(settings().interface().metaDownloads());
ui->checkForUpdates->setChecked(settings().checkForUpdates());
@@ -59,6 +60,7 @@ void GeneralSettingsTab::update()
ui->colorTable->commitColors();
settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked());
+ settings().interface().setShowChangeGameConfirmation(ui->changeGameConfirmation->isChecked());
settings().interface().setCompactDownloads(ui->compactBox->isChecked());
settings().interface().setMetaDownloads(ui->showMetaBox->isChecked());
settings().setCheckForUpdates(ui->checkForUpdates->isChecked());