From c82c7af678c088a6b94fc8a4a0f31fc9498e8220 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 04:00:54 -0400 Subject: changed rest of ShellExecuteW() calls to use shell::Execute(), shell::OpenLink() or shell::OpenFile() --- src/settings.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/settings.cpp b/src/settings.cpp index 231487bb..a844fdc9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -130,18 +130,19 @@ bool Settings::pluginBlacklisted(const QString &fileName) const void Settings::registerAsNXMHandler(bool force) { - std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); - std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); - std::wstring mode = force ? L"forcereg" : L"reg"; - std::wstring parameters = mode + L" " + m_GamePlugin->gameShortName().toStdWString(); - for (QString altGame : m_GamePlugin->validShortNames()) { - parameters += L"," + altGame.toStdWString(); + const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; + const auto executable = QCoreApplication::applicationFilePath(); + + QString mode = force ? "forcereg" : "reg"; + QString parameters = mode + " " + m_GamePlugin->gameShortName(); + for (const QString& altGame : m_GamePlugin->validShortNames()) { + parameters += "," + altGame; } - parameters += L" \"" + executable + L"\""; - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", nxmPath.c_str(), parameters.c_str(), nullptr, SW_SHOWNORMAL); - if ((INT_PTR)res <= 32) { - QMessageBox::critical(nullptr, tr("Failed"), - tr("Sorry, failed to start the helper application")); + parameters += " \"" + executable + "\""; + + if (!shell::Execute(nxmPath, parameters)) { + QMessageBox::critical( + nullptr, tr("Failed"), tr("Failed to start the helper application")); } } -- cgit v1.3.1 From 2db29cffda538c6a9be24dff705e1d032bd42008 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 20:58:30 -0400 Subject: renamed "revoke nexus authorization" to "disconnect from nexus" because "revoking" is ambiguous reworked manual key dialog to look more like MO1 moved all the UI stuff from callbacks in Settings back to SettingsDialog in a central updateNexusButtons(), which also enables/disables buttons as needed --- src/nexusmanualkey.ui | 156 ++++++++++++++++++++++++++++++++++++++++++++++--- src/settings.cpp | 78 +++++++++---------------- src/settings.h | 30 ++++++---- src/settingsdialog.cpp | 129 ++++++++++++++++++++++++++++++++++------ src/settingsdialog.h | 101 +++++++++++--------------------- src/settingsdialog.ui | 4 +- 6 files changed, 341 insertions(+), 157 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/nexusmanualkey.ui b/src/nexusmanualkey.ui index 191ca218..1a842dc3 100644 --- a/src/nexusmanualkey.ui +++ b/src/nexusmanualkey.ui @@ -7,16 +7,16 @@ 0 0 452 - 302 + 236 Dialog - + - + 0 @@ -30,10 +30,152 @@ 0 - - - Enter API key here - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1. Get your personal API key + + + + + + + Open Browser + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 2. Enter your personal API key + + + + + + + Qt::Horizontal + + + + 115 + 20 + + + + + + + + Paste + + + + + + + Clear + + + + + + + + + + + 0 + 0 + + + + + 1 + 1 + + + + Enter API key here + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <b>Sharing this key with anyone could get your Nexus account banned. You can always revoke this key from your account on the Nexus website.</b> + + + true + + + + diff --git a/src/settings.cpp b/src/settings.cpp index a844fdc9..53dd32dc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -363,6 +363,32 @@ bool Settings::getNexusApiKey(QString &apiKey) const return true; } +bool Settings::setNexusApiKey(const QString& apiKey) +{ + if (!obfuscate("APIKEY", apiKey)) { + wchar_t buffer[256]; + + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); + + qCritical() << "Storing API key failed:" << buffer; + return false; + } + + return true; +} + +bool Settings::clearNexusApiKey() +{ + return setNexusApiKey(""); +} + +bool Settings::hasNexusApiKey() const +{ + return !deObfuscate("APIKEY").isEmpty(); +} + bool Settings::getSteamLogin(QString &username, QString &password) const { if (m_Settings.contains("Settings/steam_username")) { @@ -452,17 +478,6 @@ QString Settings::executablesBlacklist() const ).toString(); } -void Settings::setNexusApiKey(QString apiKey) -{ - if (!obfuscate("APIKEY", apiKey)) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Storing API key failed:" << buffer; - } -} - void Settings::setSteamLogin(QString username, QString password) { if (username == "") { @@ -706,43 +721,10 @@ void Settings::resetDialogs() QuestionBoxMemory::resetDialogs(); } -void Settings::processApiKey(const QString &apiKey) -{ - if (!obfuscate("APIKEY", apiKey)) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Storing or deleting API key failed:" << buffer; - } -} - -void Settings::clearApiKey(QPushButton *nexusButton) -{ - obfuscate("APIKEY", ""); - nexusButton->setEnabled(true); - nexusButton->setText("Connect to Nexus"); -} - -void Settings::checkApiKey(QPushButton *nexusButton) -{ - if (deObfuscate("APIKEY").isEmpty()) { - nexusButton->setEnabled(true); - nexusButton->setText("Connect to Nexus"); - QMessageBox::warning(qApp->activeWindow(), tr("Error"), - tr("Failed to retrieve a Nexus API key! Please try again. " - "A browser window should open asking you to authorize.")); - } -} - void Settings::query(PluginContainer *pluginContainer, QWidget *parent) { - SettingsDialog dialog(pluginContainer, parent); - + SettingsDialog dialog(pluginContainer, this, parent); connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); - connect(&dialog, SIGNAL(processApiKey(const QString &)), this, SLOT(processApiKey(const QString &))); - connect(&dialog, SIGNAL(closeApiConnection(QPushButton *)), this, SLOT(checkApiKey(QPushButton *))); - connect(&dialog, SIGNAL(revokeApiKey(QPushButton *)), this, SLOT(clearApiKey(QPushButton *))); std::vector> tabs; @@ -1043,7 +1025,6 @@ void Settings::DiagnosticsTab::update() Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) : Settings::SettingsTab(parent, dialog) - , m_nexusConnect(dialog.findChild("nexusConnect")) , m_offlineBox(dialog.findChild("offlineBox")) , m_proxyBox(dialog.findChild("proxyBox")) , m_knownServersList(dialog.findChild("knownServersList")) @@ -1052,11 +1033,6 @@ Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) , m_endorsementBox(dialog.findChild("endorsementBox")) , m_hideAPICounterBox(dialog.findChild("hideAPICounterBox")) { - if (!deObfuscate("APIKEY").isEmpty()) { - m_nexusConnect->setText("Nexus API Key Stored"); - m_nexusConnect->setDisabled(true); - } - m_offlineBox->setChecked(parent->offlineMode()); m_proxyBox->setChecked(parent->useProxy()); m_endorsementBox->setChecked(parent->endorsementIntegration()); diff --git a/src/settings.h b/src/settings.h index 04a0646e..bccd1e81 100644 --- a/src/settings.h +++ b/src/settings.h @@ -187,6 +187,24 @@ public: **/ bool getNexusApiKey(QString &apiKey) const; + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + bool setNexusApiKey(const QString& apiKey); + + /** + * @brief clears the nexus login information + */ + bool clearNexusApiKey(); + + /** + * @brief returns whether an API key is currently stored + */ + bool hasNexusApiKey() const; + /** * @brief retrieve the login information for steam * @@ -240,14 +258,6 @@ public: QString executablesBlacklist() const; - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ - void setNexusApiKey(QString apiKey); - /** * @brief set the steam login information * @@ -481,7 +491,6 @@ private: void update(); private: - QPushButton *m_nexusConnect; QCheckBox *m_offlineBox; QCheckBox *m_proxyBox; QListWidget *m_knownServersList; @@ -538,9 +547,6 @@ private: private slots: void resetDialogs(); - void processApiKey(const QString &); - void clearApiKey(QPushButton *nexusButton); - void checkApiKey(QPushButton *nexusButton); signals: diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 44d7fde5..0373cef6 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -57,6 +57,10 @@ public: : QDialog(parent), ui(new Ui::NexusManualKeyDialog) { ui->setupUi(this); + + connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); + connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); + connect(ui->clear, &QPushButton::clicked, [&]{ clear(); }); } void accept() override @@ -70,15 +74,34 @@ public: return m_key; } + void openBrowser() + { + shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); + } + + void paste() + { + const auto text = QApplication::clipboard()->text(); + if (!text.isEmpty()) { + ui->key->setPlainText(text); + } + } + + void clear() + { + ui->key->clear(); + } + private: std::unique_ptr ui; QString m_key; }; -SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, QWidget *parent) +SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) + , m_settings(settings) , m_PluginContainer(pluginContainer) , m_nexusLogin(new QWebSocket) , m_KeyReceived(false) @@ -95,8 +118,9 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, QWidget *parent connect(m_nexusLogin, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(authError(QAbstractSocket::SocketError))); connect(m_nexusLogin, SIGNAL(textMessageReceived(const QString &)), this, SLOT(receiveApiKey(const QString &))); connect(m_nexusLogin, SIGNAL(disconnected()), this, SLOT(completeApiConnection())); - connect(this, SIGNAL(retryApiConnection()), this, SLOT(on_nexusConnect_clicked())); m_loginTimer.callOnTimeout(this, &SettingsDialog::loginPing); + + updateNexusButtons(); } SettingsDialog::~SettingsDialog() @@ -367,10 +391,7 @@ void SettingsDialog::on_resetDialogsButton_clicked() void SettingsDialog::on_nexusConnect_clicked() { - ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes."); - ui->nexusConnect->setDisabled(true); - QUrl url = QUrl("wss://sso.nexusmods.com"); - m_nexusLogin->open(url); + fetchNexusApiKey(); } void SettingsDialog::on_nexusManualKey_clicked() @@ -382,9 +403,21 @@ void SettingsDialog::on_nexusManualKey_clicked() } const auto key = dialog.key(); - emit processApiKey(key); - ui->nexusConnect->setText("Nexus API Key Stored"); - NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(key, true); + + if (key.isEmpty()) { + clearKey(); + } else { + if (setKey(key)) { + testApiKey(); + } + } +} + +void SettingsDialog::fetchNexusApiKey() +{ + QUrl url = QUrl("wss://sso.nexusmods.com"); + m_nexusLogin->open(url); + updateNexusButtons(); } void SettingsDialog::dispatchLogin() @@ -434,12 +467,17 @@ void SettingsDialog::receiveApiKey(const QString &response) if (data.contains("connection_token")) { m_AuthToken = data["connection_token"].toString(); } else { - m_KeyReceived = true; - emit processApiKey(data["api_key"].toString()); + const auto key = data["api_key"].toString(); + m_nexusLogin->close(); - ui->nexusConnect->setText("Nexus API Key Stored"); m_loginTimer.stop(); m_totalPings = 0; + + if (key.isEmpty()) { + clearKey(); + } else { + setKey(key); + } } } else { QString error("There was a problem with SSO initialization: %1"); @@ -450,10 +488,64 @@ void SettingsDialog::receiveApiKey(const QString &response) void SettingsDialog::completeApiConnection() { - if (m_KeyReceived == true || !m_loginTimer.isActive()) - emit closeApiConnection(ui->nexusConnect); - else - emit retryApiConnection(); + if (!m_KeyReceived && !m_loginTimer.isActive()) { + QMessageBox::warning(qApp->activeWindow(), tr("Error"), + tr("Failed to retrieve a Nexus API key! Please try again. " + "A browser window should open asking you to authorize.")); + + // try again + fetchNexusApiKey(); + } +} + +bool SettingsDialog::setKey(const QString& key) +{ + m_KeyReceived = true; + const bool ret = m_settings->setNexusApiKey(key); + updateNexusButtons(); + return ret; +} + +bool SettingsDialog::clearKey() +{ + m_KeyCleared = true; + const auto ret = m_settings->clearNexusApiKey(); + updateNexusButtons(); + return ret; +} + +void SettingsDialog::testApiKey() +{ + QString key; + if (!m_settings->getNexusApiKey(key)) { + qWarning().nospace() << "can't test API key, nothing stored"; + return; + } + + auto* am = NexusInterface::instance(m_PluginContainer)->getAccessManager(); + am->apiCheck(key, true); +} + +void SettingsDialog::updateNexusButtons() +{ + if (m_nexusLogin->state() != QAbstractSocket::UnconnectedState) { + // api key is in the process of being retrieved + ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes."); + ui->nexusConnect->setEnabled(false); + } + else if (m_settings->hasNexusApiKey()) { + // api key is present + ui->nexusConnect->setText("Nexus API Key Stored"); + ui->nexusConnect->setEnabled(false); + ui->nexusDisconnect->setEnabled(true); + ui->nexusManualKey->setEnabled(false); + } else { + // api key not present + ui->nexusConnect->setText("Connect to Nexus"); + ui->nexusConnect->setEnabled(true); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setEnabled(true); + } } void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) @@ -523,10 +615,9 @@ void SettingsDialog::on_clearCacheButton_clicked() NexusInterface::instance(m_PluginContainer)->clearCache(); } -void SettingsDialog::on_revokeNexusAuthButton_clicked() +void SettingsDialog::on_nexusDisconnect_clicked() { - emit revokeApiKey(ui->nexusConnect); - m_KeyCleared = true; + clearKey(); } void SettingsDialog::normalizePath(QLineEdit *lineEdit) diff --git a/src/settingsdialog.h b/src/settingsdialog.h index a844b391..858a36d4 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include class PluginContainer; +class Settings; namespace Ui { class SettingsDialog; @@ -44,7 +45,9 @@ class SettingsDialog : public MOBase::TutorableDialog Q_OBJECT public: - explicit SettingsDialog(PluginContainer *pluginContainer, QWidget *parent = 0); + explicit SettingsDialog( + PluginContainer *pluginContainer, Settings* settings, QWidget *parent = 0); + ~SettingsDialog(); /** @@ -62,9 +65,6 @@ public slots: signals: void resetDialogs(); - void processApiKey(const QString &); - void closeApiConnection(QPushButton *); - void revokeApiKey(QPushButton *); void retryApiConnection(); private: @@ -94,105 +94,74 @@ public: private slots: - //void on_loginCheckBox_toggled(bool checked); - void on_categoriesBtn_clicked(); - void on_execBlacklistBtn_clicked(); - void on_bsaDateBtn_clicked(); - void on_browseDownloadDirBtn_clicked(); - void on_browseModDirBtn_clicked(); - void on_browseCacheDirBtn_clicked(); - void on_resetDialogsButton_clicked(); - void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void deleteBlacklistItem(); - void on_associateButton_clicked(); - void on_clearCacheButton_clicked(); - - void on_revokeNexusAuthButton_clicked(); - + void on_nexusDisconnect_clicked(); void on_browseBaseDirBtn_clicked(); - void on_browseOverwriteDirBtn_clicked(); - void on_browseProfilesDirBtn_clicked(); - void on_browseGameDirBtn_clicked(); - void on_overwritingBtn_clicked(); - void on_overwrittenBtn_clicked(); - void on_overwritingArchiveBtn_clicked(); - void on_overwrittenArchiveBtn_clicked(); - void on_containsBtn_clicked(); - void on_containedBtn_clicked(); - void on_resetColorsBtn_clicked(); - void on_baseDirEdit_editingFinished(); - void on_downloadDirEdit_editingFinished(); - void on_modDirEdit_editingFinished(); - void on_cacheDirEdit_editingFinished(); - void on_profilesDirEdit_editingFinished(); - void on_overwriteDirEdit_editingFinished(); - void on_nexusConnect_clicked(); - void on_nexusManualKey_clicked(); + void on_resetGeometryBtn_clicked(); + void deleteBlacklistItem(); void dispatchLogin(); - void loginPing(); - void authError(QAbstractSocket::SocketError error); - void receiveApiKey(const QString &apiKey); - void completeApiConnection(); - void on_resetGeometryBtn_clicked(); - private: - Ui::SettingsDialog *ui; - PluginContainer *m_PluginContainer; - - QColor m_OverwritingColor; - QColor m_OverwrittenColor; - QColor m_OverwritingArchiveColor; - QColor m_OverwrittenArchiveColor; - QColor m_ContainsColor; - QColor m_ContainedColor; - - bool m_KeyReceived; - bool m_KeyCleared; - bool m_GeometriesReset; - QString m_UUID; - QString m_AuthToken; - - QString m_ExecutableBlacklist; - QWebSocket *m_nexusLogin; - QTimer m_loginTimer; - int m_totalPings = 0; + Ui::SettingsDialog *ui; + Settings* m_settings; + PluginContainer *m_PluginContainer; + + QColor m_OverwritingColor; + QColor m_OverwrittenColor; + QColor m_OverwritingArchiveColor; + QColor m_OverwrittenArchiveColor; + QColor m_ContainsColor; + QColor m_ContainedColor; + + bool m_KeyReceived; + bool m_KeyCleared; + bool m_GeometriesReset; + QString m_UUID; + QString m_AuthToken; + + QString m_ExecutableBlacklist; + QWebSocket *m_nexusLogin; + QTimer m_loginTimer; + int m_totalPings = 0; + + bool setKey(const QString& key); + bool clearKey(); + void updateNexusButtons(); + + void fetchNexusApiKey(); + void testApiKey(); }; - - #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 088b7a0d..08c8d9f2 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -511,12 +511,12 @@ p, li { white-space: pre-wrap; } - + Clear the stored Nexus API key and force reauthorization. - Revoke Nexus Authorization + Disconnect from Nexus -- cgit v1.3.1 From c3b57c90d1884afa6ec50c62216fdaaff483d06b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 04:35:00 -0400 Subject: refactored FormatMessage() into formatSystemMessage() fixes trying to load a wchar_t* with qCritical(), which merely prints the address --- src/settings.cpp | 61 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 18 deletions(-) (limited to 'src/settings.cpp') diff --git a/src/settings.cpp b/src/settings.cpp index 53dd32dc..b45912d4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -81,6 +81,38 @@ private: int m_SortRole; }; + +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; @@ -217,12 +249,11 @@ QString Settings::deObfuscate(const QString key) result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); CredFree(creds); } else { - if (GetLastError() != ERROR_NOT_FOUND) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Retrieving encrypted data failed:" << buffer; + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + qCritical().nospace() + << "Retrieving encrypted data failed: " + << formatSystemMessage(e); } } delete[] keyData; @@ -366,13 +397,8 @@ bool Settings::getNexusApiKey(QString &apiKey) const bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { - wchar_t buffer[256]; - - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - - qCritical() << "Storing API key failed:" << buffer; + const auto e = GetLastError(); + qCritical().nospace() << "Storing API key failed: " << formatSystemMessage(e); return false; } @@ -487,11 +513,10 @@ void Settings::setSteamLogin(QString username, QString password) m_Settings.setValue("Settings/steam_username", username); } if (!obfuscate("steam_password", password)) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Storing or deleting password failed:" << buffer; + const auto e = GetLastError(); + qCritical().nospace() + << "Storing or deleting password failed: " + << formatSystemMessage(e); } } -- 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/settings.cpp') 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 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/settings.cpp') 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