From ad77e315f5c53994d75056608df0f9ff0a390530 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 09:10:14 -0400 Subject: moved Console to util --- src/shared/util.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'src/shared/util.cpp') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 17df3b92..29e52f40 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -441,6 +441,49 @@ private: }; +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + Shortcut::Shortcut() : m_iconIndex(0) { -- cgit v1.3.1 From 3bf7717c0a8507c9befce6b74c84c4dbdcac99de Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:41:09 -0400 Subject: moved Settings out of OrganizerCore so it can be created by itself to access settings early set log level on startup replaced more qDebug() --- src/main.cpp | 100 +++++++++++++++++++++++++------------------------- src/organizercore.cpp | 6 +-- src/organizercore.h | 4 +- src/shared/util.cpp | 4 +- 4 files changed, 57 insertions(+), 57 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/main.cpp b/src/main.cpp index f55c32f2..aa842ead 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -425,8 +425,6 @@ void setupPath() { static const int BUFSIZE = 4096; - log::debug("MO at {}", QCoreApplication::applicationDirPath()); - QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); boost::scoped_array oldPath(new TCHAR[BUFSIZE]); @@ -446,8 +444,6 @@ void setupPath() void preloadDll(const QString& filename) { - log::debug("preloading {}", filename); - if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching // instances, for example @@ -513,62 +509,68 @@ void dumpEnvironment() int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - log::info( - "Starting Mod Organizer version {} revision {}", - getVersionDisplayString(), GITID); + "Starting Mod Organizer version {} revision {} in {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); -#if !defined(QT_NO_SSL) preloadSsl(); - log::info("ssl support: {}", QSslSocket::supportsSsl()); -#else - log::info("non-ssl build"); -#endif - - dumpEnvironment(); + if (!QSslSocket::supportsSsl()) { + log::warn("no ssl support"); + } QString dataPath = application.property("dataPath").toString(); - qDebug("data path: %s", qUtf8Printable(dataPath)); + log::info("data path: {}", dataPath); if (!bootstrap()) { reportError("failed to set up data paths"); return 1; } - QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); + QWindowsWindowFunctions::setWindowActivationBehavior( + QWindowsWindowFunctions::AlwaysActivateWindow); QStringList arguments = application.arguments(); try { - qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath()))); + log::info("working directory: {}", QDir::currentPath()); - QSettings settings(dataPath + "/" - + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); + QSettings initSettings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); - // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); + Settings settings(initSettings); + log::getDefault().setLevel(settings.logLevel()); - qDebug("Loaded settings:"); - settings.beginGroup("Settings"); - for (auto k : settings.allKeys()) - if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); - settings.endGroup(); + dumpEnvironment(); + // global crashDumpType sits in OrganizerCore to make a bit less ugly to + // update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - qDebug("initializing core"); + log::debug("Loaded settings:"); + + initSettings.beginGroup("Settings"); + for (auto k : initSettings.allKeys()) { + if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) { + log::debug(" {}={}", k, initSettings.value(k).toString()); + } + } + initSettings.endGroup(); + + + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); return 1; } - qDebug("initialize plugins"); + + log::debug("initializing plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), settings, pluginContainer); + application.applicationDirPath(), initSettings, pluginContainer); if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -586,14 +588,14 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (!image.isNull()) { image.save(dataPath + "/splash.png"); } else { - qDebug("no plugin splash"); + log::debug("no plugin splash"); } } organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!settings.contains("game_edition")) { + if (!initSettings.contains("game_edition")) { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -609,18 +611,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setValue("game_edition", selection.getChoiceString()); + initSettings.setValue("game_edition", selection.getChoiceString()); } } } - game->setGameVariant(settings.value("game_edition").toString()); + game->setGameVariant(initSettings.value("game_edition").toString()); - qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators( - game->gameDirectory().absolutePath()))); + log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(settings); + organizer.updateExecutablesList(initSettings); - QString selectedProfileName = determineProfile(arguments, settings); + QString selectedProfileName = determineProfile(arguments, initSettings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or @@ -640,13 +641,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", - qUtf8Printable(arguments.at(1))); + log::debug("starting download from command line: {}", arguments.at(1)); organizer.externalMessage(arguments.at(1)); } else { QString exeName = arguments.at(1); - qDebug("starting %s from command line", qUtf8Printable(exeName)); + log::debug("starting {} from command line", exeName); arguments.removeFirst(); // remove application name (ModOrganizer.exe) arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary @@ -665,8 +665,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (settings.contains("window_monitor")) { - const int monitor = settings.value("window_monitor").toInt(); + if (initSettings.contains("window_monitor")) { + const int monitor = initSettings.value("window_monitor").toInt(); if (monitor != -1) { QDesktopWidget* desktop = QApplication::desktop(); @@ -683,21 +683,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } - qDebug("initializing tutorials"); + log::debug("initializing tutorials"); TutorialManager::init( qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { // disable invalid stylesheet - settings.setValue("Settings/style", ""); + initSettings.setValue("Settings/style", ""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(settings, organizer, pluginContainer); + MainWindow mainWindow(initSettings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); @@ -714,7 +714,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, mainWindow.readSettings(); - qDebug("displaying main window"); + log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); @@ -857,7 +857,7 @@ int main(int argc, char *argv[]) if (moshortcut || arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending shortcut/download message"); + log::debug("not primary instance, sending shortcut/download message"); instance.sendMessage(arguments.at(1)); return 0; } else if (arguments.size() == 1) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 25fbc7cd..400f5391 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -268,12 +268,12 @@ bool checkService() } -OrganizerCore::OrganizerCore(const QSettings &initSettings) +OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) - , m_Settings(initSettings) + , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() , m_FinishedRun() @@ -294,7 +294,7 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); - MOBase::QuestionBoxMemory::init(initSettings.fileName()); + MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName()); m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); diff --git a/src/organizercore.h b/src/organizercore.h index ef1a4133..c368d101 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -97,7 +97,7 @@ public: static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } - OrganizerCore(const QSettings &initSettings); + OrganizerCore(Settings &settings); ~OrganizerCore(); @@ -336,7 +336,7 @@ private: Profile *m_CurrentProfile; - Settings m_Settings; + Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 29e52f40..8d8c2000 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1492,11 +1492,11 @@ QString WindowsInfo::toString() const const QString real = m_real.toString(); // version - sl.push_back("version: " + reported); + sl.push_back("version " + reported); // real version if different if (compatibilityMode()) { - sl.push_back("real version: " + real); + sl.push_back("real version " + real); } // build.UBR, such as 17763.557 -- cgit v1.3.1 From 2ef32d12306ac21c0c900ff8f353ff0b573e67ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:39:01 -0400 Subject: moved environment dump to member function added check for nahimic --- src/main.cpp | 51 +++++++++++++++++++++++++++------------------------ src/shared/util.cpp | 25 ++++++++++++++++++++++--- src/shared/util.h | 6 +++++- 3 files changed, 54 insertions(+), 28 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/main.cpp b/src/main.cpp index 15d36428..ef698dd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -482,27 +482,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void dumpEnvironment() -{ - env::Environment env; - - log::debug("windows: {}", env.windowsInfo().toString()); - - if (env.windowsInfo().compatibilityMode()) { - log::warn("MO seems to be running in compatibility mode"); - } - - log::debug("security products:"); - for (const auto& sp : env.securityProducts()) { - log::debug(" . {}", sp.toString()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : env.loadedModules()) { - log::debug(" . {}", m.toString()); - } -} - void dumpSettings(QSettings& settings) { static const QStringList ignore({ @@ -524,7 +503,7 @@ void dumpSettings(QSettings& settings) settings.endGroup(); } -void sanityChecks() +void checkMissingFiles() { // files that are likely to be eaten static const QStringList files({ @@ -545,6 +524,28 @@ void sanityChecks() } } +void checkNahimic(const env::Environment& e) +{ + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); + + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive)) { + 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) @@ -585,9 +586,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - dumpEnvironment(); + env::Environment env; + + env.dump(); dumpSettings(initSettings); - sanityChecks(); + sanityChecks(env); log::debug("initializing core"); OrganizerCore organizer(settings); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 8d8c2000..eacd1f88 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "executableslist.h" #include "instancemanager.h" #include +#include #include #include @@ -41,8 +42,7 @@ along with Mod Organizer. If not, see . #pragma comment(lib, "Wbemuuid.lib") -using MOBase::formatSystemMessage; -using MOBase::formatSystemMessageQ; +using namespace MOBase; namespace fs = std::filesystem; namespace MOShared { @@ -870,7 +870,7 @@ Environment::Environment() m_security = getSecurityProducts(); } -const std::vector& Environment::loadedModules() +const std::vector& Environment::loadedModules() const { return m_modules; } @@ -885,6 +885,25 @@ const std::vector& Environment::securityProducts() const return m_security; } +void Environment::dump() const +{ + log::debug("windows: {}", windowsInfo().toString()); + + 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()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : loadedModules()) { + log::debug(" . {}", m.toString()); + } +} + std::vector Environment::getLoadedModules() const { HandlePtr snapshot(CreateToolhelp32Snapshot( diff --git a/src/shared/util.h b/src/shared/util.h index a5d096ac..267bb780 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -410,7 +410,7 @@ public: // list of loaded modules in the current process // - const std::vector& loadedModules(); + const std::vector& loadedModules() const; // information about the operating system // @@ -420,6 +420,10 @@ public: // const std::vector& securityProducts() const; + // logs the environment + // + void dump() const; + private: std::vector m_modules; WindowsInfo m_windows; -- cgit v1.3.1 From 20ac714bf880ab7e3762428c7154d1c81b5188ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 19:00:33 -0400 Subject: fixed bad compare for nahimic log displays on startup --- src/main.cpp | 2 +- src/shared/util.cpp | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 27 +++++++ 3 files changed, 251 insertions(+), 1 deletion(-) (limited to 'src/shared/util.cpp') diff --git a/src/main.cpp b/src/main.cpp index ef698dd4..09da9408 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -529,7 +529,7 @@ void checkNahimic(const env::Environment& e) for (auto&& m : e.loadedModules()) { const QFileInfo file(m.path()); - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive)) { + 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 " diff --git a/src/shared/util.cpp b/src/shared/util.cpp index eacd1f88..8c4a3f17 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #pragma comment(lib, "Wbemuuid.lib") @@ -864,6 +865,196 @@ private: }; +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + Environment::Environment() { m_modules = getLoadedModules(); @@ -885,6 +1076,11 @@ const std::vector& Environment::securityProducts() const return m_security; } +const Metrics& Environment::metrics() const +{ + return m_metrics; +} + void Environment::dump() const { log::debug("windows: {}", windowsInfo().toString()); @@ -902,6 +1098,11 @@ void Environment::dump() const for (const auto& m : loadedModules()) { log::debug(" . {}", m.toString()); } + + log::debug("displays:"); + for (const auto& d : m_metrics.displays()) { + log::debug(" . {}", d.toString()); + } } std::vector Environment::getLoadedModules() const @@ -1137,6 +1338,28 @@ std::optional Environment::getWindowsFirewall() const } +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + + Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) { diff --git a/src/shared/util.h b/src/shared/util.h index 267bb780..fc2028db 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -401,6 +401,28 @@ private: }; +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + + // represents the process's environment // class Environment @@ -420,6 +442,10 @@ public: // const std::vector& securityProducts() const; + // information about displays + // + const Metrics& metrics() const; + // logs the environment // void dump() const; @@ -428,6 +454,7 @@ private: std::vector m_modules; WindowsInfo m_windows; std::vector m_security; + Metrics m_metrics; std::vector getLoadedModules() const; std::vector getSecurityProducts() const; -- cgit v1.3.1 From c84240b75906fdc1f2ef8b41f4f3c00421dc61fa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 19:01:58 -0400 Subject: only display "inactive" for security products --- src/shared/util.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 8c4a3f17..441b64bd 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1976,9 +1976,7 @@ QString SecurityProduct::toString() const s += "(" + ps.join("|") + ")"; } - if (m_active) { - s += ", active"; - } else { + if (!m_active) { s += ", inactive"; } -- cgit v1.3.1 From f95479b981b41f51a3ecf055c73f42440766e5d7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 22:35:12 -0400 Subject: log guid for security products --- src/shared/util.cpp | 41 ++++++++++++++++++++++++----------------- src/shared/util.h | 6 +++++- 2 files changed, 29 insertions(+), 18 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 441b64bd..4ee4b766 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1264,7 +1264,7 @@ std::vector Environment::getSecurityProductsFromWMI() const map.insert({ guid, - {QString::fromStdWString(name), provider, active, upToDate}}); + {guid, QString::fromStdWString(name), provider, active, upToDate}}); }; { @@ -1334,7 +1334,7 @@ std::optional Environment::getWindowsFirewall() const } return SecurityProduct( - "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); } @@ -1907,9 +1907,9 @@ std::optional WindowsInfo::getElevated() const SecurityProduct::SecurityProduct( - QString name, int provider, + QUuid guid, QString name, int provider, bool active, bool upToDate) : - m_name(std::move(name)), m_provider(provider), + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), m_active(active), m_upToDate(upToDate) { } @@ -1938,10 +1938,27 @@ QString SecurityProduct::toString() const { QString s; - s += m_name + " "; + s += m_name + " (" + providerToString() + ")"; + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} +QString SecurityProduct::providerToString() const +{ QStringList ps; + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { ps.push_back("firewall"); } @@ -1971,20 +1988,10 @@ QString SecurityProduct::toString() const } if (ps.empty()) { - s += "(doesn't provide anything)"; - } else { - s += "(" + ps.join("|") + ")"; - } - - if (!m_active) { - s += ", inactive"; - } - - if (!m_upToDate) { - s += ", definitions outdated"; + return "doesn't provider anything"; } - return s; + return ps.join("|"); } diff --git a/src/shared/util.h b/src/shared/util.h index fc2028db..46764367 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include #include +#include class Executable; @@ -370,7 +371,7 @@ class SecurityProduct { public: SecurityProduct( - QString name, int provider, + QUuid guid, QString name, int provider, bool active, bool upToDate); // display name of the product @@ -394,10 +395,13 @@ public: QString toString() const; private: + QUuid m_guid; QString m_name; int m_provider; bool m_active; bool m_upToDate; + + QString providerToString() const; }; -- cgit v1.3.1 From b2a1e1391fdd6bdee1c5e8d337b273447c70a506 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 23:13:57 -0400 Subject: split env --- src/CMakeLists.txt | 25 +- src/env.cpp | 479 ++++++++++++ src/env.h | 117 +++ src/envmetrics.cpp | 224 ++++++ src/envmetrics.h | 28 + src/envmodule.cpp | 390 ++++++++++ src/envmodule.h | 98 +++ src/envsecurity.cpp | 418 ++++++++++ src/envsecurity.h | 49 ++ src/envshortcut.cpp | 376 +++++++++ src/envshortcut.h | 114 +++ src/envwindows.cpp | 236 ++++++ src/envwindows.h | 106 +++ src/main.cpp | 2 + src/mainwindow.cpp | 1 + src/shared/util.cpp | 2123 +-------------------------------------------------- src/shared/util.h | 443 ----------- 17 files changed, 2663 insertions(+), 2566 deletions(-) create mode 100644 src/env.cpp create mode 100644 src/env.h create mode 100644 src/envmetrics.cpp create mode 100644 src/envmetrics.h create mode 100644 src/envmodule.cpp create mode 100644 src/envmodule.h create mode 100644 src/envsecurity.cpp create mode 100644 src/envsecurity.h create mode 100644 src/envshortcut.cpp create mode 100644 src/envshortcut.h create mode 100644 src/envwindows.cpp create mode 100644 src/envwindows.h (limited to 'src/shared/util.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dbab132..9785dc3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,12 @@ SET(organizer_SRCS filerenamer.cpp texteditor.cpp expanderwidget.cpp + env.cpp + envmetrics.cpp + envmodule.cpp + envsecurity.cpp + envshortcut.cpp + envwindows.cpp shared/windows_error.cpp shared/error_report.cpp @@ -239,6 +245,12 @@ SET(organizer_HDRS filerenamer.h texteditor.h expanderwidget.h + env.h + envmetrics.h + envmodule.h + envsecurity.h + envshortcut.h + envwindows.h shared/windows_error.h shared/error_report.h @@ -350,6 +362,15 @@ set(downloads downloadmanager ) +set(env + env + envmetrics + envmodule + envsecurity + envshortcut + envwindows +) + set(executables executableslist editexecutablesdialog @@ -447,8 +468,8 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo modinfo\\dialog - modlist plugins previews profiles settings utilities widgets + application core browser dialogs downloads env executables locking modinfo + modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) foreach(filter in list ${src_filters}) diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 00000000..641eb4a7 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,479 @@ +#include "env.h" +#include "envmetrics.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envshortcut.h" +#include "envwindows.h" +#include +#include + +namespace env +{ + +using namespace MOBase; + +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + +Environment::Environment() + : m_windows(new WindowsInfo), m_metrics(new Metrics) +{ + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); +} + +// anchor +Environment::~Environment() = default; + +const std::vector& Environment::loadedModules() const +{ + return m_modules; +} + +const WindowsInfo& Environment::windowsInfo() const +{ + return *m_windows; +} + +const std::vector& Environment::securityProducts() const +{ + return m_security; +} + +const Metrics& Environment::metrics() const +{ + return *m_metrics; +} + +void Environment::dump() const +{ + log::debug("windows: {}", m_windows->toString()); + + if (m_windows->compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : m_security) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : m_modules) { + log::debug(" . {}", m.toString()); + } + + log::debug("displays:"); + for (const auto& d : m_metrics->displays()) { + log::debug(" . {}", d.toString()); + } +} + + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + + +// returns the filename of the given process or the current one +// +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + // double the buffer size 10 times + const int MaxTries = 10; + + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + // hard failure + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const std::filesystem::path path(s); + + return path.filename().native(); + } + } + + // something failed or the path is way too long to make sense + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector runningProcessesIds() +{ + // double the buffer size 10 times + const int MaxTries = 10; + + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process has pid 0 and seems to be picked up by EnumProcesses() + continue; + } + + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + + if (e != ERROR_ACCESS_DENIED) { + // don't log access denied, will happen a lot for system processes, even + // when elevated + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + } + + continue; + } + + auto filename = processFilename(h.get()); + if (!filename.empty()) { + v.emplace_back(std::move(filename), pid); + } + } + + return v; +} + +DWORD findOtherPid() +{ + const std::wstring defaultName = L"ModOrganizer.exe"; + + std::wclog << L"looking for the other process...\n"; + + // used to skip the current process below + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + // getting the filename for this process, assumes the other process has the + // smae one + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + // getting all running processes + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + // going through processes, trying to find one with the same name and a + // different pid than this process has + for (const auto& p : processes) { + if (p.filename == filename) { + if (p.pid != thisPid) { + return p.pid; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + // first path to try, without counter in it + std::wstring path = dir + L"\\" + prefix + ext; + + for (int i=0; i; + + +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + + +template +using COMPtr = std::unique_ptr; + + +class Console +{ +public: + Console(); + ~Console(); + +private: + bool m_hasConsole; + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + +// represents the process's environment +// +class Environment +{ +public: + Environment(); + ~Environment(); + + // list of loaded modules in the current process + // + const std::vector& loadedModules() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; + + // information about the installed security products + // + const std::vector& securityProducts() const; + + // information about displays + // + const Metrics& metrics() const; + + // logs the environment + // + void dump() const; + +private: + std::vector m_modules; + std::unique_ptr m_windows; + std::vector m_security; + std::unique_ptr m_metrics; +}; + + +enum class CoreDumpTypes +{ + Mini = 1, + Data, + Full +}; + +// creates a minidump file for the given process +// +bool coredump(CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + +} // namespace env diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp new file mode 100644 index 00000000..a6988909 --- /dev/null +++ b/src/envmetrics.cpp @@ -0,0 +1,224 @@ +#include "envmetrics.h" +#include "env.h" +#include +#include +#include +#include + +namespace env +{ + +using namespace MOBase; + +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + +} // namespace diff --git a/src/envmetrics.h b/src/envmetrics.h new file mode 100644 index 00000000..62fc8c49 --- /dev/null +++ b/src/envmetrics.h @@ -0,0 +1,28 @@ +#include +#include + +namespace env +{ + +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + +} // namespace diff --git a/src/envmodule.cpp b/src/envmodule.cpp new file mode 100644 index 00000000..1717da15 --- /dev/null +++ b/src/envmodule.cpp @@ -0,0 +1,390 @@ +#include "envmodule.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +Module::Module(QString path, std::size_t fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) +{ + const auto fi = getFileInfo(); + + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); + m_versionString = fi.fileDescription; + m_md5 = getMD5(); +} + +const QString& Module::path() const +{ + return m_path; +} + +QString Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + +std::size_t Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Module::version() const +{ + return m_version; +} + +const QString& Module::versionString() const +{ + return m_versionString; +} + +const QDateTime& Module::timestamp() const +{ + return m_timestamp; +} + +const QString& Module::md5() const +{ + return m_md5; +} + +QString Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Module::toString() const +{ + QStringList sl; + + // file size + sl.push_back(displayPath()); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + // version + if (m_version.isEmpty() && m_versionString.isEmpty()) { + sl.push_back("(no version)"); + } else { + if (!m_version.isEmpty()) { + sl.push_back(m_version); + } + + if (!m_versionString.isEmpty() && m_versionString != m_version) { + sl.push_back(versionString()); + } + } + + // timestamp + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + // md5 + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + + return sl.join(", "); +} + +Module::FileInfo Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + // getting version info size + DWORD dummy = 0; + const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); + + if (size == 0) { + const auto e = GetLastError(); + + if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { + // not an error, no version information built into that module + return {}; + } + + qCritical().nospace().noquote() + << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // getting version info + auto buffer = std::make_unique(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "GetFileVersionInfoW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // the version info has two major parts: a fixed version and a localizable + // set of strings + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // the fixed version info is in the root + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = static_cast(valuePointer); + + // signature is always 0xfeef04bd + if (fi->dwSignature != 0xfeef04bd) { + qCritical().nospace().noquote() + << "bad file info signature 0x" << hex << fi->dwSignature << " for " + << "'" << m_path << "'"; + + return {}; + } + + return *fi; +} + +QString Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // getting list of available languages + auto ret = VerQueryValueW( + buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + qCritical().nospace().noquote() + << "VerQueryValueW() for translations failed on '" << m_path << "'"; + + return {}; + } + + // number of languages + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + // using the first language in the list to get FileVersion + const auto* lcp = static_cast(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + static_cast(valuePointer), valueSize - 1); +} + +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const +{ + if (fi.dwSignature == 0) { + return {}; + } + + const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + + if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { + return {}; + } + + return QString("%1.%2.%3.%4") + .arg(major).arg(minor).arg(maintenance).arg(build); +} + +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + // if the file info is invalid or doesn't have a date, use the creation + // time on the file + + // opening the file + HandlePtr h(CreateFileW( + m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + + if (h.get() == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "can't open file '" << m_path << "' for timestamp, " + << formatSystemMessageQ(e); + + return {}; + } + + // getting the file time + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + qCritical().nospace().noquote() + << "can't get file time for '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + } else { + // use the time from the file info + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + // converting to SYSTEMTIME + SYSTEMTIME utc = {}; + + if (!FileTimeToSystemTime(&ft, &utc)) { + qCritical().nospace().noquote() + << "FileTimeToSystemTime() failed on timestamp " + << "high=0x" << hex << ft.dwHighDateTime << " " + << "low=0x" << hex << ft.dwLowDateTime << " for " + << "'" << m_path << "'"; + + return {}; + } + + return QDateTime( + QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); +} + +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 {}; + } + + // opening the file + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + qCritical().nospace().noquote() + << "failed to open file '" << m_path << "' for md5"; + + return {}; + } + + // hashing + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + qCritical().nospace().noquote() + << "failed to calculate md5 for '" << m_path << "'"; + + return {}; + } + + return hash.result().toHex(); +} + + +std::vector getLoadedModules() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "CreateToolhelp32Snapshot() failed, " + << formatSystemMessageQ(e); + + return {}; + } + + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // first module, this shouldn't fail because there's at least the executable + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "Module32First() failed, " << formatSystemMessageQ(e); + + return {}; + } + + std::vector v; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } + + // next module + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + // no more modules is not an error + if (e != ERROR_NO_MORE_FILES) { + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessageQ(e); + } + + break; + } + } + + // sorting by display name + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); + + return v; +} + +} // namespace diff --git a/src/envmodule.h b/src/envmodule.h new file mode 100644 index 00000000..ea1156bd --- /dev/null +++ b/src/envmodule.h @@ -0,0 +1,98 @@ +#include +#include + +namespace env +{ + +// represents one module +// +class Module +{ +public: + explicit Module(QString path, std::size_t fileSize); + + // returns the module's path + // + const QString& path() const; + + // returns the module's path in lowercase and using forward slashes + // + QString displayPath() const; + + // returns the size in bytes, may be 0 + // + std::size_t fileSize() const; + + // returns the x.x.x.x version embedded from the version info, may be empty + // + const QString& version() const; + + // returns the FileVersion entry from the resource file, returns + // "(no version)" if not available + // + const QString& versionString() const; + + // returns the build date from the version info, or the creation time of the + // file on the filesystem, may be empty + // + const QDateTime& timestamp() const; + + // returns the md5 of the file, may be empty for system files + // + const QString& md5() const; + + // converts timestamp() to a string for display, returns "(no timestamp)" if + // not available + // + QString timestampString() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + // contains the information from the version resource + // + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; + + // returns information from the version resource + // + FileInfo getFileInfo() const; + + // uses VS_FIXEDFILEINFO to build the version string + // + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + + // uses the file date from VS_FIXEDFILEINFO if available, or gets the + // creation date on the file + // + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + // returns the md5 hash unless the path contains "\windows\" + // + QString getMD5() const; + + // gets VS_FIXEDFILEINFO from the file version info buffer + // + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + + // gets FileVersion from the file version info buffer + // + QString getFileDescription(std::byte* buffer) const; +}; + + +std::vector getLoadedModules(); + +} // namespace env diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp new file mode 100644 index 00000000..559ce4ad --- /dev/null +++ b/src/envsecurity.cpp @@ -0,0 +1,418 @@ +#include "envsecurity.h" +#include "env.h" +#include + +#include +#include +#include +#include +#pragma comment(lib, "Wbemuuid.lib") + +namespace env +{ + +using namespace MOBase; + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } + + for (;;) + { + COMPtr object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + qCritical() + << "enumerator->next() failed, " << formatSystemMessageQ(ret); + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + +private: + COMPtr m_locator; + COMPtr m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret) || !rawLocator) { + qCritical() + << "CoCreateInstance for WbemLocator failed, " + << formatSystemMessageQ(ret); + + throw failed(); + } + + m_locator.reset(static_cast(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res) || !rawService) { + qCritical() + << "locator->ConnectServer() failed for namespace " + << "'" << QString::fromStdString(ns) << "', " + << formatSystemMessageQ(res); + + throw failed(); + } + + m_service.reset(rawService); + } + + void setSecurity() + { + auto ret = CoSetProxyBlanket( + m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + + if (FAILED(ret)) + { + qCritical() + << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); + + throw failed(); + } + } + + COMPtr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessageQ(ret); + + return {}; + } + + return COMPtr(rawEnumerator); + } +}; + + +SecurityProduct::SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate) : + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) +{ +} + +const QString& SecurityProduct::name() const +{ + return m_name; +} + +int SecurityProduct::provider() const +{ + return m_provider; +} + +bool SecurityProduct::active() const +{ + return m_active; +} + +bool SecurityProduct::upToDate() const +{ + return m_upToDate; +} + +QString SecurityProduct::toString() const +{ + QString s; + + s += m_name + " (" + providerToString() + ")"; + + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} + +QString SecurityProduct::providerToString() const +{ + QStringList ps; + + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + return "doesn't provider anything"; + } + + return ps.join("|"); +} + + +std::vector getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + // display name + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get displayName, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "displayName is a " << prop.vt << ", not a bstr"; + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + // product state + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get productState, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_UI4 && prop.vt != VT_I4) { + qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + return; + } + + DWORD state = 0; + if (prop.vt == VT_I4) { + state = prop.lVal; + } else { + state = prop.ulVal; + } + + VariantClear(&prop); + + // guid + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get instanceGuid, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; + + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); + + map.insert({ + guid, + {guid, QString::fromStdWString(name), provider, active, upToDate}}); + }; + + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector v; + + for (auto&& p : map) { + v.push_back(p.second); + } + + return v; +} + +std::optional getWindowsFirewall() +{ + HRESULT hr = 0; + + COMPtr policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + qCritical() + << "CoCreateInstance for NetFwPolicy2 failed, " + << formatSystemMessageQ(hr); + + return {}; + } + + policy.reset(static_cast(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + qCritical() + << "get_FirewallEnabled failed, " + << formatSystemMessageQ(hr); + + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); +} + + +std::vector getSecurityProducts() +{ + std::vector v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; +} + +} // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h new file mode 100644 index 00000000..200cb531 --- /dev/null +++ b/src/envsecurity.h @@ -0,0 +1,49 @@ +#include +#include + +namespace env +{ + +// represents a security product, such as an antivirus or a firewall +// +class SecurityProduct +{ +public: + SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate); + + // display name of the product + // + const QString& name() const; + + // a bunch of _WSC_SECURITY_PROVIDER flags + // + int provider() const; + + // whether the product is active + // + bool active() const; + + // whether its definitions are up-to-date + // + bool upToDate() const; + + // string representation of the above + // + QString toString() const; + +private: + QUuid m_guid; + QString m_name; + int m_provider; + bool m_active; + bool m_upToDate; + + QString providerToString() const; +}; + + +std::vector getSecurityProducts(); + +} // namespace env diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp new file mode 100644 index 00000000..30ef4633 --- /dev/null +++ b/src/envshortcut.cpp @@ -0,0 +1,376 @@ +#include "envshortcut.h" +#include "env.h" +#include "executableslist.h" +#include "instancemanager.h" +#include + +namespace env +{ + +using namespace MOBase; + +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; + +// just a wrapper around IShellLink operations that throws ShellLinkException +// on errors +// +class ShellLinkWrapper +{ +public: + ShellLinkWrapper() + { + m_link = createShellLink(); + m_file = createPersistFile(); + } + + void setPath(const QString& s) + { + if (s.isEmpty()) { + throw ShellLinkException("path cannot be empty"); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr m_link; + COMPtr m_file; + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); + } + } + + COMPtr createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + throw ShellLinkException("creating IShellLink worked, pointer is null"); + } + + return COMPtr(static_cast(link)); + } + + COMPtr createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + throw ShellLinkException("querying IPersistFile worked, pointer is null"); + } + + return COMPtr(static_cast(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + debug() + << "adding shortcut to " << toString(loc) << ":\n" + << " . name: '" << m_name << "'\n" + << " . target: '" << m_target << "'\n" + << " . arguments: '" << m_arguments << "'\n" + << " . description: '" << m_description << "'\n" + << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" + << " . working directory: '" << m_workingDirectory << "'"; + + if (m_target.isEmpty()) { + critical() << "target is empty"; + return false; + } + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "shorcut file will be saved at '" << path << "'"; + + try + { + ShellLinkWrapper link; + + link.setPath(m_target); + link.setArguments(m_arguments); + link.setDescription(m_description); + link.setIcon(m_icon, m_iconIndex); + link.setWorkingDirectory(m_workingDirectory); + + link.save(path); + + return true; + } + catch(ShellLinkException& e) + { + critical() << e.what() << "\nshortcut file was not saved"; + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "path to shortcut file is '" << path << "'"; + + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; + return false; + } + + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); + + return false; + } + + return true; +} + +QString Shortcut::shortcutPath(Locations loc) const +{ + const auto dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + + const auto file = shortcutFilename(); + if (file.isEmpty()) { + return {}; + } + + return dir + QDir::separator() + file; +} + +QString Shortcut::shortcutDirectory(Locations loc) const +{ + QString dir; + + try + { + switch (loc) + { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + critical() << "bad location " << loc; + break; + } + } + catch(std::exception&) + { + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + critical() << "name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + +} // namespace diff --git a/src/envshortcut.h b/src/envshortcut.h new file mode 100644 index 00000000..904b3ab7 --- /dev/null +++ b/src/envshortcut.h @@ -0,0 +1,114 @@ +#include + +class Executable; + +namespace env +{ + +// an application shortcut that can be either on the desktop or the start menu +// +class Shortcut +{ +public: + // location of a shortcut + // + enum Locations + { + None = 0, + + // on the desktop + Desktop, + + // in the start menu + StartMenu + }; + + + // empty shortcut + // + Shortcut(); + + // shortcut from an executable + // + explicit Shortcut(const Executable& exe); + + // sets the name of the shortcut, shown on icons and start menu entries + // + Shortcut& name(const QString& s); + + // the program to start + // + Shortcut& target(const QString& s); + + // arguments to pass + // + Shortcut& arguments(const QString& s); + + // shows in the status bar of explorer, for example + // + Shortcut& description(const QString& s); + + // path to a binary that contains the icon and its index + // + Shortcut& icon(const QString& s, int index=0); + + // "start in" option for this shortcut + // + Shortcut& workingDirectory(const QString& s); + + + // returns whether this shortcut already exists at the given location; this + // does not check whether the shortcut parameters are different, it merely if + // the .lnk file exists + // + bool exists(Locations loc) const; + + // calls remove() if exists(), or add() + // + bool toggle(Locations loc); + + // adds the shortcut to the given location + // + bool add(Locations loc); + + // removes the shortcut from the given location + // + bool remove(Locations loc); + +private: + QString m_name; + QString m_target; + QString m_arguments; + QString m_description; + QString m_icon; + int m_iconIndex; + QString m_workingDirectory; + + // returns a qCritical() logger with a prefix already logged + // + QDebug critical() const; + + // returns a qDebug() logger with a prefix already logged + // + QDebug debug() const; + + + // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + QString shortcutDirectory(Locations loc) const; + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; +}; + + +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + +} // namespace diff --git a/src/envwindows.cpp b/src/envwindows.cpp new file mode 100644 index 00000000..718cf2ce --- /dev/null +++ b/src/envwindows.cpp @@ -0,0 +1,236 @@ +#include "envwindows.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +WindowsInfo::WindowsInfo() +{ + // loading ntdll.dll, the functions will be found with GetProcAddress() + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); +} + +bool WindowsInfo::compatibilityMode() const +{ + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode + return false; + } + + return (m_real != m_reported); +} + +const WindowsInfo::Version& WindowsInfo::reportedVersion() const +{ + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional WindowsInfo::isElevated() const +{ + return m_elevated; +} + +QString WindowsInfo::toString() const +{ + QStringList sl; + + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); + + // version + sl.push_back("version " + reported); + + // real version if different + if (compatibilityMode()) { + sl.push_back("real version " + real); + } + + // build.UBR, such as 17763.557 + if (m_release.UBR != 0) { + DWORD build = 0; + + if (compatibilityMode()) { + build = m_real.build; + } else { + build = m_reported.build; + } + + sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); + } + + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); + } + + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); + } + + // product name + if (!m_release.productName.isEmpty()) { + sl.push_back(m_release.productName); + } + + // elevated + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const +{ + // windows has been deprecating pretty much all the functions having to do + // with getting version information because apparently, people keep misusing + // them for feature detection + // + // there's still RtlGetVersion() though + + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + qCritical() << "RtlGetVersion() not found in ntdll.dll"; + return {}; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + // this apparently never fails + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; +} + +WindowsInfo::Release WindowsInfo::getRelease() const +{ + // there are several interesting items in the registry, but most of them + // are undocumented, not always available, and localizable + // + // most of them are used to provide as much information as possible in case + // any of the other versions fail to work + + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + Release r; + + // buildlab seems to be an internal name from the build system + r.buildLab = settings.value("BuildLabEx", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildLab", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildBranch", "").toString(); + } + } + + // localized name of windows, such as "Windows 10 Pro" + r.productName = settings.value("ProductName", "").toString(); + + // release ID, such as 1803 + r.ID = settings.value("ReleaseId", "").toString(); + + // some other build number, shown in winver.exe + r.UBR = settings.value("UBR", 0).toUInt(); + + return r; +} + +std::optional WindowsInfo::getElevated() const +{ + HandlePtr token; + + { + HANDLE rawToken = 0; + + if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "OpenProcessToken() failed: " << formatSystemMessageQ(e); + + return {}; + } + + token.reset(rawToken); + } + + TOKEN_ELEVATION e = {}; + DWORD size = sizeof(TOKEN_ELEVATION); + + if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "GetTokenInformation() failed: " << formatSystemMessageQ(e); + + return {}; + } + + return (e.TokenIsElevated != 0); +} + +} // namespace diff --git a/src/envwindows.h b/src/envwindows.h new file mode 100644 index 00000000..c23f99f4 --- /dev/null +++ b/src/envwindows.h @@ -0,0 +1,106 @@ +#include +#include + +namespace env +{ + +// a variety of information on windows +// +class WindowsInfo +{ +public: + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // + bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional isElevated() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + Version m_reported, m_real; + Release m_release; + std::optional m_elevated; + + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional getElevated() const; +}; + +} // namespace diff --git a/src/main.cpp b/src/main.cpp index 09da9408..5c5ce945 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,8 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "moshortcut.h" #include "organizercore.h" +#include "env.h" +#include "envmodule.h" #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6c2ab389..87ee2c8e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,6 +86,7 @@ along with Mod Organizer. If not, see . #include #include "localsavegames.h" #include "listdialog.h" +#include "envshortcut.h" #include #include diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 4ee4b766..07983e12 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,35 +19,9 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" -#include "error_report.h" -#include "executableslist.h" -#include "instancemanager.h" -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#pragma comment(lib, "Wbemuuid.lib") - -using namespace MOBase; -namespace fs = std::filesystem; - -namespace MOShared { +namespace MOShared +{ bool FileExists(const std::string &filename) { @@ -269,2097 +243,4 @@ MOBase::VersionInfo createVersionInfo() } } - -namespace env -{ - -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr; - - -struct LibraryFreer -{ - using pointer = HINSTANCE; - - void operator()(HINSTANCE h) - { - if (h != 0) { - ::FreeLibrary(h); - } - } -}; - -struct COMReleaser -{ - void operator()(IUnknown* p) - { - if (p) { - p->Release(); - } - } -}; - - -template -using COMPtr = std::unique_ptr; - - -class ShellLinkException -{ -public: - ShellLinkException(QString s) - : m_what(std::move(s)) - { - } - - const QString& what() const - { - return m_what; - } - -private: - QString m_what; -}; - -// just a wrapper around IShellLink operations that throws ShellLinkException -// on errors -// -class ShellLinkWrapper -{ -public: - ShellLinkWrapper() - { - m_link = createShellLink(); - m_file = createPersistFile(); - } - - void setPath(const QString& s) - { - if (s.isEmpty()) { - throw ShellLinkException("path cannot be empty"); - } - - const auto r = m_link->SetPath(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set target path '%1'").arg(s)); - } - - void setArguments(const QString& s) - { - const auto r = m_link->SetArguments(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); - } - - void setDescription(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetDescription(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set description '%1'").arg(s)); - } - - void setIcon(const QString& file, int i) - { - if (file.isEmpty()) { - return; - } - - const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); - throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); - } - - void setWorkingDirectory(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); - } - - void save(const QString& path) - { - const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); - throwOnFail(r, QString("failed to save link '%1'").arg(path)); - } - -private: - COMPtr m_link; - COMPtr m_file; - - void throwOnFail(HRESULT r, const QString& s) - { - if (FAILED(r)) { - throw ShellLinkException(QString("%1, %2") - .arg(s) - .arg(formatSystemMessageQ(r))); - } - } - - COMPtr createShellLink() - { - void* link = nullptr; - - const auto r = CoCreateInstance( - CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, &link); - - throwOnFail(r, "failed to create IShellLink instance"); - - if (!link) { - throw ShellLinkException("creating IShellLink worked, pointer is null"); - } - - return COMPtr(static_cast(link)); - } - - COMPtr createPersistFile() - { - void* file = nullptr; - - const auto r = m_link->QueryInterface(IID_IPersistFile, &file); - throwOnFail(r, "failed to get IPersistFile interface"); - - if (!file) { - throw ShellLinkException("querying IPersistFile worked, pointer is null"); - } - - return COMPtr(static_cast(file)); - } -}; - - -Console::Console() - : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) -{ - // open a console - if (!AllocConsole()) { - // failed, ignore - } - - m_hasConsole = true; - - // redirect stdin, stdout and stderr to it - freopen_s(&m_in, "CONIN$", "r", stdin); - freopen_s(&m_out, "CONOUT$", "w", stdout); - freopen_s(&m_err, "CONOUT$", "w", stderr); -} - -Console::~Console() -{ - // close redirected handles and redirect standard stream to NUL in case - // they're used after this - - if (m_err) { - std::fclose(m_err); - freopen_s(&m_err, "NUL", "w", stderr); - } - - if (m_out) { - std::fclose(m_out); - freopen_s(&m_out, "NUL", "w", stdout); - } - - if (m_in) { - std::fclose(m_in); - freopen_s(&m_in, "NUL", "r", stdin); - } - - // close console - if (m_hasConsole) { - FreeConsole(); - } -} - - -Shortcut::Shortcut() - : m_iconIndex(0) -{ -} - -Shortcut::Shortcut(const Executable& exe) - : Shortcut() -{ - m_name = exe.title(); - m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); - - m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()) - .arg(exe.title()); - - m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); - - if (exe.usesOwnIcon()) { - m_icon = exe.binaryInfo().absoluteFilePath(); - } - - m_workingDirectory = qApp->applicationDirPath(); -} - -Shortcut& Shortcut::name(const QString& s) -{ - m_name = s; - return *this; -} - -Shortcut& Shortcut::target(const QString& s) -{ - m_target = s; - return *this; -} - -Shortcut& Shortcut::arguments(const QString& s) -{ - m_arguments = s; - return *this; -} - -Shortcut& Shortcut::description(const QString& s) -{ - m_description = s; - return *this; -} - -Shortcut& Shortcut::icon(const QString& s, int index) -{ - m_icon = s; - m_iconIndex = index; - return *this; -} - -Shortcut& Shortcut::workingDirectory(const QString& s) -{ - m_workingDirectory = s; - return *this; -} - -bool Shortcut::exists(Locations loc) const -{ - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - return QFileInfo(path).exists(); -} - -bool Shortcut::toggle(Locations loc) -{ - if (exists(loc)) { - return remove(loc); - } else { - return add(loc); - } -} - -bool Shortcut::add(Locations loc) -{ - debug() - << "adding shortcut to " << toString(loc) << ":\n" - << " . name: '" << m_name << "'\n" - << " . target: '" << m_target << "'\n" - << " . arguments: '" << m_arguments << "'\n" - << " . description: '" << m_description << "'\n" - << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" - << " . working directory: '" << m_workingDirectory << "'"; - - if (m_target.isEmpty()) { - critical() << "target is empty"; - return false; - } - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "shorcut file will be saved at '" << path << "'"; - - try - { - ShellLinkWrapper link; - - link.setPath(m_target); - link.setArguments(m_arguments); - link.setDescription(m_description); - link.setIcon(m_icon, m_iconIndex); - link.setWorkingDirectory(m_workingDirectory); - - link.save(path); - - return true; - } - catch(ShellLinkException& e) - { - critical() << e.what() << "\nshortcut file was not saved"; - } - - return false; -} - -bool Shortcut::remove(Locations loc) -{ - debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "path to shortcut file is '" << path << "'"; - - if (!QFile::exists(path)) { - critical() << "can't remove '" << path << "', file not found"; - return false; - } - - if (!MOBase::shellDelete({path})) { - const auto e = ::GetLastError(); - - critical() - << "failed to remove '" << path << "', " - << formatSystemMessageQ(e); - - return false; - } - - return true; -} - -QString Shortcut::shortcutPath(Locations loc) const -{ - const auto dir = shortcutDirectory(loc); - if (dir.isEmpty()) { - return {}; - } - - const auto file = shortcutFilename(); - if (file.isEmpty()) { - return {}; - } - - return dir + QDir::separator() + file; -} - -QString Shortcut::shortcutDirectory(Locations loc) const -{ - QString dir; - - try - { - switch (loc) - { - case Desktop: - dir = MOBase::getDesktopDirectory(); - break; - - case StartMenu: - dir = MOBase::getStartMenuDirectory(); - break; - - case None: - default: - critical() << "bad location " << loc; - break; - } - } - catch(std::exception&) - { - } - - return QDir::toNativeSeparators(dir); -} - -QString Shortcut::shortcutFilename() const -{ - if (m_name.isEmpty()) { - critical() << "name is empty"; - return {}; - } - - return m_name + ".lnk"; -} - -QDebug Shortcut::debug() const -{ - return qDebug().noquote().nospace() << "system shortcut: "; -} - -QDebug Shortcut::critical() const -{ - return qCritical().noquote().nospace() << "system shortcut: "; -} - - -QString toString(Shortcut::Locations loc) -{ - switch (loc) - { - case Shortcut::None: - return "none"; - - case Shortcut::Desktop: - return "desktop"; - - case Shortcut::StartMenu: - return "start menu"; - - default: - return QString("? (%1)").arg(static_cast(loc)); - } -} - - - -class WMI -{ -public: - class failed {}; - - WMI(const std::string& ns) - { - try - { - createLocator(); - createService(ns); - setSecurity(); - } - catch(failed&) - { - } - } - - template - void query(const std::string& q, F&& f) - { - if (!m_locator || !m_service) { - return; - } - - auto enumerator = getEnumerator(q); - if (!enumerator) { - return; - } - - for (;;) - { - COMPtr object; - - { - IWbemClassObject* rawObject = nullptr; - ULONG count = 0; - auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); - - if (count == 0 || !rawObject) { - break; - } - - if (FAILED(ret)) { - qCritical() - << "enumerator->next() failed, " << formatSystemMessageQ(ret); - break; - } - - object.reset(rawObject); - } - - f(object.get()); - } - } - -private: - COMPtr m_locator; - COMPtr m_service; - - void createLocator() - { - void* rawLocator = nullptr; - - const auto ret = CoCreateInstance( - CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, - IID_IWbemLocator, &rawLocator); - - if (FAILED(ret) || !rawLocator) { - qCritical() - << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessageQ(ret); - - throw failed(); - } - - m_locator.reset(static_cast(rawLocator)); - } - - void createService(const std::string& ns) - { - IWbemServices* rawService = nullptr; - - const auto res = m_locator->ConnectServer( - _bstr_t(ns.c_str()), - nullptr, nullptr, nullptr, 0, nullptr, nullptr, - &rawService); - - if (FAILED(res) || !rawService) { - qCritical() - << "locator->ConnectServer() failed for namespace " - << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessageQ(res); - - throw failed(); - } - - m_service.reset(rawService); - } - - void setSecurity() - { - auto ret = CoSetProxyBlanket( - m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, - RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); - - if (FAILED(ret)) - { - qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); - - throw failed(); - } - } - - COMPtr getEnumerator( - const std::string& query) - { - IEnumWbemClassObject* rawEnumerator = NULL; - - auto ret = m_service->ExecQuery( - bstr_t("WQL"), - bstr_t(query.c_str()), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &rawEnumerator); - - if (FAILED(ret) || !rawEnumerator) - { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - - return {}; - } - - return COMPtr(rawEnumerator); - } -}; - - -class DisplayEnumerator -{ -public: - DisplayEnumerator() - : m_GetDpiForMonitor(nullptr) - { - m_shcore.reset(LoadLibraryW(L"Shcore.dll")); - - if (m_shcore) { - // windows 8.1+ only - m_GetDpiForMonitor = reinterpret_cast( - GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); - } - - // gets all monitors and the device they're running on - getDisplayDevices(); - } - - std::vector&& displays() && - { - return std::move(m_displays); - } - - const std::vector& displays() const & - { - return m_displays; - } - -private: - using GetDpiForMonitorFunction = - HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - std::unique_ptr m_shcore; - GetDpiForMonitorFunction* m_GetDpiForMonitor; - std::vector m_displays; - - void getDisplayDevices() - { - // don't bother if it goes over 100 - for (int i=0; i<100; ++i) { - DISPLAY_DEVICEW device = {}; - device.cb = sizeof(device); - - if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { - // no more - break; - } - - // EnumDisplayDevices() seems to be returning a lot of devices that are - // not actually monitors, but those don't have the - // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set - if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { - continue; - } - - m_displays.push_back(createDisplay(device)); - } - } - - Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) - { - Metrics::Display d; - - d.adapter = QString::fromWCharArray(device.DeviceString); - d.monitor = QString::fromWCharArray(device.DeviceName); - d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); - - getDisplaySettings(device.DeviceName, d); - getDpi(d); - - return d; - } - - void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) - { - DEVMODEW dm = {}; - dm.dmSize = sizeof(dm); - - if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { - log::error("EnumDisplaySettings() failed for '{}'", d.monitor); - return; - } - - // all these fields should be available - - if (dm.dmFields & DM_DISPLAYFREQUENCY) { - d.refreshRate = dm.dmDisplayFrequency; - } - - if (dm.dmFields & DM_PELSWIDTH) { - d.resX = dm.dmPelsWidth; - } - - if (dm.dmFields & DM_PELSHEIGHT) { - d.resY = dm.dmPelsHeight; - } - } - - void getDpi(Metrics::Display& d) - { - if (!m_GetDpiForMonitor) { - // this happens on windows 7, get the desktop dpi instead - getDesktopDpi(d); - return; - } - - // there's no way to get an HMONITOR from a device name, so all monitors - // will have to be enumerated and their name checked - HMONITOR hm = findMonitor(d.monitor); - if (!hm) { - log::error("can't get dpi for monitor '{}', not found", d.monitor); - return; - } - - UINT dpiX=0, dpiY=0; - const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - - if (FAILED(r)) { - log::error( - "GetDpiForMonitor() failed for '{}', {}", - d.monitor, formatSystemMessageQ(r)); - - return; - } - - // dpiX and dpiY are always identical, as per the documentation - d.dpi = dpiX; - } - - void getDesktopDpi(Metrics::Display& d) - { - // desktop dc - HDC dc = GetDC(0); - - if (!dc) { - const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); - return; - } - - d.dpi = GetDeviceCaps(dc, LOGPIXELSX); - - ReleaseDC(0, dc); - } - - HMONITOR findMonitor(const QString& name) - { - // passed to the enumeration callback - struct Data - { - DisplayEnumerator* self; - QString name; - HMONITOR hm; - }; - - Data data = {this, name, 0}; - - // for each monitor - EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { - auto& data = *reinterpret_cast(lp); - - MONITORINFOEX mi = {}; - mi.cbSize = sizeof(mi); - - // monitor info will include the name - if (!GetMonitorInfoW(hm, &mi)) { - const auto e = GetLastError(); - log::error( - "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); - - // error for this monitor, but continue - return TRUE; - } - - if (QString::fromWCharArray(mi.szDevice) == data.name) { - // found, stop - data.hm = hm; - return FALSE; - } - - // not found, continue to the next monitor - return TRUE; - }, reinterpret_cast(&data)); - - return data.hm; - } -}; - - -Environment::Environment() -{ - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); -} - -const std::vector& Environment::loadedModules() const -{ - return m_modules; -} - -const WindowsInfo& Environment::windowsInfo() const -{ - return m_windows; -} - -const std::vector& Environment::securityProducts() const -{ - return m_security; -} - -const Metrics& Environment::metrics() const -{ - return m_metrics; -} - -void Environment::dump() const -{ - log::debug("windows: {}", windowsInfo().toString()); - - 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()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : loadedModules()) { - log::debug(" . {}", m.toString()); - } - - log::debug("displays:"); - for (const auto& d : m_metrics.displays()) { - log::debug(" . {}", d.toString()); - } -} - -std::vector Environment::getLoadedModules() const -{ - HandlePtr snapshot(CreateToolhelp32Snapshot( - TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); - - if (snapshot.get() == INVALID_HANDLE_VALUE) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessageQ(e); - - return {}; - } - - MODULEENTRY32 me = {}; - me.dwSize = sizeof(me); - - // first module, this shouldn't fail because there's at least the executable - if (!Module32First(snapshot.get(), &me)) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessageQ(e); - - return {}; - } - - std::vector v; - - for (;;) - { - const auto path = QString::fromWCharArray(me.szExePath); - if (!path.isEmpty()) { - v.push_back(Module(path, me.modBaseSize)); - } - - // next module - if (!Module32Next(snapshot.get(), &me)) { - const auto e = GetLastError(); - - // no more modules is not an error - if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessageQ(e); - } - - break; - } - } - - // sorting by display name - std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { - return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); - }); - - return v; -} - -std::vector Environment::getSecurityProducts() const -{ - std::vector v; - - { - auto fromWMI = getSecurityProductsFromWMI(); - v.insert( - v.end(), - std::make_move_iterator(fromWMI.begin()), - std::make_move_iterator(fromWMI.end())); - } - - if (auto p=getWindowsFirewall()) { - v.push_back(std::move(*p)); - } - - return v; -} - -std::vector Environment::getSecurityProductsFromWMI() const -{ - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map map; - - auto handleProduct = [&](auto* o) { - VARIANT prop; - - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get displayName, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "displayName is a " << prop.vt << ", not a bstr"; - return; - } - - const std::wstring name = prop.bstrVal; - VariantClear(&prop); - - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get productState, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; - return; - } - - DWORD state = 0; - if (prop.vt == VT_I4) { - state = prop.lVal; - } else { - state = prop.ulVal; - } - - VariantClear(&prop); - - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get instanceGuid, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; - return; - } - - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); - - const auto provider = static_cast((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; - - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); - - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); - }; - - { - WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - { - WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - std::vector v; - - for (auto&& p : map) { - v.push_back(p.second); - } - - return v; -} - -std::optional Environment::getWindowsFirewall() const -{ - HRESULT hr = 0; - - COMPtr policy; - - { - void* rawPolicy = nullptr; - - hr = CoCreateInstance( - __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, - __uuidof(INetFwPolicy2), &rawPolicy); - - if (FAILED(hr) || !rawPolicy) { - qCritical() - << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessageQ(hr); - - return {}; - } - - policy.reset(static_cast(rawPolicy)); - } - - VARIANT_BOOL enabledVariant; - - if (policy) { - hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); - if (FAILED(hr)) - { - qCritical() - << "get_FirewallEnabled failed, " - << formatSystemMessageQ(hr); - - return {}; - } - } - - const auto enabled = (enabledVariant != VARIANT_FALSE); - if (!enabled) { - return {}; - } - - return SecurityProduct( - {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); -} - - -Metrics::Metrics() -{ - m_displays = DisplayEnumerator().displays(); -} - -const std::vector& Metrics::displays() const -{ - return m_displays; -} - -QString Metrics::Display::toString() const -{ - return QString("%1*%2 %3hz dpi=%4 on %5%6") - .arg(resX) - .arg(resY) - .arg(refreshRate) - .arg(dpi) - .arg(adapter) - .arg(primary ? " (primary)" : ""); -} - - -Module::Module(QString path, std::size_t fileSize) - : m_path(std::move(path)), m_fileSize(fileSize) -{ - const auto fi = getFileInfo(); - - m_version = getVersion(fi.ffi); - m_timestamp = getTimestamp(fi.ffi); - m_versionString = fi.fileDescription; - m_md5 = getMD5(); -} - -const QString& Module::path() const -{ - return m_path; -} - -QString Module::displayPath() const -{ - return QDir::fromNativeSeparators(m_path.toLower()); -} - -std::size_t Module::fileSize() const -{ - return m_fileSize; -} - -const QString& Module::version() const -{ - return m_version; -} - -const QString& Module::versionString() const -{ - return m_versionString; -} - -const QDateTime& Module::timestamp() const -{ - return m_timestamp; -} - -const QString& Module::md5() const -{ - return m_md5; -} - -QString Module::timestampString() const -{ - if (!m_timestamp.isValid()) { - return "(no timestamp)"; - } - - return m_timestamp.toString(Qt::DateFormat::ISODate); -} - -QString Module::toString() const -{ - QStringList sl; - - // file size - sl.push_back(displayPath()); - sl.push_back(QString("%1 B").arg(m_fileSize)); - - // version - if (m_version.isEmpty() && m_versionString.isEmpty()) { - sl.push_back("(no version)"); - } else { - if (!m_version.isEmpty()) { - sl.push_back(m_version); - } - - if (!m_versionString.isEmpty() && m_versionString != m_version) { - sl.push_back(versionString()); - } - } - - // timestamp - if (m_timestamp.isValid()) { - sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); - } else { - sl.push_back("(no timestamp)"); - } - - // md5 - if (!m_md5.isEmpty()) { - sl.push_back(m_md5); - } - - return sl.join(", "); -} - -Module::FileInfo Module::getFileInfo() const -{ - const auto wspath = m_path.toStdWString(); - - // getting version info size - DWORD dummy = 0; - const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); - - if (size == 0) { - const auto e = GetLastError(); - - if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { - // not an error, no version information built into that module - return {}; - } - - qCritical().nospace().noquote() - << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // getting version info - auto buffer = std::make_unique(size); - - if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // the version info has two major parts: a fixed version and a localizable - // set of strings - - FileInfo fi; - fi.ffi = getFixedFileInfo(buffer.get()); - fi.fileDescription = getFileDescription(buffer.get()); - - return fi; -} - -VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const -{ - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // the fixed version info is in the root - const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no fixed file info - return {}; - } - - const auto* fi = static_cast(valuePointer); - - // signature is always 0xfeef04bd - if (fi->dwSignature != 0xfeef04bd) { - qCritical().nospace().noquote() - << "bad file info signature 0x" << hex << fi->dwSignature << " for " - << "'" << m_path << "'"; - - return {}; - } - - return *fi; -} - -QString Module::getFileDescription(std::byte* buffer) const -{ - struct LANGANDCODEPAGE - { - WORD wLanguage; - WORD wCodePage; - }; - - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // getting list of available languages - auto ret = VerQueryValueW( - buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - qCritical().nospace().noquote() - << "VerQueryValueW() for translations failed on '" << m_path << "'"; - - return {}; - } - - // number of languages - const auto count = valueSize / sizeof(LANGANDCODEPAGE); - if (count == 0) { - return {}; - } - - // using the first language in the list to get FileVersion - const auto* lcp = static_cast(valuePointer); - - const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") - .arg(lcp->wLanguage, 4, 16, QChar('0')) - .arg(lcp->wCodePage, 4, 16, QChar('0')); - - ret = VerQueryValueW( - buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no file version - return {}; - } - - // valueSize includes the null terminator - return QString::fromWCharArray( - static_cast(valuePointer), valueSize - 1); -} - -QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const -{ - if (fi.dwSignature == 0) { - return {}; - } - - const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; - const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; - const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; - const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; - - if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { - return {}; - } - - return QString("%1.%2.%3.%4") - .arg(major).arg(minor).arg(maintenance).arg(build); -} - -QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const -{ - FILETIME ft = {}; - - if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { - // if the file info is invalid or doesn't have a date, use the creation - // time on the file - - // opening the file - HandlePtr h(CreateFileW( - m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); - - if (h.get() == INVALID_HANDLE_VALUE) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessageQ(e); - - return {}; - } - - // getting the file time - if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { - const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't get file time for '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - } else { - // use the time from the file info - ft.dwHighDateTime = fi.dwFileDateMS; - ft.dwLowDateTime = fi.dwFileDateLS; - } - - - // converting to SYSTEMTIME - SYSTEMTIME utc = {}; - - if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical().nospace().noquote() - << "FileTimeToSystemTime() failed on timestamp " - << "high=0x" << hex << ft.dwHighDateTime << " " - << "low=0x" << hex << ft.dwLowDateTime << " for " - << "'" << m_path << "'"; - - return {}; - } - - return QDateTime( - QDate(utc.wYear, utc.wMonth, utc.wDay), - QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); -} - -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 {}; - } - - // opening the file - QFile f(m_path); - - if (!f.open(QFile::ReadOnly)) { - qCritical().nospace().noquote() - << "failed to open file '" << m_path << "' for md5"; - - return {}; - } - - // hashing - QCryptographicHash hash(QCryptographicHash::Md5); - if (!hash.addData(&f)) { - qCritical().nospace().noquote() - << "failed to calculate md5 for '" << m_path << "'"; - - return {}; - } - - return hash.result().toHex(); -} - - -WindowsInfo::WindowsInfo() -{ - // loading ntdll.dll, the functions will be found with GetProcAddress() - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); - - if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; - return; - } else { - m_reported = getReportedVersion(ntdll.get()); - m_real = getRealVersion(ntdll.get()); - } - - m_release = getRelease(); - m_elevated = getElevated(); -} - -bool WindowsInfo::compatibilityMode() const -{ - if (m_real == Version()) { - // don't know the real version, can't guess compatibility mode - return false; - } - - return (m_real != m_reported); -} - -const WindowsInfo::Version& WindowsInfo::reportedVersion() const -{ - return m_reported; -} - -const WindowsInfo::Version& WindowsInfo::realVersion() const -{ - return m_real; -} - -const WindowsInfo::Release& WindowsInfo::release() const -{ - return m_release; -} - -std::optional WindowsInfo::isElevated() const -{ - return m_elevated; -} - -QString WindowsInfo::toString() const -{ - QStringList sl; - - const QString reported = m_reported.toString(); - const QString real = m_real.toString(); - - // version - sl.push_back("version " + reported); - - // real version if different - if (compatibilityMode()) { - sl.push_back("real version " + real); - } - - // build.UBR, such as 17763.557 - if (m_release.UBR != 0) { - DWORD build = 0; - - if (compatibilityMode()) { - build = m_real.build; - } else { - build = m_reported.build; - } - - sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); - } - - // release ID - if (!m_release.ID.isEmpty()) { - sl.push_back("release " + m_release.ID); - } - - // buildlab string - if (!m_release.buildLab.isEmpty()) { - sl.push_back(m_release.buildLab); - } - - // product name - if (!m_release.productName.isEmpty()) { - sl.push_back(m_release.productName); - } - - // elevated - QString elevated = "?"; - if (m_elevated.has_value()) { - elevated = (*m_elevated ? "yes" : "no"); - } - - sl.push_back("elevated: " + elevated); - - return sl.join(", "); -} - -WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const -{ - // windows has been deprecating pretty much all the functions having to do - // with getting version information because apparently, people keep misusing - // them for feature detection - // - // there's still RtlGetVersion() though - - using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); - - auto* RtlGetVersion = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetVersion")); - - if (!RtlGetVersion) { - qCritical() << "RtlGetVersion() not found in ntdll.dll"; - return {}; - } - - OSVERSIONINFOEX vi = {}; - vi.dwOSVersionInfoSize = sizeof(vi); - - // this apparently never fails - RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); - - return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; -} - -WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const -{ - // getting the actual windows version is more difficult because all the - // functions are lying when running in compatibility mode - // - // RtlGetNtVersionNumbers() is an undocumented function that seems to work - // fine, but it might not in the future - - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); - - auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); - - if (!RtlGetNtVersionNumbers) { - qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; - return {}; - } - - DWORD major=0, minor=0, build=0; - RtlGetNtVersionNumbers(&major, &minor, &build); - - // for whatever reason, the build number has 0xf0000000 set - build = 0x0fffffff & build; - - return {major, minor, build}; -} - -WindowsInfo::Release WindowsInfo::getRelease() const -{ - // there are several interesting items in the registry, but most of them - // are undocumented, not always available, and localizable - // - // most of them are used to provide as much information as possible in case - // any of the other versions fail to work - - QSettings settings( - R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", - QSettings::NativeFormat); - - Release r; - - // buildlab seems to be an internal name from the build system - r.buildLab = settings.value("BuildLabEx", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildLab", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildBranch", "").toString(); - } - } - - // localized name of windows, such as "Windows 10 Pro" - r.productName = settings.value("ProductName", "").toString(); - - // release ID, such as 1803 - r.ID = settings.value("ReleaseId", "").toString(); - - // some other build number, shown in winver.exe - r.UBR = settings.value("UBR", 0).toUInt(); - - return r; -} - -std::optional WindowsInfo::getElevated() const -{ - HandlePtr token; - - { - HANDLE rawToken = 0; - - if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessageQ(e); - - return {}; - } - - token.reset(rawToken); - } - - TOKEN_ELEVATION e = {}; - DWORD size = sizeof(TOKEN_ELEVATION); - - if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessageQ(e); - - return {}; - } - - return (e.TokenIsElevated != 0); -} - - -SecurityProduct::SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate) : - m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), - m_active(active), m_upToDate(upToDate) -{ -} - -const QString& SecurityProduct::name() const -{ - return m_name; -} - -int SecurityProduct::provider() const -{ - return m_provider; -} - -bool SecurityProduct::active() const -{ - return m_active; -} - -bool SecurityProduct::upToDate() const -{ - return m_upToDate; -} - -QString SecurityProduct::toString() const -{ - QString s; - - s += m_name + " (" + providerToString() + ")"; - - if (!m_active) { - s += ", inactive"; - } - - if (!m_upToDate) { - s += ", definitions outdated"; - } - - if (!m_guid.isNull()) { - s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); - } - - return s; -} - -QString SecurityProduct::providerToString() const -{ - QStringList ps; - - if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { - ps.push_back("firewall"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { - ps.push_back("autoupdate"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { - ps.push_back("antivirus"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { - ps.push_back("antispyware"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { - ps.push_back("settings"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { - ps.push_back("uac"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { - ps.push_back("service"); - } - - if (ps.empty()) { - return "doesn't provider anything"; - } - - return ps.join("|"); -} - - -struct Process -{ - std::wstring filename; - DWORD pid; - - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) - { - } -}; - -// returns the filename of the given process or the current one -// -std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) -{ - // double the buffer size 10 times - const int MaxTries = 10; - - DWORD bufferSize = MAX_PATH; - - for (int tries=0; tries(bufferSize + 1); - std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); - - DWORD writtenSize = 0; - - if (process == INVALID_HANDLE_VALUE) { - // query this process - writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); - } else { - // query another process - writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); - } - - if (writtenSize == 0) { - // hard failure - const auto e = GetLastError(); - std::wcerr << formatSystemMessage(e) << L"\n"; - break; - } else if (writtenSize >= bufferSize) { - // buffer is too small, try again - bufferSize *= 2; - } else { - // if GetModuleFileName() works, `writtenSize` does not include the null - // terminator - const std::wstring s(buffer.get(), writtenSize); - const fs::path path(s); - - return path.filename().native(); - } - } - - // something failed or the path is way too long to make sense - - std::wstring what; - if (process == INVALID_HANDLE_VALUE) { - what = L"the current process"; - } else { - what = L"pid " + std::to_wstring(reinterpret_cast(process)); - } - - std::wcerr << L"failed to get filename for " << what << L"\n"; - return {}; -} - -std::vector runningProcessesIds() -{ - // double the buffer size 10 times - const int MaxTries = 10; - - // initial size of 300 processes, unlikely to be more than that - std::size_t size = 300; - - for (int tries=0; tries(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast(size * sizeof(ids[0])); - DWORD bytesWritten = 0; - - if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) - { - const auto e = GetLastError(); - - std::wcerr - << L"failed to enumerate processes, " - << formatSystemMessage(e) << L"\n"; - - return {}; - } - - if (bytesWritten == bytesGiven) { - // no way to distinguish between an exact fit and not enough space, - // just try again - size *= 2; - continue; - } - - const auto count = bytesWritten / sizeof(ids[0]); - return std::vector(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector v; - - for (const auto& pid : pids) { - if (pid == 0) { - // the idle process has pid 0 and seems to be picked up by EnumProcesses() - continue; - } - - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); - - if (!h) { - const auto e = GetLastError(); - - if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot for system processes, even - // when elevated - std::wcerr - << L"failed to open process " << pid << L", " - << formatSystemMessage(e) << L"\n"; - } - - continue; - } - - auto filename = processFilename(h.get()); - if (!filename.empty()) { - v.emplace_back(std::move(filename), pid); - } - } - - return v; -} - -DWORD findOtherPid() -{ - const std::wstring defaultName = L"ModOrganizer.exe"; - - std::wclog << L"looking for the other process...\n"; - - // used to skip the current process below - const auto thisPid = GetCurrentProcessId(); - std::wclog << L"this process id is " << thisPid << L"\n"; - - // getting the filename for this process, assumes the other process has the - // smae one - auto filename = processFilename(); - if (filename.empty()) { - std::wcerr - << L"can't get current process filename, defaulting to " - << defaultName << L"\n"; - - filename = defaultName; - } else { - std::wclog << L"this process filename is " << filename << L"\n"; - } - - // getting all running processes - const auto processes = runningProcesses(); - std::wclog << L"there are " << processes.size() << L" processes running\n"; - - // going through processes, trying to find one with the same name and a - // different pid than this process has - for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; - } - } - } - - std::wclog - << L"no process with this filename\n" - << L"MO may not be running, or it may be running as administrator\n" - << L"you can try running this again as administrator\n"; - - return 0; -} - -std::wstring tempDir() -{ - const DWORD bufferSize = MAX_PATH + 1; - wchar_t buffer[bufferSize + 1] = {}; - - const auto written = GetTempPathW(bufferSize, buffer); - if (written == 0) { - const auto e = GetLastError(); - - std::wcerr - << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; - - return {}; - } - - // `written` does not include the null terminator - return std::wstring(buffer, buffer + written); -} - -HandlePtr tempFile(const std::wstring dir) -{ - // maximum tries of incrementing the counter - const int MaxTries = 100; - - // UTC time and date will be in the filename - const auto now = std::time(0); - const auto tm = std::gmtime(&now); - - // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where - // i can go until MaxTries - std::wostringstream oss; - oss - << L"ModOrganizer-" - << std::setw(4) << (1900 + tm->tm_year) - << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) - << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" - << std::setw(2) << std::setfill(L'0') << tm->tm_hour - << std::setw(2) << std::setfill(L'0') << tm->tm_min - << std::setw(2) << std::setfill(L'0') << tm->tm_sec; - - const std::wstring prefix = oss.str(); - const std::wstring ext = L".dmp"; - - // first path to try, without counter in it - std::wstring path = dir + L"\\" + prefix + ext; - - for (int i=0; i. #ifndef UTIL_H #define UTIL_H - #include -#include - -#define WIN32_LEAN_AND_MEAN -#include - #include -#include class Executable; @@ -51,442 +44,6 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); - -namespace env -{ - -class Console -{ -public: - Console(); - ~Console(); - -private: - bool m_hasConsole; - FILE* m_in; - FILE* m_out; - FILE* m_err; -}; - - -// an application shortcut that can be either on the desktop or the start menu -// -class Shortcut -{ -public: - // location of a shortcut - // - enum Locations - { - None = 0, - - // on the desktop - Desktop, - - // in the start menu - StartMenu - }; - - - // empty shortcut - // - Shortcut(); - - // shortcut from an executable - // - explicit Shortcut(const Executable& exe); - - // sets the name of the shortcut, shown on icons and start menu entries - // - Shortcut& name(const QString& s); - - // the program to start - // - Shortcut& target(const QString& s); - - // arguments to pass - // - Shortcut& arguments(const QString& s); - - // shows in the status bar of explorer, for example - // - Shortcut& description(const QString& s); - - // path to a binary that contains the icon and its index - // - Shortcut& icon(const QString& s, int index=0); - - // "start in" option for this shortcut - // - Shortcut& workingDirectory(const QString& s); - - - // returns whether this shortcut already exists at the given location; this - // does not check whether the shortcut parameters are different, it merely if - // the .lnk file exists - // - bool exists(Locations loc) const; - - // calls remove() if exists(), or add() - // - bool toggle(Locations loc); - - // adds the shortcut to the given location - // - bool add(Locations loc); - - // removes the shortcut from the given location - // - bool remove(Locations loc); - -private: - QString m_name; - QString m_target; - QString m_arguments; - QString m_description; - QString m_icon; - int m_iconIndex; - QString m_workingDirectory; - - // returns a qCritical() logger with a prefix already logged - // - QDebug critical() const; - - // returns a qDebug() logger with a prefix already logged - // - QDebug debug() const; - - - // returns the path where the shortcut file should be saved - // - QString shortcutPath(Locations loc) const; - - // returns the directory where the shortcut file should be saved - // - QString shortcutDirectory(Locations loc) const; - - // returns the filename of the shortcut file that should be used when saving - // - QString shortcutFilename() const; -}; - - -// returns a string representation of the given location -// -QString toString(Shortcut::Locations loc); - - -// represents one module -// -class Module -{ -public: - explicit Module(QString path, std::size_t fileSize); - - // returns the module's path - // - const QString& path() const; - - // returns the module's path in lowercase and using forward slashes - // - QString displayPath() const; - - // returns the size in bytes, may be 0 - // - std::size_t fileSize() const; - - // returns the x.x.x.x version embedded from the version info, may be empty - // - const QString& version() const; - - // returns the FileVersion entry from the resource file, returns - // "(no version)" if not available - // - const QString& versionString() const; - - // returns the build date from the version info, or the creation time of the - // file on the filesystem, may be empty - // - const QDateTime& timestamp() const; - - // returns the md5 of the file, may be empty for system files - // - const QString& md5() const; - - // converts timestamp() to a string for display, returns "(no timestamp)" if - // not available - // - QString timestampString() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - // contains the information from the version resource - // - struct FileInfo - { - VS_FIXEDFILEINFO ffi; - QString fileDescription; - }; - - QString m_path; - std::size_t m_fileSize; - QString m_version; - QDateTime m_timestamp; - QString m_versionString; - QString m_md5; - - // returns information from the version resource - // - FileInfo getFileInfo() const; - - // uses VS_FIXEDFILEINFO to build the version string - // - QString getVersion(const VS_FIXEDFILEINFO& fi) const; - - // uses the file date from VS_FIXEDFILEINFO if available, or gets the - // creation date on the file - // - QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; - - // returns the md5 hash unless the path contains "\windows\" - // - QString getMD5() const; - - // gets VS_FIXEDFILEINFO from the file version info buffer - // - VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; - - // gets FileVersion from the file version info buffer - // - QString getFileDescription(std::byte* buffer) const; -}; - - -// a variety of information on windows -// -class WindowsInfo -{ -public: - struct Version - { - DWORD major=0, minor=0, build=0; - - QString toString() const - { - return QString("%1.%2.%3").arg(major).arg(minor).arg(build); - } - - friend bool operator==(const Version& a, const Version& b) - { - return - a.major == b.major && - a.minor == b.minor && - a.build == b.build; - } - - friend bool operator!=(const Version& a, const Version& b) - { - return !(a == b); - } - }; - - struct Release - { - // the BuildLab entry from the registry, may be empty - QString buildLab; - - // product name such as "Windows 10 Pro", may not be in English, may be - // empty - QString productName; - - // release ID such as 1809, may be mepty - QString ID; - - // some sub-build number, undocumented, may be empty - DWORD UBR; - - Release() - : UBR(0) - { - } - }; - - - WindowsInfo(); - - // tries to guess whether this process is running in compatibility mode - // - bool compatibilityMode() const; - - // returns the Windows version, may not correspond to the actual version - // if the process is running in compatibility mode - // - const Version& reportedVersion() const; - - // tries to guess the real Windows version that's running, can be empty - // - const Version& realVersion() const; - - // various information about the current release - // - const Release& release() const; - - // whether this process is running as administrator, may be empty if the - // information is not available - std::optional isElevated() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - Version m_reported, m_real; - Release m_release; - std::optional m_elevated; - - // uses RtlGetVersion() to get the version number as reported by Windows - // - Version getReportedVersion(HINSTANCE ntdll) const; - - // uses RtlGetNtVersionNumbers() to get the real version number - // - Version getRealVersion(HINSTANCE ntdll) const; - - // gets various information from the registry - // - Release getRelease() const; - - // gets whether the process is elevated - // - std::optional getElevated() const; -}; - - -// represents a security product, such as an antivirus or a firewall -// -class SecurityProduct -{ -public: - SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate); - - // display name of the product - // - const QString& name() const; - - // a bunch of _WSC_SECURITY_PROVIDER flags - // - int provider() const; - - // whether the product is active - // - bool active() const; - - // whether its definitions are up-to-date - // - bool upToDate() const; - - // string representation of the above - // - QString toString() const; - -private: - QUuid m_guid; - QString m_name; - int m_provider; - bool m_active; - bool m_upToDate; - - QString providerToString() const; -}; - - -class Metrics -{ -public: - struct Display - { - int resX=0, resY=0, dpi=0; - bool primary=false; - int refreshRate = 0; - QString monitor, adapter; - - QString toString() const; - }; - - Metrics(); - - const std::vector& displays() const; - -private: - std::vector m_displays; -}; - - -// represents the process's environment -// -class Environment -{ -public: - Environment(); - - // list of loaded modules in the current process - // - const std::vector& loadedModules() const; - - // information about the operating system - // - const WindowsInfo& windowsInfo() const; - - // information about the installed security products - // - const std::vector& securityProducts() const; - - // information about displays - // - const Metrics& metrics() const; - - // logs the environment - // - void dump() const; - -private: - std::vector m_modules; - WindowsInfo m_windows; - std::vector m_security; - Metrics m_metrics; - - std::vector getLoadedModules() const; - std::vector getSecurityProducts() const; - - std::vector getSecurityProductsFromWMI() const; - std::optional getWindowsFirewall() const; -}; - - -enum class CoreDumpTypes -{ - Mini = 1, - Data, - Full -}; - -// creates a minidump file for the given process -// -bool coredump(CoreDumpTypes type); - -// finds another process with the same name as this one and creates a minidump -// file for it -// -bool coredumpOther(CoreDumpTypes type); - -} // namespace env - - MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From 3d97b0efd04326c0252411fc6639c4c6df2f2160 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 04:27:53 -0400 Subject: added ExitModOrganizer(), used instead of qApp->exit() reset geometry uses TaskDialog moved restart code for the settings dialog to MainWindow fixed settings sometimes not being saved when restarting don't log "crash dumps present" every time the settings dialog is closed --- src/main.cpp | 12 +++- src/mainwindow.cpp | 113 +++++++++++++++++++++++++------------- src/mainwindow.h | 11 ++-- src/settingsdialog.cpp | 21 +++---- src/settingsdialog.h | 9 ++- src/settingsdialognexus.cpp | 4 +- src/settingsdialogworkarounds.cpp | 23 ++++---- src/shared/util.cpp | 47 ++++++++++++++++ src/shared/util.h | 18 ++++++ src/spawn.cpp | 12 ++-- 10 files changed, 189 insertions(+), 81 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/main.cpp b/src/main.cpp index a6918d99..f08ba066 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -563,10 +563,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); + if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) { instance.clearCurrentInstance(); - return INT_MAX; + return RestartExitCode; } + return 1; } @@ -710,6 +712,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); + mainWindow.onBeforeClose(); + mainWindow.close(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); @@ -867,6 +871,7 @@ int main(int argc, char *argv[]) do { LogModel::instance().clear(); + ResetExitFlag(); // make sure the log file isn't locked in case MO was restarted and // the previous instance gets deleted @@ -904,10 +909,11 @@ int main(int argc, char *argv[]) splash = ":/MO/gui/splash"; } - int result = runApplication(application, instance, splash); - if (result != INT_MAX) { + const int result = runApplication(application, instance, splash); + if (result != RestartExitCode) { return result; } + argc = 1; moshortcut = MOShortcut(""); } while (true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 295c60a6..23733cac 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1217,7 +1217,7 @@ void MainWindow::showEvent(QShowEvent *event) QMainWindow::showEvent(event); if (!m_WasVisible) { - readSettings(m_OrganizerCore.settings()); + readSettings(); refreshFilters(); // this needs to be connected here instead of in the constructor because the @@ -1267,26 +1267,44 @@ void MainWindow::showEvent(QShowEvent *event) } } +void MainWindow::onBeforeClose() +{ + storeSettings(); +} void MainWindow::closeEvent(QCloseEvent* event) { - if (!confirmExit()) { + // this happens for two reasons: + // 1) the user requested to close the window, such as clicking the X + // 2) close() is called in runApplication() after application.exec() + // returns, which happens when qApp->exit() is called + // + // the window must never actually close for 1), because settings haven't been + // saved yet: the state of many widgets is saved to the ini, which relies on + // the window still being onscreen (or else everything is considered hidden) + // + // for 2), the settings have been saved and the window can just close + + if (ModOrganizerExiting()) { + // the user has confirmed if necessary and all settings have been saved, + // just close it + QMainWindow::closeEvent(event); + } else { + // never close the window because settings might need to be changed event->ignore(); - return; - } - storeSettings(m_OrganizerCore.settings()); + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); + } } -bool MainWindow::confirmExit() +bool MainWindow::canExit() { - m_closing = true; - if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { - m_closing = false; return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); @@ -1298,8 +1316,9 @@ bool MainWindow::confirmExit() HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); if (injected_process_still_running != INVALID_HANDLE_VALUE) { + m_exitAfterWait = true; m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_closing) { // if operation cancelled + if (!m_exitAfterWait) { // if operation cancelled return false; } } @@ -2138,20 +2157,22 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings(const Settings& settings) +void MainWindow::readSettings() { - settings.geometry().restoreGeometry(this); - settings.geometry().restoreState(this); - settings.geometry().restoreDocks(this); - settings.geometry().restoreToolbars(this); - settings.geometry().restoreState(ui->splitter); - settings.geometry().restoreState(ui->categoriesSplitter); - settings.geometry().restoreVisibility(ui->menuBar); - settings.geometry().restoreVisibility(ui->statusBar); + const auto& s = m_OrganizerCore.settings(); + + s.geometry().restoreGeometry(this); + s.geometry().restoreState(this); + s.geometry().restoreDocks(this); + s.geometry().restoreToolbars(this); + s.geometry().restoreState(ui->splitter); + s.geometry().restoreState(ui->categoriesSplitter); + s.geometry().restoreVisibility(ui->menuBar); + s.geometry().restoreVisibility(ui->statusBar); { // special case in case someone puts 0 in the INI - auto v = settings.widgets().index(ui->executablesListBox); + auto v = s.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2159,16 +2180,16 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.widgets().restoreIndex(ui->groupCombo); + s.widgets().restoreIndex(ui->groupCombo); { - settings.geometry().restoreVisibility(ui->categoriesGroup, false); + s.geometry().restoreVisibility(ui->categoriesGroup, false); const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } - if (settings.network().useProxy()) { + if (s.network().useProxy()) { activateProxy(true); } } @@ -2215,8 +2236,10 @@ void MainWindow::processUpdates(Settings& settings) { } } -void MainWindow::storeSettings(Settings& s) +void MainWindow::storeSettings() { + auto& s = m_OrganizerCore.settings(); + s.geometry().saveState(this); s.geometry().saveGeometry(this); s.geometry().saveDocks(this); @@ -2244,7 +2267,7 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - if (m_closing) + if (m_exitAfterWait) m_LockDialog = new WaitingOnCloseDialog(this); else m_LockDialog = new LockedDialog(this, true); @@ -2265,8 +2288,8 @@ void MainWindow::unlock() } --m_LockCount; if (m_LockCount == 0) { - if (m_closing && m_LockDialog->canceled()) - m_closing = false; + if (m_exitAfterWait && m_LockDialog->canceled()) + m_exitAfterWait = false; m_LockDialog->hide(); m_LockDialog->deleteLater(); m_LockDialog = nullptr; @@ -5018,18 +5041,31 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); const bool oldCheckForUpdates = settings.checkForUpdates(); + const int oldMaxDumps = settings.diagnostics().crashDumpsMax(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); + auto e = dialog.exitNeeded(); + if (oldManagedGameDirectory != settings.game().directory()) { - QMessageBox::about(this, tr("Restarting MO"), - tr("Changing the managed game directory requires restarting MO.\n" - "Any pending downloads will be paused.\n\n" - "Click OK to restart MO now.")); - dlManager->pauseAll(); - qApp->exit(INT_MAX); + e |= Exit::Restart; + } + + if (e.testFlag(Exit::Restart)) { + const auto r = MOBase::TaskDialog(this) + .title(tr("Restart Mod Organizer")) + .main("Restart Mod Organizer") + .content(tr("Mod Organizer must restart to finish configuration changes")) + .icon(QMessageBox::Question) + .button({tr("Restart"), QMessageBox::Yes}) + .button({tr("Continue"), tr("Some things might be weird."), QMessageBox::No}) + .exec(); + + if (r == QMessageBox::Yes) { + ExitModOrganizer(e); + } } InstallationManager *instManager = m_OrganizerCore.installationManager(); @@ -5092,7 +5128,10 @@ void MainWindow::on_actionSettings_triggered() updateDownloadView(); m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); - m_OrganizerCore.cycleDiagnostics(); + + if (settings.diagnostics().crashDumpsMax() != oldMaxDumps) { + m_OrganizerCore.cycleDiagnostics(); + } toggleMO2EndorseState(); @@ -5475,9 +5514,7 @@ void MainWindow::on_actionUpdate_triggered() void MainWindow::on_actionExit_triggered() { - if (confirmExit()) { - qApp->exit(); - } + ExitModOrganizer(); } void MainWindow::actionEndorseMO() @@ -6121,7 +6158,7 @@ void MainWindow::on_actionChange_Game_triggered() if (r == QMessageBox::Yes) { InstanceManager::instance().clearCurrentInstance(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 524e2b6e..b04096be 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -111,7 +111,6 @@ class MainWindow : public QMainWindow, public IUserInterface friend class OrganizerProxy; public: - explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); @@ -154,7 +153,8 @@ public: void displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; - bool confirmExit(); + bool canExit(); + void onBeforeClose(); virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); @@ -383,9 +383,8 @@ private: LockedDialogBase *m_LockDialog { nullptr }; uint64_t m_LockCount { 0 }; - bool m_closing{ false }; - bool m_showArchiveData{ true }; + bool m_exitAfterWait{ false }; MOBase::DelayedFileWriter m_ArchiveListWriter; @@ -672,8 +671,8 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); - void storeSettings(Settings& settings); - void readSettings(const Settings& settings); + void storeSettings(); + void readSettings(); void setupModList(); }; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8fb25b1c..daccfba9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) + , m_exit(Exit::None) , m_pluginContainer(pluginContainer) - , m_restartNeeded(false) { ui->setupUi(this); @@ -61,9 +61,14 @@ QWidget* SettingsDialog::parentWidgetForDialogs() } } -void SettingsDialog::setRestartNeeded() +void SettingsDialog::setExitNeeded(ExitFlags e) { - m_restartNeeded = true; + m_exit = e; +} + +ExitFlags SettingsDialog::exitNeeded() const +{ + return m_exit; } int SettingsDialog::exec() @@ -87,16 +92,6 @@ int SettingsDialog::exec() } } - if (m_restartNeeded) { - if (QMessageBox::question(parentWidgetForDialogs(), - tr("Restart Mod Organizer?"), - tr("In order to finish configuration changes, MO must be restarted.\n" - "Restart it now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->exit(INT_MAX); - } - } - return ret; } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e89da665..bae4a469 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGSDIALOG_H #include "tutorabledialog.h" +#include "util.h" class PluginContainer; class Settings; @@ -74,7 +75,9 @@ public: PluginContainer* pluginContainer(); QWidget* parentWidgetForDialogs(); - void setRestartNeeded(); + + void setExitNeeded(ExitFlags e); + ExitFlags exitNeeded() const; int exec() override; @@ -82,10 +85,10 @@ public slots: virtual void accept(); private: + Ui::SettingsDialog* ui; Settings& m_settings; std::vector> m_tabs; - Ui::SettingsDialog* ui; - bool m_restartNeeded; + ExitFlags m_exit; PluginContainer* m_pluginContainer; }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2dd8b998..d49e0a33 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 0e31fc4b..1c5fbe26 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -2,6 +2,7 @@ #include "ui_settingsdialog.h" #include "spawn.h" #include "settings.h" +#include #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) @@ -118,16 +119,18 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - const auto caption = QObject::tr("Restart Mod Organizer?"); - const auto text = QObject::tr( - "In order to reset the geometry, Mod Organizer must be restarted.\n" - "Restart now?"); - - const auto res = QMessageBox::question( - parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel); - - if (res == QMessageBox::Yes) { + const auto r = MOBase::TaskDialog(parentWidget()) + .title(QObject::tr("Restart Mod Organizer")) + .main(QObject::tr("Restart Mod Organizer")) + .content(QObject::tr("Geometries will be reset to their default values.")) + .icon(QMessageBox::Question) + .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + if (r == QMessageBox::Ok) { settings().geometry().requestReset(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); + dialog().close(); } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 07983e12..58f4eb2b 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" +#include "mainwindow.h" namespace MOShared { @@ -244,3 +245,49 @@ MOBase::VersionInfo createVersionInfo() } } // namespace MOShared + + +static bool g_exiting = false; + +MainWindow* findMainWindow() +{ + for (auto* tl : qApp->topLevelWidgets()) { + if (auto* mw=dynamic_cast(tl)) { + return mw; + } + } + + return nullptr; +} + +bool ExitModOrganizer(ExitFlags e) +{ + if (g_exiting) { + return true; + } + + if (!e.testFlag(Exit::Force)) { + if (auto* mw=findMainWindow()) { + if (!mw->canExit()) { + return false; + } + } + } + + g_exiting = true; + + const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); + qApp->exit(code); + + return true; +} + +bool ModOrganizerExiting() +{ + return g_exiting; +} + +void ResetExitFlag() +{ + g_exiting = false; +} diff --git a/src/shared/util.h b/src/shared/util.h index 7bae96f2..aea6f200 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -48,4 +48,22 @@ MOBase::VersionInfo createVersionInfo(); } // namespace MOShared + +enum class Exit +{ + None = 0x00, + Normal = 0x01, + Restart = 0x02, + Force = 0x04 +}; + +const int RestartExitCode = INT_MAX; + +using ExitFlags = QFlags; +Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); + +bool ExitModOrganizer(ExitFlags e=Exit::Normal); +bool ModOrganizerExiting(); +void ResetExitFlag(); + #endif // UTIL_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 079677f4..a34230b2 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -186,12 +186,12 @@ QMessageBox::StandardButton badSteamReg( .details(details) .icon(QMessageBox::Critical) .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program may fail to launch."), - QMessageBox::Yes}) + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); } @@ -517,7 +517,7 @@ bool restartAsAdmin(QWidget* parent) } log::debug("exiting MO"); - qApp->exit(0); + ExitModOrganizer(Exit::Force); return true; } -- cgit v1.3.1 From dfb3020f8982e325e804f9e43b28a33b54c798f9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 9 Oct 2019 01:38:53 -0400 Subject: added usvfs version in log and about dialog --- src/aboutdialog.cpp | 3 +++ src/aboutdialog.ui | 7 +++++++ src/main.cpp | 5 +++-- src/shared/util.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 1 + 5 files changed, 60 insertions(+), 2 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 41fd24f7..6ae7f56d 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "aboutdialog.h" #include "ui_aboutdialog.h" +#include "util.h" #include #include @@ -85,6 +86,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); #endif + + ui->usvfsLabel->setText(ui->usvfsLabel->text() + " " + MOShared::getUsvfsVersionString()); ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index c72a85f8..415ce0a7 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -104,6 +104,13 @@ + + + + usvfs: + + + diff --git a/src/main.cpp b/src/main.cpp index f08ba066..776c3775 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -504,8 +504,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { log::info( - "starting Mod Organizer version {} revision {} in {}", - getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); + "starting Mod Organizer version {} revision {} in {}, usvfs: {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath(), + MOShared::getUsvfsVersionString()); preloadSsl(); if (!QSslSocket::supportsSsl()) { diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 58f4eb2b..32eb825c 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "mainwindow.h" +#include +#include namespace MOShared { @@ -244,6 +246,50 @@ MOBase::VersionInfo createVersionInfo() } } +QString getUsvfsDLLVersion() +{ + // once 2.2.2 is released, this can be changed to call USVFSVersionString() + // directly; until then, using GetProcAddress() allows for mixing up devbuilds + // and usvfs dlls + + using USVFSVersionStringType = const char* WINAPI (); + + QString s; + + const auto m = ::LoadLibraryW(L"usvfs_x64.dll"); + + if (m) { + auto* f = reinterpret_cast( + ::GetProcAddress(m, "USVFSVersionString")); + + if (f) { + s = f(); + } + + ::FreeLibrary(m); + } + + if (s.isEmpty()) { + s = "?"; + } + + return s; +} + +QString getUsvfsVersionString() +{ + const QString dll = getUsvfsDLLVersion(); + const QString header = USVFS_VERSION_STRING; + + QString usvfsVersion; + + if (dll == header) { + return dll; + } else { + return "dll is " + dll + ", compiled against " + header; + } +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index aea6f200..e87244b6 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -45,6 +45,7 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); MOBase::VersionInfo createVersionInfo(); +QString getUsvfsVersionString(); } // namespace MOShared -- cgit v1.3.1 From f3c5cebb6e9262625105d4339a54a810d7816811 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 21:56:50 -0500 Subject: fixed exiting before QThread joins when pressing the X twice --- src/mainwindow.cpp | 21 ++++++++++++++------- src/processrunner.cpp | 31 +++++++++++++++++++++---------- src/shared/util.cpp | 11 ++++++++++- src/shared/util.h | 1 + 4 files changed, 46 insertions(+), 18 deletions(-) (limited to 'src/shared/util.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a29ea8ab..d1578d85 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1317,18 +1317,25 @@ void MainWindow::closeEvent(QCloseEvent* event) // // for 2), the settings have been saved and the window can just close - if (ModOrganizerExiting()) { + if (ModOrganizerCanCloseNow()) { // the user has confirmed if necessary and all settings have been saved, // just close it QMainWindow::closeEvent(event); - } else { - // never close the window because settings might need to be changed - event->ignore(); + return; + } - // start the process of exiting, which may require confirmation by calling - // canExit(), among other things - ExitModOrganizer(); + if (ModOrganizerExiting()) { + // ignore repeated attempts + event->ignore(); + return; } + + // never close the window because settings might need to be changed + event->ignore(); + + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); } bool MainWindow::canExit() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 19aae632..aead42d1 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -225,7 +225,7 @@ const std::chrono::milliseconds Infinite(-1); // std::optional timedWait( HANDLE handle, DWORD pid, UILocker::Session& ls, - std::chrono::milliseconds wait) + std::chrono::milliseconds wait, std::atomic& interrupt) { using namespace std::chrono; @@ -234,7 +234,7 @@ std::optional timedWait( start = high_resolution_clock::now(); } - for (;;) { + while (!interrupt) { // wait for a very short while, allows for processing events below const auto r = singleWait(handle, pid); @@ -286,10 +286,13 @@ std::optional timedWait( } } } + + log::debug("waiting for {} interrupted", pid); + return ProcessRunner::ForceUnlocked; } ProcessRunner::Results waitForProcessesThreadImpl( - HANDLE job, UILocker::Session& ls) + HANDLE job, UILocker::Session& ls, std::atomic& interrupt) { using namespace std::chrono; @@ -301,7 +304,7 @@ ProcessRunner::Results waitForProcessesThreadImpl( const milliseconds defaultWait(50); auto wait = defaultWait; - for (;;) { + while (!interrupt) { auto ip = getInterestingProcess(job); if (!ip.handle) { // nothing to wait on @@ -325,7 +328,7 @@ ProcessRunner::Results waitForProcessesThreadImpl( wait = Infinite; } - const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait); + const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait, interrupt); if (r) { if (*r == ProcessRunner::Results::Completed) { // process completed, check another one, reset the wait time to find @@ -344,9 +347,10 @@ ProcessRunner::Results waitForProcessesThreadImpl( } void waitForProcessesThread( - ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls) + ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls, + std::atomic& interrupt) { - result = waitForProcessesThreadImpl(job, ls); + result = waitForProcessesThreadImpl(job, ls, interrupt); ls.unlock(); } @@ -379,9 +383,11 @@ ProcessRunner::Results waitForProcesses( } auto results = ProcessRunner::Running; + std::atomic interrupt(false); auto* t = QThread::create( - waitForProcessesThread, std::ref(results), job.get(), std::ref(ls)); + waitForProcessesThread, + std::ref(results), job.get(), std::ref(ls), std::ref(interrupt)); QEventLoop events; QObject::connect(t, &QThread::finished, [&]{ @@ -391,6 +397,11 @@ ProcessRunner::Results waitForProcesses( t->start(); events.exec(); + if (t->isRunning()) { + interrupt = true; + t->wait(); + } + delete t; return results; @@ -861,7 +872,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; withLock([&](auto& ls) { - for (;;) { + for (;;) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { break; @@ -878,7 +889,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( } r = Completed; - }); + }); return r; } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 32eb825c..baceddeb 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -294,6 +294,7 @@ QString getUsvfsVersionString() static bool g_exiting = false; +static bool g_canClose = false; MainWindow* findMainWindow() { @@ -312,6 +313,9 @@ bool ExitModOrganizer(ExitFlags e) return true; } + g_exiting = true; + MOBase::Guard g([&]{ g_exiting = false; }); + if (!e.testFlag(Exit::Force)) { if (auto* mw=findMainWindow()) { if (!mw->canExit()) { @@ -320,7 +324,7 @@ bool ExitModOrganizer(ExitFlags e) } } - g_exiting = true; + g_canClose = true; const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); qApp->exit(code); @@ -328,6 +332,11 @@ bool ExitModOrganizer(ExitFlags e) return true; } +bool ModOrganizerCanCloseNow() +{ + return g_canClose; +} + bool ModOrganizerExiting() { return g_exiting; diff --git a/src/shared/util.h b/src/shared/util.h index e87244b6..e8a58549 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -65,6 +65,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); bool ExitModOrganizer(ExitFlags e=Exit::Normal); bool ModOrganizerExiting(); +bool ModOrganizerCanCloseNow(); void ResetExitFlag(); #endif // UTIL_H -- cgit v1.3.1