diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/downloadmanager.cpp | 7 | ||||
| -rw-r--r-- | src/env.cpp | 221 | ||||
| -rw-r--r-- | src/env.h | 33 | ||||
| -rw-r--r-- | src/envmodule.cpp | 17 | ||||
| -rw-r--r-- | src/envsecurity.cpp | 20 | ||||
| -rw-r--r-- | src/instancemanager.cpp | 17 | ||||
| -rw-r--r-- | src/main.cpp | 20 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 72 | ||||
| -rw-r--r-- | src/mainwindow.h | 1 | ||||
| -rw-r--r-- | src/modinfodialog.ui | 3 | ||||
| -rw-r--r-- | src/modinfodialogfiletree.cpp | 12 | ||||
| -rw-r--r-- | src/modinfodialogfiletree.h | 2 | ||||
| -rw-r--r-- | src/organizercore.cpp | 6 | ||||
| -rw-r--r-- | src/overwriteinfodialog.cpp | 15 | ||||
| -rw-r--r-- | src/sanitychecks.cpp | 48 | ||||
| -rw-r--r-- | src/settings.cpp | 10 | ||||
| -rw-r--r-- | src/settings.h | 5 | ||||
| -rw-r--r-- | src/settingsdialog.ui | 7 | ||||
| -rw-r--r-- | src/settingsdialoggeneral.cpp | 2 |
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:"); @@ -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()); |
