From d168b5db084457a106c5bb155be6eca5013f77c0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 14:55:01 -0400 Subject: added AlternativesVector typedef to FileEntry split item creation from refreshConflictLists() --- src/shared/directoryentry.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/shared') diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 12cef11d..e7af1ae7 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -49,8 +49,8 @@ class FileEntry { public: typedef unsigned int Index; - typedef boost::shared_ptr Ptr; + typedef std::vector>> AlternativesVector; public: @@ -72,7 +72,7 @@ public: // gets the list of alternative origins (origins with lower priority than the primary one). // if sortOrigins has been called, it is sorted by priority (ascending) - const std::vector>> &getAlternatives() const { return m_Alternatives; } + const AlternativesVector &getAlternatives() const { return m_Alternatives; } const std::wstring &getName() const { return m_Name; } int getOrigin() const { return m_Origin; } @@ -98,7 +98,7 @@ private: std::wstring m_Name; int m_Origin = -1; std::pair m_Archive; - std::vector>> m_Alternatives; + AlternativesVector m_Alternatives; DirectoryEntry *m_Parent; mutable FILETIME m_FileTime; -- cgit v1.3.1 From fa7e34e84a09ed7a2f453a35a48e71477c841e5b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 15:15:47 -0400 Subject: conflict lists: - the file index is now stored in the row data - the alternate origins are now added to the context menu in a "go to" submenu - context menu items are now visually disabled instead of omitted from the menu --- src/modinfodialog.cpp | 171 ++++++++++++++++++++++++++++++++---------- src/modinfodialog.h | 33 +++++--- src/shared/directoryentry.cpp | 5 ++ src/shared/directoryentry.h | 1 + 4 files changed, 160 insertions(+), 50 deletions(-) (limited to 'src/shared') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b8a8ae69..60cf7578 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -58,8 +58,10 @@ using namespace MOBase; using namespace MOShared; const auto FILENAME_USERROLE = Qt::UserRole + 1; -const auto ORIGIN_USERROLE = Qt::UserRole + 2; +const auto ALT_ORIGIN_USERROLE = Qt::UserRole + 2; const auto ARCHIVE_USERROLE = Qt::UserRole + 3; +const auto INDEX_USERROLE = Qt::UserRole + 4; +const auto HAS_ALTS_USERROLE = Qt::UserRole + 5; class ModFileListWidget : public QListWidgetItem { @@ -78,8 +80,8 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS } // if there are more than 50 selected items in the conflict tree or filetree, -// don't bother checking whether they're visible, just show both menu items -const int max_scan_for_visibility = 50; +// don't bother checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; FileRenamer::FileRenamer(QWidget* parent, QFlags flags) @@ -727,19 +729,19 @@ void ModInfoDialog::refreshConflictLists( if (fileOrigin == m_Origin->getID()) { if (!alternatives.empty()) { ui->overwriteTree->addTopLevelItem(createOverwriteItem( - archive, fileName, relativeName, alternatives)); + file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree ui->noConflictTree->addTopLevelItem(createNoConflictItem( - archive, fileName, relativeName)); + file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( - fileOrigin, archive, fileName, relativeName)); + file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; } @@ -747,7 +749,8 @@ void ModInfoDialog::refreshConflictLists( if (refreshAdvanced) { auto* advancedItem = createAdvancedConflictItem( - fileOrigin, archive, fileName, relativeName, alternatives); + file->getIndex(), fileOrigin, archive, + fileName, relativeName, alternatives); if (advancedItem) { ui->conflictsAdvancedList->addTopLevelItem(advancedItem); @@ -764,7 +767,8 @@ void ModInfoDialog::refreshConflictLists( } QTreeWidgetItem* ModInfoDialog::createOverwriteItem( - bool archive, const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, const FileEntry::AlternativesVector& alternatives) { QString altString; @@ -783,22 +787,23 @@ QTreeWidgetItem* ModInfoDialog::createOverwriteItem( const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); QTreeWidgetItem *item = new QTreeWidgetItem(fields); - setConflictItem(item, fileName, origin, archive); + setConflictItem(item, index, fileName, true, origin, archive); return item; } QTreeWidgetItem* ModInfoDialog::createNoConflictItem( - bool archive, const QString& fileName, const QString& relativeName) + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); - setConflictItem(item, fileName, "", archive); + setConflictItem(item, index, fileName, false, "", archive); return item; } QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( - int fileOrigin, bool archive, + FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName) { const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); @@ -807,13 +812,13 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( fields.append(ToQString(realOrigin.getName())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); - setConflictItem(item, fileName, ToQString(realOrigin.getName()), archive); + setConflictItem(item, index, fileName, true, ToQString(realOrigin.getName()), archive); return item; } QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( - int fileOrigin, bool archive, + FileEntry::Index index,int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) { @@ -897,10 +902,12 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } + bool hasAlts = !before.isEmpty() || !after.isEmpty(); + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it - if (before.isEmpty() && after.isEmpty()) { + if (!hasAlts) { return nullptr; } } @@ -921,18 +928,21 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( item->setText(1, relativeName); item->setText(2, after); - setConflictItem(item, fileName, "", archive); + setConflictItem(item, index, fileName, hasAlts, "", archive); return item; } void ModInfoDialog::setConflictItem( - QTreeWidgetItem* item, - const QString& fileName, const QString& origin, bool archive) const + QTreeWidgetItem* item, FileEntry::Index index, + const QString& fileName, bool hasAltOrigins, const QString& altOrigin, + bool archive) const { item->setData(0, FILENAME_USERROLE, fileName); - item->setData(0, ORIGIN_USERROLE, origin); + item->setData(0, ALT_ORIGIN_USERROLE, altOrigin); item->setData(0, ARCHIVE_USERROLE, archive); + item->setData(0, INDEX_USERROLE, index); + item->setData(0, HAS_ALTS_USERROLE, hasAltOrigins); if (archive) { QFont font = item->font(0); @@ -949,9 +959,14 @@ QString ModInfoDialog::conflictFileName(const QTreeWidgetItem* conflictItem) con return conflictItem->data(0, FILENAME_USERROLE).toString(); } -QString ModInfoDialog::conflictOrigin(const QTreeWidgetItem* conflictItem) const +QString ModInfoDialog::conflictAltOrigin(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, ALT_ORIGIN_USERROLE).toString(); +} + +bool ModInfoDialog::conflictHasAlts(const QTreeWidgetItem* conflictItem) const { - return conflictItem->data(0, ORIGIN_USERROLE).toString(); + return conflictItem->data(0, HAS_ALTS_USERROLE).toBool(); } bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const @@ -959,6 +974,12 @@ bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const return conflictItem->data(0, ARCHIVE_USERROLE).toBool(); } +FileEntry::Index ModInfoDialog::conflictFileIndex(const QTreeWidgetItem* conflictItem) const +{ + static_assert(std::is_same_v); + return conflictItem->data(0, INDEX_USERROLE).toUInt(); +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { @@ -1804,7 +1825,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) enablePreview = false; enableRename = false; - if (m_FileSelection.size() < max_scan_for_visibility) { + if (m_FileSelection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it enableHide = false; @@ -1908,7 +1929,7 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - const auto origin = conflictOrigin(item); + const auto origin = conflictAltOrigin(item); if (!origin.isEmpty()) { close(); @@ -2114,6 +2135,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) QMenu menu; + // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ openConflictItems(tree->selectedItems()); @@ -2122,6 +2144,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.open); } + // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ previewConflictItems(tree->selectedItems()); @@ -2130,6 +2153,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.preview); } + // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ changeConflictItemsVisibility(tree->selectedItems(), false); @@ -2138,6 +2162,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.hide); } + // unhide if (actions.unhide) { connect(actions.unhide, &QAction::triggered, [&]{ changeConflictItemsVisibility(tree->selectedItems(), true); @@ -2146,15 +2171,27 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.unhide); } - if (menu.isEmpty()) { - return; + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + close(); + emit modOpen(name, TAB_CONFLICTS); + }); + + actions.gotoMenu->addAction(a); + } } - menu.exec(tree->viewport()->mapToGlobal(pos)); + if (!menu.isEmpty()) { + menu.exec(tree->viewport()->mapToGlobal(pos)); + } } ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( - const QList selection) + const QList& selection) { if (selection.empty()) { return {}; @@ -2164,6 +2201,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( bool enableUnhide = true; bool enableOpen = true; bool enablePreview = true; + bool enableGoto = true; if (selection.size() == 1) { // this is a single selection @@ -2176,6 +2214,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableUnhide = canUnhideConflictItem(item); enableOpen = canOpenConflictItem(item); enablePreview = canPreviewConflictItem(item); + enableGoto = conflictHasAlts(item); } else { // this is a multiple selection, don't show open/preview so users don't open @@ -2183,7 +2222,10 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableOpen = false; enablePreview = false; - if (selection.size() < max_scan_for_visibility) { + // don't bother with this on multiple selection, at least for now + enableGoto = false; + + if (selection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it enableHide = false; @@ -2198,8 +2240,8 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableUnhide = true; } - if (enableHide && enableUnhide) { - // found both, no need to check more + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more break; } } @@ -2208,22 +2250,71 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( ConflictActions actions; - if (enableHide) { - actions.hide = new QAction(tr("Hide")); - } + actions.hide = new QAction(tr("Hide"), this); + actions.hide->setEnabled(enableHide); // note that it is possible for hidden files to appear if they override other // hidden files from another mod - if (enableUnhide) { - actions.unhide = new QAction(tr("Unhide")); + actions.unhide = new QAction(tr("Unhide"), this); + actions.unhide->setEnabled(enableUnhide); + + actions.open = new QAction(tr("Open/Execute"), this); + actions.open->setEnabled(enableOpen); + + actions.preview = new QAction(tr("Preview"), this); + actions.preview->setEnabled(enablePreview); + + actions.gotoMenu = new QMenu(tr("Go to..."), this); + actions.gotoMenu->setEnabled(enableGoto); + + if (enableGoto) { + actions.gotoActions = createGotoActions(selection); } - if (enableOpen) { - actions.open = new QAction(tr("Open/Execute")); + return actions; +} + +std::vector ModInfoDialog::createGotoActions(const QList& selection) +{ + if (!m_Origin || selection.size() != 1) { + return {}; + } + + auto* item = selection[0]; + if (!item) { + return {}; + } + + auto file = m_Origin->findFile(conflictFileIndex(item)); + if (!file) { + return {}; } - if (enablePreview) { - actions.preview = new QAction(tr("Preview")); + + std::vector mods; + + // add all alternatives + for (const auto& alt : file->getAlternatives()) { + const auto& o = m_Directory->getOriginByID(alt.first); + if (o.getID() != m_Origin->getID()) { + mods.push_back(ToQString(o.getName())); + } + } + + // add the real origin if different from this mod + const FilesOrigin& realOrigin = m_Directory->getOriginByID(file->getOrigin()); + if (realOrigin.getID() != m_Origin->getID()) { + mods.push_back(ToQString(realOrigin.getName())); + } + + std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { + return (QString::localeAwareCompare(a, b) < 0); + }); + + std::vector actions; + + for (const auto& name : mods) { + actions.push_back(new QAction(name, this)); } return actions; @@ -2231,7 +2322,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - const auto origin = conflictOrigin(item); + const auto origin = conflictAltOrigin(item); if (!origin.isEmpty()) { close(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 85505487..8510c96d 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -385,15 +385,20 @@ private slots: void createTweak(); private: + using FileEntry = MOShared::FileEntry; + struct ConflictActions { QAction* hide; QAction* unhide; QAction* open; QAction* preview; + QMenu* gotoMenu; + std::vector gotoActions; - ConflictActions() - : hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr) + ConflictActions() : + hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), + gotoMenu(nullptr) { } }; @@ -438,28 +443,33 @@ private: void refreshFiles(); QTreeWidgetItem* createOverwriteItem( - bool archive, const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); QTreeWidgetItem* createNoConflictItem( - bool archive, const QString& fileName, const QString& relativeName); + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName); QTreeWidgetItem* createOverwrittenItem( - int fileOrigin, bool archive, + FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName); QTreeWidgetItem* createAdvancedConflictItem( - int fileOrigin, bool archive, + FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); void setConflictItem( - QTreeWidgetItem* item, - const QString& fileName, const QString& origin, bool archive) const; + QTreeWidgetItem* item, FileEntry::Index index, + const QString& fileName, bool hasAltOrigins, const QString& altOrigin, + bool archive) const; QString conflictFileName(const QTreeWidgetItem* conflictItem) const; - QString conflictOrigin(const QTreeWidgetItem* conflictItem) const; + QString conflictAltOrigin(const QTreeWidgetItem* conflictItem) const; + bool conflictHasAlts(const QTreeWidgetItem* conflictItem) const; bool conflictIsArchive(const QTreeWidgetItem* conflictItem) const; + FileEntry::Index conflictFileIndex(const QTreeWidgetItem* conflictItem) const; void restoreTabState(const QByteArray &state); void restoreConflictsState(const QByteArray &state); @@ -487,7 +497,10 @@ private: void showConflictMenu(const QPoint &pos, QTreeWidget* tree); ConflictActions createConflictMenuActions( - const QList selection); + const QList& selection); + + std::vector createGotoActions( + const QList& selection); }; #endif // MODINFODIALOG_H diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 1179110a..bde515a9 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -220,6 +220,11 @@ std::vector FilesOrigin::getFiles() const return result; } +FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const +{ + return m_FileRegister.lock()->getFile(index); +} + bool FilesOrigin::containsArchive(std::wstring archiveName) { for (FileEntry::Index fileIdx : m_Files) diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index e7af1ae7..785c3ff6 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -135,6 +135,7 @@ public: const std::wstring &getPath() const { return m_Path; } std::vector getFiles() const; + FileEntry::Ptr findFile(FileEntry::Index index) const; void enable(bool enabled, time_t notAfter = LONG_MAX); bool isDisabled() const { return m_Disabled; } -- cgit v1.3.1 From eab0ec298b81138c4c602259ebf930f583113b95 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 15:26:04 -0400 Subject: refactored preloadSsl() into preloadDll() removed old HGID check moved formatSystemMessage() to uibase added Environment class, lists loaded modules, logged at startup --- src/main.cpp | 70 ++++++----- src/settings.cpp | 31 ----- src/shared/util.cpp | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 50 +++++++- 4 files changed, 418 insertions(+), 60 deletions(-) (limited to 'src/shared') diff --git a/src/main.cpp b/src/main.cpp index 6f304944..e1a6b853 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -411,27 +411,39 @@ void setupPath() ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); } -static void preloadSsl() +void preloadDll(const QString& filename) { - QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); - if (GetModuleHandleA("libeay32.dll")) - qWarning("libeay32.dll already loaded?!"); - else { - QString libeay32 = appPath + "\\libeay32.dll"; - if (!QFile::exists(libeay32)) - qWarning("libeay32.dll not found: %s", qUtf8Printable(libeay32)); - else if (!LoadLibraryW(libeay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qUtf8Printable(libeay32), GetLastError()); + qDebug().nospace() << "preloading " << filename; + + if (!GetModuleHandleW(filename.toStdWString().c_str())) { + // already loaded, this can happen when "restarting" MO by switching + // instances, for example + return; } - if (GetModuleHandleA("ssleay32.dll")) - qWarning("ssleay32.dll already loaded?!"); - else { - QString ssleay32 = appPath + "\\ssleay32.dll"; - if (!QFile::exists(ssleay32)) - qWarning("ssleay32.dll not found: %s", qUtf8Printable(ssleay32)); - else if (!LoadLibraryW(ssleay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qUtf8Printable(ssleay32), GetLastError()); + + const auto appPath = QDir::toNativeSeparators( + QCoreApplication::applicationDirPath()); + + const auto dllPath = appPath + "\\" + filename; + + if (!QFile::exists(dllPath)) { + qWarning().nospace() << dllPath << "not found"; + return; } + + if (!LoadLibraryW(dllPath.toStdWString().c_str())) { + const auto e = GetLastError(); + + qWarning().nospace() + << "failed to load " << dllPath << ": " + << formatSystemMessage(e); + } +} + +void preloadSsl() +{ + preloadDll("libeay32.dll"); + preloadDll("ssleay32.dll"); } static QString getVersionDisplayString() @@ -443,15 +455,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - qDebug("Starting Mod Organizer version %s revision %s", qUtf8Printable(getVersionDisplayString()), -#if defined(HGID) - HGID -#elif defined(GITID) - GITID -#else - "unknown" -#endif - ); + qDebug().nospace() + << "Starting Mod Organizer version " + << getVersionDisplayString() << " revision " << GITID; #if !defined(QT_NO_SSL) preloadSsl(); @@ -460,6 +466,16 @@ int runApplication(MOApplication &application, SingleInstance &instance, qDebug("non-ssl build"); #endif + { + Environment env; + + for (const auto& m : env.loadedModules()) { + qInfo().nospace().noquote() << " . " << m.toString(); + } + + return 0; + } + QString dataPath = application.property("dataPath").toString(); qDebug("data path: %s", qUtf8Printable(dataPath)); diff --git a/src/settings.cpp b/src/settings.cpp index b45912d4..59d3369a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -82,37 +82,6 @@ private: }; -QString formatSystemMessage(DWORD id) -{ - wchar_t* message = nullptr; - - const auto ret = FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - id, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message), - 0, NULL); - - QString s; - const QString idString = QString("0x%1").arg(id, 0, 16); - - if (ret == 0 || !message) { - s = idString; - } else { - s = QString("%1 (%2)") - .arg(QString::fromStdWString(message).trimmed()) - .arg(idString); - } - - LocalFree(message); - - return s; -} - - Settings *Settings::s_Instance = nullptr; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index ed7c434e..16b373db 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "error_report.h" +#include #include #include @@ -29,6 +30,8 @@ along with Mod Organizer. If not, see . #include #include +using MOBase::formatSystemMessage; + namespace MOShared { @@ -253,4 +256,328 @@ MOBase::VersionInfo createVersionInfo() } +struct HandleCloser +{ + using pointer = HANDLE; + void operator()(HANDLE h) + { + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + } + } +}; + + +Environment::Environment() +{ + getLoadedModules(); +} + +const std::vector& Environment::loadedModules() +{ + return m_modules; +} + +void Environment::getLoadedModules() +{ + std::unique_ptr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + + qCritical().nospace() + << "CreateToolhelp32Snapshot() failed, " + << formatSystemMessage(e); + + return; + } + + // Set the size of the structure before using it. + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // Retrieve information about the first module, + // and exit if unsuccessful + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + + qCritical().nospace() + << "Module32First() failed, " << formatSystemMessage(e); + + return; + } + + // Now walk the module list of the process, + // and display information about each module + qInfo() << "modules loaded in process:"; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + + m_modules.push_back(Module(path, me.modBaseSize)); + + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + if (e != ERROR_NO_MORE_FILES) { + qCritical() << "Module32Next() failed, " << formatSystemMessage(e); + } + + break; + } + } +} + + +Environment::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; +} + +const QString& Environment::Module::path() const +{ + return m_path; +} + +std::size_t Environment::Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Environment::Module::version() const +{ + return m_version; +} + +const QString& Environment::Module::versionString() const +{ + return m_versionString; +} + +QString Environment::Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Environment::Module::toString() const +{ + QStringList sl; + + sl.push_back(m_path); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + 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 != m_version) { + sl.push_back(versionString()); + } + } + + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + return sl.join(", "); +} + +Environment::Module::FileInfo Environment::Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + 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 << "', " + << formatSystemMessage(e); + + return {}; + } + + auto buffer = std::make_unique(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "GetFileVersionInfoW() failed on '" << m_path << "', " + << formatSystemMessage(e); + + return {}; + } + + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Environment::Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = reinterpret_cast(valuePointer); + + if (fi->dwSignature != 0xfeef04bd) { + qCritical().nospace().noquote() + << "bad file info signature 0x" << hex << fi->dwSignature << " for " + << "'" << m_path << "'"; + + return {}; + } + + return *fi; +} + +QString Environment::Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + 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 {}; + } + + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + const auto* lcp = reinterpret_cast(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + reinterpret_cast(valuePointer), valueSize - 1); +} + +QString Environment::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 Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + std::unique_ptr 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() + << "can't open file '" << m_path << "' for timestamp, " + << formatSystemMessage(e); + + return {}; + } + + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + qCritical() + << "can't get file time for '" << m_path << "', " + << formatSystemMessage(e); + + return {}; + } + } else { + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + SYSTEMTIME utc = {}; + if (!FileTimeToSystemTime(&ft, &utc)) { + qCritical() + << "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)); +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 1fdfb089..5d88481c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -46,8 +46,54 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); -VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName); -std::wstring GetFileVersionString(const std::wstring &fileName); +class Environment +{ +public: + class Module + { + public: + explicit Module(QString path, std::size_t fileSize); + + const QString& path() const; + std::size_t fileSize() const; + const QString& version() const; + const QString& versionString() const; + QString timestampString() const; + + QString toString() const; + + private: + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + + FileInfo getFileInfo() const; + + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + QString getFileDescription(std::byte* buffer) const; + }; + + Environment(); + + const std::vector& loadedModules(); + +private: + std::vector m_modules; + + void getLoadedModules(); +}; + MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From 7152b8e97b060e34e5a5bfa8f36c94722007d6aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 16:13:55 -0400 Subject: lower case paths, sorted list, md5 for some files --- src/shared/util.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++------- src/shared/util.h | 4 ++++ 2 files changed, 54 insertions(+), 7 deletions(-) (limited to 'src/shared') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 16b373db..772d3ca5 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -287,7 +287,7 @@ void Environment::getLoadedModules() { const auto e = GetLastError(); - qCritical().nospace() + qCritical().nospace().noquote() << "CreateToolhelp32Snapshot() failed, " << formatSystemMessage(e); @@ -304,7 +304,7 @@ void Environment::getLoadedModules() { const auto e = GetLastError(); - qCritical().nospace() + qCritical().nospace().noquote() << "Module32First() failed, " << formatSystemMessage(e); return; @@ -324,12 +324,17 @@ void Environment::getLoadedModules() const auto e = GetLastError(); if (e != ERROR_NO_MORE_FILES) { - qCritical() << "Module32Next() failed, " << formatSystemMessage(e); + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessage(e); } break; } } + + std::sort(m_modules.begin(), m_modules.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); } @@ -341,6 +346,7 @@ Environment::Module::Module(QString path, std::size_t fileSize) m_version = getVersion(fi.ffi); m_timestamp = getTimestamp(fi.ffi); m_versionString = fi.fileDescription; + m_md5 = getMD5(); } const QString& Environment::Module::path() const @@ -348,6 +354,11 @@ const QString& Environment::Module::path() const return m_path; } +QString Environment::Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + std::size_t Environment::Module::fileSize() const { return m_fileSize; @@ -376,7 +387,7 @@ QString Environment::Module::toString() const { QStringList sl; - sl.push_back(m_path); + sl.push_back(displayPath()); sl.push_back(QString("%1 B").arg(m_fileSize)); if (m_version.isEmpty() && m_versionString.isEmpty()) { @@ -397,6 +408,10 @@ QString Environment::Module::toString() const sl.push_back("(no timestamp)"); } + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + return sl.join(", "); } @@ -543,7 +558,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (h.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - qCritical() + qCritical().nospace().noquote() << "can't open file '" << m_path << "' for timestamp, " << formatSystemMessage(e); @@ -552,7 +567,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); - qCritical() + qCritical().nospace().noquote() << "can't get file time for '" << m_path << "', " << formatSystemMessage(e); @@ -566,7 +581,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const SYSTEMTIME utc = {}; if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical() + qCritical().nospace().noquote() << "FileTimeToSystemTime() failed on timestamp " << "high=0x" << hex << ft.dwHighDateTime << " " << "low=0x" << hex << ft.dwLowDateTime << " for " @@ -580,4 +595,32 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); } +QString Environment::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 {}; + } + + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + qCritical().nospace().noquote() + << "failed to open file '" << m_path << "' for md5"; + + return {}; + } + + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + qCritical().nospace().noquote() + << "failed to calculate md5 for '" << m_path << "'"; + + return {}; + } + + return hash.result().toHex(); +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 5d88481c..46bdea78 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -55,6 +55,8 @@ public: explicit Module(QString path, std::size_t fileSize); const QString& path() const; + QString displayPath() const; + std::size_t fileSize() const; const QString& version() const; const QString& versionString() const; @@ -74,11 +76,13 @@ public: QString m_version; QDateTime m_timestamp; QString m_versionString; + QString m_md5; FileInfo getFileInfo() const; QString getVersion(const VS_FIXEDFILEINFO& fi) const; QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + QString getMD5() const; VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; QString getFileDescription(std::byte* buffer) const; -- cgit v1.3.1 From fb6512b72ebf86d5273744774388deb14c8a21ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 18:16:00 -0400 Subject: log windows version --- src/main.cpp | 6 +- src/shared/util.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/shared/util.h | 94 +++++++++++++++-------- 3 files changed, 264 insertions(+), 47 deletions(-) (limited to 'src/shared') diff --git a/src/main.cpp b/src/main.cpp index e1a6b853..75148c4b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -467,8 +467,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, #endif { - Environment env; + env::Environment env; + qInfo().nospace().noquote() + << "windows: " << env.windowsVersion().toString(); + + qInfo() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { qInfo().nospace().noquote() << " . " << m.toString(); } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 772d3ca5..9337ebf6 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -256,9 +256,13 @@ MOBase::VersionInfo createVersionInfo() } +namespace env +{ + struct HandleCloser { using pointer = HANDLE; + void operator()(HANDLE h) { if (h != INVALID_HANDLE_VALUE) { @@ -267,17 +271,34 @@ struct HandleCloser } }; +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + Environment::Environment() { getLoadedModules(); } -const std::vector& Environment::loadedModules() +const std::vector& Environment::loadedModules() { return m_modules; } +const WindowsVersion& Environment::windowsVersion() const +{ + return m_windows; +} + void Environment::getLoadedModules() { std::unique_ptr snapshot(CreateToolhelp32Snapshot( @@ -312,7 +333,6 @@ void Environment::getLoadedModules() // Now walk the module list of the process, // and display information about each module - qInfo() << "modules loaded in process:"; for (;;) { @@ -338,7 +358,7 @@ void Environment::getLoadedModules() } -Environment::Module::Module(QString path, std::size_t fileSize) +Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) { const auto fi = getFileInfo(); @@ -349,32 +369,32 @@ Environment::Module::Module(QString path, std::size_t fileSize) m_md5 = getMD5(); } -const QString& Environment::Module::path() const +const QString& Module::path() const { return m_path; } -QString Environment::Module::displayPath() const +QString Module::displayPath() const { return QDir::fromNativeSeparators(m_path.toLower()); } -std::size_t Environment::Module::fileSize() const +std::size_t Module::fileSize() const { return m_fileSize; } -const QString& Environment::Module::version() const +const QString& Module::version() const { return m_version; } -const QString& Environment::Module::versionString() const +const QString& Module::versionString() const { return m_versionString; } -QString Environment::Module::timestampString() const +QString Module::timestampString() const { if (!m_timestamp.isValid()) { return "(no timestamp)"; @@ -383,7 +403,7 @@ QString Environment::Module::timestampString() const return m_timestamp.toString(Qt::DateFormat::ISODate); } -QString Environment::Module::toString() const +QString Module::toString() const { QStringList sl; @@ -415,7 +435,7 @@ QString Environment::Module::toString() const return sl.join(", "); } -Environment::Module::FileInfo Environment::Module::getFileInfo() const +Module::FileInfo Module::getFileInfo() const { const auto wspath = m_path.toStdWString(); @@ -457,7 +477,7 @@ Environment::Module::FileInfo Environment::Module::getFileInfo() const return fi; } -VS_FIXEDFILEINFO Environment::Module::getFixedFileInfo(std::byte* buffer) const +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const { void* valuePointer = nullptr; unsigned int valueSize = 0; @@ -482,7 +502,7 @@ VS_FIXEDFILEINFO Environment::Module::getFixedFileInfo(std::byte* buffer) const return *fi; } -QString Environment::Module::getFileDescription(std::byte* buffer) const +QString Module::getFileDescription(std::byte* buffer) const { struct LANGANDCODEPAGE { @@ -527,7 +547,7 @@ QString Environment::Module::getFileDescription(std::byte* buffer) const reinterpret_cast(valuePointer), valueSize - 1); } -QString Environment::Module::getVersion(const VS_FIXEDFILEINFO& fi) const +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const { if (fi.dwSignature == 0) { return {}; @@ -546,7 +566,7 @@ QString Environment::Module::getVersion(const VS_FIXEDFILEINFO& fi) const .arg(major).arg(minor).arg(maintenance).arg(build); } -QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const { FILETIME ft = {}; @@ -595,7 +615,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); } -QString Environment::Module::getMD5() const +QString Module::getMD5() const { if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { // don't calculate md5 for system files, it's not really relevant and @@ -623,4 +643,163 @@ QString Environment::Module::getMD5() const return hash.result().toHex(); } + +WindowsVersion::WindowsVersion() : + m_realMajor(0), m_realMinor(0), m_realBuild(0), + m_major(0), m_minor(0), m_build(0), m_UBR(0) +{ + getVersion(); + getRelease(); + getElevated(); +} + +QString WindowsVersion::toString() const +{ + QStringList sl; + + const QString version = QString("%1.%2.%3") + .arg(m_major).arg(m_minor).arg(m_build); + + const QString realVersion = QString("%1.%2.%3") + .arg(m_realMajor).arg(m_realMinor).arg(m_realBuild); + + sl.push_back("version: " + version); + + if (m_realMajor != m_major || m_realMinor != m_minor || m_realBuild != m_build) { + sl.push_back("real version: " + realVersion); + } + + if (!m_buildLab.isEmpty()) { + sl.push_back(m_buildLab); + } + + if (!m_productName.isEmpty()) { + sl.push_back(m_productName); + } + + if (!m_releaseID.isEmpty()) { + sl.push_back("build " + m_releaseID); + } + + if (m_UBR != 0) { + sl.push_back(QString("%1").arg(m_UBR)); + } + + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +void WindowsVersion::getVersion() +{ + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } + + getRealVersion(ntdll.get()); + getReportedVersion(ntdll.get()); +} + +void WindowsVersion::getRealVersion(HINSTANCE ntdll) +{ + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (RtlGetNtVersionNumbers) { + DWORD build = 0; + RtlGetNtVersionNumbers(&m_realMajor, &m_realMinor, &build); + + m_realBuild = 0x0fffffff & build; + } +} + +void WindowsVersion::getReportedVersion(HINSTANCE ntdll) +{ + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + qCritical() << "RtlGetVersion() not found in ntdll.dll"; + return; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + m_major = vi.dwMajorVersion; + m_minor = vi.dwMinorVersion; + m_build = vi.dwBuildNumber; +} + +void WindowsVersion::getRelease() +{ + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + m_buildLab = settings.value("BuildLabEx", "").toString(); + if (m_buildLab.isEmpty()) { + m_buildLab = settings.value("BuildLab", "").toString(); + if (m_buildLab.isEmpty()) { + m_buildLab = settings.value("BuildBranch", "").toString(); + } + } + + m_productName = settings.value("ProductName", "").toString(); + m_releaseID = settings.value("ReleaseId", "").toString(); + m_UBR = settings.value("UBR", 0).toUInt(); +} + +void WindowsVersion::getElevated() +{ + std::unique_ptr 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: " << formatSystemMessage(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: " << formatSystemMessage(e); + + return; + } + + m_elevated = (e.TokenIsElevated != 0); +} + +} // namespace env + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 46bdea78..deaf6fcc 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -22,6 +22,8 @@ along with Mod Organizer. If not, see . #include +#include + #define WIN32_LEAN_AND_MEAN #include @@ -46,58 +48,90 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); -class Environment + +namespace env +{ + +class Module { public: - class Module + explicit Module(QString path, std::size_t fileSize); + + const QString& path() const; + QString displayPath() const; + + std::size_t fileSize() const; + const QString& version() const; + const QString& versionString() const; + QString timestampString() const; + + QString toString() const; + +private: + struct FileInfo { - public: - explicit Module(QString path, std::size_t fileSize); + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; - const QString& path() const; - QString displayPath() const; + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; - std::size_t fileSize() const; - const QString& version() const; - const QString& versionString() const; - QString timestampString() const; + FileInfo getFileInfo() const; - QString toString() const; + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + QString getMD5() const; - private: - struct FileInfo - { - VS_FIXEDFILEINFO ffi; - QString fileDescription; - }; + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + QString getFileDescription(std::byte* buffer) const; +}; - QString m_path; - std::size_t m_fileSize; - QString m_version; - QDateTime m_timestamp; - QString m_versionString; - QString m_md5; - FileInfo getFileInfo() const; +class WindowsVersion +{ +public: + WindowsVersion(); - QString getVersion(const VS_FIXEDFILEINFO& fi) const; - QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; - QString getMD5() const; + QString toString() const; + +private: + DWORD m_realMajor, m_realMinor, m_realBuild; + DWORD m_major, m_minor, m_build; + QString m_buildLab, m_productName, m_releaseID; + DWORD m_UBR; + std::optional m_elevated; + + void getVersion(); + void getRealVersion(HINSTANCE ntdll); + void getReportedVersion(HINSTANCE ntdll); + void getRelease(); + void getElevated(); +}; - VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; - QString getFileDescription(std::byte* buffer) const; - }; +class Environment +{ +public: Environment(); const std::vector& loadedModules(); + const WindowsVersion& windowsVersion() const; private: std::vector m_modules; + WindowsVersion m_windows; void getLoadedModules(); }; +} // namespace env + + MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From 813ab32a2d16106f2799fba4e85c768a5b1c4850 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 18:58:15 -0400 Subject: added a warning when running in compatibility mode --- src/main.cpp | 6 ++++-- src/shared/util.cpp | 14 +++++++++++++- src/shared/util.h | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) (limited to 'src/shared') diff --git a/src/main.cpp b/src/main.cpp index 75148c4b..e43ec6d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -472,12 +472,14 @@ int runApplication(MOApplication &application, SingleInstance &instance, qInfo().nospace().noquote() << "windows: " << env.windowsVersion().toString(); + if (env.windowsVersion().compatibilityMode()) { + qWarning() << "MO seems to be running in compatibility mode"; + } + qInfo() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { qInfo().nospace().noquote() << " . " << m.toString(); } - - return 0; } QString dataPath = application.property("dataPath").toString(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 9337ebf6..33094eff 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -653,6 +653,18 @@ WindowsVersion::WindowsVersion() : getElevated(); } +bool WindowsVersion::compatibilityMode() const +{ + if (m_realMajor == 0 && m_realMinor == 0 && m_realBuild == 0) { + return false; + } + + return + m_realMajor != m_major || + m_realMinor != m_minor || + m_realBuild != m_build; +} + QString WindowsVersion::toString() const { QStringList sl; @@ -665,7 +677,7 @@ QString WindowsVersion::toString() const sl.push_back("version: " + version); - if (m_realMajor != m_major || m_realMinor != m_minor || m_realBuild != m_build) { + if (compatibilityMode()) { sl.push_back("real version: " + realVersion); } diff --git a/src/shared/util.h b/src/shared/util.h index deaf6fcc..232a97bb 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -97,6 +97,7 @@ class WindowsVersion public: WindowsVersion(); + bool compatibilityMode() const; QString toString() const; private: -- cgit v1.3.1 From 1aa174f1438d7b1c1a9dab47d69e912726578a06 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 20:02:10 -0400 Subject: comments, some refactoring switched to qDebug() --- src/main.cpp | 10 +- src/shared/util.cpp | 259 +++++++++++++++++++++++++++++++++++----------------- src/shared/util.h | 159 +++++++++++++++++++++++++++++--- 3 files changed, 326 insertions(+), 102 deletions(-) (limited to 'src/shared') diff --git a/src/main.cpp b/src/main.cpp index e43ec6d0..6c3e40be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -469,16 +469,16 @@ int runApplication(MOApplication &application, SingleInstance &instance, { env::Environment env; - qInfo().nospace().noquote() - << "windows: " << env.windowsVersion().toString(); + qDebug().nospace().noquote() + << "windows: " << env.windowsInfo().toString(); - if (env.windowsVersion().compatibilityMode()) { + if (env.windowsInfo().compatibilityMode()) { qWarning() << "MO seems to be running in compatibility mode"; } - qInfo() << "modules loaded in process:"; + qDebug() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { - qInfo().nospace().noquote() << " . " << m.toString(); + qDebug().nospace().noquote() << " . " << m.toString(); } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 33094eff..70adb791 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -294,7 +294,7 @@ const std::vector& Environment::loadedModules() return m_modules; } -const WindowsVersion& Environment::windowsVersion() const +const WindowsInfo& Environment::windowsInfo() const { return m_windows; } @@ -315,12 +315,10 @@ void Environment::getLoadedModules() return; } - // Set the size of the structure before using it. MODULEENTRY32 me = {}; me.dwSize = sizeof(me); - // Retrieve information about the first module, - // and exit if unsuccessful + // first module, this shouldn't fail because there's at least the executable if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); @@ -331,27 +329,29 @@ void Environment::getLoadedModules() return; } - // Now walk the module list of the process, - // and display information about each module - for (;;) { const auto path = QString::fromWCharArray(me.szExePath); m_modules.push_back(Module(path, me.modBaseSize)); + // next module if (!Module32Next(snapshot.get(), &me)) { const auto e = GetLastError(); - if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessage(e); + if (e == ERROR_NO_MORE_FILES) { + // not an error + break; } + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessage(e); + break; } } + // sorting by display name std::sort(m_modules.begin(), m_modules.end(), [](auto&& a, auto&& b) { return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); }); @@ -394,6 +394,16 @@ 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()) { @@ -407,9 +417,11 @@ 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 { @@ -417,17 +429,19 @@ QString Module::toString() const sl.push_back(m_version); } - if (m_versionString != 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); } @@ -439,6 +453,7 @@ 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); @@ -457,6 +472,7 @@ Module::FileInfo Module::getFileInfo() const return {}; } + // getting version info auto buffer = std::make_unique(size); if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { @@ -469,6 +485,8 @@ Module::FileInfo Module::getFileInfo() const return {}; } + // the version info has two major parts: a fixed version and a localizable + // set of strings FileInfo fi; fi.ffi = getFixedFileInfo(buffer.get()); @@ -482,6 +500,7 @@ 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) { @@ -491,6 +510,7 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const const auto* fi = reinterpret_cast(valuePointer); + // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { qCritical().nospace().noquote() << "bad file info signature 0x" << hex << fi->dwSignature << " for " @@ -513,6 +533,7 @@ QString Module::getFileDescription(std::byte* buffer) const void* valuePointer = nullptr; unsigned int valueSize = 0; + // getting list of available languages auto ret = VerQueryValueW( buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); @@ -523,11 +544,13 @@ QString Module::getFileDescription(std::byte* buffer) const 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 = reinterpret_cast(valuePointer); const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") @@ -571,6 +594,10 @@ 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 std::unique_ptr h(CreateFileW( m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); @@ -585,6 +612,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const return {}; } + // getting the file time if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); qCritical().nospace().noquote() @@ -594,12 +622,15 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const 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 " @@ -623,6 +654,7 @@ QString Module::getMD5() const return {}; } + // opening the file QFile f(m_path); if (!f.open(QFile::ReadOnly)) { @@ -632,6 +664,7 @@ QString Module::getMD5() const return {}; } + // hashing QCryptographicHash hash(QCryptographicHash::Md5); if (!hash.addData(&f)) { qCritical().nospace().noquote() @@ -644,59 +677,97 @@ QString Module::getMD5() const } -WindowsVersion::WindowsVersion() : - m_realMajor(0), m_realMinor(0), m_realBuild(0), - m_major(0), m_minor(0), m_build(0), m_UBR(0) +WindowsInfo::WindowsInfo() { - getVersion(); - getRelease(); - getElevated(); + // loading ntdll.dll, the functions will be found with GetProcAddress() + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); } -bool WindowsVersion::compatibilityMode() const +bool WindowsInfo::compatibilityMode() const { - if (m_realMajor == 0 && m_realMinor == 0 && m_realBuild == 0) { + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode return false; } - return - m_realMajor != m_major || - m_realMinor != m_minor || - m_realBuild != m_build; + return (m_real != m_reported); } -QString WindowsVersion::toString() const +const WindowsInfo::Version& WindowsInfo::reportedVersion() const { - QStringList sl; + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional WindowsInfo::isElevated() const +{ + return m_elevated; +} - const QString version = QString("%1.%2.%3") - .arg(m_major).arg(m_minor).arg(m_build); +QString WindowsInfo::toString() const +{ + QStringList sl; - const QString realVersion = QString("%1.%2.%3") - .arg(m_realMajor).arg(m_realMinor).arg(m_realBuild); + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); - sl.push_back("version: " + version); + // version + sl.push_back("version: " + reported); + // real version if different if (compatibilityMode()) { - sl.push_back("real version: " + realVersion); + sl.push_back("real version: " + real); } - if (!m_buildLab.isEmpty()) { - sl.push_back(m_buildLab); + // 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)); } - if (!m_productName.isEmpty()) { - sl.push_back(m_productName); + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); } - if (!m_releaseID.isEmpty()) { - sl.push_back("build " + m_releaseID); + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); } - if (m_UBR != 0) { - sl.push_back(QString("%1").arg(m_UBR)); + // 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"); @@ -707,36 +778,14 @@ QString WindowsVersion::toString() const return sl.join(", "); } -void WindowsVersion::getVersion() -{ - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); - - if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; - return; - } - - getRealVersion(ntdll.get()); - getReportedVersion(ntdll.get()); -} - -void WindowsVersion::getRealVersion(HINSTANCE ntdll) +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const { - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); - - auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + // 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 - if (RtlGetNtVersionNumbers) { - DWORD build = 0; - RtlGetNtVersionNumbers(&m_realMajor, &m_realMinor, &build); - - m_realBuild = 0x0fffffff & build; - } -} - -void WindowsVersion::getReportedVersion(HINSTANCE ntdll) -{ using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); auto* RtlGetVersion = reinterpret_cast( @@ -744,39 +793,81 @@ void WindowsVersion::getReportedVersion(HINSTANCE ntdll) if (!RtlGetVersion) { qCritical() << "RtlGetVersion() not found in ntdll.dll"; - return; + return {}; } OSVERSIONINFOEX vi = {}; vi.dwOSVersionInfoSize = sizeof(vi); + // this apparently never fails RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); - m_major = vi.dwMajorVersion; - m_minor = vi.dwMinorVersion; - m_build = vi.dwBuildNumber; + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; } -void WindowsVersion::getRelease() +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); - m_buildLab = settings.value("BuildLabEx", "").toString(); - if (m_buildLab.isEmpty()) { - m_buildLab = settings.value("BuildLab", "").toString(); - if (m_buildLab.isEmpty()) { - m_buildLab = settings.value("BuildBranch", "").toString(); + 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(); } } - m_productName = settings.value("ProductName", "").toString(); - m_releaseID = settings.value("ReleaseId", "").toString(); - m_UBR = settings.value("UBR", 0).toUInt(); + // 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; } -void WindowsVersion::getElevated() +std::optional WindowsInfo::getElevated() const { std::unique_ptr token; @@ -790,7 +881,7 @@ void WindowsVersion::getElevated() << "while trying to check if process is elevated, " << "OpenProcessToken() failed: " << formatSystemMessage(e); - return; + return {}; } token.reset(rawToken); @@ -806,10 +897,10 @@ void WindowsVersion::getElevated() << "while trying to check if process is elevated, " << "GetTokenInformation() failed: " << formatSystemMessage(e); - return; + return {}; } - m_elevated = (e.TokenIsElevated != 0); + return (e.TokenIsElevated != 0); } } // namespace env diff --git a/src/shared/util.h b/src/shared/util.h index 232a97bb..3ec677f4 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -52,22 +52,55 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); 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; @@ -81,51 +114,151 @@ private: 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; }; -class WindowsVersion +// a variety of information on windows +// +class WindowsInfo { public: - WindowsVersion(); + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional isElevated() const; + + // returns a string with all the above information on one line + // QString toString() const; private: - DWORD m_realMajor, m_realMinor, m_realBuild; - DWORD m_major, m_minor, m_build; - QString m_buildLab, m_productName, m_releaseID; - DWORD m_UBR; + Version m_reported, m_real; + Release m_release; std::optional m_elevated; - void getVersion(); - void getRealVersion(HINSTANCE ntdll); - void getReportedVersion(HINSTANCE ntdll); - void getRelease(); - void getElevated(); + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional getElevated() const; }; +// represents the process's environment +// class Environment { public: Environment(); + // list of loaded modules in the current process + // const std::vector& loadedModules(); - const WindowsVersion& windowsVersion() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; private: std::vector m_modules; - WindowsVersion m_windows; + WindowsInfo m_windows; void getLoadedModules(); }; -- cgit v1.3.1 From 5a74b02442302fb484b1db68cf0ce1736af4911c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 03:41:32 -0400 Subject: security products --- src/main.cpp | 7 ++ src/shared/util.cpp | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/shared/util.h | 21 ++++ 3 files changed, 308 insertions(+), 3 deletions(-) (limited to 'src/shared') diff --git a/src/main.cpp b/src/main.cpp index 6c3e40be..518d31a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -476,10 +476,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, qWarning() << "MO seems to be running in compatibility mode"; } + qDebug().nospace().noquote() << "security features:"; + for (const auto& sf : env.securityFeatures()) { + qDebug().nospace().noquote() << " . " << sf.toString(); + } + qDebug() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { qDebug().nospace().noquote() << " . " << m.toString(); } + + return 0; } QString dataPath = application.property("dataPath").toString(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 70adb791..aa2e8d0f 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -30,6 +30,11 @@ along with Mod Organizer. If not, see . #include #include +#include +#include +#include +#pragma comment(lib, "Wbemuuid.lib") + using MOBase::formatSystemMessage; namespace MOShared { @@ -283,10 +288,153 @@ struct LibraryFreer } }; +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + + for (;;) + { + std::unique_ptr object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0) { + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + + std::unique_ptr 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)) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessage(ret); + + return {}; + } + + return std::unique_ptr(rawEnumerator); + } + +private: + std::unique_ptr m_locator; + std::unique_ptr m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret)) { + qCritical() + << "CoCreateInstance for WbemLocator failed, " + << formatSystemMessage(ret); + + throw failed(); + } + + m_locator.reset(static_cast(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res)) { + qCritical() + << "locator->ConnectServer() failed for namespace " + << "'" << QString::fromStdString(ns) << "', " + << formatSystemMessage(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, " << formatSystemMessage(ret); + + throw failed(); + } + } +}; + Environment::Environment() { getLoadedModules(); + getSecurityFeatures(); } const std::vector& Environment::loadedModules() @@ -299,6 +447,11 @@ const WindowsInfo& Environment::windowsInfo() const return m_windows; } +const std::vector& Environment::securityFeatures() const +{ + return m_security; +} + void Environment::getLoadedModules() { std::unique_ptr snapshot(CreateToolhelp32Snapshot( @@ -357,6 +510,59 @@ void Environment::getLoadedModules() }); } +void Environment::getSecurityFeatures() +{ + WMI wmi("root\\SecurityCenter2"); + std::map map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() << "failed to get displayName, " << formatSystemMessage(ret); + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() << "failed to get productState, " << formatSystemMessage(ret); + return; + } + + const DWORD state = prop.ulVal; + VariantClear(&prop); + + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() << "failed to get instanceGuid, " << formatSystemMessage(ret); + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + + auto itor = map.find(guid); + + if (itor == map.end()) { + map.insert({ + guid, SecurityFeature(QString::fromStdWString(name), state)}); + } + }; + + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + + for (auto&& p : map) { + m_security.push_back(p.second); + } +} + Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) @@ -508,7 +714,7 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const return {}; } - const auto* fi = reinterpret_cast(valuePointer); + const auto* fi = static_cast(valuePointer); // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { @@ -551,7 +757,7 @@ QString Module::getFileDescription(std::byte* buffer) const } // using the first language in the list to get FileVersion - const auto* lcp = reinterpret_cast(valuePointer); + const auto* lcp = static_cast(valuePointer); const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") .arg(lcp->wLanguage, 4, 16, QChar('0')) @@ -567,7 +773,7 @@ QString Module::getFileDescription(std::byte* buffer) const // valueSize includes the null terminator return QString::fromWCharArray( - reinterpret_cast(valuePointer), valueSize - 1); + static_cast(valuePointer), valueSize - 1); } QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const @@ -903,6 +1109,77 @@ std::optional WindowsInfo::getElevated() const return (e.TokenIsElevated != 0); } + +SecurityFeature::SecurityFeature(QString name, DWORD state) + : m_name(std::move(name)), m_state(state) +{ +} + +const QString& SecurityFeature::name() const +{ + return m_name; +} + +QString SecurityFeature::toString() const +{ + QString s; + + s += m_name + " "; + + const auto provider = (m_state >> 16) & 0xff; + const auto scanner = (m_state >> 8) & 0xff; + const auto definitions = m_state & 0xff; + + QStringList ps; + if (provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + s += "(doesn't provide anything)"; + } else { + s += "(" + ps.join("|") + ")"; + } + + if (scanner & 0x10) { + s += ", active"; + } else { + s += ", inactive"; + } + + if (definitions == 0) { + s += ", definitions up to date"; + } else { + s += ", definitions outdated"; + } + + return s; +} + } // namespace env } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 3ec677f4..6b842f8c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -241,6 +241,21 @@ private: }; +class SecurityFeature +{ +public: + SecurityFeature(QString name, DWORD state); + + const QString& name() const; + + QString toString() const; + +private: + QString m_name; + DWORD m_state; +}; + + // represents the process's environment // class Environment @@ -256,11 +271,17 @@ public: // const WindowsInfo& windowsInfo() const; + // information about the installed antivirus + // + const std::vector& securityFeatures() const; + private: std::vector m_modules; WindowsInfo m_windows; + std::vector m_security; void getLoadedModules(); + void getSecurityFeatures(); }; } // namespace env -- cgit v1.3.1 From dfe8093c1ad16e1611c12e88d52d1ac38371d3f6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 08:42:58 -0400 Subject: added --crashdump to generate dumps of a running MO process added dump_running_process.bat to start another instance of MO with that flag --- CMakeLists.txt | 2 + dump_running_process.bat | 2 + src/main.cpp | 45 +++++- src/settings.cpp | 10 +- src/shared/util.cpp | 392 ++++++++++++++++++++++++++++++++++++++++++++--- src/shared/util.h | 17 ++ 6 files changed, 443 insertions(+), 25 deletions(-) create mode 100644 dump_running_process.bat (limited to 'src/shared') diff --git a/CMakeLists.txt b/CMakeLists.txt index ced097e1..94c76373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,3 +29,5 @@ if(NOT EXISTS ${vcxproj_user_file}) "\n" "\n") endif() + +INSTALL(FILES dump_running_process.bat DESTINATION bin) diff --git a/dump_running_process.bat b/dump_running_process.bat new file mode 100644 index 00000000..4697fe5e --- /dev/null +++ b/dump_running_process.bat @@ -0,0 +1,2 @@ +pushd "%~dp0" +start ModOrganizer.exe --crashdump diff --git a/src/main.cpp b/src/main.cpp index 518d31a0..f74a9cf4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -485,8 +485,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, for (const auto& m : env.loadedModules()) { qDebug().nospace().noquote() << " . " << m.toString(); } - - return 0; } QString dataPath = application.property("dataPath").toString(); @@ -682,9 +680,52 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } +int doCoreDump(env::CoreDumpTypes type) +{ + // open a console + AllocConsole(); + + // redirect stdin, stdout and stderr to it + FILE* in=nullptr; + FILE* out=nullptr; + FILE* err=nullptr; + freopen_s(&in, "CONIN$", "r", stdin); + freopen_s(&out, "CONOUT$", "w", stdout); + freopen_s(&err, "CONOUT$", "w", stderr); + + // dump + const auto b = env::coredumpOther(type); + if (!b) { + std::wcerr << L"\n>>>> a minidump file was not written\n\n"; + } + + std::wcerr << L"Press enter to continue..."; + std::wcin.get(); + + // close redirected handles + std::fclose(err); + std::fclose(out); + std::fclose(in); + + // close console + FreeConsole(); + + return (b ? 0 : 1); +} int main(int argc, char *argv[]) { + // handle --crashdump first + for (int i=1; i. #include #include #include -#include #include +#include + +#include #include #include @@ -36,6 +38,8 @@ along with Mod Organizer. If not, see . #pragma comment(lib, "Wbemuuid.lib") using MOBase::formatSystemMessage; +using MOBase::formatSystemMessageQ; +namespace fs = std::filesystem; namespace MOShared { @@ -276,6 +280,9 @@ struct HandleCloser } }; +using HandlePtr = std::unique_ptr; + + struct LibraryFreer { using pointer = HINSTANCE; @@ -362,7 +369,7 @@ public: { qCritical() << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessage(ret); + << formatSystemMessageQ(ret); return {}; } @@ -385,7 +392,7 @@ private: if (FAILED(ret)) { qCritical() << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessage(ret); + << formatSystemMessageQ(ret); throw failed(); } @@ -406,7 +413,7 @@ private: qCritical() << "locator->ConnectServer() failed for namespace " << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessage(res); + << formatSystemMessageQ(res); throw failed(); } @@ -423,7 +430,7 @@ private: if (FAILED(ret)) { qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessage(ret); + << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); throw failed(); } @@ -454,7 +461,7 @@ const std::vector& Environment::securityFeatures() const void Environment::getLoadedModules() { - std::unique_ptr snapshot(CreateToolhelp32Snapshot( + HandlePtr snapshot(CreateToolhelp32Snapshot( TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); if (snapshot.get() == INVALID_HANDLE_VALUE) @@ -463,7 +470,7 @@ void Environment::getLoadedModules() qCritical().nospace().noquote() << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return; } @@ -477,7 +484,7 @@ void Environment::getLoadedModules() const auto e = GetLastError(); qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessage(e); + << "Module32First() failed, " << formatSystemMessageQ(e); return; } @@ -498,7 +505,7 @@ void Environment::getLoadedModules() } qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessage(e); + << "Module32Next() failed, " << formatSystemMessageQ(e); break; } @@ -520,7 +527,7 @@ void Environment::getSecurityFeatures() auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get displayName, " << formatSystemMessage(ret); + qCritical() << "failed to get displayName, " << formatSystemMessageQ(ret); return; } @@ -529,7 +536,10 @@ void Environment::getSecurityFeatures() ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get productState, " << formatSystemMessage(ret); + qCritical() + << "failed to get productState, " + << formatSystemMessageQ(ret); + return; } @@ -538,7 +548,10 @@ void Environment::getSecurityFeatures() ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get instanceGuid, " << formatSystemMessage(ret); + qCritical() + << "failed to get instanceGuid, " + << formatSystemMessageQ(ret); + return; } @@ -673,7 +686,7 @@ Module::FileInfo Module::getFileInfo() const qCritical().nospace().noquote() << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -686,7 +699,7 @@ Module::FileInfo Module::getFileInfo() const qCritical().nospace().noquote() << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -804,7 +817,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const // time on the file // opening the file - std::unique_ptr h(CreateFileW( + HandlePtr h(CreateFileW( m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); @@ -813,7 +826,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const qCritical().nospace().noquote() << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -823,7 +836,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const const auto e = GetLastError(); qCritical().nospace().noquote() << "can't get file time for '" << m_path << "', " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -1075,7 +1088,7 @@ WindowsInfo::Release WindowsInfo::getRelease() const std::optional WindowsInfo::getElevated() const { - std::unique_ptr token; + HandlePtr token; { HANDLE rawToken = 0; @@ -1085,7 +1098,7 @@ std::optional WindowsInfo::getElevated() const qCritical() << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessage(e); + << "OpenProcessToken() failed: " << formatSystemMessageQ(e); return {}; } @@ -1101,7 +1114,7 @@ std::optional WindowsInfo::getElevated() const qCritical() << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessage(e); + << "GetTokenInformation() failed: " << formatSystemMessageQ(e); return {}; } @@ -1180,6 +1193,345 @@ QString SecurityFeature::toString() const return s; } + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries<10; ++tries) + { + auto buffer = std::make_unique(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) { + 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(); + } + } + + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector runningProcessesIds() +{ + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries<10; ++tries) { + auto ids = std::make_unique(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process 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 when not 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"; + + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + 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"; + } + + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + 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) +{ + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + 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"; + + std::wstring path = dir + L"\\" + prefix + ext; + for (int i=0; i<100; ++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) { + 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 {}; + } + + 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()) { + std::wclog << L"can't get the temp directory\n"; + } else { + h = tempFile(dir.c_str()); + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + } + + std::wcerr << L"nowhere to write the dump file\n"; + return {}; +} + +bool createMiniDump(HANDLE process, CoreDumpTypes type) +{ + const DWORD pid = GetProcessId(process); + + const HandlePtr file = dumpFile(); + if (!file) { + 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 6b842f8c..2296651c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -284,6 +284,23 @@ private: void getSecurityFeatures(); }; + +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 -- cgit v1.3.1 From 5e27a96a27a351701182d493c64de698aaac4ae8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 08:55:24 -0400 Subject: a few comments --- src/shared/util.cpp | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) (limited to 'src/shared') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index b14a02b1..36800d5b 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1205,11 +1205,16 @@ struct Process } }; +// 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<10; ++tries) + for (int tries=0; tries(bufferSize + 1); std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); @@ -1225,6 +1230,7 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) } if (writtenSize == 0) { + // hard failure const auto e = GetLastError(); std::wcerr << formatSystemMessage(e) << L"\n"; break; @@ -1241,6 +1247,7 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) } } + // something failed or the path is way too long to make sense std::wstring what; if (process == INVALID_HANDLE_VALUE) { @@ -1255,10 +1262,13 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) std::vector runningProcessesIds() { + // double the buffer size 10 times + const int MaxTries = 10; + // initial size of 300 processes, unlikely to be more than that std::size_t size = 300; - for (int tries=0; tries<10; ++tries) { + for (int tries=0; tries(size); std::fill(ids.get(), ids.get() + size, 0); @@ -1277,6 +1287,8 @@ std::vector runningProcessesIds() } if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again size *= 2; continue; } @@ -1296,7 +1308,7 @@ std::vector runningProcesses() for (const auto& pid : pids) { if (pid == 0) { - // the idle process seems to be picked up by EnumProcesses() + // the idle process has pid 0 and seems to be picked up by EnumProcesses() continue; } @@ -1307,7 +1319,8 @@ std::vector runningProcesses() const auto e = GetLastError(); if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot when not elevated + // 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"; @@ -1331,9 +1344,12 @@ DWORD findOtherPid() 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 @@ -1345,9 +1361,12 @@ DWORD findOtherPid() 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) { @@ -1364,7 +1383,6 @@ DWORD findOtherPid() return 0; } - std::wstring tempDir() { const DWORD bufferSize = MAX_PATH + 1; @@ -1386,9 +1404,15 @@ std::wstring tempDir() 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-" @@ -1402,8 +1426,10 @@ HandlePtr tempFile(const std::wstring dir) 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<100; ++i) { + + for (int i=0; i Date: Mon, 1 Jul 2019 12:38:35 -0400 Subject: now handles windows firewall, which apparently doens't report itself to wmi some more error checking --- src/main.cpp | 6 +- src/shared/util.cpp | 294 ++++++++++++++++++++++++++++++++++++++-------------- src/shared/util.h | 26 +++-- 3 files changed, 239 insertions(+), 87 deletions(-) (limited to 'src/shared') diff --git a/src/main.cpp b/src/main.cpp index f08e5ad3..c6c87a64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -493,9 +493,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, qWarning() << "MO seems to be running in compatibility mode"; } - qDebug().nospace().noquote() << "security features:"; - for (const auto& sf : env.securityFeatures()) { - qDebug().nospace().noquote() << " . " << sf.toString(); + qDebug().nospace().noquote() << "security products:"; + for (const auto& sp : env.securityProducts()) { + qDebug().nospace().noquote() << " . " << sp.toString(); } qDebug() << "modules loaded in process:"; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 36800d5b..072cee2d 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -35,6 +35,8 @@ along with Mod Organizer. If not, see . #include #include #include +#include + #pragma comment(lib, "Wbemuuid.lib") using MOBase::formatSystemMessage; @@ -305,6 +307,9 @@ struct COMReleaser } }; +template +using COMPtr = std::unique_ptr; + class WMI { @@ -332,17 +337,26 @@ public: } auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } for (;;) { - std::unique_ptr object; + COMPtr object; { IWbemClassObject* rawObject = nullptr; ULONG count = 0; auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); - if (count == 0) { + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + qCritical() + << "enumerator->next() failed, " << formatSystemMessageQ(ret); break; } @@ -353,33 +367,9 @@ public: } } - std::unique_ptr 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)) - { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - - return {}; - } - - return std::unique_ptr(rawEnumerator); - } - private: - std::unique_ptr m_locator; - std::unique_ptr m_service; + COMPtr m_locator; + COMPtr m_service; void createLocator() { @@ -389,7 +379,7 @@ private: CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, &rawLocator); - if (FAILED(ret)) { + if (FAILED(ret) || !rawLocator) { qCritical() << "CoCreateInstance for WbemLocator failed, " << formatSystemMessageQ(ret); @@ -409,7 +399,7 @@ private: nullptr, nullptr, nullptr, 0, nullptr, nullptr, &rawService); - if (FAILED(res)) { + if (FAILED(res) || !rawService) { qCritical() << "locator->ConnectServer() failed for namespace " << "'" << QString::fromStdString(ns) << "', " @@ -435,13 +425,37 @@ private: throw failed(); } } + + COMPtr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessageQ(ret); + + return {}; + } + + return COMPtr(rawEnumerator); + } }; Environment::Environment() { - getLoadedModules(); - getSecurityFeatures(); + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); } const std::vector& Environment::loadedModules() @@ -454,12 +468,12 @@ const WindowsInfo& Environment::windowsInfo() const return m_windows; } -const std::vector& Environment::securityFeatures() const +const std::vector& Environment::securityProducts() const { return m_security; } -void Environment::getLoadedModules() +std::vector Environment::getLoadedModules() const { HandlePtr snapshot(CreateToolhelp32Snapshot( TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); @@ -472,7 +486,7 @@ void Environment::getLoadedModules() << "CreateToolhelp32Snapshot() failed, " << formatSystemMessageQ(e); - return; + return {}; } MODULEENTRY32 me = {}; @@ -486,54 +500,88 @@ void Environment::getLoadedModules() qCritical().nospace().noquote() << "Module32First() failed, " << formatSystemMessageQ(e); - return; + return {}; } + std::vector v; + for (;;) { const auto path = QString::fromWCharArray(me.szExePath); - - m_modules.push_back(Module(path, me.modBaseSize)); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } // next module if (!Module32Next(snapshot.get(), &me)) { const auto e = GetLastError(); - if (e == ERROR_NO_MORE_FILES) { - // not an error - break; - } - + // 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(m_modules.begin(), m_modules.end(), [](auto&& a, auto&& b) { + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); }); + + return v; +} + +std::vector Environment::getSecurityProducts() const +{ + std::vector v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; } -void Environment::getSecurityFeatures() +std::vector Environment::getSecurityProductsFromWMI() const { - WMI wmi("root\\SecurityCenter2"); - std::map map; + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; auto handleProduct = [&](auto* o) { VARIANT prop; + // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get displayName, " << formatSystemMessageQ(ret); + 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() @@ -543,9 +591,21 @@ void Environment::getSecurityFeatures() return; } - const DWORD state = prop.ulVal; + 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() @@ -555,25 +615,94 @@ void Environment::getSecurityFeatures() return; } + if (prop.vt != VT_BSTR) { + qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + return; + } + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); VariantClear(&prop); + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; - auto itor = map.find(guid); + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); - if (itor == map.end()) { - map.insert({ - guid, SecurityFeature(QString::fromStdWString(name), state)}); - } + map.insert({ + guid, + {QString::fromStdWString(name), provider, active, upToDate}}); }; - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector v; for (auto&& p : map) { - m_security.push_back(p.second); + v.push_back(p.second); } + + return v; +} + +std::optional Environment::getWindowsFirewall() const +{ + HRESULT hr = 0; + + COMPtr policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + qCritical() + << "CoCreateInstance for NetFwPolicy2 failed, " + << formatSystemMessage(hr); + + return {}; + } + + policy.reset(static_cast(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + qCritical() + << "get_FirewallEnabled failed, " + << formatSystemMessage(hr); + + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); } @@ -1123,52 +1252,67 @@ std::optional WindowsInfo::getElevated() const } -SecurityFeature::SecurityFeature(QString name, DWORD state) - : m_name(std::move(name)), m_state(state) +SecurityProduct::SecurityProduct( + QString name, int provider, + bool active, bool upToDate) : + m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) { } -const QString& SecurityFeature::name() const +const QString& SecurityProduct::name() const { return m_name; } -QString SecurityFeature::toString() const +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 + " "; - const auto provider = (m_state >> 16) & 0xff; - const auto scanner = (m_state >> 8) & 0xff; - const auto definitions = m_state & 0xff; QStringList ps; - if (provider & WSC_SECURITY_PROVIDER_FIREWALL) { + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { ps.push_back("firewall"); } - if (provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { ps.push_back("autoupdate"); } - if (provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { ps.push_back("antivirus"); } - if (provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { ps.push_back("antispyware"); } - if (provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { ps.push_back("settings"); } - if (provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { ps.push_back("uac"); } - if (provider & WSC_SECURITY_PROVIDER_SERVICE) { + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { ps.push_back("service"); } @@ -1178,15 +1322,13 @@ QString SecurityFeature::toString() const s += "(" + ps.join("|") + ")"; } - if (scanner & 0x10) { + if (m_active) { s += ", active"; } else { s += ", inactive"; } - if (definitions == 0) { - s += ", definitions up to date"; - } else { + if (!m_upToDate) { s += ", definitions outdated"; } diff --git a/src/shared/util.h b/src/shared/util.h index 2296651c..e0da8a99 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -241,18 +241,25 @@ private: }; -class SecurityFeature +class SecurityProduct { public: - SecurityFeature(QString name, DWORD state); + SecurityProduct( + QString name, int provider, + bool active, bool upToDate); const QString& name() const; + int provider() const; + bool active() const; + bool upToDate() const; QString toString() const; private: QString m_name; - DWORD m_state; + int m_provider; + bool m_active; + bool m_upToDate; }; @@ -271,17 +278,20 @@ public: // const WindowsInfo& windowsInfo() const; - // information about the installed antivirus + // information about the installed security products // - const std::vector& securityFeatures() const; + const std::vector& securityProducts() const; private: std::vector m_modules; WindowsInfo m_windows; - std::vector m_security; + std::vector m_security; - void getLoadedModules(); - void getSecurityFeatures(); + std::vector getLoadedModules() const; + std::vector getSecurityProducts() const; + + std::vector getSecurityProductsFromWMI() const; + std::optional getWindowsFirewall() const; }; -- cgit v1.3.1 From ffebd4c1016265eeebab67d7f22d8f5bfd67703e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 14:51:08 -0400 Subject: a few more comments --- src/shared/util.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/shared') diff --git a/src/shared/util.h b/src/shared/util.h index e0da8a99..4df1d13c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -241,6 +241,8 @@ private: }; +// represents a security product, such as an antivirus or a firewall +// class SecurityProduct { public: @@ -248,11 +250,24 @@ public: 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: -- cgit v1.3.1 From 9166bd9c5bf02484bd7486374e508ca304111f5a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 18:50:28 -0400 Subject: added new Shortcut class, moved stuff that was in MainWindow into it added more error handling and logging, some was missing removed some redundancy of setting the menu icons for add/remove, they're all set when clicking the button anyway --- src/mainwindow.cpp | 177 ++++------------------------- src/mainwindow.h | 12 +- src/shared/util.cpp | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/shared/util.h | 96 ++++++++++++++++ 4 files changed, 442 insertions(+), 163 deletions(-) (limited to 'src/shared') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7681b482..f9e4138e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -220,6 +220,9 @@ MainWindow::MainWindow(QSettings &initSettings , m_PluginContainer(pluginContainer) , m_DidUpdateMasterList(false) , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) + , m_LinkToolbar(nullptr) + , m_LinkDesktop(nullptr) + , m_LinkStartMenu(nullptr) { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); @@ -335,9 +338,9 @@ MainWindow::MainWindow(QSettings &initSettings resizeLists(modListAdjusted, pluginListAdjusted); QMenu *linkMenu = new QMenu(this); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); + m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); + m_LinkDesktop = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); + m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn); @@ -2397,88 +2400,6 @@ void MainWindow::on_startButton_clicked() { ui->startButton->setEnabled(true); } -static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, - LPCSTR linkFileName, LPCWSTR description, - LPCTSTR iconFileName, int iconNumber, - LPCWSTR currentDirectory) -{ - HRESULT result = E_INVALIDARG; - if ((targetFileName != nullptr) && (wcslen(targetFileName) > 0) && - (arguments != nullptr) && - (linkFileName != nullptr) && (strlen(linkFileName) > 0) && - (description != nullptr) && - (currentDirectory != nullptr)) { - - IShellLink* shellLink; - result = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, (LPVOID*)&shellLink); - - if (!SUCCEEDED(result)) { - qCritical("failed to create IShellLink instance"); - return result; - } - - result = shellLink->SetPath(targetFileName); - if (!SUCCEEDED(result)) { - qCritical("failed to set target path %ls", targetFileName); - shellLink->Release(); - return result; - } - - result = shellLink->SetArguments(arguments); - if (!SUCCEEDED(result)) { - qCritical("failed to set arguments: %ls", arguments); - shellLink->Release(); - return result; - } - - if (wcslen(description) > 0) { - result = shellLink->SetDescription(description); - if (!SUCCEEDED(result)) { - qCritical("failed to set description: %ls", description); - shellLink->Release(); - return result; - } - } - - if (wcslen(currentDirectory) > 0) { - result = shellLink->SetWorkingDirectory(currentDirectory); - if (!SUCCEEDED(result)) { - qCritical("failed to set working directory: %ls", currentDirectory); - shellLink->Release(); - return result; - } - } - - if (iconFileName != nullptr) { - result = shellLink->SetIconLocation(iconFileName, iconNumber); - if (!SUCCEEDED(result)) { - qCritical("failed to load program icon: %ls %d", iconFileName, iconNumber); - shellLink->Release(); - return result; - } - } - - IPersistFile *persistFile; - result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(result)) { - wchar_t linkFileNameW[MAX_PATH]; - if (MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH) > 0) { - result = persistFile->Save(linkFileNameW, TRUE); - } else { - qCritical("failed to create link: %s", linkFileName); - } - persistFile->Release(); - } else { - qCritical("failed to create IPersistFile instance"); - } - - shellLink->Release(); - } - return result; -} - - bool MainWindow::modifyExecutablesDialog() { bool result = false; @@ -5144,74 +5065,39 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { - Executable &exe(getSelectedExecutable()); + Executable& exe = getSelectedExecutable(); + exe.setShownOnToolbar(!exe.isShownOnToolbar()); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); updatePinnedExecutables(); } -namespace { -QString getLinkfile(const QString &dir, const Executable &exec) +void MainWindow::linkDesktop() { - return QDir::fromNativeSeparators(dir) + "/" + exec.title() + ".lnk"; + env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::Desktop); } -QString getDesktopLinkfile(const Executable &exec) +void MainWindow::linkMenu() { - return getLinkfile(getDesktopDirectory(), exec); + env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::StartMenu); } -QString getStartMenuLinkfile(const Executable &exec) +void MainWindow::on_linkButton_pressed() { - return getLinkfile(getStartMenuDirectory(), exec); -} -} + const Executable& exe = getSelectedExecutable(); -void MainWindow::addWindowsLink(const ShortcutType mapping) -{ - const Executable &selectedExecutable(getSelectedExecutable()); - QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(), - selectedExecutable); + const QIcon addIcon(":/MO/gui/link"); + const QIcon removeIcon(":/MO/gui/remove"); - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo const exeInfo(qApp->applicationFilePath()); - // create link - QString executable = QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath()); - - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString( - QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.title())); - std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.title())); - std::wstring iconFile = ToWString(executable); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath())); - - if (CreateShortcut(targetFile.c_str() - , parameter.c_str() - , QDir::toNativeSeparators(linkName).toUtf8().constData() - , description.c_str() - , (selectedExecutable.usesOwnIcon() ? iconFile.c_str() : nullptr), 0 - , currentDirectory.c_str()) == 0) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} + env::Shortcut shortcut(exe); -void MainWindow::linkDesktop() -{ - addWindowsLink(ShortcutType::Desktop); -} + m_LinkToolbar->setIcon( + exe.isShownOnToolbar() ? removeIcon : addIcon); -void MainWindow::linkMenu() -{ - addWindowsLink(ShortcutType::StartMenu); + m_LinkDesktop->setIcon( + shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon); + + m_LinkStartMenu->setIcon( + shortcut.exists(env::Shortcut::StartMenu) ? removeIcon : addIcon); } void MainWindow::on_actionSettings_triggered() @@ -6476,21 +6362,6 @@ Executable &MainWindow::getSelectedExecutable() return m_OrganizerCore.executablesList()->get(name); } -void MainWindow::on_linkButton_pressed() -{ - const Executable &selectedExecutable(getSelectedExecutable()); - - const QIcon addIcon(":/MO/gui/link"); - const QIcon removeIcon(":/MO/gui/remove"); - - const QFileInfo linkDesktopFile(getDesktopLinkfile(selectedExecutable)); - const QFileInfo linkMenuFile(getStartMenuLinkfile(selectedExecutable)); - - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(selectedExecutable.isShownOnToolbar() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Desktop))->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::StartMenu))->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); -} - void MainWindow::on_showHiddenBox_toggled(bool checked) { m_OrganizerCore.downloadManager()->setShowHidden(checked); diff --git a/src/mainwindow.h b/src/mainwindow.h index 00f15a2b..eee269cf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -403,18 +403,14 @@ private: MOBase::DelayedFileWriter m_ArchiveListWriter; + QAction* m_LinkToolbar; + QAction* m_LinkDesktop; + QAction* m_LinkStartMenu; + // icon set by the stylesheet, used to remember its original appearance // when painting the count QIcon m_originalNotificationIcon; - enum class ShortcutType { - Toolbar, - Desktop, - StartMenu - }; - - void addWindowsLink(ShortcutType const); - Executable const &getSelectedExecutable() const; Executable &getSelectedExecutable(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 072cee2d..2bf1dba6 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "error_report.h" +#include "executableslist.h" +#include "instancemanager.h" #include #include @@ -307,10 +309,324 @@ struct COMReleaser } }; + template using COMPtr = std::unique_ptr; +class ShellLinkException {}; + +// 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()) { + critical() << "path cannot be empty"; + throw ShellLinkException(); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr m_link; + COMPtr m_file; + + QDebug critical() + { + return qCritical().noquote().nospace() << "system shortcut: "; + } + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + critical() << s << ", " << formatSystemMessageQ(r); + throw ShellLinkException(); + } + } + + COMPtr createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + critical() << "creating IShellLink worked, but pointer is null"; + throw ShellLinkException(); + } + + return COMPtr(static_cast(link)); + } + + COMPtr createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + critical() << "querying IPersistFile worked, but pointer is null"; + throw ShellLinkException(); + } + + return COMPtr(static_cast(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + if (m_target.isEmpty()) { + qCritical() << "system shortcut: target is empty"; + return false; + } + + 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&) + { + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + if (!QFile::exists(path)) { + qCritical().nospace().noquote() + << "system shortcut: can't remove '" << path << "', file not found"; + + return false; + } + + if (!QFile::remove(path)) { + qCritical().nospace().noquote() + << "system shortcut: failed to remove '" << path << "'"; + + 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: + qCritical() << "system shortcut: bad location " << loc; + return {}; + } + } + catch(std::exception&) + { + return {}; + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + qCritical() << "system shortcut: name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + + + class WMI { public: @@ -674,7 +990,7 @@ std::optional Environment::getWindowsFirewall() const if (FAILED(hr) || !rawPolicy) { qCritical() << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessage(hr); + << formatSystemMessageQ(hr); return {}; } @@ -690,7 +1006,7 @@ std::optional Environment::getWindowsFirewall() const { qCritical() << "get_FirewallEnabled failed, " - << formatSystemMessage(hr); + << formatSystemMessageQ(hr); return {}; } diff --git a/src/shared/util.h b/src/shared/util.h index 4df1d13c..a40fa201 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -29,6 +29,8 @@ along with Mod Organizer. If not, see . #include +class Executable; + namespace MOShared { /// Test if a file (or directory) by the specified name exists @@ -52,6 +54,100 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); 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 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; +}; + + // represents one module // class Module -- cgit v1.3.1 From f7c844fcf7684cbde0a3290b1950c52f88236be3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 19:23:52 -0400 Subject: put the error message in the ShellLinkException instead added more debug logging when creating and deleting shortcuts --- src/shared/util.cpp | 111 ++++++++++++++++++++++++++++++++++++++-------------- src/shared/util.h | 13 ++++++ 2 files changed, 95 insertions(+), 29 deletions(-) (limited to 'src/shared') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 2bf1dba6..17df3b92 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -314,7 +314,22 @@ template using COMPtr = std::unique_ptr; -class ShellLinkException {}; +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 @@ -331,8 +346,7 @@ public: void setPath(const QString& s) { if (s.isEmpty()) { - critical() << "path cannot be empty"; - throw ShellLinkException(); + throw ShellLinkException("path cannot be empty"); } const auto r = m_link->SetPath(s.toStdWString().c_str()); @@ -385,16 +399,12 @@ private: COMPtr m_link; COMPtr m_file; - QDebug critical() - { - return qCritical().noquote().nospace() << "system shortcut: "; - } - void throwOnFail(HRESULT r, const QString& s) { if (FAILED(r)) { - critical() << s << ", " << formatSystemMessageQ(r); - throw ShellLinkException(); + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); } } @@ -409,8 +419,7 @@ private: throwOnFail(r, "failed to create IShellLink instance"); if (!link) { - critical() << "creating IShellLink worked, but pointer is null"; - throw ShellLinkException(); + throw ShellLinkException("creating IShellLink worked, pointer is null"); } return COMPtr(static_cast(link)); @@ -424,8 +433,7 @@ private: throwOnFail(r, "failed to get IPersistFile interface"); if (!file) { - critical() << "querying IPersistFile worked, but pointer is null"; - throw ShellLinkException(); + throw ShellLinkException("querying IPersistFile worked, pointer is null"); } return COMPtr(static_cast(file)); @@ -515,16 +523,27 @@ bool Shortcut::toggle(Locations loc) bool Shortcut::add(Locations loc) { - const auto path = shortcutPath(loc); - if (path.isEmpty()) { + 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; } - if (m_target.isEmpty()) { - qCritical() << "system shortcut: target is empty"; + const auto path = shortcutPath(loc); + if (path.isEmpty()) { return false; } + debug() << "shorcut file will be saved at '" << path << "'"; + try { ShellLinkWrapper link; @@ -539,8 +558,9 @@ bool Shortcut::add(Locations loc) return true; } - catch(ShellLinkException&) + catch(ShellLinkException& e) { + critical() << e.what() << "\nshortcut file was not saved"; } return false; @@ -548,21 +568,26 @@ bool Shortcut::add(Locations loc) bool Shortcut::remove(Locations loc) { + debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + const auto path = shortcutPath(loc); if (path.isEmpty()) { return false; } - if (!QFile::exists(path)) { - qCritical().nospace().noquote() - << "system shortcut: can't remove '" << path << "', file not found"; + debug() << "path to shortcut file is '" << path << "'"; + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; return false; } - if (!QFile::remove(path)) { - qCritical().nospace().noquote() - << "system shortcut: failed to remove '" << path << "'"; + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); return false; } @@ -603,13 +628,12 @@ QString Shortcut::shortcutDirectory(Locations loc) const case None: default: - qCritical() << "system shortcut: bad location " << loc; - return {}; + critical() << "bad location " << loc; + break; } } catch(std::exception&) { - return {}; } return QDir::toNativeSeparators(dir); @@ -618,13 +642,42 @@ QString Shortcut::shortcutDirectory(Locations loc) const QString Shortcut::shortcutFilename() const { if (m_name.isEmpty()) { - qCritical() << "system shortcut: name is empty"; + critical() << "name is empty"; return {}; } return m_name + ".lnk"; } +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + class WMI diff --git a/src/shared/util.h b/src/shared/util.h index a40fa201..c4a2ed7d 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -133,6 +133,14 @@ private: 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 // @@ -148,6 +156,11 @@ private: }; +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + + // represents one module // class Module -- cgit v1.3.1