summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorisanae <14251494+isanae@users.noreply.github.com>2019-07-18 23:13:57 -0400
committerisanae <14251494+isanae@users.noreply.github.com>2019-07-22 07:33:38 -0400
commitb2a1e1391fdd6bdee1c5e8d337b273447c70a506 (patch)
treebbcee13a329fd9276a43a2d524f48ff83c32517b /src
parentf95479b981b41f51a3ecf055c73f42440766e5d7 (diff)
split env
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt25
-rw-r--r--src/env.cpp479
-rw-r--r--src/env.h117
-rw-r--r--src/envmetrics.cpp224
-rw-r--r--src/envmetrics.h28
-rw-r--r--src/envmodule.cpp390
-rw-r--r--src/envmodule.h98
-rw-r--r--src/envsecurity.cpp418
-rw-r--r--src/envsecurity.h49
-rw-r--r--src/envshortcut.cpp376
-rw-r--r--src/envshortcut.h114
-rw-r--r--src/envwindows.cpp236
-rw-r--r--src/envwindows.h106
-rw-r--r--src/main.cpp2
-rw-r--r--src/mainwindow.cpp1
-rw-r--r--src/shared/util.cpp2123
-rw-r--r--src/shared/util.h443
17 files changed, 2663 insertions, 2566 deletions
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 <log.h>
+#include <utility.h>
+
+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<Module>& Environment::loadedModules() const
+{
+ return m_modules;
+}
+
+const WindowsInfo& Environment::windowsInfo() const
+{
+ return *m_windows;
+}
+
+const std::vector<SecurityProduct>& 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<MaxTries; ++tries)
+ {
+ auto buffer = std::make_unique<wchar_t[]>(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<std::uintptr_t>(process));
+ }
+
+ std::wcerr << L"failed to get filename for " << what << L"\n";
+ return {};
+}
+
+std::vector<DWORD> 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<MaxTries; ++tries) {
+ auto ids = std::make_unique<DWORD[]>(size);
+ std::fill(ids.get(), ids.get() + size, 0);
+
+ DWORD bytesGiven = static_cast<DWORD>(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<DWORD>(ids.get(), ids.get() + count);
+ }
+
+ std::cerr << L"too many processes to enumerate";
+ return {};
+}
+
+std::vector<Process> runningProcesses()
+{
+ const auto pids = runningProcessesIds();
+ std::vector<Process> 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<MaxTries; ++i) {
+ std::wclog << L"trying file '" << path << L"'\n";
+
+ HandlePtr h (CreateFileW(
+ path.c_str(), GENERIC_WRITE, 0, nullptr,
+ CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
+
+ if (h.get() != INVALID_HANDLE_VALUE) {
+ // worked
+ return h;
+ }
+
+ const auto e = GetLastError();
+
+ if (e != ERROR_FILE_EXISTS) {
+ // probably no write access
+ std::wcerr
+ << L"failed to create dump file, " << formatSystemMessage(e) << L"\n";
+
+ return {};
+ }
+
+ // try again with "-i"
+ path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext;
+ }
+
+ std::wcerr << L"can't create dump file, ran out of filenames\n";
+ return {};
+}
+
+HandlePtr dumpFile()
+{
+ // try the current directory
+ HandlePtr h = tempFile(L".");
+ if (h.get() != INVALID_HANDLE_VALUE) {
+ return h;
+ }
+
+ std::wclog << L"cannot write dump file in current directory\n";
+
+ // try the temp directory
+ const auto dir = tempDir();
+
+ if (!dir.empty()) {
+ h = tempFile(dir.c_str());
+ if (h.get() != INVALID_HANDLE_VALUE) {
+ return h;
+ }
+ }
+
+ return {};
+}
+
+bool createMiniDump(HANDLE process, CoreDumpTypes type)
+{
+ const DWORD pid = GetProcessId(process);
+
+ const HandlePtr file = dumpFile();
+ if (!file) {
+ std::wcerr << L"nowhere to write the dump file\n";
+ return false;
+ }
+
+ auto flags = _MINIDUMP_TYPE(
+ MiniDumpNormal |
+ MiniDumpWithHandleData |
+ MiniDumpWithUnloadedModules |
+ MiniDumpWithProcessThreadData);
+
+ if (type == CoreDumpTypes::Data) {
+ std::wclog << L"writing minidump with data\n";
+ flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs);
+ } else if (type == CoreDumpTypes::Full) {
+ std::wclog << L"writing full minidump\n";
+ flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory);
+ } else {
+ std::wclog << L"writing mini minidump\n";
+ }
+
+ const auto ret = MiniDumpWriteDump(
+ process, pid, file.get(), flags, nullptr, nullptr, nullptr);
+
+ if (!ret) {
+ const auto e = GetLastError();
+
+ std::wcerr
+ << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n";
+
+ return false;
+ }
+
+ std::wclog << L"minidump written correctly\n";
+ return true;
+}
+
+
+bool coredump(CoreDumpTypes type)
+{
+ std::wclog << L"creating minidump for the current process\n";
+ return createMiniDump(GetCurrentProcess(), type);
+}
+
+bool coredumpOther(CoreDumpTypes type)
+{
+ std::wclog << L"creating minidump for an running process\n";
+
+ const auto pid = findOtherPid();
+ if (pid == 0) {
+ std::wcerr << L"no other process found\n";
+ return false;
+ }
+
+ std::wclog << L"found other process with pid " << pid << L"\n";
+
+ HandlePtr handle(OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
+
+ if (!handle) {
+ const auto e = GetLastError();
+
+ std::wcerr
+ << L"failed to open process " << pid << L", "
+ << formatSystemMessage(e) << L"\n";
+
+ return false;
+ }
+
+ return createMiniDump(handle.get(), type);
+}
+
+} // namespace
diff --git a/src/env.h b/src/env.h
new file mode 100644
index 00000000..1e913a40
--- /dev/null
+++ b/src/env.h
@@ -0,0 +1,117 @@
+namespace env
+{
+
+class Module;
+class SecurityProduct;
+class WindowsInfo;
+class Metrics;
+
+struct HandleCloser
+{
+ using pointer = HANDLE;
+
+ void operator()(HANDLE h)
+ {
+ if (h != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(h);
+ }
+ }
+};
+
+using HandlePtr = std::unique_ptr<HANDLE, HandleCloser>;
+
+
+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 <class T>
+using COMPtr = std::unique_ptr<T, COMReleaser>;
+
+
+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<Module>& loadedModules() const;
+
+ // information about the operating system
+ //
+ const WindowsInfo& windowsInfo() const;
+
+ // information about the installed security products
+ //
+ const std::vector<SecurityProduct>& securityProducts() const;
+
+ // information about displays
+ //
+ const Metrics& metrics() const;
+
+ // logs the environment
+ //
+ void dump() const;
+
+private:
+ std::vector<Module> m_modules;
+ std::unique_ptr<WindowsInfo> m_windows;
+ std::vector<SecurityProduct> m_security;
+ std::unique_ptr<Metrics> 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 <Windows.h>
+#include <shellscalingapi.h>
+#include <log.h>
+#include <utility.h>
+
+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<GetDpiForMonitorFunction*>(
+ GetProcAddress(m_shcore.get(), "GetDpiForMonitor"));
+ }
+
+ // gets all monitors and the device they're running on
+ getDisplayDevices();
+ }
+
+ std::vector<Metrics::Display>&& displays() &&
+ {
+ return std::move(m_displays);
+ }
+
+ const std::vector<Metrics::Display>& displays() const &
+ {
+ return m_displays;
+ }
+
+private:
+ using GetDpiForMonitorFunction =
+ HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
+
+ std::unique_ptr<HINSTANCE, LibraryFreer> m_shcore;
+ GetDpiForMonitorFunction* m_GetDpiForMonitor;
+ std::vector<Metrics::Display> 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<Data*>(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<LPARAM>(&data));
+
+ return data.hm;
+ }
+};
+
+
+Metrics::Metrics()
+{
+ m_displays = DisplayEnumerator().displays();
+}
+
+const std::vector<Metrics::Display>& 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 <QString>
+#include <vector>
+
+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<Display>& displays() const;
+
+private:
+ std::vector<Display> 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 <utility.h>
+
+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<std::byte[]>(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<VS_FIXEDFILEINFO*>(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<LANGANDCODEPAGE*>(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<wchar_t*>(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<Module> 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<Module> 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 <QString>
+#include <QDateTime>
+
+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<Module> 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 <utility.h>
+
+#include <Wbemidl.h>
+#include <wscapi.h>
+#include <comdef.h>
+#include <netfw.h>
+#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 <class F>
+ void query(const std::string& q, F&& f)
+ {
+ if (!m_locator || !m_service) {
+ return;
+ }
+
+ auto enumerator = getEnumerator(q);
+ if (!enumerator) {
+ return;
+ }
+
+ for (;;)
+ {
+ COMPtr<IWbemClassObject> 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<IWbemLocator> m_locator;
+ COMPtr<IWbemServices> 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<IWbemLocator*>(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<IEnumWbemClassObject> 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<IEnumWbemClassObject>(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<SecurityProduct> 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<QUuid, SecurityProduct> 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<int>((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<SecurityProduct> v;
+
+ for (auto&& p : map) {
+ v.push_back(p.second);
+ }
+
+ return v;
+}
+
+std::optional<SecurityProduct> getWindowsFirewall()
+{
+ HRESULT hr = 0;
+
+ COMPtr<INetFwPolicy2> 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<INetFwPolicy2*>(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<SecurityProduct> getSecurityProducts()
+{
+ std::vector<SecurityProduct> 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 <QUuid>
+#include <QString>
+
+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<SecurityProduct> 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 <utility.h>
+
+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<IShellLink> m_link;
+ COMPtr<IPersistFile> m_file;
+
+ void throwOnFail(HRESULT r, const QString& s)
+ {
+ if (FAILED(r)) {
+ throw ShellLinkException(QString("%1, %2")
+ .arg(s)
+ .arg(formatSystemMessageQ(r)));
+ }
+ }
+
+ COMPtr<IShellLink> 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<IShellLink>(static_cast<IShellLink*>(link));
+ }
+
+ COMPtr<IPersistFile> 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<IPersistFile>(static_cast<IPersistFile*>(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<int>(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 <QString>
+
+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 <utility.h>
+
+namespace env
+{
+
+using namespace MOBase;
+
+WindowsInfo::WindowsInfo()
+{
+ // loading ntdll.dll, the functions will be found with GetProcAddress()
+ std::unique_ptr<HINSTANCE, LibraryFreer> 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<bool> 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<RtlGetVersionType*>(
+ 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<RtlGetNtVersionNumbersType*>(
+ 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<bool> 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 <QString>
+#include <optional>
+
+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<bool> 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<bool> 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<bool> 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 <http://www.gnu.org/licenses/>.
#include "instancemanager.h"
#include "moshortcut.h"
#include "organizercore.h"
+#include "env.h"
+#include "envmodule.h"
#include <eh.h>
#include <windows_error.h>
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 <http://www.gnu.org/licenses/>.
#include <usvfs.h>
#include "localsavegames.h"
#include "listdialog.h"
+#include "envshortcut.h"
#include <QAbstractItemDelegate>
#include <QAbstractProxyModel>
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 <http://www.gnu.org/licenses/>.
#include "util.h"
#include "windows_error.h"
-#include "error_report.h"
-#include "executableslist.h"
-#include "instancemanager.h"
-#include <utility.h>
-#include <log.h>
-
-#include <sstream>
-#include <locale>
-#include <algorithm>
-#include <set>
-#include <filesystem>
-
-#include <DbgHelp.h>
-#include <boost/scoped_array.hpp>
-#include <QApplication>
-
-#include <comdef.h>
-#include <Wbemidl.h>
-#include <wscapi.h>
-#include <netfw.h>
-#include <shellscalingapi.h>
-
-#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<HANDLE, HandleCloser>;
-
-
-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 <class T>
-using COMPtr = std::unique_ptr<T, COMReleaser>;
-
-
-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<IShellLink> m_link;
- COMPtr<IPersistFile> m_file;
-
- void throwOnFail(HRESULT r, const QString& s)
- {
- if (FAILED(r)) {
- throw ShellLinkException(QString("%1, %2")
- .arg(s)
- .arg(formatSystemMessageQ(r)));
- }
- }
-
- COMPtr<IShellLink> 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<IShellLink>(static_cast<IShellLink*>(link));
- }
-
- COMPtr<IPersistFile> 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<IPersistFile>(static_cast<IPersistFile*>(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<int>(loc));
- }
-}
-
-
-
-class WMI
-{
-public:
- class failed {};
-
- WMI(const std::string& ns)
- {
- try
- {
- createLocator();
- createService(ns);
- setSecurity();
- }
- catch(failed&)
- {
- }
- }
-
- template <class F>
- void query(const std::string& q, F&& f)
- {
- if (!m_locator || !m_service) {
- return;
- }
-
- auto enumerator = getEnumerator(q);
- if (!enumerator) {
- return;
- }
-
- for (;;)
- {
- COMPtr<IWbemClassObject> 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<IWbemLocator> m_locator;
- COMPtr<IWbemServices> 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<IWbemLocator*>(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<IEnumWbemClassObject> 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<IEnumWbemClassObject>(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<GetDpiForMonitorFunction*>(
- GetProcAddress(m_shcore.get(), "GetDpiForMonitor"));
- }
-
- // gets all monitors and the device they're running on
- getDisplayDevices();
- }
-
- std::vector<Metrics::Display>&& displays() &&
- {
- return std::move(m_displays);
- }
-
- const std::vector<Metrics::Display>& displays() const &
- {
- return m_displays;
- }
-
-private:
- using GetDpiForMonitorFunction =
- HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
-
- std::unique_ptr<HINSTANCE, LibraryFreer> m_shcore;
- GetDpiForMonitorFunction* m_GetDpiForMonitor;
- std::vector<Metrics::Display> 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<Data*>(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<LPARAM>(&data));
-
- return data.hm;
- }
-};
-
-
-Environment::Environment()
-{
- m_modules = getLoadedModules();
- m_security = getSecurityProducts();
-}
-
-const std::vector<Module>& Environment::loadedModules() const
-{
- return m_modules;
-}
-
-const WindowsInfo& Environment::windowsInfo() const
-{
- return m_windows;
-}
-
-const std::vector<SecurityProduct>& 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<Module> 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<Module> 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<SecurityProduct> Environment::getSecurityProducts() const
-{
- std::vector<SecurityProduct> 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<SecurityProduct> 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<QUuid, SecurityProduct> 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<int>((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<SecurityProduct> v;
-
- for (auto&& p : map) {
- v.push_back(p.second);
- }
-
- return v;
-}
-
-std::optional<SecurityProduct> Environment::getWindowsFirewall() const
-{
- HRESULT hr = 0;
-
- COMPtr<INetFwPolicy2> 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<INetFwPolicy2*>(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::Display>& 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<std::byte[]>(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<VS_FIXEDFILEINFO*>(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<LANGANDCODEPAGE*>(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<wchar_t*>(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<HINSTANCE, LibraryFreer> 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<bool> 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<RtlGetVersionType*>(
- 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<RtlGetNtVersionNumbersType*>(
- 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<bool> 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<MaxTries; ++tries)
- {
- auto buffer = std::make_unique<wchar_t[]>(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<std::uintptr_t>(process));
- }
-
- std::wcerr << L"failed to get filename for " << what << L"\n";
- return {};
-}
-
-std::vector<DWORD> 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<MaxTries; ++tries) {
- auto ids = std::make_unique<DWORD[]>(size);
- std::fill(ids.get(), ids.get() + size, 0);
-
- DWORD bytesGiven = static_cast<DWORD>(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<DWORD>(ids.get(), ids.get() + count);
- }
-
- std::cerr << L"too many processes to enumerate";
- return {};
-}
-
-std::vector<Process> runningProcesses()
-{
- const auto pids = runningProcessesIds();
- std::vector<Process> 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<MaxTries; ++i) {
- std::wclog << L"trying file '" << path << L"'\n";
-
- HandlePtr h (CreateFileW(
- path.c_str(), GENERIC_WRITE, 0, nullptr,
- CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
-
- if (h.get() != INVALID_HANDLE_VALUE) {
- // worked
- return h;
- }
-
- const auto e = GetLastError();
-
- if (e != ERROR_FILE_EXISTS) {
- // probably no write access
- std::wcerr
- << L"failed to create dump file, " << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- // try again with "-i"
- path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext;
- }
-
- std::wcerr << L"can't create dump file, ran out of filenames\n";
- return {};
-}
-
-HandlePtr dumpFile()
-{
- // try the current directory
- HandlePtr h = tempFile(L".");
- if (h.get() != INVALID_HANDLE_VALUE) {
- return h;
- }
-
- std::wclog << L"cannot write dump file in current directory\n";
-
- // try the temp directory
- const auto dir = tempDir();
-
- if (!dir.empty()) {
- h = tempFile(dir.c_str());
- if (h.get() != INVALID_HANDLE_VALUE) {
- return h;
- }
- }
-
- return {};
-}
-
-bool createMiniDump(HANDLE process, CoreDumpTypes type)
-{
- const DWORD pid = GetProcessId(process);
-
- const HandlePtr file = dumpFile();
- if (!file) {
- std::wcerr << L"nowhere to write the dump file\n";
- return false;
- }
-
- auto flags = _MINIDUMP_TYPE(
- MiniDumpNormal |
- MiniDumpWithHandleData |
- MiniDumpWithUnloadedModules |
- MiniDumpWithProcessThreadData);
-
- if (type == CoreDumpTypes::Data) {
- std::wclog << L"writing minidump with data\n";
- flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs);
- } else if (type == CoreDumpTypes::Full) {
- std::wclog << L"writing full minidump\n";
- flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory);
- } else {
- std::wclog << L"writing mini minidump\n";
- }
-
- const auto ret = MiniDumpWriteDump(
- process, pid, file.get(), flags, nullptr, nullptr, nullptr);
-
- if (!ret) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n";
-
- return false;
- }
-
- std::wclog << L"minidump written correctly\n";
- return true;
-}
-
-
-bool coredump(CoreDumpTypes type)
-{
- std::wclog << L"creating minidump for the current process\n";
- return createMiniDump(GetCurrentProcess(), type);
-}
-
-bool coredumpOther(CoreDumpTypes type)
-{
- std::wclog << L"creating minidump for an running process\n";
-
- const auto pid = findOtherPid();
- if (pid == 0) {
- std::wcerr << L"no other process found\n";
- return false;
- }
-
- std::wclog << L"found other process with pid " << pid << L"\n";
-
- HandlePtr handle(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!handle) {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
-
- return false;
- }
-
- return createMiniDump(handle.get(), type);
-}
-
-} // namespace env
-
} // namespace MOShared
diff --git a/src/shared/util.h b/src/shared/util.h
index 46764367..7bae96f2 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -20,15 +20,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#ifndef UTIL_H
#define UTIL_H
-
#include <string>
-#include <optional>
-
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-
#include <versioninfo.h>
-#include <QUuid>
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<bool> 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<bool> 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<bool> 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<Display>& displays() const;
-
-private:
- std::vector<Display> m_displays;
-};
-
-
-// represents the process's environment
-//
-class Environment
-{
-public:
- Environment();
-
- // list of loaded modules in the current process
- //
- const std::vector<Module>& loadedModules() const;
-
- // information about the operating system
- //
- const WindowsInfo& windowsInfo() const;
-
- // information about the installed security products
- //
- const std::vector<SecurityProduct>& securityProducts() const;
-
- // information about displays
- //
- const Metrics& metrics() const;
-
- // logs the environment
- //
- void dump() const;
-
-private:
- std::vector<Module> m_modules;
- WindowsInfo m_windows;
- std::vector<SecurityProduct> m_security;
- Metrics m_metrics;
-
- std::vector<Module> getLoadedModules() const;
- std::vector<SecurityProduct> getSecurityProducts() const;
-
- std::vector<SecurityProduct> getSecurityProductsFromWMI() const;
- std::optional<SecurityProduct> 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