From 3757aa3f532c943f8a47e997d0a4f250d9dacdd3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 15:07:35 -0400 Subject: split into settingsutilities --- src/settingsutilities.cpp | 247 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 src/settingsutilities.cpp (limited to 'src/settingsutilities.cpp') diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp new file mode 100644 index 00000000..7ac95b5f --- /dev/null +++ b/src/settingsutilities.cpp @@ -0,0 +1,247 @@ +#include "settingsutilities.h" +#include "expanderwidget.h" +#include + +using namespace MOBase; + +void logRemoval(const QString& name) +{ + log::debug("setting '{}' removed", name); +} + + +QString settingName(const QString& section, const QString& key) +{ + if (section.isEmpty()) { + return key; + } else if (key.isEmpty()) { + return section; + } else { + if (section.compare("General", Qt::CaseInsensitive) == 0) { + return key; + } else { + return section + "/" + key; + } + } +} + +void removeImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key) +{ + if (key.isEmpty()) { + if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) { + // not there + return; + } + } else { + if (!settings.contains(settingName(section, key))) { + // not there + return; + } + } + + logRemoval(displayName); + settings.remove(settingName(section, key)); +} + +void remove(QSettings& settings, const QString& section, const QString& key) +{ + removeImpl(settings, settingName(section, key), section, key); +} + +void removeSection(QSettings& settings, const QString& section) +{ + removeImpl(settings, section, section, ""); +} + + +ScopedGroup::ScopedGroup(QSettings& s, const QString& name) + : m_settings(s), m_name(name) +{ + m_settings.beginGroup(m_name); +} + +ScopedGroup::~ScopedGroup() +{ + m_settings.endGroup(); +} + +void ScopedGroup::remove(const QString& key) +{ + removeImpl(m_settings, settingName(m_name, key), "", key); +} + +QStringList ScopedGroup::keys() const +{ + return m_settings.childKeys(); +} + + +ScopedReadArray::ScopedReadArray(QSettings& s, const QString& section) + : m_settings(s), m_count(0) +{ + m_count = m_settings.beginReadArray(section); +} + +ScopedReadArray::~ScopedReadArray() +{ + m_settings.endArray(); +} + +int ScopedReadArray::count() const +{ + return m_count; +} + +QStringList ScopedReadArray::keys() const +{ + return m_settings.childKeys(); +} + + +ScopedWriteArray::ScopedWriteArray(QSettings& s, const QString& section) + : m_settings(s), m_section(section), m_i(0) +{ + m_settings.beginWriteArray(section); +} + +ScopedWriteArray::~ScopedWriteArray() +{ + m_settings.endArray(); +} + +void ScopedWriteArray::next() +{ + m_settings.setArrayIndex(m_i); + ++m_i; +} + + +QString widgetNameWithTopLevel(const QWidget* widget) +{ + QStringList components; + + auto* tl = widget->window(); + + if (tl == widget) { + // this is a top level widget, such as a dialog + components.push_back(widget->objectName()); + } else { + // this is a widget + const auto toplevelName = tl->objectName(); + if (!toplevelName.isEmpty()) { + components.push_back(toplevelName); + } + + const auto widgetName = widget->objectName(); + if (!widgetName.isEmpty()) { + components.push_back(widgetName); + } + } + + if (components.isEmpty()) { + // can't do much + return "unknown_widget"; + } + + return components.join("_"); +} + +QString widgetName(const QMainWindow* w) +{ + return w->objectName(); +} + +QString widgetName(const QHeaderView* w) +{ + return widgetNameWithTopLevel(w->parentWidget()); +} + +QString widgetName(const ExpanderWidget* w) +{ + return widgetNameWithTopLevel(w->button()); +} + +QString widgetName(const QWidget* w) +{ + return widgetNameWithTopLevel(w); +} + +QString dockSettingName(const QDockWidget* dock) +{ + return "MainWindow_docks_" + dock->objectName() + "_size"; +} + +QString indexSettingName(const QWidget* widget) +{ + return widgetNameWithTopLevel(widget) + "_index"; +} + +QString checkedSettingName(const QAbstractButton* b) +{ + return widgetNameWithTopLevel(b) + "_checked"; +} + +void warnIfNotCheckable(const QAbstractButton* b) +{ + if (!b->isCheckable()) { + log::warn( + "button '{}' used in the settings as a checkbox or radio button " + "but is not checkable", b->objectName()); + } +} + + +bool setWindowsCredential(const QString key, const QString data) +{ + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + bool result = false; + if (data.isEmpty()) { + result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); + if (!result) + if (GetLastError() == ERROR_NOT_FOUND) + result = true; + } else { + wchar_t* charData = new wchar_t[data.size()]; + data.toWCharArray(charData); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = keyData; + cred.CredentialBlob = (LPBYTE)charData; + cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + result = CredWriteW(&cred, 0); + delete[] charData; + } + delete[] keyData; + return result; +} + +QString getWindowsCredential(const QString key) +{ + QString result; + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + PCREDENTIALW creds; + if (CredReadW(keyData, 1, 0, &creds)) { + wchar_t *charData = (wchar_t *)creds->CredentialBlob; + result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); + CredFree(creds); + } else { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + } + } + delete[] keyData; + return result; +} -- cgit v1.3.1 From 209c27c7a27e2f6cb34f122a929c15eb3d1e60b7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 15:47:21 -0400 Subject: only remove section when the array is larger, prevents logging changes when nothing actually changed changed back section names that were originally lowercase, arrays end up in two different sections --- src/settings.cpp | 69 ++++++++++++++++++++++++++++++----------------- src/settingsutilities.cpp | 8 +++--- src/settingsutilities.h | 4 ++- 3 files changed, 52 insertions(+), 29 deletions(-) (limited to 'src/settingsutilities.cpp') diff --git a/src/settings.cpp b/src/settings.cpp index 14189be3..8ae7c343 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -221,9 +221,19 @@ std::vector> Settings::executables() const void Settings::setExecutables(const std::vector>& v) { - removeSection(m_Settings, "customExecutables"); + const auto current = executables(); - ScopedWriteArray swa(m_Settings, "customExecutables"); + if (current == v) { + // no change + return; + } + + if (current.size() > v.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "customExecutables"); + } + + ScopedWriteArray swa(m_Settings, "customExecutables", v.size()); for (const auto& map : v) { swa.next(); @@ -1156,9 +1166,9 @@ void PluginSettings::addBlacklistPlugin(const QString &fileName) void PluginSettings::writePluginBlacklist() { - removeSection(m_Settings, "PluginBlacklist"); + removeSection(m_Settings, "pluginBlacklist"); - ScopedWriteArray swa(m_Settings, "PluginBlacklist"); + ScopedWriteArray swa(m_Settings, "pluginBlacklist"); for (const QString &plugin : m_PluginBlacklist) { swa.next(); @@ -1222,7 +1232,7 @@ std::map PathSettings::recent() const { std::map map; - ScopedReadArray sra(m_Settings, "RecentDirectories"); + ScopedReadArray sra(m_Settings, "recentDirectories"); sra.for_each([&] { const QVariant name = sra.get("name"); @@ -1238,9 +1248,14 @@ std::map PathSettings::recent() const void PathSettings::setRecent(const std::map& map) { - removeSection(m_Settings, "RecentDirectories"); + const auto current = recent(); - ScopedWriteArray swa(m_Settings, "recentDirectories"); + if (current.size() > map.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "recentDirectories"); + } + + ScopedWriteArray swa(m_Settings, "recentDirectories", map.size()); for (auto&& p : map) { swa.next(); @@ -1472,33 +1487,37 @@ ServerList NetworkSettings::serversFromOldMap() const return list; } -void NetworkSettings::updateServers(ServerList servers) +void NetworkSettings::updateServers(ServerList newServers) { // clean up unavailable servers - servers.cleanup(); + newServers.cleanup(); - removeSection(m_Settings, "Servers"); + const auto current = servers(); - { - ScopedWriteArray swa(m_Settings, "Servers"); + if (current.size() > newServers.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "Servers"); + } - for (const auto& server : servers) { - swa.next(); - swa.set("name", server.name()); - swa.set("premium", server.isPremium()); - swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); - swa.set("preferred", server.preferred()); + ScopedWriteArray swa(m_Settings, "Servers", newServers.size()); - QString lastDownloads; - for (const auto& speed : server.lastDownloads()) { - if (speed > 0) { - lastDownloads += QString("%1 ").arg(speed); - } - } + for (const auto& server : newServers) { + swa.next(); - swa.set("lastDownloads", lastDownloads.trimmed()); + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); + + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } } + + swa.set("lastDownloads", lastDownloads.trimmed()); } } diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 7ac95b5f..d5e2dd9a 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -100,10 +100,12 @@ QStringList ScopedReadArray::keys() const } -ScopedWriteArray::ScopedWriteArray(QSettings& s, const QString& section) - : m_settings(s), m_section(section), m_i(0) +ScopedWriteArray::ScopedWriteArray( + QSettings& s, const QString& section, std::size_t size) + : m_settings(s), m_section(section), m_i(0) { - m_settings.beginWriteArray(section); + m_settings.beginWriteArray( + section, size == NoSize ? -1 : static_cast(size)); } ScopedWriteArray::~ScopedWriteArray() diff --git a/src/settingsutilities.h b/src/settingsutilities.h index b70e55ef..ca754759 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -204,7 +204,9 @@ private: class ScopedWriteArray { public: - ScopedWriteArray(QSettings& s, const QString& section); + static const auto NoSize = std::numeric_limits::max(); + + ScopedWriteArray(QSettings& s, const QString& section, std::size_t size=NoSize); ~ScopedWriteArray(); ScopedWriteArray(const ScopedWriteArray&) = delete; -- cgit v1.3.1 From 7f0fa1069f07d90a92be7073b11bab86bac7b2d2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 16:09:31 -0400 Subject: don't log widget and geometry setting changes fixed mod info dialog tab order using different settings for read and write mod info dialog now doesn't complain when no tab order exists in the settings only remove section when the array is larger, prevents logging changes when nothing actually changed --- src/modinfodialog.cpp | 9 ++++++--- src/settings.cpp | 27 ++++++++++++++++++++------- src/settings.h | 2 +- src/settingsutilities.cpp | 18 ++++++++++++++++++ src/settingsutilities.h | 6 ++++++ 5 files changed, 51 insertions(+), 11 deletions(-) (limited to 'src/settingsutilities.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2178ef34..c7e071ad 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -383,9 +383,12 @@ void ModInfoDialog::reAddTabs( // ordered tab names from settings const auto orderedNames = m_core->settings().geometry().modInfoTabOrder(); - // whether the tabs can be sorted; if the object name of a tab widget is not - // found in orderedNames, the list cannot be sorted safely - bool canSort = true; + // whether the tabs can be sorted + // + // if the object name of a tab widget is not found in orderedNames, the list + // cannot be sorted safely; if the list is empty, it's probably a first run + // and there's nothing to sort + bool canSort = !orderedNames.empty(); // gathering visible tabs std::vector visibleTabs; diff --git a/src/settings.cpp b/src/settings.cpp index 8ae7c343..a33005b6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -210,7 +210,7 @@ std::vector> Settings::executables() const std::map map; for (auto&& key : sra.keys()) { - map[key] = m_Settings.value(key); + map[key] = sra.get(key); } v.push_back(map); @@ -693,7 +693,7 @@ QStringList GeometrySettings::modInfoTabOrder() const } } else { // string list since 2.2.1 - QString string = m_Settings.value("mod_info_tab_order").toString(); + QString string = get(m_Settings, "Widgets", "ModInfoTabOrder", ""); QTextStream stream(&string); while (!stream.atEnd()) { @@ -708,7 +708,7 @@ QStringList GeometrySettings::modInfoTabOrder() const void GeometrySettings::setModInfoTabOrder(const QString& names) { - set(m_Settings, "Geometry", "mod_info_tab_order", names); + set(m_Settings, "Widgets", "ModInfoTabOrder", names); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) @@ -1061,13 +1061,21 @@ void PluginSettings::clearPlugins() { m_Plugins.clear(); m_PluginSettings.clear(); - m_PluginBlacklist.clear(); + m_PluginBlacklist = readPluginBlacklist(); +} + +QSet PluginSettings::readPluginBlacklist() const +{ + QSet set; + ScopedReadArray sra(m_Settings, "pluginBlacklist"); sra.for_each([&]{ - m_PluginBlacklist.insert(sra.get("name")); + set.insert(sra.get("name")); }); + + return set; } void PluginSettings::registerPlugin(IPlugin *plugin) @@ -1166,9 +1174,14 @@ void PluginSettings::addBlacklistPlugin(const QString &fileName) void PluginSettings::writePluginBlacklist() { - removeSection(m_Settings, "pluginBlacklist"); + const auto current = readPluginBlacklist(); + + if (current.size() > m_PluginBlacklist.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "pluginBlacklist"); + } - ScopedWriteArray swa(m_Settings, "pluginBlacklist"); + ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); for (const QString &plugin : m_PluginBlacklist) { swa.next(); diff --git a/src/settings.h b/src/settings.h index 68cfb9b7..2ff8da1c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -261,8 +261,8 @@ private: QMap m_PluginDescriptions; QSet m_PluginBlacklist; - void readPluginBlacklist(); void writePluginBlacklist(); + QSet readPluginBlacklist() const; }; diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index d5e2dd9a..7a9dcc35 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -4,8 +4,26 @@ using namespace MOBase; +bool shouldLogSetting(const QString& displayName) +{ + // don't log Geometry/ and Widgets/, too noisy and not very useful + static const QStringList ignorePrefixes = {"Geometry/", "Widgets/"}; + + for (auto&& prefix : ignorePrefixes) { + if (displayName.startsWith(prefix, Qt::CaseInsensitive)) { + return false; + } + } + + return true; +} + void logRemoval(const QString& name) { + if (!shouldLogSetting(name)) { + return; + } + log::debug("setting '{}' removed", name); } diff --git a/src/settingsutilities.h b/src/settingsutilities.h index ca754759..d99abb06 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -24,10 +24,16 @@ struct ValueConverter>> }; +bool shouldLogSetting(const QString& displayName); + template void logChange( const QString& displayName, std::optional oldValue, const T& newValue) { + if (!shouldLogSetting(displayName)) { + return; + } + using VC = ValueConverter; if (oldValue) { -- cgit v1.3.1 From 09b95e39434b9efc49a606957c94cd42309b7fb6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 13 Sep 2019 15:15:18 -0400 Subject: moved all spawn dialogs into a namespace starting steam with spawn() instead of QProcess dialogs for bad steam registry key and failure refactored credentials code, added logging add environment variables to env --- src/env.cpp | 42 +++++ src/env.h | 12 +- src/organizercore.cpp | 4 - src/settingsutilities.cpp | 148 ++++++++++++----- src/settingsutilities.h | 4 +- src/spawn.cpp | 410 +++++++++++++++++++++++++--------------------- 6 files changed, 383 insertions(+), 237 deletions(-) (limited to 'src/settingsutilities.cpp') diff --git a/src/env.cpp b/src/env.cpp index 34f53294..1aaaa8ef 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -171,6 +171,48 @@ void Environment::dumpDisks(const Settings& s) const } +QString path() +{ + return get("PATH"); +} + +QString addPath(const QString& s) +{ + auto old = path(); + set("PATH", get("PATH") + ";" + s); + return old; +} + +QString setPath(const QString& s) +{ + return set("PATH", s); +} + +QString get(const QString& name) +{ + std::wstring s(4000, L' '); + + DWORD realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast(s.size())); + + if (realSize > s.size()) { + s.resize(realSize); + + ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast(s.size())); + } + + return QString::fromStdWString(s); +} + +QString set(const QString& n, const QString& v) +{ + auto old = get(n); + ::SetEnvironmentVariableW(n.toStdWString().c_str(), v.toStdWString().c_str()); + return old; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index 6222c86b..7bdc9b85 100644 --- a/src/env.h +++ b/src/env.h @@ -162,6 +162,16 @@ private: }; +// environment variables +// +QString get(const QString& name); +QString set(const QString& name, const QString& value); + +QString path(); +QString addPath(const QString& s); +QString setPath(const QString& s); + + enum class CoreDumpTypes { Mini = 1, @@ -169,7 +179,7 @@ enum class CoreDumpTypes Full }; -// creates a minidump file for the given process +// creates a minidump file for this process // bool coredump(CoreDumpTypes type); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dd4edbce..fbc9083b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1288,7 +1288,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, sp.currentDirectory = currentDirectory; sp.hooked = true; - prepareStart(); QWidget *window = qApp->activeWindow(); @@ -1296,7 +1295,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, window = nullptr; } - if (!spawn::checkBinary(window, sp)) { return INVALID_HANDLE_VALUE; } @@ -1369,8 +1367,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - log::debug("Spawning proxyed process <{}>", cmdline); - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); sp.arguments = cmdline; sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 7a9dcc35..6c99a602 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -213,55 +213,117 @@ void warnIfNotCheckable(const QAbstractButton* b) } -bool setWindowsCredential(const QString key, const QString data) +QString credentialName(const QString& key) { - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); - - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; - - result = CredWriteW(&cred, 0); - delete[] charData; + return "ModOrganizer2_" + key; +} + +bool deleteWindowsCredential(const QString& key) +{ + const auto credName = credentialName(key); + + if (!CredDeleteW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0)) { + const auto e = GetLastError(); + if (e == ERROR_NOT_FOUND) { + // not an error if the key already doesn't exist + log::debug("can't delete windows credential {}, doesn't exist", credName); + return true; + } else { + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + return false; + } } - delete[] keyData; - return result; + + log::debug("deleted windows credential {}", credName); + + return true; } -QString getWindowsCredential(const QString key) -{ - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { +bool addWindowsCredential(const QString& key, const QString& data) +{ + const auto credName = credentialName(key); + + const auto wname = credName.toStdWString(); + const auto wdata = data.toStdWString(); + + const auto* blob = reinterpret_cast(wdata.data()); + const auto blobSize = wdata.size() * sizeof(decltype(wdata)::value_type); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = const_cast(wname.c_str()); + cred.CredentialBlob = const_cast(blob); + cred.CredentialBlobSize = static_cast(blobSize); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + if (!CredWriteW(&cred, 0)) { const auto e = GetLastError(); + + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + + return false; + } + + log::debug("added windows credential {}", credName); + + return true; +} + +struct CredentialFreer +{ + void operator()(CREDENTIALW* c) + { + if (c) { + CredFree(c); + } + } +}; + +using CredentialPtr = std::unique_ptr; + +QString getWindowsCredential(const QString& key) +{ + const QString credName = credentialName(key); + + CREDENTIALW* rawCreds = nullptr; + + const auto ret = CredReadW( + credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0, &rawCreds); + + CredentialPtr creds(rawCreds); + + if (!ret) { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + log::error( + "failed to retrieve windows credential {}: {}", + credName, formatSystemMessage(e)); } + + return {}; + } + + QString value; + if (creds->CredentialBlob) { + value = QString::fromWCharArray( + reinterpret_cast(creds->CredentialBlob), + creds->CredentialBlobSize / sizeof(wchar_t)); + } + + return value; +} + +bool setWindowsCredential(const QString& key, const QString& data) +{ + if (data.isEmpty()) { + return deleteWindowsCredential(key); + } else { + return addWindowsCredential(key, data); } - delete[] keyData; - return result; } diff --git a/src/settingsutilities.h b/src/settingsutilities.h index c3eef12f..a6737144 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -270,7 +270,7 @@ QString checkedSettingName(const QAbstractButton* b); void warnIfNotCheckable(const QAbstractButton* b); -bool setWindowsCredential(const QString key, const QString data); -QString getWindowsCredential(const QString key); +bool setWindowsCredential(const QString& key, const QString& data); +QString getWindowsCredential(const QString& key); #endif // SETTINGSUTILITIES_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 80f82a28..94737871 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -43,96 +43,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -namespace spawn -{ - -// details -namespace -{ - -std::wstring pathEnv() +namespace spawn::dialogs { - std::wstring s(4000, L' '); - - DWORD realSize = ::GetEnvironmentVariableW( - L"PATH", s.data(), static_cast(s.size())); - - if (realSize > s.size()) { - s.resize(realSize); - - ::GetEnvironmentVariableW( - TEXT("PATH"), s.data(), static_cast(s.size())); - } - - return s; -} - -void setPathEnv(const std::wstring& s) -{ - ::SetEnvironmentVariableW(L"PATH", s.c_str()); -} - -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) -{ - BOOL inheritHandles = FALSE; - - STARTUPINFO si = {}; - si.cb = sizeof(si); - - // inherit handles if we plan to use stdout or stderr reroute - if (sp.stdOut != INVALID_HANDLE_VALUE) { - si.hStdOutput = sp.stdOut; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - if (sp.stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = sp.stdErr; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); - - std::wstring commandLine = L"\"" + bin + L"\""; - if (sp.arguments[0] != L'\0') { - commandLine += L" " + sp.arguments.toStdWString(); - } - - QString moPath = QCoreApplication::applicationDirPath(); - - const auto oldPath = pathEnv(); - setPathEnv(oldPath + L";" + QDir::toNativeSeparators(moPath).toStdWString()); - - PROCESS_INFORMATION pi; - BOOL success = FALSE; - - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); - - if (sp.hooked) { - success = ::CreateProcessHooked( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); - } else { - success = ::CreateProcess( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); - } - - const auto e = GetLastError(); - setPathEnv(oldPath); - - if (!success) { - return e; - } - - processHandle = pi.hProcess; - threadHandle = pi.hThread; - - return ERROR_SUCCESS; -} std::wstring makeRightsDetails(const env::FileSecurity& fs) { @@ -196,7 +108,7 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) elevated = L"(not available)"; } - return fmt::format( + std::wstring f = L"Error {code} {codename}: {error}\n" L" . binary: '{bin}'\n" L" . owner: {owner}\n" @@ -204,8 +116,13 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) L" . arguments: '{args}'\n" L" . cwd: '{cwd}'{cwdexists}\n" L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" - L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n" - L" . MO elevated: {elevated}", + L" . MO elevated: {elevated}"; + + if (sp.hooked) { + f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}"; + } + + return fmt::format(f, fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), @@ -225,36 +142,82 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) fmt::arg(L"elevated", elevated)); } -void spawnFailed(const SpawnParameters& sp, DWORD code) +QString makeContent(const SpawnParameters& sp, DWORD code) { - const auto details = QString::fromStdWString(makeDetails(sp, code)); - log::error("{}", details); - - const auto title = QObject::tr("Cannot launch program"); - - const auto mainText = QObject::tr("Cannot start %1") - .arg(sp.binary.fileName()); - - QString content; - if (code == ERROR_INVALID_PARAMETER) { - content = QObject::tr( + return QObject::tr( "This error typically happens because an antivirus has deleted critical " "files from Mod Organizer's installation folder or has made them " "generally inaccessible. Add an exclusion for Mod Organizer's " "installation folder in your antivirus, reinstall Mod Organizer and try " "again."); } else if (code == ERROR_ACCESS_DENIED) { - content = QObject::tr( + return QObject::tr( "This error typically happens because an antivirus is preventing Mod " "Organizer from starting programs. Add an exclusion for Mod Organizer's " "installation folder in your antivirus and try again."); } else if (code == ERROR_FILE_NOT_FOUND) { - content = QObject::tr("The file '%1' does not exist.") + return QObject::tr("The file '%1' does not exist.") .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); } else { - content = QString::fromStdWString(formatSystemMessage(code)); + return QString::fromStdWString(formatSystemMessage(code)); } +} + +QMessageBox::StandardButton badSteamReg( + QWidget* parent, const QString& keyName, const QString& valueName) +{ + const auto details = QString( + "can't start steam, registry value at '%1' is empty or doesn't exist") + .arg(keyName + "\\" + valueName); + + log::error("{}", details); + + return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) + .main(QObject::tr("Cannot start Steam")) + .content(QObject::tr( + "The path to the Steam executable cannot be found. You might try " + "reinstalling Steam.")) + .details(details) + .button({ + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); +} + +QMessageBox::StandardButton startSteamFailed( + QWidget* parent, const SpawnParameters& sp, DWORD e) +{ + const auto details = makeDetails(sp, e); + log::error("{}", details); + + return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) + .main(QObject::tr("Cannot start Steam")) + .content(makeContent(sp, e)) + .details(QString::fromStdWString(details)) + .button({ + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); +} + +void spawnFailed(const SpawnParameters& sp, DWORD code) +{ + const auto details = QString::fromStdWString(makeDetails(sp, code)); + log::error("{}", details); + + const auto title = QObject::tr("Cannot launch program"); + + const auto mainText = QObject::tr("Cannot start %1") + .arg(sp.binary.fileName()); QWidget *window = qApp->activeWindow(); if ((window != nullptr) && (!window->isVisible())) { @@ -263,7 +226,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) MOBase::TaskDialog(window, title) .main(mainText) - .content(content) + .content(makeContent(sp, code)) .details(details) .exec(); } @@ -301,48 +264,143 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) .content(content) .details(details) .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) + QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); return (r == QMessageBox::Yes); } -void startBinaryAdmin(const SpawnParameters& sp) +QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) { - if (!confirmRestartAsAdmin(sp)) { - log::debug("user declined"); - return; + return QuestionBoxMemory::query( + parent, "steamQuery", sp.binary.fileName(), + QObject::tr("Start Steam?"), + QObject::tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); +} + +QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) +{ + return QuestionBoxMemory::query( + parent, "steamAdminQuery", sp.binary.fileName(), + QObject::tr("Steam: Access Denied"), + QObject::tr("MO was denied access to the Steam process. This normally indicates that " + "Steam is being run as administrator while MO is not. This can cause issues " + "launching the game. It is recommended to not run Steam as administrator unless " + "absolutely necessary.\n\n" + "Restart MO as administrator?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); +} + +} // namepsace + + +namespace spawn +{ + +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) +{ + BOOL inheritHandles = FALSE; + + STARTUPINFO si = {}; + si.cb = sizeof(si); + + // inherit handles if we plan to use stdout or stderr reroute + if (sp.stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = sp.stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; } - log::info("restarting MO as administrator"); + if (sp.stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = sp.stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + + std::wstring commandLine = L"\"" + bin + L"\""; + if (sp.arguments[0] != L'\0') { + commandLine += L" " + sp.arguments.toStdWString(); + } + + QString moPath = QCoreApplication::applicationDirPath(); + const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); + + PROCESS_INFORMATION pi; + BOOL success = FALSE; + + if (sp.hooked) { + success = ::CreateProcessHooked( + nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + cwd.c_str(), &si, &pi); + } else { + success = ::CreateProcess( + nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + cwd.c_str(), &si, &pi); + } + + const auto e = GetLastError(); + env::setPath(oldPath); + + if (!success) { + return e; + } + processHandle = pi.hProcess; + threadHandle = pi.hThread; + + return ERROR_SUCCESS; +} + +bool restartAsAdmin() +{ WCHAR cwd[MAX_PATH] = {}; if (!GetCurrentDirectory(MAX_PATH, cwd)) { cwd[0] = L'\0'; } - if (Helper::adminLaunch( + if (!Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - log::debug("exiting MO"); - qApp->exit(0); + // todo + log::error("admin launch failed"); + return false; } + + log::debug("exiting MO"); + qApp->exit(0); + return true; } -} // namespace +void startBinaryAdmin(const SpawnParameters& sp) +{ + if (!dialogs::confirmRestartAsAdmin(sp)) { + log::debug("user declined"); + return; + } + + log::info("restarting MO as administrator"); + restartAsAdmin(); +} bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { - spawnFailed(sp, ERROR_FILE_NOT_FOUND); + dialogs::spawnFailed(sp, ERROR_FILE_NOT_FOUND); return false; } @@ -379,10 +437,23 @@ SteamStatus getSteamStatus() return ss; } -bool startSteam(QWidget *widget) +QString makeSteamArguments(const QString& username, const QString& password) { - log::debug("starting steam"); + QString args; + + if (username != "") { + args += "-login " + username; + + if (password != "") { + args += " " + password; + } + } + + return args; +} +bool startSteam(QWidget* parent) +{ const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam"; const QString valueName = "SteamExe"; @@ -390,42 +461,41 @@ bool startSteam(QWidget *widget) const QString exe = steamSettings.value(valueName, "").toString(); if (exe.isEmpty()) { - log::error( - "can't start steam, registry value at '{}' is empty", - keyName + "\\" + valueName); - - return false; + return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes); } - const QString program = QString("\"%1\"").arg(exe); + SpawnParameters sp; + sp.binary = exe; // See if username and password supplied. If so, pass them into steam. - QStringList args; QString username, password; if (Settings::instance().steam().login(username, password)) { - args.push_back("-login"); - args.push_back(username); - - if (password != "") { - args.push_back(password); - } + sp.arguments = makeSteamArguments(username, password); } log::debug( "starting steam process:\n" " . program: '{}'\n" " . username={}, password={}", - program, + sp.binary.filePath().toStdString(), (username.isEmpty() ? "no" : "yes"), (password.isEmpty() ? "no" : "yes")); - if (!QProcess::startDetached(program, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(program)); - return false; + HANDLE ph = INVALID_HANDLE_VALUE; + HANDLE th = INVALID_HANDLE_VALUE; + const auto e = spawn(sp, ph, th); + + if (e != ERROR_SUCCESS) { + // make sure username and passwords are not shown + sp.arguments = makeSteamArguments( + (username.isEmpty() ? "" : "USERNAME"), + (password.isEmpty() ? "" : "PASSWORD")); + + return (dialogs::startSteamFailed(parent, sp, e) == QMessageBox::Yes); } QMessageBox::information( - widget, QObject::tr("Waiting"), + parent, QObject::tr("Waiting"), QObject::tr("Please press OK once you're logged into steam.")); return true; @@ -448,29 +518,6 @@ bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings) return false; } -QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) -{ - return QuestionBoxMemory::query( - parent, "steamQuery", sp.binary.fileName(), - QObject::tr("Start Steam?"), - QObject::tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); -} - -QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) -{ - return QuestionBoxMemory::query( - parent, "steamAdminQuery", sp.binary.fileName(), - QObject::tr("Steam: Access Denied"), - QObject::tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); -} - bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) @@ -492,16 +539,20 @@ bool checkSteam( if (!ss.running) { log::debug("steam isn't running, asking to start steam"); - const auto c = confirmStartSteam(parent, sp); + const auto c = dialogs::confirmStartSteam(parent, sp); if (c == QDialogButtonBox::Yes) { log::debug("user wants to start steam"); - startSteam(parent); + + if (!startSteam(parent)) { + // cancel + return false; + } // double-check that Steam is started ss = getSteamStatus(); if (!ss.running) { - log::error("could not start steam, continuing and hoping for the best"); + log::error("steam is still not running, continuing and hoping for the best"); return true; } } else if (c == QDialogButtonBox::No) { @@ -515,25 +566,10 @@ bool checkSteam( if (ss.running && !ss.accessible) { log::debug("steam is running but is not accessible, asking to restart MO"); - const auto c = confirmRestartAsAdminForSteam(parent, sp); + const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp); if (c == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - cwd[0] = L'\0'; - } - - if (Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) - { - log::debug("exiting MO"); - qApp->exit(0); - return false; - } - - log::error("unable to relaunch MO as admin"); + restartAsAdmin(); return false; } else if (c == QDialogButtonBox::No) { log::debug("user declined to restart MO, continuing"); @@ -686,7 +722,7 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) default: { - spawnFailed(sp, e); + dialogs::spawnFailed(sp, e); return INVALID_HANDLE_VALUE; } } -- cgit v1.3.1 From c603681115b6071f241f6931685d36a92b6403f8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 10:07:52 -0400 Subject: moved helper stuff to spawn so it can reuse error handling removed unused helper::init() removed logging when deleting a credential that doesn't exist, happens all the time --- src/envsecurity.cpp | 2 +- src/helper.cpp | 107 +--------------------------- src/helper.h | 42 +---------- src/settingsdialogworkarounds.cpp | 2 +- src/settingsutilities.cpp | 18 ++--- src/spawn.cpp | 145 +++++++++++++++++++++++++++++++++++--- src/spawn.h | 25 +++++++ 7 files changed, 175 insertions(+), 166 deletions(-) (limited to 'src/settingsutilities.cpp') diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 6e3fadbe..3b4cdcaa 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -275,7 +275,7 @@ std::vector getSecurityProductsFromWMI() } if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - log::error("productState is a {}, is not a VT_UI4", prop.vt); + log::error("productState is a {}, not a VT_UI4", prop.vt); return; } diff --git a/src/helper.cpp b/src/helper.cpp index 59a2d3d1..24446cb8 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -17,109 +17,4 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "helper.h" -#include "utility.h" -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include - -using MOBase::reportError; - - -namespace Helper { - - -static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine, BOOL async) -{ - wchar_t fileName[MAX_PATH]; - _snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory); - - SHELLEXECUTEINFOW execInfo = {0}; - - execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - execInfo.hwnd = nullptr; - execInfo.lpVerb = L"runas"; - execInfo.lpFile = fileName; - execInfo.lpParameters = commandLine; - execInfo.lpDirectory = moDirectory; - execInfo.nShow = SW_SHOW; - - ::ShellExecuteExW(&execInfo); - - if (execInfo.hProcess == 0) { - reportError(QObject::tr("helper failed")); - return false; - } - - if (async) { - return true; - } - - if (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0) { - reportError(QObject::tr("helper failed")); - return false; - } - - DWORD exitCode; - GetExitCodeProcess(execInfo.hProcess, &exitCode); - return exitCode == NOERROR; -} - - -bool init(const std::wstring &moPath, const std::wstring &dataPath) -{ - DWORD userNameLen = UNLEN + 1; - wchar_t userName[UNLEN + 1]; - - if (!GetUserName(userName, &userNameLen)) { - reportError(QObject::tr("failed to determine account name")); - return false; - } - wchar_t *commandLine = new wchar_t[32768]; - - _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"", - dataPath.c_str(), userName); - - bool res = helperExec(moPath.c_str(), commandLine, FALSE); - delete [] commandLine; - - return res; -} - - -bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) -{ - wchar_t *commandLine = new wchar_t[32768]; - _snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"", - dataPath.c_str()); - - bool res = helperExec(moPath.c_str(), commandLine, FALSE); - delete [] commandLine; - - return res; -} - - -bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir) -{ - wchar_t *commandLine = new wchar_t[32768]; - _snwprintf(commandLine, 32768, L"adminLaunch %d \"%ls\" \"%ls\"", - ::GetCurrentProcessId(), - moFile.c_str(), - workingDir.c_str() - ); - - bool res = helperExec(moPath.c_str(), commandLine, TRUE); - delete [] commandLine; - - return res; -} - - -} // namespace +// moved to spawn.cpp diff --git a/src/helper.h b/src/helper.h index f6667a84..335b8647 100644 --- a/src/helper.h +++ b/src/helper.h @@ -20,45 +20,7 @@ along with Mod Organizer. If not, see . #ifndef HELPER_H #define HELPER_H - -#include - - -/** - * @brief Convenience functions to work with the external helper program. - * - * The mo_helper program is used to make changes on the system that require administrative - * rights, so that ModOrganizer itself can run without special privileges - **/ -namespace Helper { - -/** - * @brief initialise the specified directory for use with mod organizer. - * - * This will create all required sub-directories and give the user running ModOrganizer - * write-access - * - * @param moPath absolute path to the ModOrganizer base directory - * @return true on success - **/ -bool init(const std::wstring &moPath, const std::wstring &dataPath); - -/** - * @brief sets the last modified time for all .bsa-files in the target directory well into the past - * @param moPath absolute path to the modOrganizer base directory - * @param dataPath the path taht contains the .bsa-files, usually the data directory of the game - **/ -bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); - -/** - * @brief waits for the current process to exit and restarts it as an administrator - * @param moPath absolute path to the modOrganizer base directory - * @param moFile file name of modOrganizer - * @param workingDir current working directory - **/ -bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir); - -} - +// all helper code moved to spawn.h and spawn.cpp +#include "spawn.h" #endif // HELPER_H diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 4d811e40..f89b021c 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -83,7 +83,7 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() const auto* game = qApp->property("managed_game").value(); QDir dir = game->dataDirectory(); - Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), + helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), dir.absolutePath().toStdWString()); } diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 6c99a602..db7c1818 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -224,16 +224,18 @@ bool deleteWindowsCredential(const QString& key) if (!CredDeleteW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0)) { const auto e = GetLastError(); + + // not an error if the key already doesn't exist, and don't log it because + // it happens all the time when the settings dialog is closed since it + // doesn't check first if (e == ERROR_NOT_FOUND) { - // not an error if the key already doesn't exist - log::debug("can't delete windows credential {}, doesn't exist", credName); return true; - } else { - log::error( - "failed to delete windows credential {}, {}", - credName, formatSystemMessage(e)); - return false; } + + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + return false; } log::debug("deleted windows credential {}", credName); @@ -269,7 +271,7 @@ bool addWindowsCredential(const QString& key, const QString& data) return false; } - log::debug("added windows credential {}", credName); + log::debug("set windows credential {}", credName); return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index 94737871..6c524681 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -64,7 +64,7 @@ std::wstring makeRightsDetails(const env::FileSecurity& fs) return s; } -std::wstring makeDetails(const SpawnParameters& sp, DWORD code) +QString makeDetails(const SpawnParameters& sp, DWORD code, const QString& more={}) { std::wstring owner, rights; @@ -109,7 +109,7 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) } std::wstring f = - L"Error {code} {codename}: {error}\n" + L"Error {code} {codename}{more}: {error}\n" L" . binary: '{bin}'\n" L" . owner: {owner}\n" L" . rights: {rights}\n" @@ -122,9 +122,12 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}"; } - return fmt::format(f, + const std::wstring wmore = (more.isEmpty() ? L"" : (", " + more).toStdWString()); + + const auto s = fmt::format(f, fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), + fmt::arg(L"more", wmore), fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), fmt::arg(L"owner", owner), fmt::arg(L"rights", rights), @@ -140,6 +143,8 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) fmt::arg(L"x86_proxy", usvfs_x86_proxy), fmt::arg(L"x64_proxy", usvfs_x64_proxy), fmt::arg(L"elevated", elevated)); + + return QString::fromStdWString(s); } QString makeContent(const SpawnParameters& sp, DWORD code) @@ -198,7 +203,7 @@ QMessageBox::StandardButton startSteamFailed( return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) .main(QObject::tr("Cannot start Steam")) .content(makeContent(sp, e)) - .details(QString::fromStdWString(details)) + .details(details) .button({ QObject::tr("Continue without starting Steam"), QObject::tr("The program may fail to launch."), @@ -211,7 +216,7 @@ QMessageBox::StandardButton startSteamFailed( void spawnFailed(const SpawnParameters& sp, DWORD code) { - const auto details = QString::fromStdWString(makeDetails(sp, code)); + const auto details = makeDetails(sp, code); log::error("{}", details); const auto title = QObject::tr("Cannot launch program"); @@ -231,10 +236,38 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) .exec(); } +void helperFailed( + DWORD code, const QString& why, const std::wstring& binary, + const std::wstring& cwd, const std::wstring& args) +{ + SpawnParameters sp; + sp.binary = QString::fromStdWString(binary); + sp.currentDirectory.setPath(QString::fromStdWString(cwd)); + sp.arguments = QString::fromStdWString(args); + + const auto details = makeDetails(sp, code, "in " + why); + log::error("{}", details); + + const auto title = QObject::tr("Cannot launch helper"); + + const auto mainText = QObject::tr("Cannot start %1") + .arg(sp.binary.fileName()); + + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + + MOBase::TaskDialog(window, title) + .main(mainText) + .content(makeContent(sp, code)) + .details(details) + .exec(); +} + bool confirmRestartAsAdmin(const SpawnParameters& sp) { - const auto details = QString::fromStdWString( - makeDetails(sp, ERROR_ELEVATION_REQUIRED)); + const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED); log::error("{}", details); @@ -370,18 +403,18 @@ bool restartAsAdmin() cwd[0] = L'\0'; } - if (!Helper::adminLaunch( + if (!helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - // todo log::error("admin launch failed"); return false; } log::debug("exiting MO"); qApp->exit(0); + return true; } @@ -728,4 +761,96 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } -} // namespace \ No newline at end of file +} // namespace + + + +namespace helper +{ + +bool helperExec( + const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async) +{ + const std::wstring fileName = moDirectory + L"\\helper.exe"; + + env::HandlePtr process; + + { + SHELLEXECUTEINFOW execInfo = {}; + + ULONG flags = SEE_MASK_FLAG_NO_UI ; + if (!async) + flags |= SEE_MASK_NOCLOSEPROCESS; + + execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + execInfo.fMask = flags; + execInfo.hwnd = 0; + execInfo.lpVerb = L"runas"; + execInfo.lpFile = fileName.c_str(); + execInfo.lpParameters = commandLine.c_str(); + execInfo.lpDirectory = moDirectory.c_str(); + execInfo.nShow = SW_SHOW; + + if (!::ShellExecuteExW(&execInfo) && execInfo.hProcess == 0) { + const auto e = GetLastError(); + + spawn::dialogs::helperFailed( + e, "ShellExecuteExW()", fileName, moDirectory, commandLine); + + return false; + } + + if (async) { + return true; + } + + process.reset(execInfo.hProcess); + } + + const auto r = ::WaitForSingleObject(process.get(), INFINITE); + + if (r != WAIT_OBJECT_0) { + // for WAIT_ABANDONED, the documentation doesn't mention that GetLastError() + // returns something meaningful, but code ERROR_ABANDONED_WAIT_0 exists, so + // use that instead + const auto code = (r == WAIT_ABANDONED ? + ERROR_ABANDONED_WAIT_0 : GetLastError()); + + spawn::dialogs::helperFailed( + code, "WaitForSingleObject()", fileName, moDirectory, commandLine); + + return false; + } + + DWORD exitCode = 0; + if (!GetExitCodeProcess(process.get(), &exitCode)) { + const auto e = GetLastError(); + + spawn::dialogs::helperFailed( + e, "GetExitCodeProcess()", fileName, moDirectory, commandLine); + + return false; + } + + return (exitCode == 0); +} + +bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) +{ + const std::wstring commandLine = fmt::format( + L"backdateBSA \"{}\"", dataPath); + + return helperExec(moPath, commandLine, FALSE); +} + + +bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir) +{ + const std::wstring commandLine = fmt::format( + L"adminLaunch {} \"{}\" \"{}\"", + ::GetCurrentProcessId(), moFile, workingDir); + + return helperExec(moPath, commandLine, true); +} + +} // namespace diff --git a/src/spawn.h b/src/spawn.h index 9398b6cc..da626329 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -71,5 +71,30 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); } // namespace + +// convenience functions to work with the external helper program, which is used +// to make changes on the system that require administrative rights, so that +// ModOrganizer itself can run without special privileges +// +namespace helper +{ + +/** +* @brief sets the last modified time for all .bsa-files in the target directory well into the past +* @param moPath absolute path to the modOrganizer base directory +* @param dataPath the path taht contains the .bsa-files, usually the data directory of the game +**/ +bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); + +/** +* @brief waits for the current process to exit and restarts it as an administrator +* @param moPath absolute path to the modOrganizer base directory +* @param moFile file name of modOrganizer +* @param workingDir current working directory +**/ +bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir); + +} // namespace + #endif // SPAWN_H -- cgit v1.3.1