From 0015a12cf8916d8000c6d14356b4c17c62f4a588 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Wed, 11 Sep 2019 23:36:13 -0400
Subject: rewritten spawn() to use std::wstring instead of manual buffers error
dialogs now use TaskDialog with more user-friendly text
---
src/spawn.cpp | 414 +++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 307 insertions(+), 107 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index f77da35f..614bc92f 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -21,164 +21,364 @@ along with Mod Organizer. If not, see .
#include "report.h"
#include "utility.h"
+#include "env.h"
+#include "envwindows.h"
+#include "envsecurity.h"
+#include
#include
+#include
#include
#include
#include
#include
#include "helper.h"
-
#include
#include
#include
-
-
#include
-
-#include
+#include
using namespace MOBase;
using namespace MOShared;
+namespace
+{
+
+struct SpawnParameters
+{
+ std::wstring binary;
+ std::wstring arguments;
+ std::wstring currentDirectory;
+ bool suspended = false;
+ bool hooked = false;
+ HANDLE stdOut = INVALID_HANDLE_VALUE;
+ HANDLE stdErr = INVALID_HANDLE_VALUE;
+};
-static const int BUFSIZE = 4096;
-static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory,
- bool suspended, bool hooked,
- HANDLE stdOut, HANDLE stdErr,
- HANDLE& processHandle, HANDLE& threadHandle)
+std::wstring pathEnv()
+{
+ 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;
- ::ZeroMemory(&si, sizeof(si));
- if (stdOut != INVALID_HANDLE_VALUE) {
- si.hStdOutput = stdOut;
+
+ 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 (stdErr != INVALID_HANDLE_VALUE) {
- si.hStdError = stdErr;
+
+ if (sp.stdErr != INVALID_HANDLE_VALUE) {
+ si.hStdError = sp.stdErr;
inheritHandles = TRUE;
si.dwFlags |= STARTF_USESTDHANDLES;
}
- si.cb = sizeof(si);
- size_t length = wcslen(binary) + wcslen(arguments) + 4;
- wchar_t *commandLine = nullptr;
- if (arguments[0] != L'\0') {
- commandLine = new wchar_t[length];
- _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments);
- } else {
- commandLine = new wchar_t[length];
- _snwprintf_s(commandLine, length, _TRUNCATE, L"\"%ls\"", binary);
- }
- QString moPath = QCoreApplication::applicationDirPath();
+ std::wstring commandLine;
- boost::scoped_array oldPath(new TCHAR[BUFSIZE]);
- DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE);
- if (offset > BUFSIZE) {
- oldPath.reset(new TCHAR[offset]);
- ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset);
+ if (sp.arguments[0] != L'\0') {
+ commandLine = L"\"" + sp.binary + L"\" " + sp.arguments;
+ } else {
+ commandLine = L"\"" + sp.binary + L"\"";
}
- {
- boost::scoped_array newPath(new TCHAR[offset + moPath.length() + 2]);
- _tcsncpy(newPath.get(), oldPath.get(), offset);
- newPath.get()[offset] = '\0';
- _tcsncat(newPath.get(), TEXT(";"), 1);
- _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length());
+ QString moPath = QCoreApplication::applicationDirPath();
- ::SetEnvironmentVariable(TEXT("PATH"), newPath.get());
- }
+ const auto oldPath = pathEnv();
+ setPathEnv(oldPath + L";" + QDir::toNativeSeparators(moPath).toStdWString());
PROCESS_INFORMATION pi;
BOOL success = FALSE;
- if (hooked) {
- success = ::CreateProcessHooked(nullptr,
- commandLine,
- nullptr, nullptr, // no special process or thread attributes
- inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
- CREATE_BREAKAWAY_FROM_JOB,
- nullptr, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
+
+ if (sp.hooked) {
+ success = ::CreateProcessHooked(
+ nullptr, const_cast(commandLine.c_str()), nullptr, nullptr,
+ inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
+ sp.currentDirectory.c_str(), &si, &pi);
} else {
- success = ::CreateProcess(nullptr,
- commandLine,
- nullptr, nullptr, // no special process or thread attributes
- inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
- CREATE_BREAKAWAY_FROM_JOB,
- nullptr, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
+ success = ::CreateProcess(
+ nullptr, const_cast(commandLine.c_str()), nullptr, nullptr,
+ inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
+ sp.currentDirectory.c_str(), &si, &pi);
}
- ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get());
-
- delete [] commandLine;
+ const auto e = GetLastError();
+ setPathEnv(oldPath);
if (!success) {
- throw windows_error("failed to start process");
+ return e;
}
processHandle = pi.hProcess;
threadHandle = pi.hThread;
- return true;
+
+ return ERROR_SUCCESS;
}
+std::wstring makeRightsDetails(const env::FileSecurity& fs)
+{
+ if (fs.rights.normalRights) {
+ return L"(normal rights)";
+ }
+
+ if (fs.rights.list.isEmpty()) {
+ return L"(none)";
+ }
+
+ std::wstring s = fs.rights.list.join("|").toStdWString();
+ if (!fs.rights.hasExecute) {
+ s += L" (execute is missing)";
+ }
+
+ return s;
+}
-HANDLE startBinary(const QFileInfo &binary,
- const QString &arguments,
- const QDir ¤tDirectory,
- bool hooked,
- HANDLE stdOut,
- HANDLE stdErr)
+std::wstring makeDetails(const SpawnParameters& sp, DWORD code)
{
+ const QFileInfo bin(QString::fromStdWString(sp.binary));
+ std::wstring owner, rights;
+
+ if (bin.isFile()) {
+ const auto fs = env::getFileSecurity(bin.absoluteFilePath());
+
+ if (fs.error.isEmpty()) {
+ owner = fs.owner.toStdWString();
+ rights = makeRightsDetails(fs);
+ } else {
+ owner = fs.error.toStdWString();
+ rights = fs.error.toStdWString();
+ }
+ } else {
+ owner = L"(file not found)";
+ rights = L"(file not found)";
+ }
+
+ const bool cwdExists = (sp.currentDirectory.empty() ?
+ true : QFileInfo(QString::fromStdWString(sp.currentDirectory)).isDir());
+
+ const auto appDir = QCoreApplication::applicationDirPath();
+ const auto sep = QDir::separator();
+
+ const std::wstring usvfs_x86_dll =
+ QFileInfo(appDir + sep + "usvfs_x86.dll").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x64_dll =
+ QFileInfo(appDir + sep + "usvfs_x64.dll").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x86_proxy =
+ QFileInfo(appDir + sep + "usvfs_proxy_x86.exe").isFile() ? L"ok" : L"not found";
+
+ const std::wstring usvfs_x64_proxy =
+ QFileInfo(appDir + sep + "usvfs_proxy_x64.exe").isFile() ? L"ok" : L"not found";
+
+ std::wstring elevated;
+ if (auto b=env::Environment().windowsInfo().isElevated()) {
+ elevated = (*b ? L"yes" : L"no");
+ } else {
+ elevated = L"(not available)";
+ }
+
+ return fmt::format(
+ L"Error {code} {codename}: {error}\n"
+ L" . binary: '{bin}'\n"
+ L" . owner: {owner}\n"
+ L" . rights: {rights}\n"
+ L" . arguments: '{args}'\n"
+ L" . cwd: '{cwd}'{cwdexists}\n"
+ L" . stdout: {stdout}, stderr: {stderr}, suspended: {susp}, hooked: {hooked}\n"
+ L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n"
+ L" . MO elevated: {elevated}",
+ fmt::arg(L"code", code),
+ fmt::arg(L"codename", errorCodeName(code)),
+ fmt::arg(L"bin", sp.binary),
+ fmt::arg(L"owner", owner),
+ fmt::arg(L"rights", rights),
+ fmt::arg(L"error", formatSystemMessage(code)),
+ fmt::arg(L"args", sp.arguments),
+ fmt::arg(L"cwd", sp.currentDirectory),
+ fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")),
+ fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
+ fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
+ fmt::arg(L"susp", (sp.suspended ? L"yes" : L"no")),
+ fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")),
+ fmt::arg(L"x86_dll", usvfs_x86_dll),
+ fmt::arg(L"x64_dll", usvfs_x64_dll),
+ fmt::arg(L"x86_proxy", usvfs_x86_proxy),
+ fmt::arg(L"x64_proxy", usvfs_x64_proxy),
+ fmt::arg(L"elevated", elevated));
+}
+
+void spawnFailed(const SpawnParameters& sp, DWORD code)
+{
+ const auto details = QString::fromStdWString(makeDetails(sp, code));
+ log::error("{}", details);
+
+ const auto binary = QFileInfo(QString::fromStdWString(sp.binary));
+
+ const auto title = QObject::tr("Cannot launch program");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(binary.fileName());
+
+ QString content;
+
+ if (code == ERROR_INVALID_PARAMETER) {
+ content = 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(
+ "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 {
+ content = QString::fromStdWString(formatSystemMessage(code));
+ }
+
+ QWidget *window = qApp->activeWindow();
+ if ((window != nullptr) && (!window->isVisible())) {
+ window = nullptr;
+ }
+
+ MOBase::TaskDialog(window, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .exec();
+}
+
+bool confirmRestartAsAdmin(const SpawnParameters& sp)
+{
+ const auto details = QString::fromStdWString(
+ makeDetails(sp, ERROR_ELEVATION_REQUIRED));
+
+ log::error("{}", details);
+
+ const auto binary = QFileInfo(QString::fromStdWString(sp.binary));
+
+ const auto title = QObject::tr("Elevation required");
+
+ const auto mainText = QObject::tr("Cannot start %1")
+ .arg(binary.fileName());
+
+ const auto content = QObject::tr(
+ "This program is requesting to run as administrator but Mod Organizer "
+ "itself is not running as administrator. Running programs as administrator "
+ "is typically unnecessary as long as the game and Mod Organizer have been "
+ "installed outside \"Program Files\".\r\n\r\n"
+ "You can restart Mod Organizer as administrator and try launching the "
+ "program again.");
+
+
+ QWidget *window = qApp->activeWindow();
+ if ((window != nullptr) && (!window->isVisible())) {
+ window = nullptr;
+ }
+
+ log::debug("asking user to restart MO as administrator");
+
+ const auto r = MOBase::TaskDialog(window, title)
+ .main(mainText)
+ .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})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QMessageBox::Yes);
+}
+
+} // namespace
+
+void startBinaryAdmin(const SpawnParameters& sp)
+{
+ if (!confirmRestartAsAdmin(sp)) {
+ log::debug("user declined");
+ return;
+ }
+
+ log::info("restarting MO as administrator");
+
+ 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))) {
+ qApp->exit(0);
+ }
+}
+
+HANDLE startBinary(
+ const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory,
+ bool hooked, HANDLE stdOut, HANDLE stdErr)
+{
+ SpawnParameters sp;
+
+ sp.binary = QDir::toNativeSeparators(binary.absoluteFilePath()).toStdWString();
+ sp.arguments = arguments.toStdWString();
+ sp.currentDirectory = QDir::toNativeSeparators(currentDirectory.absolutePath()).toStdWString();
+ sp.suspended = true;
+ sp.hooked = hooked;
+ sp.stdOut = stdOut;
+ sp.stdErr = stdErr;
+
HANDLE processHandle, threadHandle;
- std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
- std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
+ const auto e = spawn(sp, processHandle, threadHandle);
- try {
- if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(),
- true, hooked, stdOut, stdErr, processHandle, threadHandle)) {
- reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
+ switch (e)
+ {
+ case ERROR_SUCCESS:
+ {
+ ::CloseHandle(threadHandle);
+ return processHandle;
}
- } catch (const windows_error &e) {
- if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) {
- if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"),
- QObject::tr("This process requires elevation to run.\n"
- "This is a potential security risk so I highly advise you to investigate if\n"
- "\"%1\"\n"
- "can be installed to work without elevation.\n\n"
- "Restart Mod Organizer as an elevated process?\n"
- "You will be asked if you want to allow helper.exe to make changes to the system. "
- "You will need to relaunch the process above manually.").arg(
- QDir::toNativeSeparators(binary.absoluteFilePath())),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- WCHAR cwd[MAX_PATH];
- if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(::GetLastError()));
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- return INVALID_HANDLE_VALUE;
- }
- qApp->exit(0);
- }
+
+ case ERROR_ELEVATION_REQUIRED:
+ {
+ startBinaryAdmin(sp);
return INVALID_HANDLE_VALUE;
+ }
- } else {
- reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what()));
+ default:
+ {
+ spawnFailed(sp, e);
return INVALID_HANDLE_VALUE;
}
}
-
- ::CloseHandle(threadHandle);
- return processHandle;
}
--
cgit v1.3.1
From 971cecf343777894cec5144da11d16ef97d0db31 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 12 Sep 2019 01:09:40 -0400
Subject: split spawnBinaryProcess() into spawn, no changes
---
src/mainwindow.cpp | 2 +-
src/organizercore.cpp | 286 ++--------------------------------------------
src/spawn.cpp | 310 +++++++++++++++++++++++++++++++++++++++++++++++++-
src/spawn.h | 18 +++
4 files changed, 338 insertions(+), 278 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 054262e9..6737e330 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -6434,7 +6434,7 @@ void MainWindow::on_bossButton_clicked()
return;
}
- HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
+ HANDLE loot = spawn::startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
parameters.join(" "),
qApp->applicationDirPath() + "/loot",
true,
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 91e16716..9403b47d 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -135,34 +135,6 @@ static DWORD getProcessParentID(DWORD pid)
return res;
}
-static void startSteam(QWidget *widget)
-{
- QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam",
- QSettings::NativeFormat);
- QString exe = steamSettings.value("SteamExe", "").toString();
- if (!exe.isEmpty()) {
- exe = QString("\"%1\"").arg(exe);
- // See if username and password supplied. If so, pass them into steam.
- QStringList args;
- QString username;
- QString password;
- if (Settings::instance().steam().login(username, password)) {
- args << "-login";
- args << username;
- if (password != "") {
- args << password;
- }
- }
- if (!QProcess::startDetached(exe, args)) {
- reportError(QObject::tr("Failed to start \"%1\"").arg(exe));
- } else {
- QMessageBox::information(
- widget, QObject::tr("Waiting"),
- QObject::tr("Please press OK once you're logged into steam."));
- }
- }
-}
-
template
QStringList toStringList(InputIterator current, InputIterator end)
{
@@ -173,86 +145,6 @@ QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
-bool checkService()
-{
- SC_HANDLE serviceManagerHandle = NULL;
- SC_HANDLE serviceHandle = NULL;
- LPSERVICE_STATUS_PROCESS serviceStatus = NULL;
- LPQUERY_SERVICE_CONFIG serviceConfig = NULL;
- bool serviceRunning = true;
-
- DWORD bytesNeeded;
-
- try {
- serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceManagerHandle) {
- log::warn("failed to open service manager (query status) (error {})", GetLastError());
- throw 1;
- }
-
- serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceHandle) {
- log::warn("failed to open EventLog service (query status) (error {})", GetLastError());
- throw 2;
- }
-
- if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- log::warn("failed to get size of service config (error {})", GetLastError());
- throw 3;
- }
-
- DWORD serviceConfigSize = bytesNeeded;
- serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize);
- if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) {
- log::warn("failed to query service config (error {})", GetLastError());
- throw 4;
- }
-
- if (serviceConfig->dwStartType == SERVICE_DISABLED) {
- log::error("Windows Event Log service is disabled!");
- serviceRunning = false;
- }
-
- if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- log::warn("failed to get size of service status (error {})", GetLastError());
- throw 5;
- }
-
- DWORD serviceStatusSize = bytesNeeded;
- serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize);
- if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) {
- log::warn("failed to query service status (error {})", GetLastError());
- throw 6;
- }
-
- if (serviceStatus->dwCurrentState != SERVICE_RUNNING) {
- log::error("Windows Event Log service is not running");
- serviceRunning = false;
- }
- }
- catch (int e) {
- UNUSED_VAR(e);
- serviceRunning = false;
- }
-
- if (serviceStatus) {
- LocalFree(serviceStatus);
- }
- if (serviceConfig) {
- LocalFree(serviceConfig);
- }
- if (serviceHandle) {
- CloseServiceHandle(serviceHandle);
- }
- if (serviceManagerHandle) {
- CloseServiceHandle(serviceManagerHandle);
- }
-
- return serviceRunning;
-}
-
OrganizerCore::OrganizerCore(Settings &settings)
: m_UserInterface(nullptr)
@@ -361,67 +253,6 @@ void OrganizerCore::storeSettings()
}
}
-bool OrganizerCore::testForSteam(bool *found, bool *access)
-{
- HANDLE hProcessSnap;
- HANDLE hProcess;
- PROCESSENTRY32 pe32;
- DWORD lastError;
-
- if (found == nullptr || access == nullptr) {
- return false;
- }
-
- // Take a snapshot of all processes in the system.
- hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (hProcessSnap == INVALID_HANDLE_VALUE) {
- lastError = GetLastError();
- log::error("unable to get snapshot of processes (error {})", lastError);
- return false;
- }
-
- // Retrieve information about the first process,
- // and exit if unsuccessful
- pe32.dwSize = sizeof(PROCESSENTRY32);
- if (!Process32First(hProcessSnap, &pe32)) {
- lastError = GetLastError();
- log::error("unable to get first process (error {})", lastError);
- CloseHandle(hProcessSnap);
- return false;
- }
-
- *found = false;
- *access = true;
-
- // Now walk the snapshot of processes, and
- // display information about each process in turn
- do {
- if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) ||
- (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) {
-
- *found = true;
-
- // Try to open the process to determine if MO has the proper access
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
- FALSE, pe32.th32ProcessID);
- if (hProcess == NULL) {
- lastError = GetLastError();
- if (lastError == ERROR_ACCESS_DENIED) {
- *access = false;
- }
- } else {
- CloseHandle(hProcess);
- }
- break;
- }
-
-} while(Process32Next(hProcessSnap, &pe32));
-
-CloseHandle(hProcessSnap);
-return true;
-
-}
-
void OrganizerCore::updateExecutablesList()
{
if (m_PluginContainer == nullptr) {
@@ -1453,93 +1284,18 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
{
prepareStart();
- if (!binary.exists()) {
- reportError(
- tr("Executable not found: %1").arg(qUtf8Printable(binary.absoluteFilePath())));
- return INVALID_HANDLE_VALUE;
- }
-
- if (!steamAppID.isEmpty()) {
- ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
- } else {
- ::SetEnvironmentVariableW(L"SteamAPPId",
- ToWString(m_Settings.steam().appID()).c_str());
- }
-
QWidget *window = qApp->activeWindow();
if ((window != nullptr) && (!window->isVisible())) {
window = nullptr;
}
- // This could possibly be extracted somewhere else but it's probably for when
- // we have more than one provider of game registration.
- if ((QFileInfo(
- managedGame()->gameDirectory().absoluteFilePath("steam_api.dll"))
- .exists()
- || QFileInfo(managedGame()->gameDirectory().absoluteFilePath(
- "steam_api64.dll"))
- .exists())
- && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) {
-
- bool steamFound = true;
- bool steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- log::error("unable to determine state of Steam");
- }
-
- if (!steamFound) {
- QDialogButtonBox::StandardButton result;
- result = QuestionBoxMemory::query(window, "steamQuery", binary.fileName(),
- tr("Start Steam?"),
- 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);
- if (result == QDialogButtonBox::Yes) {
- startSteam(window);
-
- // double-check that Steam is started and MO has access
- steamFound = true;
- steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- log::error("unable to determine state of Steam");
- } else if (!steamFound) {
- log::error("could not find Steam");
- }
- } else if (result == QDialogButtonBox::Cancel) {
- return INVALID_HANDLE_VALUE;
- }
- }
+ if (!spawn::checkBinary(binary)) {
+ return INVALID_HANDLE_VALUE;
+ }
- if (!steamAccess) {
- QDialogButtonBox::StandardButton result;
- result = QuestionBoxMemory::query(window, "steamAdminQuery", binary.fileName(),
- tr("Steam: Access Denied"),
- 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);
- if (result == QDialogButtonBox::Yes) {
- WCHAR cwd[MAX_PATH];
- if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- log::error("unable to get current directory (error {})", GetLastError());
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- log::error("unable to relaunch MO as admin");
- return INVALID_HANDLE_VALUE;
- }
- qApp->exit(0);
- return INVALID_HANDLE_VALUE;
- } else if (result == QDialogButtonBox::Cancel) {
- return INVALID_HANDLE_VALUE;
- }
- }
+ if (!spawn::checkSteam(window, managedGame()->gameDirectory(), binary, steamAppID, m_Settings)) {
+ return INVALID_HANDLE_VALUE;
}
while (m_DirectoryUpdate) {
@@ -1566,32 +1322,12 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
return INVALID_HANDLE_VALUE;
}
- // Check if the Windows Event Logging service is running. For some reason, this seems to be
- // critical to the successful running of usvfs.
- if (!checkService()) {
- if (QuestionBoxMemory::query(window, QString("eventLogService"), binary.fileName(),
- tr("Windows Event Log Error"),
- tr("The Windows Event Log service is disabled and/or not running. This prevents"
- " USVFS from running properly. Your mods may not be working in the executable"
- " that you are launching. Note that you may have to restart MO and/or your PC"
- " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
- return INVALID_HANDLE_VALUE;
- }
+ if (!spawn::checkEnvironment(window, binary)) {
+ return INVALID_HANDLE_VALUE;
}
- for (auto exec : settings().executablesBlacklist().split(";")) {
- if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) {
- if (QuestionBoxMemory::query(window, QString("blacklistedExecutable"), binary.fileName(),
- tr("Blacklisted Executable"),
- tr("The executable you are attempted to launch is blacklisted in the virtual file"
- " system. This will likely prevent the executable, and any executables that are"
- " launched by this one, from seeing any mods. This could extend to INI files, save"
- " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
- return INVALID_HANDLE_VALUE;
- }
- }
+ if (!spawn::checkBlacklist(window, m_Settings, binary)) {
+ return INVALID_HANDLE_VALUE;
}
QString modsPath = settings().paths().mods();
@@ -1628,11 +1364,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
log::debug("Spawning proxyed process <{}>", cmdline);
- return startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
+ return spawn::startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
cmdline, QCoreApplication::applicationDirPath(), true);
} else {
log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath);
- return startBinary(binary, arguments, currentDirectory, true);
+ return spawn::startBinary(binary, arguments, currentDirectory, true);
}
} else {
log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 614bc92f..4376e5bf 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -24,6 +24,7 @@ along with Mod Organizer. If not, see .
#include "env.h"
#include "envwindows.h"
#include "envsecurity.h"
+#include "settings.h"
#include
#include
#include
@@ -41,6 +42,10 @@ along with Mod Organizer. If not, see .
using namespace MOBase;
using namespace MOShared;
+namespace spawn
+{
+
+// details
namespace
{
@@ -320,8 +325,6 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp)
return (r == QMessageBox::Yes);
}
-} // namespace
-
void startBinaryAdmin(const SpawnParameters& sp)
{
if (!confirmRestartAsAdmin(sp)) {
@@ -344,6 +347,307 @@ void startBinaryAdmin(const SpawnParameters& sp)
}
}
+} // namespace
+
+
+bool checkBinary(const QFileInfo& binary)
+{
+ if (!binary.exists()) {
+ reportError(
+ QObject::tr("Executable not found: %1")
+ .arg(qUtf8Printable(binary.absoluteFilePath())));
+
+ return false;
+ }
+
+ return true;
+}
+
+bool testForSteam(bool *found, bool *access)
+{
+ HANDLE hProcessSnap;
+ HANDLE hProcess;
+ PROCESSENTRY32 pe32;
+ DWORD lastError;
+
+ if (found == nullptr || access == nullptr) {
+ return false;
+ }
+
+ // Take a snapshot of all processes in the system.
+ hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hProcessSnap == INVALID_HANDLE_VALUE) {
+ lastError = GetLastError();
+ log::error("unable to get snapshot of processes (error {})", lastError);
+ return false;
+ }
+
+ // Retrieve information about the first process,
+ // and exit if unsuccessful
+ pe32.dwSize = sizeof(PROCESSENTRY32);
+ if (!Process32First(hProcessSnap, &pe32)) {
+ lastError = GetLastError();
+ log::error("unable to get first process (error {})", lastError);
+ CloseHandle(hProcessSnap);
+ return false;
+ }
+
+ *found = false;
+ *access = true;
+
+ // Now walk the snapshot of processes, and
+ // display information about each process in turn
+ do {
+ if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) ||
+ (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) {
+
+ *found = true;
+
+ // Try to open the process to determine if MO has the proper access
+ hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
+ FALSE, pe32.th32ProcessID);
+ if (hProcess == NULL) {
+ lastError = GetLastError();
+ if (lastError == ERROR_ACCESS_DENIED) {
+ *access = false;
+ }
+ } else {
+ CloseHandle(hProcess);
+ }
+ break;
+ }
+
+ } while(Process32Next(hProcessSnap, &pe32));
+
+ CloseHandle(hProcessSnap);
+ return true;
+}
+
+void startSteam(QWidget *widget)
+{
+ QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam",
+ QSettings::NativeFormat);
+ QString exe = steamSettings.value("SteamExe", "").toString();
+ if (!exe.isEmpty()) {
+ exe = QString("\"%1\"").arg(exe);
+ // See if username and password supplied. If so, pass them into steam.
+ QStringList args;
+ QString username;
+ QString password;
+ if (Settings::instance().steam().login(username, password)) {
+ args << "-login";
+ args << username;
+ if (password != "") {
+ args << password;
+ }
+ }
+ if (!QProcess::startDetached(exe, args)) {
+ reportError(QObject::tr("Failed to start \"%1\"").arg(exe));
+ } else {
+ QMessageBox::information(
+ widget, QObject::tr("Waiting"),
+ QObject::tr("Please press OK once you're logged into steam."));
+ }
+ }
+}
+
+bool checkSteam(
+ QWidget* parent, const QDir& gameDirectory,
+ const QFileInfo &binary, const QString &steamAppID, const Settings& settings)
+{
+ if (!steamAppID.isEmpty()) {
+ ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
+ } else {
+ ::SetEnvironmentVariableW(L"SteamAPPId",
+ ToWString(settings.steam().appID()).c_str());
+ }
+
+ if ((QFileInfo(gameDirectory.absoluteFilePath("steam_api.dll")).exists() ||
+ QFileInfo(gameDirectory.absoluteFilePath("steam_api64.dll")).exists())
+ && (settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) {
+
+ bool steamFound = true;
+ bool steamAccess = true;
+ if (!testForSteam(&steamFound, &steamAccess)) {
+ log::error("unable to determine state of Steam");
+ }
+
+ if (!steamFound) {
+ QDialogButtonBox::StandardButton result;
+ result = QuestionBoxMemory::query(parent, "steamQuery", 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);
+ if (result == QDialogButtonBox::Yes) {
+ startSteam(parent);
+
+ // double-check that Steam is started and MO has access
+ steamFound = true;
+ steamAccess = true;
+ if (!testForSteam(&steamFound, &steamAccess)) {
+ log::error("unable to determine state of Steam");
+ } else if (!steamFound) {
+ log::error("could not find Steam");
+ }
+
+ } else if (result == QDialogButtonBox::Cancel) {
+ return false;
+ }
+ }
+
+ if (!steamAccess) {
+ QDialogButtonBox::StandardButton result;
+ result = QuestionBoxMemory::query(parent, "steamAdminQuery", 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);
+ if (result == QDialogButtonBox::Yes) {
+ WCHAR cwd[MAX_PATH];
+ if (!GetCurrentDirectory(MAX_PATH, cwd)) {
+ log::error("unable to get current directory (error {})", GetLastError());
+ cwd[0] = L'\0';
+ }
+ if (!Helper::adminLaunch(
+ qApp->applicationDirPath().toStdWString(),
+ qApp->applicationFilePath().toStdWString(),
+ std::wstring(cwd))) {
+ log::error("unable to relaunch MO as admin");
+ return false;
+ }
+ qApp->exit(0);
+ return false;
+ } else if (result == QDialogButtonBox::Cancel) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool checkService()
+{
+ SC_HANDLE serviceManagerHandle = NULL;
+ SC_HANDLE serviceHandle = NULL;
+ LPSERVICE_STATUS_PROCESS serviceStatus = NULL;
+ LPQUERY_SERVICE_CONFIG serviceConfig = NULL;
+ bool serviceRunning = true;
+
+ DWORD bytesNeeded;
+
+ try {
+ serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
+ if (!serviceManagerHandle) {
+ log::warn("failed to open service manager (query status) (error {})", GetLastError());
+ throw 1;
+ }
+
+ serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
+ if (!serviceHandle) {
+ log::warn("failed to open EventLog service (query status) (error {})", GetLastError());
+ throw 2;
+ }
+
+ if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded)
+ || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
+ log::warn("failed to get size of service config (error {})", GetLastError());
+ throw 3;
+ }
+
+ DWORD serviceConfigSize = bytesNeeded;
+ serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize);
+ if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) {
+ log::warn("failed to query service config (error {})", GetLastError());
+ throw 4;
+ }
+
+ if (serviceConfig->dwStartType == SERVICE_DISABLED) {
+ log::error("Windows Event Log service is disabled!");
+ serviceRunning = false;
+ }
+
+ if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded)
+ || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
+ log::warn("failed to get size of service status (error {})", GetLastError());
+ throw 5;
+ }
+
+ DWORD serviceStatusSize = bytesNeeded;
+ serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize);
+ if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) {
+ log::warn("failed to query service status (error {})", GetLastError());
+ throw 6;
+ }
+
+ if (serviceStatus->dwCurrentState != SERVICE_RUNNING) {
+ log::error("Windows Event Log service is not running");
+ serviceRunning = false;
+ }
+ }
+ catch (int) {
+ serviceRunning = false;
+ }
+
+ if (serviceStatus) {
+ LocalFree(serviceStatus);
+ }
+ if (serviceConfig) {
+ LocalFree(serviceConfig);
+ }
+ if (serviceHandle) {
+ CloseServiceHandle(serviceHandle);
+ }
+ if (serviceManagerHandle) {
+ CloseServiceHandle(serviceManagerHandle);
+ }
+
+ return serviceRunning;
+}
+
+bool checkEnvironment(QWidget* parent, const QFileInfo& binary)
+{
+ // Check if the Windows Event Logging service is running. For some reason, this seems to be
+ // critical to the successful running of usvfs.
+ if (!checkService()) {
+ if (QuestionBoxMemory::query(parent, QString("eventLogService"), binary.fileName(),
+ QObject::tr("Windows Event Log Error"),
+ QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents"
+ " USVFS from running properly. Your mods may not be working in the executable"
+ " that you are launching. Note that you may have to restart MO and/or your PC"
+ " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()),
+ QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& binary)
+{
+ for (auto exec : settings.executablesBlacklist().split(";")) {
+ if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) {
+ if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), binary.fileName(),
+ QObject::tr("Blacklisted Executable"),
+ QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file"
+ " system. This will likely prevent the executable, and any executables that are"
+ " launched by this one, from seeing any mods. This could extend to INI files, save"
+ " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()),
+ QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
HANDLE startBinary(
const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory,
bool hooked, HANDLE stdOut, HANDLE stdErr)
@@ -382,3 +686,5 @@ HANDLE startBinary(
}
}
}
+
+} // namespace
\ No newline at end of file
diff --git a/src/spawn.h b/src/spawn.h
index 9a2dbfbd..9a5afb4a 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -26,6 +26,22 @@ along with Mod Organizer. If not, see .
#include
#include
+class Settings;
+
+namespace spawn
+{
+
+bool checkBinary(const QFileInfo& binary);
+
+bool checkSteam(
+ QWidget* parent, const QDir& gameDirectory,
+ const QFileInfo &binary, const QString &steamAppID, const Settings& settings);
+
+bool checkEnvironment(QWidget* parent, const QFileInfo& binary);
+
+bool checkBlacklist(
+ QWidget* parent, const Settings& settings, const QFileInfo& binary);
+
/**
* @brief spawn a binary with Mod Organizer injected
*
@@ -45,5 +61,7 @@ HANDLE startBinary(const QFileInfo &binary, const QString &arguments,
const QDir ¤tDirectory, bool hooked,
HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
+} // namespace
+
#endif // SPAWN_H
--
cgit v1.3.1
From bf3d7527801bcba16ba01735efd3d5acc052f485 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 12 Sep 2019 01:48:48 -0400
Subject: made SpawnParameters public, changed to Qt types removed 'suspended',
not used
---
src/mainwindow.cpp | 12 ++++---
src/organizercore.cpp | 24 ++++++++++----
src/spawn.cpp | 92 ++++++++++++++++++---------------------------------
src/spawn.h | 46 +++++++++++++++-----------
4 files changed, 83 insertions(+), 91 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 6737e330..7774d87d 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -6434,11 +6434,13 @@ void MainWindow::on_bossButton_clicked()
return;
}
- HANDLE loot = spawn::startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
- parameters.join(" "),
- qApp->applicationDirPath() + "/loot",
- true,
- stdOutWrite);
+ spawn::SpawnParameters sp;
+ sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe");
+ sp.arguments = parameters.join(" ");
+ sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot");
+ sp.stdOut = stdOutWrite;
+
+ HANDLE loot = spawn::startBinary(this, sp);
// we don't use the write end
::CloseHandle(stdOutWrite);
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 9403b47d..dd4edbce 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1282,6 +1282,13 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
const QString &customOverwrite,
const QList &forcedLibraries)
{
+ spawn::SpawnParameters sp;
+ sp.binary = binary;
+ sp.arguments = arguments;
+ sp.currentDirectory = currentDirectory;
+ sp.hooked = true;
+
+
prepareStart();
QWidget *window = qApp->activeWindow();
@@ -1290,11 +1297,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
}
- if (!spawn::checkBinary(binary)) {
+ if (!spawn::checkBinary(window, sp)) {
return INVALID_HANDLE_VALUE;
}
- if (!spawn::checkSteam(window, managedGame()->gameDirectory(), binary, steamAppID, m_Settings)) {
+ if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) {
return INVALID_HANDLE_VALUE;
}
@@ -1322,11 +1329,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
return INVALID_HANDLE_VALUE;
}
- if (!spawn::checkEnvironment(window, binary)) {
+ if (!spawn::checkEnvironment(window, sp)) {
return INVALID_HANDLE_VALUE;
}
- if (!spawn::checkBlacklist(window, m_Settings, binary)) {
+ if (!spawn::checkBlacklist(window, sp, m_Settings)) {
return INVALID_HANDLE_VALUE;
}
@@ -1364,11 +1371,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
log::debug("Spawning proxyed process <{}>", cmdline);
- return spawn::startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
- cmdline, QCoreApplication::applicationDirPath(), true);
+ sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
+ sp.arguments = cmdline;
+ sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
+
+ return spawn::startBinary(window, sp);
} else {
log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath);
- return spawn::startBinary(binary, arguments, currentDirectory, true);
+ return spawn::startBinary(window, sp);
}
} else {
log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 4376e5bf..a56df23a 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -49,17 +49,6 @@ namespace spawn
namespace
{
-struct SpawnParameters
-{
- std::wstring binary;
- std::wstring arguments;
- std::wstring currentDirectory;
- bool suspended = false;
- bool hooked = false;
- HANDLE stdOut = INVALID_HANDLE_VALUE;
- HANDLE stdErr = INVALID_HANDLE_VALUE;
-};
-
std::wstring pathEnv()
{
@@ -103,12 +92,11 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand
si.dwFlags |= STARTF_USESTDHANDLES;
}
- std::wstring commandLine;
+ const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString();
+ std::wstring commandLine = L"\"" + bin + L"\"";
if (sp.arguments[0] != L'\0') {
- commandLine = L"\"" + sp.binary + L"\" " + sp.arguments;
- } else {
- commandLine = L"\"" + sp.binary + L"\"";
+ commandLine += L" " + sp.arguments.toStdWString();
}
QString moPath = QCoreApplication::applicationDirPath();
@@ -119,16 +107,18 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand
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,
- sp.currentDirectory.c_str(), &si, &pi);
+ cwd.c_str(), &si, &pi);
} else {
success = ::CreateProcess(
nullptr, const_cast(commandLine.c_str()), nullptr, nullptr,
inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr,
- sp.currentDirectory.c_str(), &si, &pi);
+ cwd.c_str(), &si, &pi);
}
const auto e = GetLastError();
@@ -164,11 +154,10 @@ std::wstring makeRightsDetails(const env::FileSecurity& fs)
std::wstring makeDetails(const SpawnParameters& sp, DWORD code)
{
- const QFileInfo bin(QString::fromStdWString(sp.binary));
std::wstring owner, rights;
- if (bin.isFile()) {
- const auto fs = env::getFileSecurity(bin.absoluteFilePath());
+ if (sp.binary.isFile()) {
+ const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath());
if (fs.error.isEmpty()) {
owner = fs.owner.toStdWString();
@@ -182,8 +171,8 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code)
rights = L"(file not found)";
}
- const bool cwdExists = (sp.currentDirectory.empty() ?
- true : QFileInfo(QString::fromStdWString(sp.currentDirectory)).isDir());
+ const bool cwdExists = (sp.currentDirectory.isEmpty() ?
+ true : sp.currentDirectory.exists());
const auto appDir = QCoreApplication::applicationDirPath();
const auto sep = QDir::separator();
@@ -214,21 +203,20 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code)
L" . rights: {rights}\n"
L" . arguments: '{args}'\n"
L" . cwd: '{cwd}'{cwdexists}\n"
- L" . stdout: {stdout}, stderr: {stderr}, suspended: {susp}, hooked: {hooked}\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}",
fmt::arg(L"code", code),
fmt::arg(L"codename", errorCodeName(code)),
- fmt::arg(L"bin", sp.binary),
+ fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()),
fmt::arg(L"owner", owner),
fmt::arg(L"rights", rights),
fmt::arg(L"error", formatSystemMessage(code)),
- fmt::arg(L"args", sp.arguments),
- fmt::arg(L"cwd", sp.currentDirectory),
+ fmt::arg(L"args", sp.arguments.toStdWString()),
+ fmt::arg(L"cwd", QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()),
fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")),
fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")),
- fmt::arg(L"susp", (sp.suspended ? L"yes" : L"no")),
fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")),
fmt::arg(L"x86_dll", usvfs_x86_dll),
fmt::arg(L"x64_dll", usvfs_x64_dll),
@@ -242,12 +230,10 @@ void spawnFailed(const SpawnParameters& sp, DWORD code)
const auto details = QString::fromStdWString(makeDetails(sp, code));
log::error("{}", details);
- const auto binary = QFileInfo(QString::fromStdWString(sp.binary));
-
const auto title = QObject::tr("Cannot launch program");
const auto mainText = QObject::tr("Cannot start %1")
- .arg(binary.fileName());
+ .arg(sp.binary.fileName());
QString content;
@@ -286,12 +272,10 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp)
log::error("{}", details);
- const auto binary = QFileInfo(QString::fromStdWString(sp.binary));
-
const auto title = QObject::tr("Elevation required");
const auto mainText = QObject::tr("Cannot start %1")
- .arg(binary.fileName());
+ .arg(sp.binary.fileName());
const auto content = QObject::tr(
"This program is requesting to run as administrator but Mod Organizer "
@@ -350,12 +334,12 @@ void startBinaryAdmin(const SpawnParameters& sp)
} // namespace
-bool checkBinary(const QFileInfo& binary)
+bool checkBinary(QWidget* parent, const SpawnParameters& sp)
{
- if (!binary.exists()) {
+ if (!sp.binary.exists()) {
reportError(
QObject::tr("Executable not found: %1")
- .arg(qUtf8Printable(binary.absoluteFilePath())));
+ .arg(sp.binary.absoluteFilePath()));
return false;
}
@@ -452,8 +436,8 @@ void startSteam(QWidget *widget)
}
bool checkSteam(
- QWidget* parent, const QDir& gameDirectory,
- const QFileInfo &binary, const QString &steamAppID, const Settings& settings)
+ QWidget* parent, const SpawnParameters& sp,
+ const QDir& gameDirectory, const QString &steamAppID, const Settings& settings)
{
if (!steamAppID.isEmpty()) {
::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
@@ -474,7 +458,7 @@ bool checkSteam(
if (!steamFound) {
QDialogButtonBox::StandardButton result;
- result = QuestionBoxMemory::query(parent, "steamQuery", binary.fileName(),
+ result = 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?"),
@@ -498,7 +482,7 @@ bool checkSteam(
if (!steamAccess) {
QDialogButtonBox::StandardButton result;
- result = QuestionBoxMemory::query(parent, "steamAdminQuery", binary.fileName(),
+ result = 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 "
@@ -609,17 +593,17 @@ bool checkService()
return serviceRunning;
}
-bool checkEnvironment(QWidget* parent, const QFileInfo& binary)
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
{
// Check if the Windows Event Logging service is running. For some reason, this seems to be
// critical to the successful running of usvfs.
if (!checkService()) {
- if (QuestionBoxMemory::query(parent, QString("eventLogService"), binary.fileName(),
+ if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(),
QObject::tr("Windows Event Log Error"),
QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents"
" USVFS from running properly. Your mods may not be working in the executable"
" that you are launching. Note that you may have to restart MO and/or your PC"
- " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()),
+ " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()),
QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
return false;
}
@@ -628,16 +612,16 @@ bool checkEnvironment(QWidget* parent, const QFileInfo& binary)
return true;
}
-bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& binary)
+bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings)
{
for (auto exec : settings.executablesBlacklist().split(";")) {
- if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) {
- if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), binary.fileName(),
+ if (exec.compare(sp.binary.fileName(), Qt::CaseInsensitive) == 0) {
+ if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), sp.binary.fileName(),
QObject::tr("Blacklisted Executable"),
QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file"
" system. This will likely prevent the executable, and any executables that are"
" launched by this one, from seeing any mods. This could extend to INI files, save"
- " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()),
+ " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()),
QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
return false;
}
@@ -648,20 +632,8 @@ bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo&
}
-HANDLE startBinary(
- const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory,
- bool hooked, HANDLE stdOut, HANDLE stdErr)
+HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
{
- SpawnParameters sp;
-
- sp.binary = QDir::toNativeSeparators(binary.absoluteFilePath()).toStdWString();
- sp.arguments = arguments.toStdWString();
- sp.currentDirectory = QDir::toNativeSeparators(currentDirectory.absolutePath()).toStdWString();
- sp.suspended = true;
- sp.hooked = hooked;
- sp.stdOut = stdOut;
- sp.stdErr = stdErr;
-
HANDLE processHandle, threadHandle;
const auto e = spawn(sp, processHandle, threadHandle);
diff --git a/src/spawn.h b/src/spawn.h
index 9a5afb4a..9398b6cc 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -31,20 +31,7 @@ class Settings;
namespace spawn
{
-bool checkBinary(const QFileInfo& binary);
-
-bool checkSteam(
- QWidget* parent, const QDir& gameDirectory,
- const QFileInfo &binary, const QString &steamAppID, const Settings& settings);
-
-bool checkEnvironment(QWidget* parent, const QFileInfo& binary);
-
-bool checkBlacklist(
- QWidget* parent, const Settings& settings, const QFileInfo& binary);
-
-/**
- * @brief spawn a binary with Mod Organizer injected
- *
+/*
* @param binary the binary to spawn
* @param arguments arguments to pass to the binary
* @param profileName name of the active profile
@@ -53,13 +40,34 @@ bool checkBlacklist(
* @param hooked if set, the binary is started with mo injected
* @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process
* @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process
+*/
+struct SpawnParameters
+{
+ QFileInfo binary;
+ QString arguments;
+ QDir currentDirectory;
+ bool hooked = false;
+ HANDLE stdOut = INVALID_HANDLE_VALUE;
+ HANDLE stdErr = INVALID_HANDLE_VALUE;
+};
+
+
+bool checkBinary(QWidget* parent, const SpawnParameters& sp);
+
+bool checkSteam(
+ QWidget* parent, const SpawnParameters& sp,
+ const QDir& gameDirectory, const QString &steamAppID, const Settings& settings);
+
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp);
+
+bool checkBlacklist(
+ QWidget* parent, const SpawnParameters& sp, const Settings& settings);
+
+/**
+ * @brief spawn a binary with Mod Organizer injected
* @return the process handle
- * @todo is the profile name even used any more?
- * @todo is the hooked parameter used?
**/
-HANDLE startBinary(const QFileInfo &binary, const QString &arguments,
- const QDir ¤tDirectory, bool hooked,
- HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
+HANDLE startBinary(QWidget* parent, const SpawnParameters& sp);
} // namespace
--
cgit v1.3.1
From ac6bc5fd01e115d523de65a02e46b2cde1188d37 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 12 Sep 2019 02:45:03 -0400
Subject: testForSteam() now uses env to get processes moved processes from
env.cpp to envmodule.cpp, merged what crash dumps did with what was in
testForSteam()
---
src/env.cpp | 105 ++++++------------------------------------------------
src/env.h | 5 +++
src/envmodule.cpp | 81 +++++++++++++++++++++++++++++++++++++++++
src/envmodule.h | 24 ++++++++++++-
src/spawn.cpp | 62 +++++++-------------------------
5 files changed, 131 insertions(+), 146 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/env.cpp b/src/env.cpp
index e2b85560..34f53294 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -72,6 +72,11 @@ const std::vector& Environment::loadedModules() const
return m_modules;
}
+std::vector Environment::runningProcesses() const
+{
+ return getRunningProcesses();
+}
+
const WindowsInfo& Environment::windowsInfo() const
{
if (!m_windows) {
@@ -166,18 +171,6 @@ void Environment::dumpDisks(const Settings& s) const
}
-struct Process
-{
- std::wstring filename;
- DWORD pid;
-
- Process(std::wstring f, DWORD id)
- : filename(std::move(f)), pid(id)
- {
- }
-};
-
-
// returns the filename of the given process or the current one
//
std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
@@ -233,84 +226,6 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
return {};
}
-std::vector runningProcessesIds()
-{
- // double the buffer size 10 times
- const int MaxTries = 10;
-
- // initial size of 300 processes, unlikely to be more than that
- std::size_t size = 300;
-
- for (int tries=0; tries(size);
- std::fill(ids.get(), ids.get() + size, 0);
-
- DWORD bytesGiven = static_cast(size * sizeof(ids[0]));
- DWORD bytesWritten = 0;
-
- if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten))
- {
- const auto e = GetLastError();
-
- std::wcerr
- << L"failed to enumerate processes, "
- << formatSystemMessage(e) << L"\n";
-
- return {};
- }
-
- if (bytesWritten == bytesGiven) {
- // no way to distinguish between an exact fit and not enough space,
- // just try again
- size *= 2;
- continue;
- }
-
- const auto count = bytesWritten / sizeof(ids[0]);
- return std::vector(ids.get(), ids.get() + count);
- }
-
- std::cerr << L"too many processes to enumerate";
- return {};
-}
-
-std::vector runningProcesses()
-{
- const auto pids = runningProcessesIds();
- std::vector v;
-
- for (const auto& pid : pids) {
- if (pid == 0) {
- // the idle process has pid 0 and seems to be picked up by EnumProcesses()
- continue;
- }
-
- HandlePtr h(OpenProcess(
- PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid));
-
- if (!h) {
- const auto e = GetLastError();
-
- if (e != ERROR_ACCESS_DENIED) {
- // don't log access denied, will happen a lot for system processes, even
- // when elevated
- std::wcerr
- << L"failed to open process " << pid << L", "
- << formatSystemMessage(e) << L"\n";
- }
-
- continue;
- }
-
- auto filename = processFilename(h.get());
- if (!filename.empty()) {
- v.emplace_back(std::move(filename), pid);
- }
- }
-
- return v;
-}
-
DWORD findOtherPid()
{
const std::wstring defaultName = L"ModOrganizer.exe";
@@ -322,7 +237,7 @@ DWORD findOtherPid()
std::wclog << L"this process id is " << thisPid << L"\n";
// getting the filename for this process, assumes the other process has the
- // smae one
+ // same one
auto filename = processFilename();
if (filename.empty()) {
std::wcerr
@@ -335,15 +250,15 @@ DWORD findOtherPid()
}
// getting all running processes
- const auto processes = runningProcesses();
+ const auto processes = getRunningProcesses();
std::wclog << L"there are " << processes.size() << L" processes running\n";
// going through processes, trying to find one with the same name and a
// different pid than this process has
for (const auto& p : processes) {
- if (p.filename == filename) {
- if (p.pid != thisPid) {
- return p.pid;
+ if (p.name() == filename) {
+ if (p.pid() != thisPid) {
+ return p.pid();
}
}
}
diff --git a/src/env.h b/src/env.h
index 46095ca3..6222c86b 100644
--- a/src/env.h
+++ b/src/env.h
@@ -7,6 +7,7 @@ namespace env
{
class Module;
+class Process;
class SecurityProduct;
class WindowsInfo;
class Metrics;
@@ -129,6 +130,10 @@ public:
//
const std::vector& loadedModules() const;
+ // list of running processes; not cached
+ //
+ std::vector runningProcesses() const;
+
// information about the operating system
//
const WindowsInfo& windowsInfo() const;
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index 8cea414a..3f1f8912 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -320,6 +320,40 @@ QString Module::getMD5() const
}
+Process::Process(DWORD pid, QString name)
+ : m_pid(pid), m_name(std::move(name))
+{
+}
+
+DWORD Process::pid() const
+{
+ return m_pid;
+}
+
+const QString& Process::name() const
+{
+ return m_name;
+}
+
+// whether this process can be accessed; fails if the current process doesn't
+// have the proper permissions
+//
+bool Process::canAccess() const
+{
+ HandlePtr h(OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid));
+
+ if (!h) {
+ const auto e = GetLastError();
+ if (e == ERROR_ACCESS_DENIED) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
std::vector getLoadedModules()
{
HandlePtr snapshot(CreateToolhelp32Snapshot(
@@ -373,4 +407,51 @@ std::vector getLoadedModules()
return v;
}
+
+std::vector getRunningProcesses()
+{
+ HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
+
+ if (snapshot.get() == INVALID_HANDLE_VALUE)
+ {
+ const auto e = GetLastError();
+ log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e));
+ return {};
+ }
+
+ PROCESSENTRY32 entry = {};
+ entry.dwSize = sizeof(entry);
+
+ // first process, this shouldn't fail because there's at least one process
+ // running
+ if (!Process32First(snapshot.get(), &entry)) {
+ const auto e = GetLastError();
+ log::error("Process32First() failed, {}", formatSystemMessage(e));
+ return {};
+ }
+
+ std::vector v;
+
+ for (;;)
+ {
+ v.push_back(Process(
+ entry.th32ProcessID,
+ QString::fromStdWString(entry.szExeFile)));
+
+ // next process
+ if (!Process32Next(snapshot.get(), &entry))
+ {
+ const auto e = GetLastError();
+
+ // no more processes is not an error
+ if (e != ERROR_NO_MORE_FILES)
+ log::error("Process32Next() failed, {}", formatSystemMessage(e));
+
+ break;
+ }
+ }
+
+ return v;
+}
+
} // namespace
diff --git a/src/envmodule.h b/src/envmodule.h
index 3f0f99ab..deb7520f 100644
--- a/src/envmodule.h
+++ b/src/envmodule.h
@@ -12,7 +12,7 @@ namespace env
class Module
{
public:
- explicit Module(QString path, std::size_t fileSize);
+ Module(QString path, std::size_t fileSize);
// returns the module's path
//
@@ -96,6 +96,28 @@ private:
};
+// represents one process
+//
+class Process
+{
+public:
+ Process(DWORD pid, QString name);
+
+ DWORD pid() const;
+ const QString& name() const;
+
+ // whether this process can be accessed; fails if the current process doesn't
+ // have the proper permissions
+ //
+ bool canAccess() const;
+
+private:
+ DWORD m_pid;
+ QString m_name;
+};
+
+
+std::vector getRunningProcesses();
std::vector getLoadedModules();
} // namespace env
diff --git a/src/spawn.cpp b/src/spawn.cpp
index a56df23a..604f9ffa 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -24,6 +24,7 @@ along with Mod Organizer. If not, see .
#include "env.h"
#include "envwindows.h"
#include "envsecurity.h"
+#include "envmodule.h"
#include "settings.h"
#include
#include
@@ -49,7 +50,6 @@ namespace spawn
namespace
{
-
std::wstring pathEnv()
{
std::wstring s(4000, L' ');
@@ -249,6 +249,9 @@ void spawnFailed(const SpawnParameters& sp, DWORD code)
"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.")
+ .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath()));
} else {
content = QString::fromStdWString(formatSystemMessage(code));
}
@@ -337,10 +340,7 @@ void startBinaryAdmin(const SpawnParameters& sp)
bool checkBinary(QWidget* parent, const SpawnParameters& sp)
{
if (!sp.binary.exists()) {
- reportError(
- QObject::tr("Executable not found: %1")
- .arg(sp.binary.absoluteFilePath()));
-
+ spawnFailed(sp, ERROR_FILE_NOT_FOUND);
return false;
}
@@ -349,61 +349,23 @@ bool checkBinary(QWidget* parent, const SpawnParameters& sp)
bool testForSteam(bool *found, bool *access)
{
- HANDLE hProcessSnap;
- HANDLE hProcess;
- PROCESSENTRY32 pe32;
- DWORD lastError;
-
if (found == nullptr || access == nullptr) {
return false;
}
- // Take a snapshot of all processes in the system.
- hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- if (hProcessSnap == INVALID_HANDLE_VALUE) {
- lastError = GetLastError();
- log::error("unable to get snapshot of processes (error {})", lastError);
- return false;
- }
-
- // Retrieve information about the first process,
- // and exit if unsuccessful
- pe32.dwSize = sizeof(PROCESSENTRY32);
- if (!Process32First(hProcessSnap, &pe32)) {
- lastError = GetLastError();
- log::error("unable to get first process (error {})", lastError);
- CloseHandle(hProcessSnap);
- return false;
- }
-
+ const auto ps = env::Environment().runningProcesses();
*found = false;
- *access = true;
-
- // Now walk the snapshot of processes, and
- // display information about each process in turn
- do {
- if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) ||
- (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) {
+ for (const auto& p : ps) {
+ if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) ||
+ (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0))
+ {
*found = true;
-
- // Try to open the process to determine if MO has the proper access
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
- FALSE, pe32.th32ProcessID);
- if (hProcess == NULL) {
- lastError = GetLastError();
- if (lastError == ERROR_ACCESS_DENIED) {
- *access = false;
- }
- } else {
- CloseHandle(hProcess);
- }
+ *access = p.canAccess();
break;
}
+ }
- } while(Process32Next(hProcessSnap, &pe32));
-
- CloseHandle(hProcessSnap);
return true;
}
--
cgit v1.3.1
From b867b0bccdb32d94e1646d8f3f8e8a39d4b2446e Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 12 Sep 2019 03:49:17 -0400
Subject: refactored steam handling
---
src/spawn.cpp | 247 +++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 159 insertions(+), 88 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 604f9ffa..80f82a28 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -329,7 +329,9 @@ void startBinaryAdmin(const SpawnParameters& sp)
if (Helper::adminLaunch(
qApp->applicationDirPath().toStdWString(),
qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
+ std::wstring(cwd)))
+ {
+ log::debug("exiting MO");
qApp->exit(0);
}
}
@@ -347,129 +349,198 @@ bool checkBinary(QWidget* parent, const SpawnParameters& sp)
return true;
}
-bool testForSteam(bool *found, bool *access)
+struct SteamStatus
{
- if (found == nullptr || access == nullptr) {
- return false;
- }
+ bool running=false;
+ bool accessible=false;
+};
+
+SteamStatus getSteamStatus()
+{
+ SteamStatus ss;
const auto ps = env::Environment().runningProcesses();
- *found = false;
for (const auto& p : ps) {
if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) ||
(p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0))
{
- *found = true;
- *access = p.canAccess();
+ ss.running = true;
+ ss.accessible = p.canAccess();
+
+ log::debug(
+ "'{}' is running, accessible={}",
+ p.name(), (ss.accessible ? "yes" : "no"));
+
break;
}
}
- return true;
+ return ss;
}
-void startSteam(QWidget *widget)
+bool startSteam(QWidget *widget)
{
- QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam",
- QSettings::NativeFormat);
- QString exe = steamSettings.value("SteamExe", "").toString();
- if (!exe.isEmpty()) {
- exe = QString("\"%1\"").arg(exe);
- // See if username and password supplied. If so, pass them into steam.
- QStringList args;
- QString username;
- QString password;
- if (Settings::instance().steam().login(username, password)) {
- args << "-login";
- args << username;
- if (password != "") {
- args << password;
- }
+ log::debug("starting steam");
+
+ const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam";
+ const QString valueName = "SteamExe";
+
+ const QSettings steamSettings(keyName, QSettings::NativeFormat);
+ 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;
+ }
+
+ const QString program = QString("\"%1\"").arg(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);
}
- if (!QProcess::startDetached(exe, args)) {
- reportError(QObject::tr("Failed to start \"%1\"").arg(exe));
- } else {
- QMessageBox::information(
- widget, QObject::tr("Waiting"),
- QObject::tr("Please press OK once you're logged into steam."));
+ }
+
+ log::debug(
+ "starting steam process:\n"
+ " . program: '{}'\n"
+ " . username={}, password={}",
+ program,
+ (username.isEmpty() ? "no" : "yes"),
+ (password.isEmpty() ? "no" : "yes"));
+
+ if (!QProcess::startDetached(program, args)) {
+ reportError(QObject::tr("Failed to start \"%1\"").arg(program));
+ return false;
+ }
+
+ QMessageBox::information(
+ widget, QObject::tr("Waiting"),
+ QObject::tr("Please press OK once you're logged into steam."));
+
+ return true;
+}
+
+bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings)
+{
+ static const std::vector files = {
+ "steam_api.dll", "steam_api64.dll"
+ };
+
+ for (const auto& file : files) {
+ const QFileInfo fi(gameDirectory.absoluteFilePath(file));
+ if (fi.exists()) {
+ log::debug("found '{}'", fi.absoluteFilePath());
+ return true;
}
}
+
+ 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)
{
+ log::debug("checking steam");
+
if (!steamAppID.isEmpty()) {
- ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
+ ::SetEnvironmentVariableW(L"SteamAPPId", steamAppID.toStdWString().c_str());
} else {
- ::SetEnvironmentVariableW(L"SteamAPPId",
- ToWString(settings.steam().appID()).c_str());
+ ::SetEnvironmentVariableW(L"SteamAPPId", settings.steam().appID().toStdWString().c_str());
}
- if ((QFileInfo(gameDirectory.absoluteFilePath("steam_api.dll")).exists() ||
- QFileInfo(gameDirectory.absoluteFilePath("steam_api64.dll")).exists())
- && (settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) {
+ if (!gameRequiresSteam(gameDirectory, settings)) {
+ log::debug("games doesn't seem to require steam");
+ return true;
+ }
- bool steamFound = true;
- bool steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- log::error("unable to determine state of Steam");
- }
+ auto ss = getSteamStatus();
- if (!steamFound) {
- QDialogButtonBox::StandardButton result;
- result = 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);
- if (result == QDialogButtonBox::Yes) {
- startSteam(parent);
-
- // double-check that Steam is started and MO has access
- steamFound = true;
- steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- log::error("unable to determine state of Steam");
- } else if (!steamFound) {
- log::error("could not find Steam");
- }
-
- } else if (result == QDialogButtonBox::Cancel) {
- return false;
+ if (!ss.running) {
+ log::debug("steam isn't running, asking to start steam");
+ const auto c = confirmStartSteam(parent, sp);
+
+ if (c == QDialogButtonBox::Yes) {
+ log::debug("user wants to start steam");
+ startSteam(parent);
+
+ // double-check that Steam is started
+ ss = getSteamStatus();
+ if (!ss.running) {
+ log::error("could not start steam, continuing and hoping for the best");
+ return true;
}
+ } else if (c == QDialogButtonBox::No) {
+ log::debug("user declined to start steam");
+ return true;
+ } else {
+ log::debug("user cancelled");
+ return false;
}
+ }
+
+ if (ss.running && !ss.accessible) {
+ log::debug("steam is running but is not accessible, asking to restart MO");
+ const auto c = confirmRestartAsAdminForSteam(parent, sp);
- if (!steamAccess) {
- QDialogButtonBox::StandardButton result;
- result = 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);
- if (result == QDialogButtonBox::Yes) {
- WCHAR cwd[MAX_PATH];
- if (!GetCurrentDirectory(MAX_PATH, cwd)) {
- log::error("unable to get current directory (error {})", GetLastError());
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- log::error("unable to relaunch MO as admin");
- return false;
- }
+ 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;
- } else if (result == QDialogButtonBox::Cancel) {
- return false;
}
+
+ log::error("unable to relaunch MO as admin");
+ return false;
+ } else if (c == QDialogButtonBox::No) {
+ log::debug("user declined to restart MO, continuing");
+ return true;
+ } else {
+ log::debug("user cancelled");
+ return false;
}
}
--
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/spawn.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/spawn.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
From c50722100c485d2945082d573158a7083efe2f23 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 10:11:56 -0400
Subject: removed helper.h and helper.cpp, merged into spawn
---
src/CMakeLists.txt | 3 ---
src/helper.cpp | 20 --------------------
src/helper.h | 26 --------------------------
src/main.cpp | 1 -
src/organizercore.cpp | 1 -
src/settingsdialogworkarounds.cpp | 2 +-
src/spawn.cpp | 1 -
7 files changed, 1 insertion(+), 53 deletions(-)
delete mode 100644 src/helper.cpp
delete mode 100644 src/helper.h
(limited to 'src/spawn.cpp')
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 60822834..7c29ff48 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -87,7 +87,6 @@ SET(organizer_SRCS
waitingonclosedialog.cpp
loadmechanism.cpp
installationmanager.cpp
- helper.cpp
filedialogmemory.cpp
executableslist.cpp
editexecutablesdialog.cpp
@@ -208,7 +207,6 @@ SET(organizer_HDRS
waitingonclosedialog.h
loadmechanism.h
installationmanager.h
- helper.h
filedialogmemory.h
executableslist.h
editexecutablesdialog.h
@@ -464,7 +462,6 @@ set(utilities
csvbuilder
shared/error_report
eventfilter
- helper
shared/leaktrace
persistentcookiejar
serverinfo
diff --git a/src/helper.cpp b/src/helper.cpp
deleted file mode 100644
index 24446cb8..00000000
--- a/src/helper.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see .
-*/
-
-// moved to spawn.cpp
diff --git a/src/helper.h b/src/helper.h
deleted file mode 100644
index 335b8647..00000000
--- a/src/helper.h
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see .
-*/
-
-#ifndef HELPER_H
-#define HELPER_H
-
-// all helper code moved to spawn.h and spawn.cpp
-#include "spawn.h"
-
-#endif // HELPER_H
diff --git a/src/main.cpp b/src/main.cpp
index 74b04970..ba988ae3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -38,7 +38,6 @@ along with Mod Organizer. If not, see .
#include "executableslist.h"
#include "singleinstance.h"
#include "utility.h"
-#include "helper.h"
#include "loglist.h"
#include "selectiondialog.h"
#include "moapplication.h"
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index fbc9083b..0da5b604 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -33,7 +33,6 @@
#include "lockeddialog.h"
#include "instancemanager.h"
#include
-#include "helper.h"
#include "previewdialog.h"
#include
diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp
index f89b021c..ccbfcbfe 100644
--- a/src/settingsdialogworkarounds.cpp
+++ b/src/settingsdialogworkarounds.cpp
@@ -1,6 +1,6 @@
#include "settingsdialogworkarounds.h"
#include "ui_settingsdialog.h"
-#include "helper.h"
+#include "spawn.h"
#include
WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 6c524681..64766adf 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -33,7 +33,6 @@ along with Mod Organizer. If not, see .
#include
#include
#include
-#include "helper.h"
#include
#include
#include
--
cgit v1.3.1
From c1ab18b614aa6212f942d8d91afa0b191802f599 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 11:14:14 -0400
Subject: moved the content of checkService() to env::getService(), refactored
it
---
src/env.cpp | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/env.h | 58 +++++++++++++++
src/spawn.cpp | 92 ++++--------------------
3 files changed, 301 insertions(+), 77 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/env.cpp b/src/env.cpp
index 1aaaa8ef..78b5dc96 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -213,6 +213,234 @@ QString set(const QString& n, const QString& v)
}
+Service::Service(QString name)
+ : Service(std::move(name), StartType::None, Status::None)
+{
+}
+
+Service::Service(QString name, StartType st, Status s)
+ : m_name(std::move(name)), m_startType(st), m_status(s)
+{
+}
+
+const QString& Service::name() const
+{
+ return m_name;
+}
+
+bool Service::isValid() const
+{
+ return (m_startType != StartType::None) && (m_status != Status::None);
+}
+
+Service::StartType Service::startType() const
+{
+ return m_startType;
+}
+
+Service::Status Service::status() const
+{
+ return m_status;
+}
+
+QString Service::toString() const
+{
+ return QString("service '%1', start=%2, status=%3")
+ .arg(m_name)
+ .arg(env::toString(m_startType))
+ .arg(env::toString(m_status));
+}
+
+
+QString toString(Service::StartType st)
+{
+ using ST = Service::StartType;
+
+ switch (st)
+ {
+ case ST::None:
+ return "none";
+
+ case ST::Disabled:
+ return "disabled";
+
+ case ST::Enabled:
+ return "enabled";
+
+ default:
+ return QString("unknown %1").arg(static_cast(st));
+ }
+}
+
+QString toString(Service::Status st)
+{
+ using S = Service::Status;
+
+ switch (st)
+ {
+ case S::None:
+ return "none";
+
+ case S::Stopped:
+ return "stopped";
+
+ case S::Running:
+ return "running";
+
+ default:
+ return QString("unknown %1").arg(static_cast(st));
+ }
+}
+
+Service::StartType getServiceStartType(SC_HANDLE s, const QString& name)
+{
+ DWORD needed = 0;
+
+ if (!QueryServiceConfig(s, NULL, 0, &needed)) {
+ const auto e = GetLastError();
+
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ log::error(
+ "QueryServiceConfig() for size for '{}' failed, {}",
+ name, GetLastError());
+
+ return Service::StartType::None;
+ }
+ }
+
+ const auto size = needed;
+ MallocPtr config(
+ static_cast(std::malloc(size)));
+
+ if (!QueryServiceConfig(s, config.get(), size, &needed)) {
+ const auto e = GetLastError();
+
+ log::error(
+ "QueryServiceConfig() for '{}' failed", name, formatSystemMessage(e));
+
+ return Service::StartType::None;
+ }
+
+
+ switch (config->dwStartType)
+ {
+ case SERVICE_AUTO_START: // fall-through
+ case SERVICE_BOOT_START:
+ case SERVICE_DEMAND_START:
+ case SERVICE_SYSTEM_START:
+ {
+ return Service::StartType::Enabled;
+ }
+
+ case SERVICE_DISABLED:
+ {
+ return Service::StartType::Disabled;
+ }
+
+ default:
+ {
+ log::error(
+ "unknown service start type {} for '{}'",
+ config->dwStartType, name);
+
+ return Service::StartType::None;
+ }
+ }
+}
+
+Service::Status getServiceStatus(SC_HANDLE s, const QString& name)
+{
+ DWORD needed = 0;
+
+ if (!QueryServiceStatusEx(s, SC_STATUS_PROCESS_INFO, NULL, 0, &needed)) {
+ const auto e = GetLastError();
+
+ if (e != ERROR_INSUFFICIENT_BUFFER) {
+ log::error(
+ "QueryServiceStatusEx() for size for '{}' failed, {}",
+ name, GetLastError());
+
+ return Service::Status::None;
+ }
+ }
+
+ const auto size = needed;
+ MallocPtr status(
+ static_cast(std::malloc(size)));
+
+ const auto r = QueryServiceStatusEx(
+ s, SC_STATUS_PROCESS_INFO, reinterpret_cast(status.get()),
+ size, &needed);
+
+ if (!r) {
+ const auto e = GetLastError();
+
+ log::error(
+ "QueryServiceStatusEx() failed for '{}', {}",
+ name, formatSystemMessage(e));
+
+ return Service::Status::None;
+ }
+
+
+ switch (status->dwCurrentState)
+ {
+ case SERVICE_START_PENDING: // fall-through
+ case SERVICE_CONTINUE_PENDING:
+ case SERVICE_RUNNING:
+ {
+ return Service::Status::Running;
+ }
+
+ case SERVICE_STOPPED: // fall-through
+ case SERVICE_STOP_PENDING:
+ case SERVICE_PAUSE_PENDING:
+ case SERVICE_PAUSED:
+ {
+ return Service::Status::Stopped;
+ }
+
+ default:
+ {
+ log::error(
+ "unknown service status {} for '{}'",
+ status->dwCurrentState, name);
+
+ return Service::Status::None;
+ }
+ }
+}
+
+Service getService(const QString& name)
+{
+ // service manager
+ const LocalPtr scm(OpenSCManager(
+ NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG));
+
+ if (!scm) {
+ const auto e = GetLastError();
+ log::error("OpenSCManager() failed, {}", formatSystemMessage(e));
+ return Service(name);
+ }
+
+ // service
+ const LocalPtr s(OpenService(
+ scm.get(), name.toStdWString().c_str(),
+ SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG));
+
+ if (!s) {
+ const auto e = GetLastError();
+ log::error("OpenService() failed for '{}', {}", name, formatSystemMessage(e));
+ return Service(name);
+ }
+
+ const auto startType = getServiceStartType(s.get(), name);
+ const auto status = getServiceStatus(s.get(), name);
+
+ return {name, startType, status};
+}
+
+
// 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 7bdc9b85..1760c7fe 100644
--- a/src/env.h
+++ b/src/env.h
@@ -93,6 +93,24 @@ struct MallocFreer
template
using MallocPtr = std::unique_ptr;
+
+// used by LocalPtr, calls LocalFree() as the deleter
+//
+template
+struct LocalFreer
+{
+ using pointer = T;
+
+ void operator()(T p)
+ {
+ ::LocalFree(p);
+ }
+};
+
+template
+using LocalPtr = std::unique_ptr>;
+
+
// creates a console in the constructor and destroys it in the destructor,
// also redirects standard streams
//
@@ -172,6 +190,46 @@ QString addPath(const QString& s);
QString setPath(const QString& s);
+class Service
+{
+public:
+ enum class StartType
+ {
+ None = 0,
+ Disabled,
+ Enabled
+ };
+
+ enum class Status
+ {
+ None = 0,
+ Stopped,
+ Running
+ };
+
+
+ explicit Service(QString name);
+ Service(QString name, StartType st, Status s);
+
+ bool isValid() const;
+
+ const QString& name() const;
+ StartType startType() const;
+ Status status() const;
+
+ QString toString() const;
+
+private:
+ QString m_name;
+ StartType m_startType;
+ Status m_status;
+};
+
+
+Service getService(const QString& name);
+QString toString(Service::StartType st);
+QString toString(Service::Status st);
+
enum class CoreDumpTypes
{
Mini = 1,
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 64766adf..551a5adb 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -428,7 +428,6 @@ void startBinaryAdmin(const SpawnParameters& sp)
restartAsAdmin();
}
-
bool checkBinary(QWidget* parent, const SpawnParameters& sp)
{
if (!sp.binary.exists()) {
@@ -557,9 +556,9 @@ bool checkSteam(
log::debug("checking steam");
if (!steamAppID.isEmpty()) {
- ::SetEnvironmentVariableW(L"SteamAPPId", steamAppID.toStdWString().c_str());
+ env::set("SteamAPPId", steamAppID);
} else {
- ::SetEnvironmentVariableW(L"SteamAPPId", settings.steam().appID().toStdWString().c_str());
+ env::set("SteamAPPId", settings.steam().appID());
}
if (!gameRequiresSteam(gameDirectory, settings)) {
@@ -584,7 +583,7 @@ bool checkSteam(
// double-check that Steam is started
ss = getSteamStatus();
if (!ss.running) {
- log::error("steam is still not running, continuing and hoping for the best");
+ log::error("steam is still not running, hoping for the best");
return true;
}
} else if (c == QDialogButtonBox::No) {
@@ -615,90 +614,29 @@ bool checkSteam(
return true;
}
-bool checkService()
+bool checkEventLogService()
{
- SC_HANDLE serviceManagerHandle = NULL;
- SC_HANDLE serviceHandle = NULL;
- LPSERVICE_STATUS_PROCESS serviceStatus = NULL;
- LPQUERY_SERVICE_CONFIG serviceConfig = NULL;
- bool serviceRunning = true;
-
- DWORD bytesNeeded;
-
- try {
- serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceManagerHandle) {
- log::warn("failed to open service manager (query status) (error {})", GetLastError());
- throw 1;
- }
-
- serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceHandle) {
- log::warn("failed to open EventLog service (query status) (error {})", GetLastError());
- throw 2;
- }
-
- if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- log::warn("failed to get size of service config (error {})", GetLastError());
- throw 3;
- }
-
- DWORD serviceConfigSize = bytesNeeded;
- serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize);
- if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) {
- log::warn("failed to query service config (error {})", GetLastError());
- throw 4;
- }
-
- if (serviceConfig->dwStartType == SERVICE_DISABLED) {
- log::error("Windows Event Log service is disabled!");
- serviceRunning = false;
- }
-
- if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- log::warn("failed to get size of service status (error {})", GetLastError());
- throw 5;
- }
+ const auto s = env::getService("EventLog");
- DWORD serviceStatusSize = bytesNeeded;
- serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize);
- if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) {
- log::warn("failed to query service status (error {})", GetLastError());
- throw 6;
- }
-
- if (serviceStatus->dwCurrentState != SERVICE_RUNNING) {
- log::error("Windows Event Log service is not running");
- serviceRunning = false;
- }
- }
- catch (int) {
- serviceRunning = false;
+ if (!s.isValid()) {
+ log::error("cannot determine the status of the EventLog, continuing");
+ return true;
}
- if (serviceStatus) {
- LocalFree(serviceStatus);
- }
- if (serviceConfig) {
- LocalFree(serviceConfig);
- }
- if (serviceHandle) {
- CloseServiceHandle(serviceHandle);
- }
- if (serviceManagerHandle) {
- CloseServiceHandle(serviceManagerHandle);
+ if (s.status() == env::Service::Status::Running) {
+ log::debug("{}", s.toString());
+ return true;
+ } else {
+ log::error("{}", s.toString());
+ return false;
}
-
- return serviceRunning;
}
bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
{
// Check if the Windows Event Logging service is running. For some reason, this seems to be
// critical to the successful running of usvfs.
- if (!checkService()) {
+ if (!checkEventLogService()) {
if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(),
QObject::tr("Windows Event Log Error"),
QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents"
--
cgit v1.3.1
From 8bc67a86d64c86cf7f1eeb2c656dd414c0716d0b Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 11:17:52 -0400
Subject: moved event log warning to dialogs
---
src/spawn.cpp | 45 ++++++++++++++++++++++-----------------------
1 file changed, 22 insertions(+), 23 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 551a5adb..c9025d98 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -330,7 +330,22 @@ QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const S
QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
}
-} // namepsace
+bool eventLogNotRunning(
+ QWidget* parent, const env::Service& s, const SpawnParameters& sp)
+{
+ const auto r = QuestionBoxMemory::query(
+ parent, QString("eventLogService"), sp.binary.fileName(),
+ QObject::tr("Windows Event Log Error"),
+ QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents"
+ " USVFS from running properly. Your mods may not be working in the executable"
+ " that you are launching. Note that you may have to restart MO and/or your PC"
+ " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()),
+ QDialogButtonBox::Yes | QDialogButtonBox::No);
+
+ return (r != QDialogButtonBox::No);
+}
+
+} // namespace
namespace spawn
@@ -614,8 +629,11 @@ bool checkSteam(
return true;
}
-bool checkEventLogService()
+bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
{
+ // check if the Windows Event Logging service is running; for some reason,
+ // this seems to be critical to the successful running of usvfs.
+
const auto s = env::getService("EventLog");
if (!s.isValid()) {
@@ -626,29 +644,10 @@ bool checkEventLogService()
if (s.status() == env::Service::Status::Running) {
log::debug("{}", s.toString());
return true;
- } else {
- log::error("{}", s.toString());
- return false;
- }
-}
-
-bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
-{
- // Check if the Windows Event Logging service is running. For some reason, this seems to be
- // critical to the successful running of usvfs.
- if (!checkEventLogService()) {
- if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(),
- QObject::tr("Windows Event Log Error"),
- QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents"
- " USVFS from running properly. Your mods may not be working in the executable"
- " that you are launching. Note that you may have to restart MO and/or your PC"
- " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
- return false;
- }
}
- return true;
+ log::error("{}", s.toString());
+ return dialogs::eventLogNotRunning(parent, s, sp);
}
bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings)
--
cgit v1.3.1
From a5db7ed864ac58657bf9bfbbc292cdccbeeaa38b Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 11:23:21 -0400
Subject: added Settings::isExecutableBlacklisted() moved blacklisted
confirmation to dialogs
---
src/settings.cpp | 11 +++++++++++
src/settings.h | 1 +
src/spawn.cpp | 29 ++++++++++++++++-------------
3 files changed, 28 insertions(+), 13 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/settings.cpp b/src/settings.cpp
index 7fdda2bf..ae487c18 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -222,6 +222,17 @@ QString Settings::executablesBlacklist() const
return get(m_Settings, "Settings", "executable_blacklist", def);
}
+bool Settings::isExecutableBlacklisted(const QString& s) const
+{
+ for (auto exec : executablesBlacklist().split(";")) {
+ if (exec.compare(s, Qt::CaseInsensitive) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
void Settings::setExecutablesBlacklist(const QString& s)
{
set(m_Settings, "Settings", "executable_blacklist", s);
diff --git a/src/settings.h b/src/settings.h
index 815ed160..cd478a5b 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -678,6 +678,7 @@ public:
// by MO but given to usvfs when starting an executable
//
QString executablesBlacklist() const;
+ bool isExecutableBlacklisted(const QString& s) const;
void setExecutablesBlacklist(const QString& s);
// ? looks obsolete, only used by dead code
diff --git a/src/spawn.cpp b/src/spawn.cpp
index c9025d98..45324d79 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -345,6 +345,20 @@ bool eventLogNotRunning(
return (r != QDialogButtonBox::No);
}
+bool confirmBlacklisted(QWidget* parent, const SpawnParameters& sp)
+{
+ const auto r = QuestionBoxMemory::query(
+ parent, QString("blacklistedExecutable"), sp.binary.fileName(),
+ QObject::tr("Blacklisted Executable"),
+ QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file"
+ " system. This will likely prevent the executable, and any executables that are"
+ " launched by this one, from seeing any mods. This could extend to INI files, save"
+ " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()),
+ QDialogButtonBox::Yes | QDialogButtonBox::No);
+
+ return (r != QDialogButtonBox::No);
+}
+
} // namespace
@@ -652,18 +666,8 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings)
{
- for (auto exec : settings.executablesBlacklist().split(";")) {
- if (exec.compare(sp.binary.fileName(), Qt::CaseInsensitive) == 0) {
- if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), sp.binary.fileName(),
- QObject::tr("Blacklisted Executable"),
- QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file"
- " system. This will likely prevent the executable, and any executables that are"
- " launched by this one, from seeing any mods. This could extend to INI files, save"
- " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) {
- return false;
- }
- }
+ if (settings.isExecutableBlacklisted(sp.binary.fileName())) {
+ return dialogs::confirmBlacklisted(parent, sp);
}
return true;
@@ -779,7 +783,6 @@ bool backdateBSAs(const std::wstring &moPath, const std::wstring &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(
--
cgit v1.3.1
From 9bac57e3e864bd300fadccfaa194a6f3d28c9de2 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 15:03:03 -0400
Subject: steam confirmation now using TaskDialog fixed dialog choices not
remembering files
---
src/settings.cpp | 2 +-
src/settingsdialog.ui | 2 +-
src/spawn.cpp | 95 ++++++++++++++++++++++++++++++++++-----------------
3 files changed, 65 insertions(+), 34 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/settings.cpp b/src/settings.cpp
index ae487c18..7cea52fb 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -946,7 +946,7 @@ QuestionBoxMemory::Button WidgetSettings::questionButton(
if (!filename.isEmpty()) {
const auto fileSetting = windowName + "/" + filename;
- if (auto v=getOptional(m_Settings, sectionName, filename)) {
+ if (auto v=getOptional(m_Settings, sectionName, fileSetting)) {
return static_cast(*v);
}
}
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index 1a3726fb..40079441 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -191,7 +191,7 @@ p, li { white-space: pre-wrap; }
This will make all dialogs show up again where you checked the "Remember selection"-box.
- Reset Dialogs
+ Reset Dialog Choices
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 45324d79..e18e6bb3 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -183,6 +183,7 @@ QMessageBox::StandardButton badSteamReg(
"The path to the Steam executable cannot be found. You might try "
"reinstalling Steam."))
.details(details)
+ .icon(QMessageBox::Critical)
.button({
QObject::tr("Continue without starting Steam"),
QObject::tr("The program may fail to launch."),
@@ -203,6 +204,7 @@ QMessageBox::StandardButton startSteamFailed(
.main(QObject::tr("Cannot start Steam"))
.content(makeContent(sp, e))
.details(details)
+ .icon(QMessageBox::Critical)
.button({
QObject::tr("Continue without starting Steam"),
QObject::tr("The program may fail to launch."),
@@ -232,6 +234,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code)
.main(mainText)
.content(makeContent(sp, code))
.details(details)
+ .icon(QMessageBox::Critical)
.exec();
}
@@ -261,6 +264,7 @@ void helperFailed(
.main(mainText)
.content(makeContent(sp, code))
.details(details)
+ .icon(QMessageBox::Critical)
.exec();
}
@@ -295,26 +299,45 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp)
.main(mainText)
.content(content)
.details(details)
+ .icon(QMessageBox::Question)
.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);
}
-QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp)
+QMessageBox::StandardButton confirmStartSteam(
+ QWidget* window, const SpawnParameters& sp, const QString& details)
{
- 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);
+ const auto title = QObject::tr("Launch Steam");
+ const auto mainText = QObject::tr("This program requires Steam");
+ const auto content = QObject::tr(
+ "Mod Organizer has detected that this program likely requires Steam to be "
+ "running to function properly.");
+
+ return MOBase::TaskDialog(window, title)
+ .main(mainText)
+ .content(content)
+ .details(details)
+ .icon(QMessageBox::Question)
+ .button({
+ QObject::tr("Start Steam"),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program might fail to run."),
+ QMessageBox::No})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .remember("steamQuery", sp.binary.fileName())
+ .exec();
}
QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp)
@@ -561,27 +584,14 @@ bool startSteam(QWidget* parent)
return true;
}
-bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings)
-{
- static const std::vector files = {
- "steam_api.dll", "steam_api64.dll"
- };
-
- for (const auto& file : files) {
- const QFileInfo fi(gameDirectory.absoluteFilePath(file));
- if (fi.exists()) {
- log::debug("found '{}'", fi.absoluteFilePath());
- return true;
- }
- }
-
- return false;
-}
-
bool checkSteam(
QWidget* parent, const SpawnParameters& sp,
const QDir& gameDirectory, const QString &steamAppID, const Settings& settings)
{
+ static const std::vector steamFiles = {
+ "steam_api.dll", "steam_api64.dll"
+ };
+
log::debug("checking steam");
if (!steamAppID.isEmpty()) {
@@ -590,16 +600,37 @@ bool checkSteam(
env::set("SteamAPPId", settings.steam().appID());
}
- if (!gameRequiresSteam(gameDirectory, settings)) {
- log::debug("games doesn't seem to require steam");
+
+ bool steamRequired = false;
+ QString details;
+
+ for (const auto& file : steamFiles) {
+ const QFileInfo fi(gameDirectory.absoluteFilePath(file));
+ if (fi.exists()) {
+ details = QString(
+ "managed game is located at '%1' and file '%2' exists")
+ .arg(gameDirectory.absolutePath())
+ .arg(fi.absoluteFilePath());
+
+ log::debug("{}", details);
+ steamRequired = true;
+
+ break;
+ }
+ }
+
+ if (!steamRequired) {
+ log::debug("program doesn't seem to require steam");
return true;
}
+
auto ss = getSteamStatus();
if (!ss.running) {
log::debug("steam isn't running, asking to start steam");
- const auto c = dialogs::confirmStartSteam(parent, sp);
+
+ const auto c = dialogs::confirmStartSteam(parent, sp, details);
if (c == QDialogButtonBox::Yes) {
log::debug("user wants to start steam");
--
cgit v1.3.1
From f92e2c376d36132a9676b30f0b08543f27a13064 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 16:27:41 -0400
Subject: TaskDialog for restarting as admin for steam added a parent widget
parameter to a bunch of places fixed paths still getting changed even if
folders can't be created made private the member variables that were
temporarily public during rework
---
src/settingsdialog.cpp | 44 ++++++++++------
src/settingsdialog.h | 20 +++----
src/settingsdialoggeneral.cpp | 7 ++-
src/settingsdialognexus.cpp | 14 ++---
src/settingsdialogpaths.cpp | 6 ++-
src/settingsdialogworkarounds.cpp | 6 ++-
src/spawn.cpp | 106 ++++++++++++++++++++------------------
src/spawn.h | 7 ++-
8 files changed, 121 insertions(+), 89 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index 1d3d4a39..8fb25b1c 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti
: TutorableDialog("SettingsDialog", parent)
, ui(new Ui::SettingsDialog)
, m_settings(settings)
- , m_PluginContainer(pluginContainer)
- , m_keyChanged(false)
+ , m_pluginContainer(pluginContainer)
+ , m_restartNeeded(false)
{
ui->setupUi(this);
@@ -47,6 +47,25 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti
m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this)));
}
+PluginContainer* SettingsDialog::pluginContainer()
+{
+ return m_pluginContainer;
+}
+
+QWidget* SettingsDialog::parentWidgetForDialogs()
+{
+ if (isVisible()) {
+ return this;
+ } else {
+ return parentWidget();
+ }
+}
+
+void SettingsDialog::setRestartNeeded()
+{
+ m_restartNeeded = true;
+}
+
int SettingsDialog::exec()
{
GeometrySaver gs(m_settings, this);
@@ -68,13 +87,8 @@ int SettingsDialog::exec()
}
}
- bool restartNeeded = false;
- if (getApiKeyChanged()) {
- restartNeeded = true;
- }
-
- if (restartNeeded) {
- if (QMessageBox::question(nullptr,
+ if (m_restartNeeded) {
+ if (QMessageBox::question(parentWidgetForDialogs(),
tr("Restart Mod Organizer?"),
tr("In order to finish configuration changes, MO must be restarted.\n"
"Restart it now?"),
@@ -111,7 +125,7 @@ void SettingsDialog::accept()
QDir::fromNativeSeparators(
Settings::instance().paths().mods(true))) &&
(QMessageBox::question(
- nullptr, tr("Confirm"),
+ parentWidgetForDialogs(), tr("Confirm"),
tr("Changing the mod directory affects all your profiles! "
"Mods not present (or named differently) in the new location "
"will be disabled in all profiles. "
@@ -124,11 +138,6 @@ void SettingsDialog::accept()
TutorableDialog::accept();
}
-bool SettingsDialog::getApiKeyChanged()
-{
- return m_keyChanged;
-}
-
SettingsTab::SettingsTab(Settings& s, SettingsDialog& d)
: ui(d.ui), m_settings(s), m_dialog(d)
@@ -146,3 +155,8 @@ SettingsDialog& SettingsTab::dialog()
{
return m_dialog;
}
+
+QWidget* SettingsTab::parentWidget()
+{
+ return m_dialog.parentWidgetForDialogs();
+}
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index 6a99cb8d..e89da665 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -42,6 +42,7 @@ protected:
Settings& settings();
SettingsDialog& dialog();
+ QWidget* parentWidget();
private:
Settings& m_settings;
@@ -56,11 +57,12 @@ private:
**/
class SettingsDialog : public MOBase::TutorableDialog
{
- Q_OBJECT
+ Q_OBJECT;
+ friend class SettingsTab;
public:
explicit SettingsDialog(
- PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0);
+ PluginContainer* pluginContainer, Settings& settings, QWidget* parent = 0);
~SettingsDialog();
@@ -70,23 +72,21 @@ public:
*/
QString getColoredButtonStyleSheet() const;
- // temp
- Ui::SettingsDialog *ui;
- bool m_keyChanged;
- PluginContainer *m_PluginContainer;
+ PluginContainer* pluginContainer();
+ QWidget* parentWidgetForDialogs();
+ void setRestartNeeded();
int exec() override;
public slots:
virtual void accept();
-public:
- bool getApiKeyChanged();
-
private:
Settings& m_settings;
std::vector> m_tabs;
-
+ Ui::SettingsDialog* ui;
+ bool m_restartNeeded;
+ PluginContainer* m_pluginContainer;
};
#endif // SETTINGSDIALOG_H
diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp
index 3f7ece38..8ecdcbb9 100644
--- a/src/settingsdialoggeneral.cpp
+++ b/src/settingsdialoggeneral.cpp
@@ -238,8 +238,11 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked()
void GeneralSettingsTab::on_resetDialogsButton_clicked()
{
- if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"),
- QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"),
+ if (QMessageBox::question(
+ parentWidget(), QObject::tr("Confirm?"),
+ QObject::tr(
+ "This will reset all the choices you made to dialogs and make them all "
+ "visible again. Continue?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
resetDialogs();
}
diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp
index 0b08f13f..826075c0 100644
--- a/src/settingsdialognexus.cpp
+++ b/src/settingsdialognexus.cpp
@@ -226,7 +226,7 @@ void NexusSettingsTab::on_nexusDisconnect_clicked()
void NexusSettingsTab::on_clearCacheButton_clicked()
{
QDir(Settings::instance().paths().cache()).removeRecursively();
- NexusInterface::instance(dialog().m_PluginContainer)->clearCache();
+ NexusInterface::instance(dialog().pluginContainer())->clearCache();
}
void NexusSettingsTab::on_associateButton_clicked()
@@ -238,7 +238,7 @@ void NexusSettingsTab::validateKey(const QString& key)
{
if (!m_nexusValidator) {
m_nexusValidator.reset(new NexusKeyValidator(
- *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()));
+ *NexusInterface::instance(dialog().pluginContainer())->getAccessManager()));
m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){
onValidatorStateChanged(s, e);
@@ -294,7 +294,7 @@ void NexusSettingsTab::onValidatorStateChanged(
void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user)
{
- NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user);
+ NexusInterface::instance(dialog().pluginContainer())->setUserAccount(user);
if (!user.apiKey().isEmpty()) {
if (setKey(user.apiKey())) {
@@ -311,7 +311,7 @@ void NexusSettingsTab::addNexusLog(const QString& s)
bool NexusSettingsTab::setKey(const QString& key)
{
- dialog().m_keyChanged = true;
+ dialog().setRestartNeeded();
const bool ret = settings().nexus().setApiKey(key);
updateNexusState();
return ret;
@@ -319,10 +319,10 @@ bool NexusSettingsTab::setKey(const QString& key)
bool NexusSettingsTab::clearKey()
{
- dialog().m_keyChanged = true;
+ dialog().setRestartNeeded();
const auto ret = settings().nexus().clearApiKey();
- NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey();
+ NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey();
updateNexusState();
return ret;
@@ -371,7 +371,7 @@ void NexusSettingsTab::updateNexusButtons()
void NexusSettingsTab::updateNexusData()
{
- const auto user = NexusInterface::instance(dialog().m_PluginContainer)
+ const auto user = NexusInterface::instance(dialog().pluginContainer())
->getAPIUserAccount();
if (user.isValid()) {
diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp
index aeb4dd5d..c6fd40a7 100644
--- a/src/settingsdialogpaths.cpp
+++ b/src/settingsdialogpaths.cpp
@@ -68,10 +68,12 @@ void PathsSettingsTab::update()
if (!QDir(realPath).exists()) {
if (!QDir().mkpath(realPath)) {
- QMessageBox::warning(qApp->activeWindow(), QObject::tr("Error"),
+ QMessageBox::warning(parentWidget(), QObject::tr("Error"),
QObject::tr("Failed to create \"%1\", you may not have the "
- "necessary permission. path remains unchanged.")
+ "necessary permissions. Path remains unchanged.")
.arg(realPath));
+
+ continue;
}
}
diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp
index ccbfcbfe..5e70e5a6 100644
--- a/src/settingsdialogworkarounds.cpp
+++ b/src/settingsdialogworkarounds.cpp
@@ -83,7 +83,9 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked()
const auto* game = qApp->property("managed_game").value();
QDir dir = game->dataDirectory();
- helper::backdateBSAs(qApp->applicationDirPath().toStdWString(),
+ helper::backdateBSAs(
+ parentWidget(),
+ qApp->applicationDirPath().toStdWString(),
dir.absolutePath().toStdWString());
}
@@ -95,7 +97,7 @@ void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked()
"Restart now?");
const auto res = QMessageBox::question(
- nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel);
+ parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel);
if (res == QMessageBox::Yes) {
settings().geometry().requestReset();
diff --git a/src/spawn.cpp b/src/spawn.cpp
index e18e6bb3..a0cf9fb6 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -215,7 +215,7 @@ QMessageBox::StandardButton startSteamFailed(
.exec();
}
-void spawnFailed(const SpawnParameters& sp, DWORD code)
+void spawnFailed(QWidget* parent, const SpawnParameters& sp, DWORD code)
{
const auto details = makeDetails(sp, code);
log::error("{}", details);
@@ -225,12 +225,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code)
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)
+ MOBase::TaskDialog(parent, title)
.main(mainText)
.content(makeContent(sp, code))
.details(details)
@@ -239,7 +234,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code)
}
void helperFailed(
- DWORD code, const QString& why, const std::wstring& binary,
+ QWidget* parent, DWORD code, const QString& why, const std::wstring& binary,
const std::wstring& cwd, const std::wstring& args)
{
SpawnParameters sp;
@@ -255,12 +250,7 @@ void helperFailed(
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)
+ MOBase::TaskDialog(parent, title)
.main(mainText)
.content(makeContent(sp, code))
.details(details)
@@ -268,7 +258,7 @@ void helperFailed(
.exec();
}
-bool confirmRestartAsAdmin(const SpawnParameters& sp)
+bool confirmRestartAsAdmin(QWidget* parent, const SpawnParameters& sp)
{
const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED);
@@ -287,15 +277,9 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp)
"You can restart Mod Organizer as administrator and try launching the "
"program again.");
-
- QWidget *window = qApp->activeWindow();
- if ((window != nullptr) && (!window->isVisible())) {
- window = nullptr;
- }
-
log::debug("asking user to restart MO as administrator");
- const auto r = MOBase::TaskDialog(window, title)
+ const auto r = MOBase::TaskDialog(parent, title)
.main(mainText)
.content(content)
.details(details)
@@ -313,7 +297,7 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp)
}
QMessageBox::StandardButton confirmStartSteam(
- QWidget* window, const SpawnParameters& sp, const QString& details)
+ QWidget* parent, const SpawnParameters& sp, const QString& details)
{
const auto title = QObject::tr("Launch Steam");
const auto mainText = QObject::tr("This program requires Steam");
@@ -321,7 +305,7 @@ QMessageBox::StandardButton confirmStartSteam(
"Mod Organizer has detected that this program likely requires Steam to be "
"running to function properly.");
- return MOBase::TaskDialog(window, title)
+ return MOBase::TaskDialog(parent, title)
.main(mainText)
.content(content)
.details(details)
@@ -340,17 +324,35 @@ QMessageBox::StandardButton confirmStartSteam(
.exec();
}
-QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp)
+QMessageBox::StandardButton 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);
+ const auto title = QObject::tr("Elevation required");
+ const auto mainText = QObject::tr("Steam is running as administrator");
+ const auto content = QObject::tr(
+ "Running Steam as administrator is typically unnecessary and can cause "
+ "problems when Mod Organizer is not running as administrato\r\n\r\n"
+ "You can restart Mod Organizer as administrator and try launching the "
+ "program again.");
+
+ return MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details("")
+ .icon(QMessageBox::Question)
+ .button({
+ 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("Continue"),
+ QObject::tr("The program might fail to run."),
+ QMessageBox::No})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .remember("steamAdminQuery", sp.binary.fileName())
+ .exec();
}
bool eventLogNotRunning(
@@ -447,7 +449,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand
return ERROR_SUCCESS;
}
-bool restartAsAdmin()
+bool restartAsAdmin(QWidget* parent)
{
WCHAR cwd[MAX_PATH] = {};
if (!GetCurrentDirectory(MAX_PATH, cwd)) {
@@ -455,6 +457,7 @@ bool restartAsAdmin()
}
if (!helper::adminLaunch(
+ parent,
qApp->applicationDirPath().toStdWString(),
qApp->applicationFilePath().toStdWString(),
std::wstring(cwd)))
@@ -469,21 +472,21 @@ bool restartAsAdmin()
return true;
}
-void startBinaryAdmin(const SpawnParameters& sp)
+void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp)
{
- if (!dialogs::confirmRestartAsAdmin(sp)) {
+ if (!dialogs::confirmRestartAsAdmin(parent, sp)) {
log::debug("user declined");
return;
}
log::info("restarting MO as administrator");
- restartAsAdmin();
+ restartAsAdmin(parent);
}
bool checkBinary(QWidget* parent, const SpawnParameters& sp)
{
if (!sp.binary.exists()) {
- dialogs::spawnFailed(sp, ERROR_FILE_NOT_FOUND);
+ dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND);
return false;
}
@@ -660,7 +663,7 @@ bool checkSteam(
const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp);
if (c == QDialogButtonBox::Yes) {
- restartAsAdmin();
+ restartAsAdmin(parent);
return false;
} else if (c == QDialogButtonBox::No) {
log::debug("user declined to restart MO, continuing");
@@ -720,13 +723,13 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
case ERROR_ELEVATION_REQUIRED:
{
- startBinaryAdmin(sp);
+ startBinaryAdmin(parent, sp);
return INVALID_HANDLE_VALUE;
}
default:
{
- dialogs::spawnFailed(sp, e);
+ dialogs::spawnFailed(parent, sp, e);
return INVALID_HANDLE_VALUE;
}
}
@@ -740,6 +743,7 @@ namespace helper
{
bool helperExec(
+ QWidget* parent,
const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async)
{
const std::wstring fileName = moDirectory + L"\\helper.exe";
@@ -766,7 +770,7 @@ bool helperExec(
const auto e = GetLastError();
spawn::dialogs::helperFailed(
- e, "ShellExecuteExW()", fileName, moDirectory, commandLine);
+ parent, e, "ShellExecuteExW()", fileName, moDirectory, commandLine);
return false;
}
@@ -788,7 +792,8 @@ bool helperExec(
ERROR_ABANDONED_WAIT_0 : GetLastError());
spawn::dialogs::helperFailed(
- code, "WaitForSingleObject()", fileName, moDirectory, commandLine);
+ parent, code, "WaitForSingleObject()",
+ fileName, moDirectory, commandLine);
return false;
}
@@ -798,7 +803,7 @@ bool helperExec(
const auto e = GetLastError();
spawn::dialogs::helperFailed(
- e, "GetExitCodeProcess()", fileName, moDirectory, commandLine);
+ parent, e, "GetExitCodeProcess()", fileName, moDirectory, commandLine);
return false;
}
@@ -806,21 +811,24 @@ bool helperExec(
return (exitCode == 0);
}
-bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath)
+bool backdateBSAs(
+ QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath)
{
const std::wstring commandLine = fmt::format(
L"backdateBSA \"{}\"", dataPath);
- return helperExec(moPath, commandLine, FALSE);
+ return helperExec(parent, moPath, commandLine, FALSE);
}
-bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir)
+bool adminLaunch(
+ QWidget* parent, 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);
+ return helperExec(parent, moPath, commandLine, true);
}
} // namespace
diff --git a/src/spawn.h b/src/spawn.h
index da626329..9e1e2539 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -84,7 +84,8 @@ namespace helper
* @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);
+bool backdateBSAs(
+ QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath);
/**
* @brief waits for the current process to exit and restarts it as an administrator
@@ -92,7 +93,9 @@ bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath);
* @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);
+bool adminLaunch(
+ QWidget* parent, const std::wstring &moPath,
+ const std::wstring &moFile, const std::wstring &workingDir);
} // namespace
--
cgit v1.3.1
From cbfd3692ce95f43daa081c5f16a5c9160cb7c459 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 16:41:36 -0400
Subject: TaskDialog for event log not running
---
src/spawn.cpp | 46 +++++++++++++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 15 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index a0cf9fb6..b7aa90c0 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -338,12 +338,11 @@ QMessageBox::StandardButton confirmRestartAsAdminForSteam(
return MOBase::TaskDialog(parent, title)
.main(mainText)
.content(content)
- .details("")
.icon(QMessageBox::Question)
.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("Continue"),
QObject::tr("The program might fail to run."),
@@ -358,16 +357,29 @@ QMessageBox::StandardButton confirmRestartAsAdminForSteam(
bool eventLogNotRunning(
QWidget* parent, const env::Service& s, const SpawnParameters& sp)
{
- const auto r = QuestionBoxMemory::query(
- parent, QString("eventLogService"), sp.binary.fileName(),
- QObject::tr("Windows Event Log Error"),
- QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents"
- " USVFS from running properly. Your mods may not be working in the executable"
- " that you are launching. Note that you may have to restart MO and/or your PC"
- " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No);
+ const auto title = QObject::tr("Event Log not running");
+ const auto mainText = QObject::tr("The Event Log service is not running");
+ const auto content = QObject::tr(
+ "The Windows Event Log service is not running. This can prevent USVFS from "
+ "running properly and your mods may not be recognized by the program being "
+ "launched.");
- return (r != QDialogButtonBox::No);
+ const auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details(s.toString())
+ .icon(QMessageBox::Question)
+ .remember("eventLogService", sp.binary.fileName())
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("Your mods might not work."),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QDialogButtonBox::Yes);
}
bool confirmBlacklisted(QWidget* parent, const SpawnParameters& sp)
@@ -681,11 +693,15 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
{
// check if the Windows Event Logging service is running; for some reason,
// this seems to be critical to the successful running of usvfs.
+ const auto serviceName = "EventLog";
- const auto s = env::getService("EventLog");
+ const auto s = env::getService(serviceName);
if (!s.isValid()) {
- log::error("cannot determine the status of the EventLog, continuing");
+ log::error(
+ "cannot determine the status of the {} service, continuing",
+ serviceName);
+
return true;
}
--
cgit v1.3.1
From 94b0c4634290b41398915c6635982dc7b3928f60 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 17:10:30 -0400
Subject: TaskDialog for blacklisted, with button to change the blacklist
---
src/settingsdialogworkarounds.cpp | 51 +++++++++++++++++++++++--------
src/settingsdialogworkarounds.h | 15 ++++++++--
src/spawn.cpp | 63 +++++++++++++++++++++++++++++----------
3 files changed, 98 insertions(+), 31 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp
index 5e70e5a6..0e31fc4b 100644
--- a/src/settingsdialogworkarounds.cpp
+++ b/src/settingsdialogworkarounds.cpp
@@ -1,6 +1,7 @@
#include "settingsdialogworkarounds.h"
#include "ui_settingsdialog.h"
#include "spawn.h"
+#include "settings.h"
#include
WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
@@ -26,7 +27,7 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
ui->lockGUIBox->setChecked(settings().interface().lockGUI());
ui->enableArchiveParsingBox->setChecked(settings().archiveParsing());
- setExecutableBlacklist(settings().executablesBlacklist());
+ m_ExecutableBlacklist = settings().executablesBlacklist();
QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); });
QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); });
@@ -49,14 +50,29 @@ void WorkaroundsSettingsTab::update()
settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked());
settings().interface().setLockGUI(ui->lockGUIBox->isChecked());
settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked());
- settings().setExecutablesBlacklist(getExecutableBlacklist());
+ settings().setExecutablesBlacklist(m_ExecutableBlacklist);
}
-void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
+bool WorkaroundsSettingsTab::changeBlacklistNow(
+ QWidget* parent, Settings& settings)
+{
+ const auto current = settings.executablesBlacklist();
+
+ if (auto s=changeBlacklistLater(parent, current)) {
+ settings.setExecutablesBlacklist(*s);
+ return true;
+ }
+
+ return false;
+}
+
+std::optional WorkaroundsSettingsTab::changeBlacklistLater(
+ QWidget* parent, const QString& current)
{
bool ok = false;
+
QString result = QInputDialog::getMultiLineText(
- &dialog(),
+ parent,
QObject::tr("Executables Blacklist"),
QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n"
"Mods and other virtualized files will not be visible to these executables and\n"
@@ -64,17 +80,28 @@ void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
"Example:\n"
" Chrome.exe\n"
" Firefox.exe"),
- m_ExecutableBlacklist.split(";").join("\n"),
+ current.split(";").join("\n"),
&ok
);
- if (ok) {
- QStringList blacklist;
- for (auto exec : result.split("\n")) {
- if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) {
- blacklist << exec.trimmed();
- }
+
+ if (!ok) {
+ return {};
+ }
+
+ QStringList blacklist;
+ for (auto exec : result.split("\n")) {
+ if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) {
+ blacklist << exec.trimmed();
}
- m_ExecutableBlacklist = blacklist.join(";");
+ }
+
+ return blacklist.join(";");
+}
+
+void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked()
+{
+ if (auto s=changeBlacklistLater(parentWidget(), m_ExecutableBlacklist)) {
+ m_ExecutableBlacklist = *s;
}
}
diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h
index d5d6815f..cffc54a0 100644
--- a/src/settingsdialogworkarounds.h
+++ b/src/settingsdialogworkarounds.h
@@ -8,6 +8,18 @@ class WorkaroundsSettingsTab : public SettingsTab
{
public:
WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog);
+
+ // shows the blacklist dialog from the given settings, and changes the
+ // settings when the user accepts it
+ //
+ static bool changeBlacklistNow(QWidget* parent, Settings& settings);
+
+ // shows the blacklist dialog from the given string and returns the new
+ // blacklist if the user accepted it
+ //
+ static std::optional changeBlacklistLater(
+ QWidget* parent, const QString& current);
+
void update();
private:
@@ -16,9 +28,6 @@ private:
void on_bsaDateBtn_clicked();
void on_execBlacklistBtn_clicked();
void on_resetGeometryBtn_clicked();
-
- QString getExecutableBlacklist() { return m_ExecutableBlacklist; }
- void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; }
};
#endif // SETTINGSDIALOGWORKAROUNDS_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index b7aa90c0..ab9d90a0 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -26,6 +26,7 @@ along with Mod Organizer. If not, see .
#include "envsecurity.h"
#include "envmodule.h"
#include "settings.h"
+#include "settingsdialogworkarounds.h"
#include
#include
#include
@@ -379,21 +380,45 @@ bool eventLogNotRunning(
QMessageBox::Cancel})
.exec();
- return (r == QDialogButtonBox::Yes);
+ return (r == QMessageBox::Yes);
}
-bool confirmBlacklisted(QWidget* parent, const SpawnParameters& sp)
+QMessageBox::StandardButton confirmBlacklisted(
+ QWidget* parent, const SpawnParameters& sp)
{
- const auto r = QuestionBoxMemory::query(
- parent, QString("blacklistedExecutable"), sp.binary.fileName(),
- QObject::tr("Blacklisted Executable"),
- QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file"
- " system. This will likely prevent the executable, and any executables that are"
- " launched by this one, from seeing any mods. This could extend to INI files, save"
- " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()),
- QDialogButtonBox::Yes | QDialogButtonBox::No);
-
- return (r != QDialogButtonBox::No);
+ const auto title = QObject::tr("Blacklisted program");
+ const auto mainText = QObject::tr("The program %1 is blacklisted")
+ .arg(sp.binary.fileName());
+ const auto content = QObject::tr(
+ "The program you are attempting to launch is blacklisted in the virtual "
+ "filesystem. This will likely prevent it from seeing any mods, INI files "
+ "or any other virtualized files.");
+
+ auto r = MOBase::TaskDialog(parent, title)
+ .main(mainText)
+ .content(content)
+ .details("")
+ .icon(QMessageBox::Question)
+ .remember("blacklistedExecutable", sp.binary.fileName())
+ .button({
+ QObject::tr("Continue"),
+ QObject::tr("Your mods might not work"),
+ QMessageBox::Yes})
+ .button({
+ QObject::tr("Change the blacklist"),
+ QMessageBox::Retry})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ if (r == QMessageBox::Retry) {
+ if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, Settings::instance())) {
+ r = QMessageBox::Cancel;
+ }
+ }
+
+ return r;
}
} // namespace
@@ -716,11 +741,17 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings)
{
- if (settings.isExecutableBlacklisted(sp.binary.fileName())) {
- return dialogs::confirmBlacklisted(parent, sp);
- }
+ for (;;) {
+ if (!settings.isExecutableBlacklisted(sp.binary.fileName())) {
+ return true;
+ }
- return true;
+ const auto r = dialogs::confirmBlacklisted(parent, sp);
+
+ if (r != QMessageBox::Retry) {
+ return (r == QMessageBox::Yes);
+ }
+ }
}
--
cgit v1.3.1
From 54871e7b56a326f81e3de17f53604150d770793b Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 19 Sep 2019 17:14:09 -0400
Subject: added details to blacklist dialog
---
src/spawn.cpp | 15 ++++++++++-----
src/spawn.h | 2 +-
2 files changed, 11 insertions(+), 6 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index ab9d90a0..d2c8ddd5 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -384,7 +384,7 @@ bool eventLogNotRunning(
}
QMessageBox::StandardButton confirmBlacklisted(
- QWidget* parent, const SpawnParameters& sp)
+ QWidget* parent, const SpawnParameters& sp, Settings& settings)
{
const auto title = QObject::tr("Blacklisted program");
const auto mainText = QObject::tr("The program %1 is blacklisted")
@@ -394,10 +394,14 @@ QMessageBox::StandardButton confirmBlacklisted(
"filesystem. This will likely prevent it from seeing any mods, INI files "
"or any other virtualized files.");
+ const auto details =
+ "Executable: " + sp.binary.fileName() + "\n"
+ "Current blacklist: " + settings.executablesBlacklist();
+
auto r = MOBase::TaskDialog(parent, title)
.main(mainText)
.content(content)
- .details("")
+ .details(details)
.icon(QMessageBox::Question)
.remember("blacklistedExecutable", sp.binary.fileName())
.button({
@@ -413,7 +417,7 @@ QMessageBox::StandardButton confirmBlacklisted(
.exec();
if (r == QMessageBox::Retry) {
- if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, Settings::instance())) {
+ if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, settings)) {
r = QMessageBox::Cancel;
}
}
@@ -739,14 +743,15 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp)
return dialogs::eventLogNotRunning(parent, s, sp);
}
-bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings)
+bool checkBlacklist(
+ QWidget* parent, const SpawnParameters& sp, Settings& settings)
{
for (;;) {
if (!settings.isExecutableBlacklisted(sp.binary.fileName())) {
return true;
}
- const auto r = dialogs::confirmBlacklisted(parent, sp);
+ const auto r = dialogs::confirmBlacklisted(parent, sp, settings);
if (r != QMessageBox::Retry) {
return (r == QMessageBox::Yes);
diff --git a/src/spawn.h b/src/spawn.h
index 9e1e2539..31b44739 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -61,7 +61,7 @@ bool checkSteam(
bool checkEnvironment(QWidget* parent, const SpawnParameters& sp);
bool checkBlacklist(
- QWidget* parent, const SpawnParameters& sp, const Settings& settings);
+ QWidget* parent, const SpawnParameters& sp, Settings& settings);
/**
* @brief spawn a binary with Mod Organizer injected
--
cgit v1.3.1
From b45565911487e91c11f641f5cca685c231f91faf Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Fri, 20 Sep 2019 00:45:22 -0400
Subject: registry details when steam fails to start typos
---
src/spawn.cpp | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index d2c8ddd5..1b46c479 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -196,9 +196,17 @@ QMessageBox::StandardButton badSteamReg(
}
QMessageBox::StandardButton startSteamFailed(
- QWidget* parent, const SpawnParameters& sp, DWORD e)
+ QWidget* parent,
+ const QString& keyName, const QString& valueName, const QString& exe,
+ const SpawnParameters& sp, DWORD e)
{
- const auto details = makeDetails(sp, e);
+ auto details = QString(
+ "a steam install was found in the registry at '%1': '%2'\n\n")
+ .arg(keyName + "\\" + valueName)
+ .arg(exe);
+
+ details += makeDetails(sp, e);
+
log::error("{}", details);
return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam"))
@@ -332,7 +340,8 @@ QMessageBox::StandardButton confirmRestartAsAdminForSteam(
const auto mainText = QObject::tr("Steam is running as administrator");
const auto content = QObject::tr(
"Running Steam as administrator is typically unnecessary and can cause "
- "problems when Mod Organizer is not running as administrato\r\n\r\n"
+ "problems when Mod Organizer itself is not running as administrator."
+ "\r\n\r\n"
"You can restart Mod Organizer as administrator and try launching the "
"program again.");
@@ -618,7 +627,10 @@ bool startSteam(QWidget* parent)
(username.isEmpty() ? "" : "USERNAME"),
(password.isEmpty() ? "" : "PASSWORD"));
- return (dialogs::startSteamFailed(parent, sp, e) == QMessageBox::Yes);
+ const auto r = dialogs::startSteamFailed(
+ parent, keyName, valueName, exe, sp, e);
+
+ return (r == QMessageBox::Yes);
}
QMessageBox::information(
--
cgit v1.3.1
From 2feaed68b4bd2191aeaf02c33990a0bdf7f8738c Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Fri, 20 Sep 2019 01:19:01 -0400
Subject: missing period
---
src/spawn.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src/spawn.cpp')
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 1b46c479..079677f4 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -415,7 +415,7 @@ QMessageBox::StandardButton confirmBlacklisted(
.remember("blacklistedExecutable", sp.binary.fileName())
.button({
QObject::tr("Continue"),
- QObject::tr("Your mods might not work"),
+ QObject::tr("Your mods might not work."),
QMessageBox::Yes})
.button({
QObject::tr("Change the blacklist"),
--
cgit v1.3.1
From 3d97b0efd04326c0252411fc6639c4c6df2f2160 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Mon, 7 Oct 2019 04:27:53 -0400
Subject: added ExitModOrganizer(), used instead of qApp->exit() reset geometry
uses TaskDialog moved restart code for the settings dialog to MainWindow
fixed settings sometimes not being saved when restarting don't log "crash
dumps present" every time the settings dialog is closed
---
src/main.cpp | 12 +++-
src/mainwindow.cpp | 113 +++++++++++++++++++++++++-------------
src/mainwindow.h | 11 ++--
src/settingsdialog.cpp | 21 +++----
src/settingsdialog.h | 9 ++-
src/settingsdialognexus.cpp | 4 +-
src/settingsdialogworkarounds.cpp | 23 ++++----
src/shared/util.cpp | 47 ++++++++++++++++
src/shared/util.h | 18 ++++++
src/spawn.cpp | 12 ++--
10 files changed, 189 insertions(+), 81 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/main.cpp b/src/main.cpp
index a6918d99..f08ba066 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -563,10 +563,12 @@ int runApplication(MOApplication &application, SingleInstance &instance,
if (game == nullptr) {
InstanceManager &instance = InstanceManager::instance();
QString instanceName = instance.currentInstance();
+
if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) {
instance.clearCurrentInstance();
- return INT_MAX;
+ return RestartExitCode;
}
+
return 1;
}
@@ -710,6 +712,8 @@ int runApplication(MOApplication &application, SingleInstance &instance,
splash.finish(&mainWindow);
res = application.exec();
+ mainWindow.onBeforeClose();
+ mainWindow.close();
NexusInterface::instance(&pluginContainer)
->getAccessManager()->setTopLevelWidget(nullptr);
@@ -867,6 +871,7 @@ int main(int argc, char *argv[])
do {
LogModel::instance().clear();
+ ResetExitFlag();
// make sure the log file isn't locked in case MO was restarted and
// the previous instance gets deleted
@@ -904,10 +909,11 @@ int main(int argc, char *argv[])
splash = ":/MO/gui/splash";
}
- int result = runApplication(application, instance, splash);
- if (result != INT_MAX) {
+ const int result = runApplication(application, instance, splash);
+ if (result != RestartExitCode) {
return result;
}
+
argc = 1;
moshortcut = MOShortcut("");
} while (true);
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 295c60a6..23733cac 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1217,7 +1217,7 @@ void MainWindow::showEvent(QShowEvent *event)
QMainWindow::showEvent(event);
if (!m_WasVisible) {
- readSettings(m_OrganizerCore.settings());
+ readSettings();
refreshFilters();
// this needs to be connected here instead of in the constructor because the
@@ -1267,26 +1267,44 @@ void MainWindow::showEvent(QShowEvent *event)
}
}
+void MainWindow::onBeforeClose()
+{
+ storeSettings();
+}
void MainWindow::closeEvent(QCloseEvent* event)
{
- if (!confirmExit()) {
+ // this happens for two reasons:
+ // 1) the user requested to close the window, such as clicking the X
+ // 2) close() is called in runApplication() after application.exec()
+ // returns, which happens when qApp->exit() is called
+ //
+ // the window must never actually close for 1), because settings haven't been
+ // saved yet: the state of many widgets is saved to the ini, which relies on
+ // the window still being onscreen (or else everything is considered hidden)
+ //
+ // for 2), the settings have been saved and the window can just close
+
+ if (ModOrganizerExiting()) {
+ // the user has confirmed if necessary and all settings have been saved,
+ // just close it
+ QMainWindow::closeEvent(event);
+ } else {
+ // never close the window because settings might need to be changed
event->ignore();
- return;
- }
- storeSettings(m_OrganizerCore.settings());
+ // start the process of exiting, which may require confirmation by calling
+ // canExit(), among other things
+ ExitModOrganizer();
+ }
}
-bool MainWindow::confirmExit()
+bool MainWindow::canExit()
{
- m_closing = true;
-
if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) {
if (QMessageBox::question(this, tr("Downloads in progress"),
tr("There are still downloads in progress, do you really want to quit?"),
QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) {
- m_closing = false;
return false;
} else {
m_OrganizerCore.downloadManager()->pauseAll();
@@ -1298,8 +1316,9 @@ bool MainWindow::confirmExit()
HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId());
if (injected_process_still_running != INVALID_HANDLE_VALUE)
{
+ m_exitAfterWait = true;
m_OrganizerCore.waitForApplication(injected_process_still_running);
- if (!m_closing) { // if operation cancelled
+ if (!m_exitAfterWait) { // if operation cancelled
return false;
}
}
@@ -2138,20 +2157,22 @@ void MainWindow::activateProxy(bool activate)
busyDialog.hide();
}
-void MainWindow::readSettings(const Settings& settings)
+void MainWindow::readSettings()
{
- settings.geometry().restoreGeometry(this);
- settings.geometry().restoreState(this);
- settings.geometry().restoreDocks(this);
- settings.geometry().restoreToolbars(this);
- settings.geometry().restoreState(ui->splitter);
- settings.geometry().restoreState(ui->categoriesSplitter);
- settings.geometry().restoreVisibility(ui->menuBar);
- settings.geometry().restoreVisibility(ui->statusBar);
+ const auto& s = m_OrganizerCore.settings();
+
+ s.geometry().restoreGeometry(this);
+ s.geometry().restoreState(this);
+ s.geometry().restoreDocks(this);
+ s.geometry().restoreToolbars(this);
+ s.geometry().restoreState(ui->splitter);
+ s.geometry().restoreState(ui->categoriesSplitter);
+ s.geometry().restoreVisibility(ui->menuBar);
+ s.geometry().restoreVisibility(ui->statusBar);
{
// special case in case someone puts 0 in the INI
- auto v = settings.widgets().index(ui->executablesListBox);
+ auto v = s.widgets().index(ui->executablesListBox);
if (!v || v == 0) {
v = 1;
}
@@ -2159,16 +2180,16 @@ void MainWindow::readSettings(const Settings& settings)
ui->executablesListBox->setCurrentIndex(*v);
}
- settings.widgets().restoreIndex(ui->groupCombo);
+ s.widgets().restoreIndex(ui->groupCombo);
{
- settings.geometry().restoreVisibility(ui->categoriesGroup, false);
+ s.geometry().restoreVisibility(ui->categoriesGroup, false);
const auto v = ui->categoriesGroup->isVisible();
setCategoryListVisible(v);
ui->displayCategoriesBtn->setChecked(v);
}
- if (settings.network().useProxy()) {
+ if (s.network().useProxy()) {
activateProxy(true);
}
}
@@ -2215,8 +2236,10 @@ void MainWindow::processUpdates(Settings& settings) {
}
}
-void MainWindow::storeSettings(Settings& s)
+void MainWindow::storeSettings()
{
+ auto& s = m_OrganizerCore.settings();
+
s.geometry().saveState(this);
s.geometry().saveGeometry(this);
s.geometry().saveDocks(this);
@@ -2244,7 +2267,7 @@ ILockedWaitingForProcess* MainWindow::lock()
++m_LockCount;
return m_LockDialog;
}
- if (m_closing)
+ if (m_exitAfterWait)
m_LockDialog = new WaitingOnCloseDialog(this);
else
m_LockDialog = new LockedDialog(this, true);
@@ -2265,8 +2288,8 @@ void MainWindow::unlock()
}
--m_LockCount;
if (m_LockCount == 0) {
- if (m_closing && m_LockDialog->canceled())
- m_closing = false;
+ if (m_exitAfterWait && m_LockDialog->canceled())
+ m_exitAfterWait = false;
m_LockDialog->hide();
m_LockDialog->deleteLater();
m_LockDialog = nullptr;
@@ -5018,18 +5041,31 @@ void MainWindow::on_actionSettings_triggered()
bool proxy = settings.network().useProxy();
DownloadManager *dlManager = m_OrganizerCore.downloadManager();
const bool oldCheckForUpdates = settings.checkForUpdates();
+ const int oldMaxDumps = settings.diagnostics().crashDumpsMax();
SettingsDialog dialog(&m_PluginContainer, settings, this);
dialog.exec();
+ auto e = dialog.exitNeeded();
+
if (oldManagedGameDirectory != settings.game().directory()) {
- QMessageBox::about(this, tr("Restarting MO"),
- tr("Changing the managed game directory requires restarting MO.\n"
- "Any pending downloads will be paused.\n\n"
- "Click OK to restart MO now."));
- dlManager->pauseAll();
- qApp->exit(INT_MAX);
+ e |= Exit::Restart;
+ }
+
+ if (e.testFlag(Exit::Restart)) {
+ const auto r = MOBase::TaskDialog(this)
+ .title(tr("Restart Mod Organizer"))
+ .main("Restart Mod Organizer")
+ .content(tr("Mod Organizer must restart to finish configuration changes"))
+ .icon(QMessageBox::Question)
+ .button({tr("Restart"), QMessageBox::Yes})
+ .button({tr("Continue"), tr("Some things might be weird."), QMessageBox::No})
+ .exec();
+
+ if (r == QMessageBox::Yes) {
+ ExitModOrganizer(e);
+ }
}
InstallationManager *instManager = m_OrganizerCore.installationManager();
@@ -5092,7 +5128,10 @@ void MainWindow::on_actionSettings_triggered()
updateDownloadView();
m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel());
- m_OrganizerCore.cycleDiagnostics();
+
+ if (settings.diagnostics().crashDumpsMax() != oldMaxDumps) {
+ m_OrganizerCore.cycleDiagnostics();
+ }
toggleMO2EndorseState();
@@ -5475,9 +5514,7 @@ void MainWindow::on_actionUpdate_triggered()
void MainWindow::on_actionExit_triggered()
{
- if (confirmExit()) {
- qApp->exit();
- }
+ ExitModOrganizer();
}
void MainWindow::actionEndorseMO()
@@ -6121,7 +6158,7 @@ void MainWindow::on_actionChange_Game_triggered()
if (r == QMessageBox::Yes) {
InstanceManager::instance().clearCurrentInstance();
- qApp->exit(INT_MAX);
+ ExitModOrganizer(Exit::Restart);
}
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 524e2b6e..b04096be 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -111,7 +111,6 @@ class MainWindow : public QMainWindow, public IUserInterface
friend class OrganizerProxy;
public:
-
explicit MainWindow(Settings &settings,
OrganizerCore &organizerCore, PluginContainer &pluginContainer,
QWidget *parent = 0);
@@ -154,7 +153,8 @@ public:
void displayModInformation(
ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override;
- bool confirmExit();
+ bool canExit();
+ void onBeforeClose();
virtual bool closeWindow();
virtual void setWindowEnabled(bool enabled);
@@ -383,9 +383,8 @@ private:
LockedDialogBase *m_LockDialog { nullptr };
uint64_t m_LockCount { 0 };
- bool m_closing{ false };
-
bool m_showArchiveData{ true };
+ bool m_exitAfterWait{ false };
MOBase::DelayedFileWriter m_ArchiveListWriter;
@@ -672,8 +671,8 @@ private slots: // ui slots
void on_categoriesOrBtn_toggled(bool checked);
void on_managedArchiveLabel_linkHovered(const QString &link);
- void storeSettings(Settings& settings);
- void readSettings(const Settings& settings);
+ void storeSettings();
+ void readSettings();
void setupModList();
};
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index 8fb25b1c..daccfba9 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti
: TutorableDialog("SettingsDialog", parent)
, ui(new Ui::SettingsDialog)
, m_settings(settings)
+ , m_exit(Exit::None)
, m_pluginContainer(pluginContainer)
- , m_restartNeeded(false)
{
ui->setupUi(this);
@@ -61,9 +61,14 @@ QWidget* SettingsDialog::parentWidgetForDialogs()
}
}
-void SettingsDialog::setRestartNeeded()
+void SettingsDialog::setExitNeeded(ExitFlags e)
{
- m_restartNeeded = true;
+ m_exit = e;
+}
+
+ExitFlags SettingsDialog::exitNeeded() const
+{
+ return m_exit;
}
int SettingsDialog::exec()
@@ -87,16 +92,6 @@ int SettingsDialog::exec()
}
}
- if (m_restartNeeded) {
- if (QMessageBox::question(parentWidgetForDialogs(),
- tr("Restart Mod Organizer?"),
- tr("In order to finish configuration changes, MO must be restarted.\n"
- "Restart it now?"),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- qApp->exit(INT_MAX);
- }
- }
-
return ret;
}
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index e89da665..bae4a469 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -21,6 +21,7 @@ along with Mod Organizer. If not, see .
#define SETTINGSDIALOG_H
#include "tutorabledialog.h"
+#include "util.h"
class PluginContainer;
class Settings;
@@ -74,7 +75,9 @@ public:
PluginContainer* pluginContainer();
QWidget* parentWidgetForDialogs();
- void setRestartNeeded();
+
+ void setExitNeeded(ExitFlags e);
+ ExitFlags exitNeeded() const;
int exec() override;
@@ -82,10 +85,10 @@ public slots:
virtual void accept();
private:
+ Ui::SettingsDialog* ui;
Settings& m_settings;
std::vector> m_tabs;
- Ui::SettingsDialog* ui;
- bool m_restartNeeded;
+ ExitFlags m_exit;
PluginContainer* m_pluginContainer;
};
diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp
index 2dd8b998..d49e0a33 100644
--- a/src/settingsdialognexus.cpp
+++ b/src/settingsdialognexus.cpp
@@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s)
bool NexusSettingsTab::setKey(const QString& key)
{
- dialog().setRestartNeeded();
+ dialog().setExitNeeded(Exit::Restart);
const bool ret = settings().nexus().setApiKey(key);
updateNexusState();
return ret;
@@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key)
bool NexusSettingsTab::clearKey()
{
- dialog().setRestartNeeded();
+ dialog().setExitNeeded(Exit::Restart);
const auto ret = settings().nexus().clearApiKey();
NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey();
diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp
index 0e31fc4b..1c5fbe26 100644
--- a/src/settingsdialogworkarounds.cpp
+++ b/src/settingsdialogworkarounds.cpp
@@ -2,6 +2,7 @@
#include "ui_settingsdialog.h"
#include "spawn.h"
#include "settings.h"
+#include
#include
WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d)
@@ -118,16 +119,18 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked()
void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked()
{
- const auto caption = QObject::tr("Restart Mod Organizer?");
- const auto text = QObject::tr(
- "In order to reset the geometry, Mod Organizer must be restarted.\n"
- "Restart now?");
-
- const auto res = QMessageBox::question(
- parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel);
-
- if (res == QMessageBox::Yes) {
+ const auto r = MOBase::TaskDialog(parentWidget())
+ .title(QObject::tr("Restart Mod Organizer"))
+ .main(QObject::tr("Restart Mod Organizer"))
+ .content(QObject::tr("Geometries will be reset to their default values."))
+ .icon(QMessageBox::Question)
+ .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok})
+ .button({QObject::tr("Cancel"), QMessageBox::Cancel})
+ .exec();
+
+ if (r == QMessageBox::Ok) {
settings().geometry().requestReset();
- qApp->exit(INT_MAX);
+ ExitModOrganizer(Exit::Restart);
+ dialog().close();
}
}
diff --git a/src/shared/util.cpp b/src/shared/util.cpp
index 07983e12..58f4eb2b 100644
--- a/src/shared/util.cpp
+++ b/src/shared/util.cpp
@@ -19,6 +19,7 @@ along with Mod Organizer. If not, see .
#include "util.h"
#include "windows_error.h"
+#include "mainwindow.h"
namespace MOShared
{
@@ -244,3 +245,49 @@ MOBase::VersionInfo createVersionInfo()
}
} // namespace MOShared
+
+
+static bool g_exiting = false;
+
+MainWindow* findMainWindow()
+{
+ for (auto* tl : qApp->topLevelWidgets()) {
+ if (auto* mw=dynamic_cast(tl)) {
+ return mw;
+ }
+ }
+
+ return nullptr;
+}
+
+bool ExitModOrganizer(ExitFlags e)
+{
+ if (g_exiting) {
+ return true;
+ }
+
+ if (!e.testFlag(Exit::Force)) {
+ if (auto* mw=findMainWindow()) {
+ if (!mw->canExit()) {
+ return false;
+ }
+ }
+ }
+
+ g_exiting = true;
+
+ const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0);
+ qApp->exit(code);
+
+ return true;
+}
+
+bool ModOrganizerExiting()
+{
+ return g_exiting;
+}
+
+void ResetExitFlag()
+{
+ g_exiting = false;
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index 7bae96f2..aea6f200 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -48,4 +48,22 @@ MOBase::VersionInfo createVersionInfo();
} // namespace MOShared
+
+enum class Exit
+{
+ None = 0x00,
+ Normal = 0x01,
+ Restart = 0x02,
+ Force = 0x04
+};
+
+const int RestartExitCode = INT_MAX;
+
+using ExitFlags = QFlags;
+Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags);
+
+bool ExitModOrganizer(ExitFlags e=Exit::Normal);
+bool ModOrganizerExiting();
+void ResetExitFlag();
+
#endif // UTIL_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 079677f4..a34230b2 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -186,12 +186,12 @@ QMessageBox::StandardButton badSteamReg(
.details(details)
.icon(QMessageBox::Critical)
.button({
- QObject::tr("Continue without starting Steam"),
- QObject::tr("The program may fail to launch."),
- QMessageBox::Yes})
+ QObject::tr("Continue without starting Steam"),
+ QObject::tr("The program may fail to launch."),
+ QMessageBox::Yes})
.button({
- QObject::tr("Cancel"),
- QMessageBox::Cancel})
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
.exec();
}
@@ -517,7 +517,7 @@ bool restartAsAdmin(QWidget* parent)
}
log::debug("exiting MO");
- qApp->exit(0);
+ ExitModOrganizer(Exit::Force);
return true;
}
--
cgit v1.3.1
From d91580cc2669e2ab018c2aaab472cd763e5441d8 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Fri, 18 Oct 2019 15:39:07 -0400
Subject: initial Spawner and SpawnedProcess added steam app id to spawn
parameters removed threadHandle, unused moved most of the stuff from
OrganizerCore::spawnBinaryProcess() to Spawner replaced m_UserInterface by
m_MainWindow
---
src/mainwindow.cpp | 4 +-
src/organizercore.cpp | 192 ++++++++++++++++--------------------------------
src/organizercore.h | 9 ++-
src/plugincontainer.cpp | 1 +
src/spawn.cpp | 131 +++++++++++++++++++++++++++++++--
src/spawn.h | 34 +++++++++
6 files changed, 230 insertions(+), 141 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 12ed40b3..b5b9ab0d 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -411,7 +411,7 @@ MainWindow::MainWindow(Settings &settings
m_Tutorial.expose("modList", m_OrganizerCore.modList());
m_Tutorial.expose("espList", m_OrganizerCore.pluginList());
- m_OrganizerCore.setUserInterface(this, this);
+ m_OrganizerCore.setUserInterface(this);
for (const QString &fileName : m_PluginContainer.pluginFileNames()) {
installTranslator(QFileInfo(fileName).baseName());
}
@@ -595,7 +595,7 @@ MainWindow::~MainWindow()
cleanup();
m_PluginContainer.setUserInterface(nullptr, nullptr);
- m_OrganizerCore.setUserInterface(nullptr, nullptr);
+ m_OrganizerCore.setUserInterface(nullptr);
m_IntegratedBrowser.close();
delete ui;
} catch (std::exception &e) {
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 5613e8ce..3c986004 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1,5 +1,5 @@
#include "organizercore.h"
-
+#include "mainwindow.h"
#include "delayedfilewriter.h"
#include "guessedvalue.h"
#include "imodinterface.h"
@@ -127,7 +127,7 @@ QStringList toStringList(InputIterator current, InputIterator end)
OrganizerCore::OrganizerCore(Settings &settings)
- : m_UserInterface(nullptr)
+ : m_MainWindow(nullptr)
, m_PluginContainer(nullptr)
, m_GameName()
, m_CurrentProfile(nullptr)
@@ -249,44 +249,43 @@ void OrganizerCore::updateExecutablesList()
m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(IUserInterface *userInterface,
- QWidget *widget)
+void OrganizerCore::setUserInterface(MainWindow* mainWindow)
{
storeSettings();
- m_UserInterface = userInterface;
+ m_MainWindow = mainWindow;
- if (widget != nullptr) {
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget,
+ if (m_MainWindow != nullptr) {
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow,
SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget,
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow,
SLOT(modlistChanged(QModelIndexList, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), widget,
+ connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow,
SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget,
+ connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow,
SLOT(modRenamed(QString, QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget,
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow,
SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), widget,
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow,
SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(clearOverwrite()), widget,
+ connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow,
SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget,
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow,
SLOT(displayColumnSelection(QPoint)));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget,
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow,
SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), widget,
+ connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow,
SLOT(modorder_changed()));
- connect(&m_PluginList, SIGNAL(writePluginsList()), widget,
+ connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow,
SLOT(esplist_changed()));
- connect(&m_PluginList, SIGNAL(esplist_changed()), widget,
+ connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow,
SLOT(esplist_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget,
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow,
SLOT(showMessage(QString)));
}
- m_InstallationManager.setParentWidget(widget);
- m_Updater.setUserInterface(widget);
+ m_InstallationManager.setParentWidget(m_MainWindow);
+ m_Updater.setUserInterface(m_MainWindow);
checkForUpdates();
}
@@ -295,7 +294,7 @@ void OrganizerCore::checkForUpdates()
{
// this currently wouldn't work reliably if the ui isn't initialized yet to
// display the result
- if (m_UserInterface != nullptr) {
+ if (m_MainWindow != nullptr) {
m_Updater.testForUpdate(m_Settings);
}
}
@@ -755,13 +754,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
int modIndex = ModInfo::getIndex(modName);
if (modIndex != UINT_MAX) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if (hasIniTweaks && (m_UserInterface != nullptr)
+ if (hasIniTweaks && (m_MainWindow != nullptr)
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
tr("This mod contains ini tweaks. Do you "
"want to configure them now?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)) {
- m_UserInterface->displayModInformation(
+ m_MainWindow->displayModInformation(
modInfo, modIndex, ModInfoTabIDs::IniFiles);
}
m_ModInstalled(modName);
@@ -822,13 +821,13 @@ void OrganizerCore::installDownload(int index)
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
modInfo->addInstalledFile(modID, fileID);
- if (hasIniTweaks && m_UserInterface != nullptr
+ if (hasIniTweaks && m_MainWindow != nullptr
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
tr("This mod contains ini tweaks. Do you "
"want to configure them now?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)) {
- m_UserInterface->displayModInformation(
+ m_MainWindow->displayModInformation(
modInfo, modIndex, ModInfoTabIDs::IniFiles);
}
@@ -1250,11 +1249,11 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
std::unique_ptr dlg;
ILockedWaitingForProcess* uilock = nullptr;
- if (m_UserInterface != nullptr) {
- uilock = m_UserInterface->lock();
+ if (m_MainWindow != nullptr) {
+ uilock = m_MainWindow->lock();
}
else {
- // i.e. when running command line shortcuts there is no m_UserInterface
+ // i.e. when running command line shortcuts there is no user interface
dlg.reset(new LockedDialog);
dlg->show();
dlg->setEnabled(true);
@@ -1262,8 +1261,8 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
}
ON_BLOCK_EXIT([&]() {
- if (m_UserInterface != nullptr) {
- m_UserInterface->unlock();
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->unlock();
} });
DWORD ignoreExitCode;
@@ -1275,35 +1274,21 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
}
-HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
- const QString &arguments,
- const QString &profileName,
- const QDir ¤tDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList &forcedLibraries)
+HANDLE OrganizerCore::spawnBinaryProcess(
+ const QFileInfo &binary, const QString &arguments, const QString &profileName,
+ const QDir ¤tDirectory, const QString &steamAppID,
+ const QString &customOverwrite,
+ const QList &forcedLibraries)
{
spawn::SpawnParameters sp;
sp.binary = binary;
sp.arguments = arguments;
sp.currentDirectory = currentDirectory;
+ sp.steamAppID = steamAppID;
sp.hooked = true;
prepareStart();
- QWidget *window = qApp->activeWindow();
- if ((window != nullptr) && (!window->isVisible())) {
- window = nullptr;
- }
-
- if (!spawn::checkBinary(window, sp)) {
- return INVALID_HANDLE_VALUE;
- }
-
- if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) {
- return INVALID_HANDLE_VALUE;
- }
-
while (m_DirectoryUpdate) {
::Sleep(100);
QCoreApplication::processEvents();
@@ -1315,72 +1300,25 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
}
// TODO: should also pass arguments
- if (m_AboutToRun(binary.absoluteFilePath())) {
- try {
- m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
- m_USVFS.updateForcedLibraries(forcedLibraries);
-
- } catch (const UsvfsConnectorException &e) {
- log::debug(e.what());
- return INVALID_HANDLE_VALUE;
- } catch (const std::exception &e) {
- QMessageBox::warning(window, tr("Error"), e.what());
- return INVALID_HANDLE_VALUE;
- }
-
- if (!spawn::checkEnvironment(window, sp)) {
- return INVALID_HANDLE_VALUE;
- }
-
- if (!spawn::checkBlacklist(window, sp, m_Settings)) {
- return INVALID_HANDLE_VALUE;
- }
-
- QString modsPath = settings().paths().mods();
-
- // Check if this a request with either an executable or a working directory under our mods folder
- // then will start the process in a virtualized "environment" with the appropriate paths fixed:
- // (i.e. mods\FNIS\path\exe => game\data\path\exe)
- QString cwdPath = currentDirectory.absolutePath();
- bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
- QString binPath = binary.absoluteFilePath();
- bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
- if (virtualizedCwd || virtualizedBin) {
- if (virtualizedCwd) {
- int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
- QString adjustedCwd = cwdPath.mid(cwdOffset, -1);
- cwdPath = m_GamePlugin->dataDirectory().absolutePath();
- if (cwdOffset >= 0)
- cwdPath += adjustedCwd;
-
- }
-
- if (virtualizedBin) {
- int binOffset = binPath.indexOf('/', modsPath.length() + 1);
- QString adjustedBin = binPath.mid(binOffset, -1);
- binPath = m_GamePlugin->dataDirectory().absolutePath();
- if (binOffset >= 0)
- binPath += adjustedBin;
- }
-
- QString cmdline
- = QString("launch \"%1\" \"%2\" %3")
- .arg(QDir::toNativeSeparators(cwdPath),
- QDir::toNativeSeparators(binPath), arguments);
+ if (!m_AboutToRun(binary.absoluteFilePath())) {
+ log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
+ return INVALID_HANDLE_VALUE;
+ }
- sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
- sp.arguments = cmdline;
- sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
+ try {
+ m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
+ m_USVFS.updateForcedLibraries(forcedLibraries);
- return spawn::startBinary(window, sp);
- } else {
- log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath);
- return spawn::startBinary(window, sp);
- }
- } else {
- log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
+ } catch (const UsvfsConnectorException &e) {
+ log::debug(e.what());
+ return INVALID_HANDLE_VALUE;
+ } catch (const std::exception &e) {
+ QMessageBox::warning(m_MainWindow, tr("Error"), e.what());
return INVALID_HANDLE_VALUE;
}
+
+ auto process = spawn::Spawner().spawn(m_MainWindow, m_GamePlugin, sp, m_Settings);
+ return process.releaseHandle();
}
HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut)
@@ -1498,13 +1436,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
return true;
ILockedWaitingForProcess* uilock = nullptr;
- if (m_UserInterface != nullptr) {
- uilock = m_UserInterface->lock();
+ if (m_MainWindow != nullptr) {
+ uilock = m_MainWindow->lock();
}
ON_BLOCK_EXIT([&] () {
- if (m_UserInterface != nullptr) {
- m_UserInterface->unlock();
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->unlock();
} });
return waitForProcessCompletion(handle, exitCode, uilock);
}
@@ -1762,8 +1700,8 @@ void OrganizerCore::refreshBSAList()
m_ActiveArchives = m_DefaultArchives;
}
- if (m_UserInterface != nullptr) {
- m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives);
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives);
}
m_ArchivesInit = true;
@@ -1879,8 +1817,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false);
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->archivesWriter().writeImmediately(false);
}
std::vector archives = enabledArchives();
@@ -2064,8 +2002,8 @@ void OrganizerCore::modStatusChanged(unsigned int index)
= m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
origin.enable(false);
}
- if (m_UserInterface != nullptr) {
- m_UserInterface->archivesWriter().write();
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->archivesWriter().write();
}
}
modInfo->clearCaches();
@@ -2114,8 +2052,8 @@ void OrganizerCore::modStatusChanged(QList index) {
origin.enable(false);
}
}
- if (m_UserInterface != nullptr) {
- m_UserInterface->archivesWriter().write();
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->archivesWriter().write();
}
}
@@ -2290,8 +2228,8 @@ bool OrganizerCore::saveCurrentLists()
try {
savePluginList();
- if (m_UserInterface != nullptr) {
- m_UserInterface->archivesWriter().write();
+ if (m_MainWindow != nullptr) {
+ m_MainWindow->archivesWriter().write();
}
} catch (const std::exception &e) {
reportError(tr("failed to save load order: %1").arg(e.what()));
diff --git a/src/organizercore.h b/src/organizercore.h
index 0d0a092c..04c96ba6 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -3,7 +3,7 @@
#include "selfupdater.h"
-#include "iuserinterface.h" //should be class IUserInterface;
+#include "ilockedwaitingforprocess.h"
#include "settings.h"
#include "modlist.h"
#include "modinfo.h"
@@ -26,6 +26,8 @@
class ModListSortProxy;
class PluginListSortProxy;
class Profile;
+class MainWindow;
+
namespace MOBase {
template class GuessedValue;
class IModInterface;
@@ -101,7 +103,7 @@ public:
~OrganizerCore();
- void setUserInterface(IUserInterface *userInterface, QWidget *widget);
+ void setUserInterface(MainWindow* mainWindow);
void connectPlugins(PluginContainer *container);
void disconnectPlugins();
@@ -328,8 +330,7 @@ private:
static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1;
private:
-
- IUserInterface *m_UserInterface;
+ MainWindow* m_MainWindow;
PluginContainer *m_PluginContainer;
QString m_GameName;
MOBase::IPluginGame *m_GamePlugin;
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp
index c0706ba8..767d3eb8 100644
--- a/src/plugincontainer.cpp
+++ b/src/plugincontainer.cpp
@@ -3,6 +3,7 @@
#include "organizerproxy.h"
#include "report.h"
#include
+#include
#include
#include
#include
diff --git a/src/spawn.cpp b/src/spawn.cpp
index a34230b2..3c7d64ce 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -27,6 +27,7 @@ along with Mod Organizer. If not, see .
#include "envmodule.h"
#include "settings.h"
#include "settingsdialogworkarounds.h"
+#include
#include
#include
#include
@@ -440,7 +441,7 @@ QMessageBox::StandardButton confirmBlacklisted(
namespace spawn
{
-DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle)
+DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle)
{
BOOL inheritHandles = FALSE;
@@ -494,7 +495,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand
}
processHandle = pi.hProcess;
- threadHandle = pi.hThread;
+ ::CloseHandle(pi.hThread);
return ERROR_SUCCESS;
}
@@ -618,8 +619,7 @@ bool startSteam(QWidget* parent)
(password.isEmpty() ? "no" : "yes"));
HANDLE ph = INVALID_HANDLE_VALUE;
- HANDLE th = INVALID_HANDLE_VALUE;
- const auto e = spawn(sp, ph, th);
+ const auto e = spawn(sp, ph);
if (e != ERROR_SUCCESS) {
// make sure username and passwords are not shown
@@ -772,17 +772,59 @@ bool checkBlacklist(
}
+void adjustForVirtualized(
+ const IPluginGame* game, SpawnParameters& sp, const Settings& settings)
+{
+ const QString modsPath = settings.paths().mods();
+
+ // Check if this a request with either an executable or a working directory
+ // under our mods folder then will start the process in a virtualized
+ // "environment" with the appropriate paths fixed:
+ // (i.e. mods\FNIS\path\exe => game\data\path\exe)
+ QString cwdPath = sp.currentDirectory.absolutePath();
+ bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
+ QString binPath = sp.binary.absoluteFilePath();
+ bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
+ if (virtualizedCwd || virtualizedBin) {
+ if (virtualizedCwd) {
+ int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
+ QString adjustedCwd = cwdPath.mid(cwdOffset, -1);
+ cwdPath = game->dataDirectory().absolutePath();
+ if (cwdOffset >= 0)
+ cwdPath += adjustedCwd;
+
+ }
+
+ if (virtualizedBin) {
+ int binOffset = binPath.indexOf('/', modsPath.length() + 1);
+ QString adjustedBin = binPath.mid(binOffset, -1);
+ binPath = game->dataDirectory().absolutePath();
+ if (binOffset >= 0)
+ binPath += adjustedBin;
+ }
+
+ QString cmdline
+ = QString("launch \"%1\" \"%2\" %3")
+ .arg(QDir::toNativeSeparators(cwdPath),
+ QDir::toNativeSeparators(binPath), sp.arguments);
+
+ sp.binary = QFileInfo(QCoreApplication::applicationFilePath());
+ sp.arguments = cmdline;
+ sp.currentDirectory.setPath(QCoreApplication::applicationDirPath());
+ }
+}
+
+
HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
{
- HANDLE processHandle, threadHandle;
- const auto e = spawn(sp, processHandle, threadHandle);
+ HANDLE handle = INVALID_HANDLE_VALUE;
+ const auto e = spawn::spawn(sp, handle);
switch (e)
{
case ERROR_SUCCESS:
{
- ::CloseHandle(threadHandle);
- return processHandle;
+ return handle;
}
case ERROR_ELEVATION_REQUIRED:
@@ -799,6 +841,79 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp)
}
}
+
+
+SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp)
+ : m_handle(handle), m_parameters(std::move(sp))
+{
+}
+
+SpawnedProcess::SpawnedProcess(SpawnedProcess&& other)
+ : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters))
+{
+ other.m_handle = INVALID_HANDLE_VALUE;
+}
+
+SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other)
+{
+ if (this != &other) {
+ destroy();
+
+ m_handle = other.m_handle;
+ other.m_handle = INVALID_HANDLE_VALUE;
+
+ m_parameters = std::move(other.m_parameters);
+ }
+
+ return *this;
+}
+
+SpawnedProcess::~SpawnedProcess()
+{
+ destroy();
+}
+
+HANDLE SpawnedProcess::releaseHandle()
+{
+ const auto h = m_handle;
+ m_handle = INVALID_HANDLE_VALUE;
+ return h;
+}
+
+void SpawnedProcess::destroy()
+{
+ if (m_handle != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(m_handle);
+ m_handle = INVALID_HANDLE_VALUE;
+ }
+}
+
+
+SpawnedProcess Spawner::spawn(
+ QWidget* parent, const IPluginGame* game,
+ SpawnParameters sp, Settings& settings)
+{
+ if (!checkBinary(parent, sp)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ if (!spawn::checkEnvironment(parent, sp)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ if (!spawn::checkBlacklist(parent, sp, settings)) {
+ return {INVALID_HANDLE_VALUE, sp};
+ }
+
+ adjustForVirtualized(game, sp, settings);
+
+ return {startBinary(parent, sp), sp};
+}
+
} // namespace
diff --git a/src/spawn.h b/src/spawn.h
index 31b44739..d2853cd5 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -27,6 +27,7 @@ along with Mod Organizer. If not, see .
#include
class Settings;
+namespace MOBase { class IPluginGame; }
namespace spawn
{
@@ -46,6 +47,7 @@ struct SpawnParameters
QFileInfo binary;
QString arguments;
QDir currentDirectory;
+ QString steamAppID;
bool hooked = false;
HANDLE stdOut = INVALID_HANDLE_VALUE;
HANDLE stdErr = INVALID_HANDLE_VALUE;
@@ -69,6 +71,38 @@ bool checkBlacklist(
**/
HANDLE startBinary(QWidget* parent, const SpawnParameters& sp);
+
+class SpawnedProcess
+{
+public:
+ SpawnedProcess(HANDLE handle, SpawnParameters sp);
+
+ SpawnedProcess(const SpawnedProcess&) = delete;
+ SpawnedProcess& operator=(const SpawnedProcess&) = delete;
+ SpawnedProcess(SpawnedProcess&& other);
+ SpawnedProcess& operator=(SpawnedProcess&& other);
+ ~SpawnedProcess();
+
+ HANDLE releaseHandle();
+
+private:
+ HANDLE m_handle;
+ SpawnParameters m_parameters;
+
+ void destroy();
+};
+
+
+class Spawner
+{
+public:
+ SpawnedProcess spawn(
+ QWidget* parent, const MOBase::IPluginGame* game,
+ SpawnParameters sp, Settings& settings);
+
+private:
+};
+
} // namespace
--
cgit v1.3.1
From 4e8dcc5157706e1478396179f5dc11305532b159 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 24 Oct 2019 04:27:22 -0400
Subject: moved findJavaInstallation() and getFileExecutionContext() to spawn
fixed env::get() returning garbage after value
---
src/editexecutablesdialog.cpp | 3 +-
src/env.cpp | 31 +++--
src/mainwindow.cpp | 68 +++++------
src/organizercore.cpp | 264 +++++++++++++++---------------------------
src/organizercore.h | 12 --
src/spawn.cpp | 183 ++++++++++++++++++++++++++++-
src/spawn.h | 19 +++
7 files changed, 351 insertions(+), 229 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index 8535b7a7..32b31357 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -24,6 +24,7 @@ along with Mod Organizer. If not, see .
#include "modlist.h"
#include "forcedloaddialog.h"
#include "organizercore.h"
+#include "spawn.h"
#include
#include
@@ -800,7 +801,7 @@ QFileInfo EditExecutablesDialog::browseBinary(const QString& initial)
void EditExecutablesDialog::setJarBinary(const QFileInfo& binary)
{
- auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath());
+ auto java = spawn::findJavaInstallation(binary.absoluteFilePath());
if (java.isEmpty()) {
QMessageBox::information(
diff --git a/src/env.cpp b/src/env.cpp
index 78b5dc96..507607d1 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -190,19 +190,36 @@ QString setPath(const QString& s)
QString get(const QString& name)
{
- std::wstring s(4000, L' ');
+ std::size_t bufferSize = 4000;
+ auto buffer = std::make_unique(bufferSize);
DWORD realSize = ::GetEnvironmentVariableW(
- name.toStdWString().c_str(), s.data(), static_cast(s.size()));
+ name.toStdWString().c_str(),
+ buffer.get(), static_cast(bufferSize));
- if (realSize > s.size()) {
- s.resize(realSize);
+ if (realSize > bufferSize) {
+ bufferSize = realSize;
+ buffer = std::make_unique(bufferSize);
- ::GetEnvironmentVariableW(
- name.toStdWString().c_str(), s.data(), static_cast(s.size()));
+ realSize = ::GetEnvironmentVariableW(
+ name.toStdWString().c_str(),
+ buffer.get(), static_cast(bufferSize));
}
- return QString::fromStdWString(s);
+ if (realSize == 0) {
+ const auto e = ::GetLastError();
+
+ // don't log if not found
+ if (e != ERROR_ENVVAR_NOT_FOUND) {
+ log::error(
+ "failed to get environment variable '{}', {}",
+ name, formatSystemMessage(e));
+ }
+
+ return {};
+ }
+
+ return QString::fromWCharArray(buffer.get(), realSize);
}
QString set(const QString& n, const QString& v)
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 2474fdc0..0181a335 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -5295,43 +5295,42 @@ void MainWindow::addAsExecutable()
return;
}
- using FileExecutionTypes = OrganizerCore::FileExecutionTypes;
+ const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString());
+ const auto fec = spawn::getFileExecutionContext(this, target);
- QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString());
- QFileInfo binaryInfo;
- QString arguments;
- FileExecutionTypes type;
-
- if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) {
- return;
- }
-
- switch (type)
+ switch (fec.type)
{
- case FileExecutionTypes::Executable: {
- QString name = QInputDialog::getText(this, tr("Enter Name"),
- tr("Please enter a name for the executable"), QLineEdit::Normal,
- targetInfo.completeBaseName());
-
- if (!name.isEmpty()) {
- //Note: If this already exists, you'll lose custom settings
- m_OrganizerCore.executablesList()->setExecutable(Executable()
- .title(name)
- .binaryInfo(binaryInfo)
- .arguments(arguments)
- .workingDirectory(targetInfo.absolutePath()));
-
- refreshExecutablesList();
- }
-
- break;
+ case spawn::FileExecutionTypes::Executable:
+ {
+ const QString name = QInputDialog::getText(
+ this, tr("Enter Name"),
+ tr("Enter a name for the executable"),
+ QLineEdit::Normal,
+ target.completeBaseName());
+
+ if (!name.isEmpty()) {
+ //Note: If this already exists, you'll lose custom settings
+ m_OrganizerCore.executablesList()->setExecutable(Executable()
+ .title(name)
+ .binaryInfo(fec.binary)
+ .arguments(fec.arguments)
+ .workingDirectory(target.absolutePath()));
+
+ refreshExecutablesList();
}
- case FileExecutionTypes::Other: // fall-through
- default: {
- QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable."));
- break;
- }
+ break;
+ }
+
+ case spawn::FileExecutionTypes::Other: // fall-through
+ default:
+ {
+ QMessageBox::information(
+ this, tr("Not an executable"),
+ tr("This is not a recognized executable."));
+
+ break;
+ }
}
}
@@ -5458,7 +5457,8 @@ void MainWindow::openDataFile()
return;
}
- QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString());
+ const QString path = m_ContextItem->data(0, Qt::UserRole).toString();
+ const QFileInfo targetInfo(path);
m_OrganizerCore.runFile(this, targetInfo);
}
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 6bf2c290..78f9517c 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -976,76 +976,6 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const
return res;
}
-QString OrganizerCore::findJavaInstallation(const QString& jarFile)
-{
- if (!jarFile.isEmpty()) {
- // try to find java automatically based on the given jar file
- std::wstring jarFileW = jarFile.toStdWString();
-
- WCHAR buffer[MAX_PATH];
- if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
- DWORD binaryType = 0UL;
- if (!::GetBinaryTypeW(buffer, &binaryType)) {
- log::debug(
- "failed to determine binary type of \"{}\": {}",
- QString::fromWCharArray(buffer), ::GetLastError());
- } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) {
- return QString::fromWCharArray(buffer);
- }
- }
- }
-
- // second attempt: look to the registry
- QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat);
- if (reg.contains("CurrentVersion")) {
- QString currentVersion = reg.value("CurrentVersion").toString();
- return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe");
- }
-
- // not found
- return {};
-}
-
-bool OrganizerCore::getFileExecutionContext(
- QWidget* parent, const QFileInfo &targetInfo,
- QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type)
-{
- QString extension = targetInfo.suffix();
- if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) ||
- (extension.compare("com", Qt::CaseInsensitive) == 0) ||
- (extension.compare("bat", Qt::CaseInsensitive) == 0)) {
- binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe");
- arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
- type = FileExecutionTypes::Executable;
- return true;
- } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) {
- binaryInfo = targetInfo;
- type = FileExecutionTypes::Executable;
- return true;
- } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) {
- auto java = findJavaInstallation(targetInfo.absoluteFilePath());
-
- if (java.isEmpty()) {
- java = QFileDialog::getOpenFileName(
- parent, QObject::tr("Select binary"),
- QString(), QObject::tr("Binary") + " (*.exe)");
- }
-
- if (java.isEmpty()) {
- return false;
- }
-
- binaryInfo = QFileInfo(java);
- arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
- type = FileExecutionTypes::Executable;
-
- return true;
- } else {
- type = FileExecutionTypes::Other;
- return true;
- }
-}
-
bool OrganizerCore::previewFileWithAlternatives(
QWidget* parent, QString fileName, int selectedOrigin)
{
@@ -1177,35 +1107,23 @@ bool OrganizerCore::previewFile(
bool OrganizerCore::runFile(
QWidget* parent, const QFileInfo& targetInfo)
{
- QFileInfo binaryInfo;
- QString arguments;
- FileExecutionTypes type;
+ const auto fec = spawn::getFileExecutionContext(parent, targetInfo);
- if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) {
- return false;
- }
-
- switch (type)
+ switch (fec.type)
{
- case FileExecutionTypes::Executable: {
- runExecutableFile(
- binaryInfo, arguments, currentProfile()->name(),
- targetInfo.absolutePath());
-
+ case spawn::FileExecutionTypes::Executable:
+ {
+ runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir());
return true;
}
- case FileExecutionTypes::Other: {
- ::ShellExecuteW(nullptr, L"open",
- ToWString(targetInfo.absoluteFilePath()).c_str(),
- nullptr, nullptr, SW_SHOWNORMAL);
-
- return true;
+ case spawn::FileExecutionTypes::Other: // fall-through
+ default:
+ {
+ const auto r = shell::Open(targetInfo.absoluteFilePath());
+ return r.success();
}
}
-
- // nop
- return false;
}
bool OrganizerCore::runExecutableFile(
@@ -1281,6 +1199,87 @@ bool OrganizerCore::runShortcut(const MOShortcut& shortcut)
return runExecutable(exe, false);
}
+HANDLE OrganizerCore::runExecutableOrExecutableFile(
+ const QString &executable, const QStringList &args, const QString &cwd,
+ const QString &profile, const QString &forcedCustomOverwrite,
+ bool ignoreCustomOverwrite)
+{
+ QString profileName = profile;
+ if (profile == "") {
+ if (m_CurrentProfile != nullptr) {
+ profileName = m_CurrentProfile->name();
+ } else {
+ throw MyException(tr("No profile set"));
+ }
+ }
+
+ QFileInfo binary;
+ QString arguments = args.join(" ");
+ QString currentDirectory = cwd;
+ QString steamAppID;
+ QString customOverwrite;
+ QList forcedLibraries;
+
+ if (executable.contains('\\') || executable.contains('/')) {
+ // file path
+
+ binary = QFileInfo(executable);
+ if (binary.isRelative()) {
+ // relative path, should be relative to game directory
+ binary = managedGame()->gameDirectory().absoluteFilePath(executable);
+ }
+
+ if (currentDirectory == "") {
+ currentDirectory = binary.absolutePath();
+ }
+
+ try {
+ const Executable& exe = m_ExecutablesList.getByBinary(binary);
+ steamAppID = exe.steamAppID();
+ customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
+ if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
+ }
+ } catch (const std::runtime_error &) {
+ // nop
+ }
+ } else {
+ // only a file name, search executables list
+ try {
+ const Executable &exe = m_ExecutablesList.get(executable);
+ steamAppID = exe.steamAppID();
+ customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
+ if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
+ }
+ if (arguments == "") {
+ arguments = exe.arguments();
+ }
+ binary = exe.binaryInfo();
+ if (currentDirectory == "") {
+ currentDirectory = exe.workingDirectory();
+ }
+ } catch (const std::runtime_error &) {
+ log::warn("\"{}\" not set up as executable", executable);
+ binary = QFileInfo(executable);
+ }
+ }
+
+ if (!forcedCustomOverwrite.isEmpty())
+ customOverwrite = forcedCustomOverwrite;
+
+ if (ignoreCustomOverwrite)
+ customOverwrite.clear();
+
+ return spawnAndWait(binary,
+ arguments,
+ profileName,
+ currentDirectory,
+ steamAppID,
+ customOverwrite,
+ forcedLibraries);
+}
+
HANDLE OrganizerCore::spawnAndWait(
const QFileInfo &binary, const QString &arguments, const QString &profileName,
const QDir ¤tDirectory, const QString &steamAppID,
@@ -1362,87 +1361,6 @@ HANDLE OrganizerCore::spawnAndWait(
return handle;
}
-HANDLE OrganizerCore::runExecutableOrExecutableFile(
- const QString &executable, const QStringList &args, const QString &cwd,
- const QString &profile, const QString &forcedCustomOverwrite,
- bool ignoreCustomOverwrite)
-{
- QString profileName = profile;
- if (profile == "") {
- if (m_CurrentProfile != nullptr) {
- profileName = m_CurrentProfile->name();
- } else {
- throw MyException(tr("No profile set"));
- }
- }
-
- QFileInfo binary;
- QString arguments = args.join(" ");
- QString currentDirectory = cwd;
- QString steamAppID;
- QString customOverwrite;
- QList forcedLibraries;
-
- if (executable.contains('\\') || executable.contains('/')) {
- // file path
-
- binary = QFileInfo(executable);
- if (binary.isRelative()) {
- // relative path, should be relative to game directory
- binary = managedGame()->gameDirectory().absoluteFilePath(executable);
- }
-
- if (currentDirectory == "") {
- currentDirectory = binary.absolutePath();
- }
-
- try {
- const Executable& exe = m_ExecutablesList.getByBinary(binary);
- steamAppID = exe.steamAppID();
- customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- } catch (const std::runtime_error &) {
- // nop
- }
- } else {
- // only a file name, search executables list
- try {
- const Executable &exe = m_ExecutablesList.get(executable);
- steamAppID = exe.steamAppID();
- customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- if (arguments == "") {
- arguments = exe.arguments();
- }
- binary = exe.binaryInfo();
- if (currentDirectory == "") {
- currentDirectory = exe.workingDirectory();
- }
- } catch (const std::runtime_error &) {
- log::warn("\"{}\" not set up as executable", executable);
- binary = QFileInfo(executable);
- }
- }
-
- if (!forcedCustomOverwrite.isEmpty())
- customOverwrite = forcedCustomOverwrite;
-
- if (ignoreCustomOverwrite)
- customOverwrite.clear();
-
- return spawnAndWait(binary,
- arguments,
- profileName,
- currentDirectory,
- steamAppID,
- customOverwrite,
- forcedLibraries);
-}
-
bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
{
if (!Settings::instance().interface().lockGUI())
diff --git a/src/organizercore.h b/src/organizercore.h
index fa05a20f..f802c8cb 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -91,12 +91,6 @@ private:
typedef boost::signals2::signal SignalModInstalled;
public:
- enum class FileExecutionTypes
- {
- Executable = 1,
- Other = 2
- };
-
static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
OrganizerCore(Settings &settings);
@@ -152,12 +146,6 @@ public:
void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); }
void loggedInAction(QWidget* parent, std::function f);
- static QString findJavaInstallation(const QString& jarFile={});
-
- static bool getFileExecutionContext(
- QWidget* parent, const QFileInfo &targetInfo,
- QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type);
-
bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1);
bool previewFile(QWidget* parent, const QString& originName, const QString& path);
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 3c7d64ce..dd93bfaa 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -901,11 +901,11 @@ SpawnedProcess Spawner::spawn(
return {INVALID_HANDLE_VALUE, sp};
}
- if (!spawn::checkEnvironment(parent, sp)) {
+ if (!checkEnvironment(parent, sp)) {
return {INVALID_HANDLE_VALUE, sp};
}
- if (!spawn::checkBlacklist(parent, sp, settings)) {
+ if (!checkBlacklist(parent, sp, settings)) {
return {INVALID_HANDLE_VALUE, sp};
}
@@ -914,6 +914,185 @@ SpawnedProcess Spawner::spawn(
return {startBinary(parent, sp), sp};
}
+
+QString getExecutableForJarFile(const QString& jarFile)
+{
+ const std::wstring jarFileW = jarFile.toStdWString();
+
+ WCHAR buffer[MAX_PATH];
+
+ const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer);
+ const auto r = static_cast(reinterpret_cast(hinst));
+
+ // anything <= 32 signals failure
+ if (r <= 32) {
+ log::warn(
+ "failed to find executable associated with file '{}', {}",
+ jarFile, shell::formatError(r));
+
+ return {};
+ }
+
+ DWORD binaryType = 0;
+
+ if (!::GetBinaryTypeW(buffer, &binaryType)) {
+ const auto e = ::GetLastError();
+
+ log::warn(
+ "failed to determine binary type of '{}', {}",
+ QString::fromWCharArray(buffer), formatSystemMessage(e));
+
+ return {};
+ }
+
+ if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) {
+ log::warn(
+ "unexpected binary type {} for file '{}'",
+ binaryType, QString::fromWCharArray(buffer));
+
+ return {};
+ }
+
+ return QString::fromWCharArray(buffer);
+}
+
+QString getJavaHome()
+{
+ const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment";
+ const QString value = "CurrentVersion";
+
+ QSettings reg(key, QSettings::NativeFormat);
+
+ if (!reg.contains(value)) {
+ log::warn("key '{}\\{}' doesn't exist", key, value);
+ return {};
+ }
+
+ const QString currentVersion = reg.value("CurrentVersion").toString();
+ const QString javaHome = QString("%1/JavaHome").arg(currentVersion);
+
+ if (!reg.contains(javaHome)) {
+ log::warn(
+ "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist",
+ currentVersion, key, value, key, javaHome);
+
+ return {};
+ }
+
+ const auto path = reg.value(javaHome).toString();
+ return path + "\\bin\\javaw.exe";
+}
+
+QString findJavaInstallation(const QString& jarFile)
+{
+ // try to find java automatically based on the given jar file
+ if (!jarFile.isEmpty()) {
+ const auto s = getExecutableForJarFile(jarFile);
+ if (!s.isEmpty()) {
+ return s;
+ }
+ }
+
+ // second attempt: look to the registry
+ const auto s = getJavaHome();
+ if (!s.isEmpty()) {
+ return s;
+ }
+
+ // not found
+ return {};
+}
+
+bool isBatchFile(const QFileInfo& target)
+{
+ const auto batchExtensions = {"cmd", "bat"};
+
+ const QString extension = target.suffix();
+ for (auto&& e : batchExtensions) {
+ if (extension.compare(e, Qt::CaseInsensitive) == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isExeFile(const QFileInfo& target)
+{
+ return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0);
+}
+
+bool isJavaFile(const QFileInfo& target)
+{
+ return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0);
+}
+
+QFileInfo getCmdPath()
+{
+ const auto p = env::get("COMSPEC2");
+ if (!p.isEmpty()) {
+ return p;
+ }
+
+ QString systemDirectory;
+
+ const std::size_t buffer_size = 1000;
+ wchar_t buffer[buffer_size + 1] = {};
+
+ const auto length = ::GetSystemDirectoryW(buffer, buffer_size);
+ if (length != 0) {
+ systemDirectory = QString::fromWCharArray(buffer, length);
+
+ if (!systemDirectory.endsWith("\\")) {
+ systemDirectory += "\\";
+ }
+ } else {
+ systemDirectory = "C:\\Windows\\System32\\";
+ }
+
+ return systemDirectory + "cmd.exe";
+}
+
+FileExecutionContext getFileExecutionContext(
+ QWidget* parent, const QFileInfo& target)
+{
+ if (isExeFile(target)) {
+ return {
+ target,
+ "",
+ FileExecutionTypes::Executable
+ };
+ }
+
+ if (isBatchFile(target)) {
+ return {
+ getCmdPath(),
+ QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())),
+ FileExecutionTypes::Executable
+ };
+ }
+
+ if (isJavaFile(target)) {
+ auto java = findJavaInstallation(target.absoluteFilePath());
+
+ if (java.isEmpty()) {
+ java = QFileDialog::getOpenFileName(
+ parent, QObject::tr("Select binary"),
+ QString(), QObject::tr("Binary") + " (*.exe)");
+ }
+
+ if (!java.isEmpty()) {
+ return {
+ QFileInfo(java),
+ QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())),
+ FileExecutionTypes::Executable
+ };
+ }
+ }
+
+ return {{}, {}, FileExecutionTypes::Other};
+}
+
} // namespace
diff --git a/src/spawn.h b/src/spawn.h
index d2853cd5..866e1795 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -103,6 +103,25 @@ public:
private:
};
+
+enum class FileExecutionTypes
+{
+ Executable = 1,
+ Other
+};
+
+struct FileExecutionContext
+{
+ QFileInfo binary;
+ QString arguments;
+ FileExecutionTypes type;
+};
+
+QString findJavaInstallation(const QString& jarFile);
+
+FileExecutionContext getFileExecutionContext(
+ QWidget* parent, const QFileInfo& target);
+
} // namespace
--
cgit v1.3.1
From 8f24f6298f62e36db1c7a624052e70b41c5e7e27 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 24 Oct 2019 05:27:39 -0400
Subject: wait for executable when opening files
---
src/organizercore.cpp | 22 ++++++++++++++++++++--
src/organizercore.h | 5 ++++-
src/spawn.cpp | 4 ++--
3 files changed, 26 insertions(+), 5 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 78f9517c..d3f4a83c 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1120,8 +1120,19 @@ bool OrganizerCore::runFile(
case spawn::FileExecutionTypes::Other: // fall-through
default:
{
- const auto r = shell::Open(targetInfo.absoluteFilePath());
- return r.success();
+ auto r = shell::Open(targetInfo.absoluteFilePath());
+ if (!r.success()) {
+ return false;
+ }
+
+ // not all files will return a valid handle even if opening them was
+ // successful, such as inproc handlers (like the photo viewer)
+ if (r.processHandle() != INVALID_HANDLE_VALUE) {
+ // steal because it gets closed after the wait
+ return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr);
+ }
+
+ return true;
}
}
}
@@ -1333,6 +1344,13 @@ HANDLE OrganizerCore::spawnAndWait(
return INVALID_HANDLE_VALUE;
}
+ waitForProcessCompletionWithLock(handle, exitCode);
+ return handle;
+}
+
+bool OrganizerCore::waitForProcessCompletionWithLock(
+ HANDLE handle, LPDWORD exitCode)
+{
if (Settings::instance().interface().lockGUI()) {
std::unique_ptr dlg;
ILockedWaitingForProcess* uilock = nullptr;
diff --git a/src/organizercore.h b/src/organizercore.h
index f802c8cb..3d3c7325 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -306,7 +306,10 @@ private:
const QList &forcedLibraries = QList(),
LPDWORD exitCode = nullptr);
- bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
+ bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode);
+
+ bool waitForProcessCompletion(
+ HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
private slots:
diff --git a/src/spawn.cpp b/src/spawn.cpp
index dd93bfaa..fe1e9e3e 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -1029,7 +1029,7 @@ bool isJavaFile(const QFileInfo& target)
QFileInfo getCmdPath()
{
- const auto p = env::get("COMSPEC2");
+ const auto p = env::get("COMSPEC");
if (!p.isEmpty()) {
return p;
}
@@ -1111,7 +1111,7 @@ bool helperExec(
{
SHELLEXECUTEINFOW execInfo = {};
- ULONG flags = SEE_MASK_FLAG_NO_UI ;
+ ULONG flags = SEE_MASK_FLAG_NO_UI;
if (!async)
flags |= SEE_MASK_NOCLOSEPROCESS;
--
cgit v1.3.1
From bff5a22f48b933fe9eba3a15497882ddf2a03990 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 24 Oct 2019 07:11:08 -0400
Subject: spawning an executable now only waits for that particular process
added waitForAllUSVFSProcesses() to OrganizerCore, used when closing MO
---
src/envmodule.cpp | 73 ++++++++++++++++++++++---
src/envmodule.h | 3 ++
src/mainwindow.cpp | 14 ++---
src/organizercore.cpp | 145 ++++++++++++++++++++++++++------------------------
src/organizercore.h | 11 +++-
src/spawn.cpp | 63 ++++++++++++++++++++++
src/spawn.h | 14 +++++
7 files changed, 233 insertions(+), 90 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index 3f1f8912..abbe02e5 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -408,7 +408,8 @@ std::vector getLoadedModules()
}
-std::vector getRunningProcesses()
+template
+void forEachRunningProcess(F&& f)
{
HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
@@ -416,7 +417,7 @@ std::vector getRunningProcesses()
{
const auto e = GetLastError();
log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e));
- return {};
+ return;
}
PROCESSENTRY32 entry = {};
@@ -427,16 +428,14 @@ std::vector getRunningProcesses()
if (!Process32First(snapshot.get(), &entry)) {
const auto e = GetLastError();
log::error("Process32First() failed, {}", formatSystemMessage(e));
- return {};
+ return;
}
- std::vector v;
-
for (;;)
{
- v.push_back(Process(
- entry.th32ProcessID,
- QString::fromStdWString(entry.szExeFile)));
+ if (!f(entry)) {
+ break;
+ }
// next process
if (!Process32Next(snapshot.get(), &entry))
@@ -450,8 +449,66 @@ std::vector getRunningProcesses()
break;
}
}
+}
+
+std::vector getRunningProcesses()
+{
+ std::vector v;
+
+ forEachRunningProcess([&](auto&& entry) {
+ v.push_back(Process(
+ entry.th32ProcessID,
+ QString::fromStdWString(entry.szExeFile)));
+
+ return true;
+ });
return v;
}
+QString getProcessName(HANDLE process)
+{
+ const QString badName = "unknown";
+
+ if (process == 0 || process == INVALID_HANDLE_VALUE) {
+ return badName;
+ }
+
+ const DWORD bufferSize = MAX_PATH;
+ wchar_t buffer[bufferSize + 1] = {};
+
+ const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize);
+
+ if (realSize == 0) {
+ const auto e = ::GetLastError();
+ log::error("GetProcessImageFileNameW() failed, {}", formatSystemMessage(e));
+ return badName;
+ }
+
+ auto s = QString::fromWCharArray(buffer, realSize);
+
+ const auto lastSlash = s.lastIndexOf("\\");
+ if (lastSlash != -1) {
+ s = s.mid(lastSlash + 1);
+ }
+
+ return s;
+}
+
+DWORD getProcessParentID(DWORD pid)
+{
+ DWORD ppid = 0;
+
+ forEachRunningProcess([&](auto&& entry) {
+ if (entry.th32ProcessID == pid) {
+ ppid = entry.th32ParentProcessID;
+ return false;
+ }
+
+ return true;
+ });
+
+ return ppid;
+}
+
} // namespace
diff --git a/src/envmodule.h b/src/envmodule.h
index deb7520f..212f6f7b 100644
--- a/src/envmodule.h
+++ b/src/envmodule.h
@@ -120,6 +120,9 @@ private:
std::vector getRunningProcesses();
std::vector getLoadedModules();
+QString getProcessName(HANDLE process);
+DWORD getProcessParentID(DWORD pid);
+
} // namespace env
#endif // ENV_MODULE_H
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 0181a335..bbb63333 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1311,16 +1311,10 @@ bool MainWindow::canExit()
}
}
- std::vector hiddenList;
- hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
- HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId());
- if (injected_process_still_running != INVALID_HANDLE_VALUE)
- {
- m_exitAfterWait = true;
- m_OrganizerCore.waitForApplication(injected_process_still_running);
- if (!m_exitAfterWait) { // if operation cancelled
- return false;
- }
+ m_exitAfterWait = true;
+ m_OrganizerCore.waitForAllUSVFSProcessesWithLock();
+ if (!m_exitAfterWait) { // if operation cancelled
+ return false;
}
setCursor(Qt::WaitCursor);
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index d3f4a83c..2a228fda 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -34,6 +34,7 @@
#include "instancemanager.h"
#include
#include "previewdialog.h"
+#include "envmodule.h"
#include
#include
@@ -74,47 +75,6 @@ using namespace MOBase;
//static
CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None;
-static std::wstring getProcessName(HANDLE process)
-{
- wchar_t buffer[MAX_PATH];
- const wchar_t *fileName = L"unknown";
-
- if (process == nullptr) return fileName;
-
- if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) {
- fileName = wcsrchr(buffer, L'\\');
- if (fileName == nullptr) {
- fileName = buffer;
- }
- else {
- fileName += 1;
- }
- }
-
- return fileName;
-}
-
-// Get parent PID for the given process, return 0 on failure
-static DWORD getProcessParentID(DWORD pid)
-{
- HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
- PROCESSENTRY32 pe = { 0 };
- pe.dwSize = sizeof(PROCESSENTRY32);
-
- DWORD res = 0;
- if (Process32First(th, &pe))
- do {
- if (pe.th32ProcessID == pid) {
- res = pe.th32ParentProcessID;
- break;
- }
- } while (Process32Next(th, &pe));
-
- CloseHandle(th);
-
- return res;
-}
-
template
QStringList toStringList(InputIterator current, InputIterator end)
{
@@ -1348,35 +1308,46 @@ HANDLE OrganizerCore::spawnAndWait(
return handle;
}
-bool OrganizerCore::waitForProcessCompletionWithLock(
- HANDLE handle, LPDWORD exitCode)
+void OrganizerCore::withLock(std::function f)
{
- if (Settings::instance().interface().lockGUI()) {
- std::unique_ptr dlg;
- ILockedWaitingForProcess* uilock = nullptr;
+ std::unique_ptr dlg;
+ ILockedWaitingForProcess* uilock = nullptr;
+
+ if (m_MainWindow != nullptr) {
+ uilock = m_MainWindow->lock();
+ }
+ else {
+ // i.e. when running command line shortcuts there is no user interface
+ dlg.reset(new LockedDialog);
+ dlg->show();
+ dlg->setEnabled(true);
+ uilock = dlg.get();
+ }
+ ON_BLOCK_EXIT([&]() {
if (m_MainWindow != nullptr) {
- uilock = m_MainWindow->lock();
- }
- else {
- // i.e. when running command line shortcuts there is no user interface
- dlg.reset(new LockedDialog);
- dlg->show();
- dlg->setEnabled(true);
- uilock = dlg.get();
- }
+ m_MainWindow->unlock();
+ } });
- ON_BLOCK_EXIT([&]() {
- if (m_MainWindow != nullptr) {
- m_MainWindow->unlock();
- } });
+ f(uilock);
+}
+
+bool OrganizerCore::waitForProcessCompletionWithLock(
+ HANDLE handle, LPDWORD exitCode)
+{
+ if (!Settings::instance().interface().lockGUI()) {
+ return true;
+ }
+
+ bool r = false;
+ withLock([&](auto* uilock) {
DWORD ignoreExitCode;
- waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock);
+ r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock);
cycleDiagnostics();
- }
+ });
- return handle;
+ return r;
}
bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
@@ -1393,30 +1364,64 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
if (m_MainWindow != nullptr) {
m_MainWindow->unlock();
} });
+
return waitForProcessCompletion(handle, exitCode, uilock);
}
-bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
+bool OrganizerCore::waitForProcessCompletion(
+ HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
+{
+ const auto r = spawn::waitForProcess(handle, exitCode, uilock);
+
+ switch (r)
+ {
+ case spawn::WaitResults::Completed: // fall-through
+ case spawn::WaitResults::Unlocked:
+ return true;
+
+ case spawn::WaitResults::Error: // fall-through
+ default:
+ return false;
+ }
+}
+
+bool OrganizerCore::waitForAllUSVFSProcessesWithLock()
{
+ bool r = false;
+
+ withLock([&](auto* uilock) {
+ r = waitForAllUSVFSProcesses(uilock);
+ });
+
+ return r;
+}
+
+bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
+{
+ // Certain process names we wish to "hide" for aesthetic reason:
+ std::vector hiddenList;
+ hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
+
bool originalHandle = true;
bool newHandle = true;
bool uiunlocked = false;
+ HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId());
+ DWORD* exitCode = nullptr;
DWORD currentPID = 0;
QString processName;
+
auto waitForChildUntil = GetTickCount64();
if (handle != INVALID_HANDLE_VALUE) {
currentPID = GetProcessId(handle);
- processName = QString::fromStdWString(getProcessName(handle));
+ processName = env::getProcessName(handle);
}
- // Certain process names we wish to "hide" for aesthetic reason:
bool waitingOnHidden = false;
- std::vector hiddenList;
- hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
for (QString hide : hiddenList)
if (processName.contains(hide, Qt::CaseInsensitive))
waitingOnHidden = true;
+
// The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes.
// On the one hand we want to display the real executable without it feeling laggy, on the other we don't want
// to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden"
@@ -1489,7 +1494,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL
newHandle = handle != INVALID_HANDLE_VALUE;
if (newHandle) {
currentPID = GetProcessId(handle);
- processName = QString::fromStdWString(getProcessName(handle));
+ processName = env::getProcessName(handle);
for (QString hide : hiddenList)
if (processName.contains(hide, Qt::CaseInsensitive))
waitingOnHidden = true;
@@ -1541,13 +1546,13 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde
continue;
}
- QString pname = QString::fromStdWString(getProcessName(handle));
+ QString pname = env::getProcessName(handle);
bool phidden = false;
for (auto hide : hiddenList)
if (pname.contains(hide, Qt::CaseInsensitive))
phidden = true;
- bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid;
+ bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid;
if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) {
if (best_match != INVALID_HANDLE_VALUE)
diff --git a/src/organizercore.h b/src/organizercore.h
index 3d3c7325..ffdb6830 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -167,6 +167,8 @@ public:
const QString &profile, const QString &forcedCustomOverwrite = "",
bool ignoreCustomOverwrite = false);
+ bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr);
+ bool waitForAllUSVFSProcessesWithLock();
void loginSuccessfulUpdate(bool necessary);
void loginFailedUpdate(const QString &message);
@@ -221,8 +223,6 @@ public:
DownloadManager *downloadManager();
PluginList *pluginList();
ModList *modList();
- bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr);
- HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid);
bool onModInstalled(const std::function &func);
bool onAboutToRun(const std::function &func);
bool onFinishedRun(const std::function &func);
@@ -311,6 +311,13 @@ private:
bool waitForProcessCompletion(
HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
+ bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock);
+
+ void withLock(std::function f);
+
+ HANDLE findAndOpenAUSVFSProcess(
+ const std::vector& hiddenList, DWORD preferedParentPid);
+
private slots:
void directory_refreshed();
diff --git a/src/spawn.cpp b/src/spawn.cpp
index fe1e9e3e..f0b3b2c7 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -28,6 +28,7 @@ along with Mod Organizer. If not, see .
#include "settings.h"
#include "settingsdialogworkarounds.h"
#include
+#include
#include
#include
#include
@@ -1093,6 +1094,68 @@ FileExecutionContext getFileExecutionContext(
return {{}, {}, FileExecutionTypes::Other};
}
+WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock)
+{
+ if (handle == INVALID_HANDLE_VALUE) {
+ return WaitResults::Error;
+ }
+
+ const DWORD pid = ::GetProcessId(handle);
+ const QString processName = QString("%1 (%2)")
+ .arg(env::getProcessName(handle))
+ .arg(pid);
+
+ if (uilock)
+ uilock->setProcessName(processName);
+
+ constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1;
+ DWORD res = WAIT_TIMEOUT;
+
+ log::debug(
+ "waiting for process completion '{}' ({})",
+ processName, pid);
+
+ for (;;) {
+ // Wait for a an event on the handle, a key press, mouse click or timeout
+ const auto res = MsgWaitForMultipleObjects(
+ 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON);
+
+ if (res == WAIT_FAILED) {
+ // error
+ const auto e = ::GetLastError();
+
+ log::error(
+ "failed waiting for process completion '{}' ({}), {}",
+ processName, pid, formatSystemMessage(e));
+
+ return WaitResults::Error;
+ } else if (res == WAIT_OBJECT_0) {
+ // completed
+ log::debug("process '{}' ({}) completed", processName, pid);
+
+ if (exitCode) {
+ if (!::GetExitCodeProcess(handle, exitCode)) {
+ const auto e = ::GetLastError();
+ log::warn(
+ "failed to get exit code of process '{}' ({}): {}",
+ processName, pid, formatSystemMessage(e));
+ }
+ }
+
+ return WaitResults::Completed;
+ }
+
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::sendPostedEvents();
+ QCoreApplication::processEvents();
+
+ if (uilock && uilock->unlockForced()) {
+ log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid);
+ return WaitResults::Unlocked;
+ }
+ }
+}
+
} // namespace
diff --git a/src/spawn.h b/src/spawn.h
index 866e1795..441cad2c 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -27,6 +27,8 @@ along with Mod Organizer. If not, see .
#include
class Settings;
+class ILockedWaitingForProcess;
+
namespace MOBase { class IPluginGame; }
namespace spawn
@@ -84,6 +86,7 @@ public:
~SpawnedProcess();
HANDLE releaseHandle();
+ void wait();
private:
HANDLE m_handle;
@@ -122,6 +125,17 @@ QString findJavaInstallation(const QString& jarFile);
FileExecutionContext getFileExecutionContext(
QWidget* parent, const QFileInfo& target);
+
+enum class WaitResults
+{
+ Completed = 1,
+ Error,
+ Unlocked
+};
+
+WaitResults waitForProcess(
+ HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock);
+
} // namespace
--
cgit v1.3.1
From 18b438cf27a552e69e984bfee63187b6471682ab Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Tue, 29 Oct 2019 07:28:12 -0400
Subject: split to getRunningUSVFSProcesses() simplified
waitForAllUSVFSProcesses() to always get the list of running processes after
one process completes
---
src/envmodule.cpp | 6 ++
src/envmodule.h | 2 +
src/organizercore.cpp | 224 ++++++++++++++-----------------------------------
src/spawn.cpp | 53 ++++++++----
src/spawn.h | 4 +
src/usvfsconnector.cpp | 47 +++++++++++
src/usvfsconnector.h | 2 +
7 files changed, 163 insertions(+), 175 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index abbe02e5..160a54fa 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -511,4 +511,10 @@ DWORD getProcessParentID(DWORD pid)
return ppid;
}
+
+DWORD getProcessParentID(HANDLE handle)
+{
+ return getProcessParentID(GetProcessId(handle));
+}
+
} // namespace
diff --git a/src/envmodule.h b/src/envmodule.h
index 212f6f7b..6c0a028d 100644
--- a/src/envmodule.h
+++ b/src/envmodule.h
@@ -121,7 +121,9 @@ std::vector getRunningProcesses();
std::vector getLoadedModules();
QString getProcessName(HANDLE process);
+
DWORD getProcessParentID(DWORD pid);
+DWORD getProcessParentID(HANDLE handle);
} // namespace env
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 2a228fda..2a97b998 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1355,17 +1355,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
if (!Settings::instance().interface().lockGUI())
return true;
- ILockedWaitingForProcess* uilock = nullptr;
- if (m_MainWindow != nullptr) {
- uilock = m_MainWindow->lock();
- }
+ bool r = false;
- ON_BLOCK_EXIT([&] () {
- if (m_MainWindow != nullptr) {
- m_MainWindow->unlock();
- } });
+ withLock([&](auto* uilock) {
+ r = waitForProcessCompletion(handle, exitCode, uilock);
+ });
- return waitForProcessCompletion(handle, exitCode, uilock);
+ return r;
}
bool OrganizerCore::waitForProcessCompletion(
@@ -1387,6 +1383,9 @@ bool OrganizerCore::waitForProcessCompletion(
bool OrganizerCore::waitForAllUSVFSProcessesWithLock()
{
+ if (!Settings::instance().interface().lockGUI())
+ return true;
+
bool r = false;
withLock([&](auto* uilock) {
@@ -1396,178 +1395,81 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock()
return r;
}
-bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
+HANDLE getInterestingProcess(
+ const std::vector& handles,
+ const std::vector& hidden, DWORD preferedParentPid)
{
- // Certain process names we wish to "hide" for aesthetic reason:
- std::vector hiddenList;
- hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
-
- bool originalHandle = true;
- bool newHandle = true;
- bool uiunlocked = false;
-
- HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId());
- DWORD* exitCode = nullptr;
- DWORD currentPID = 0;
- QString processName;
-
- auto waitForChildUntil = GetTickCount64();
- if (handle != INVALID_HANDLE_VALUE) {
- currentPID = GetProcessId(handle);
- processName = env::getProcessName(handle);
- }
-
- bool waitingOnHidden = false;
- for (QString hide : hiddenList)
- if (processName.contains(hide, Qt::CaseInsensitive))
- waitingOnHidden = true;
-
- // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes.
- // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want
- // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden"
- // process. For this reason we use exponential backoff and also start with a delibrately low value to improve
- // the responsiveness of the initial update
- DWORD64 nextHiddenCheck = GetTickCount64();
- DWORD64 nextHiddenCheckDelay = 50;
-
- constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1;
- DWORD res = WAIT_TIMEOUT;
- while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT))
- {
- if (newHandle) {
- processName += QString(" (%1)").arg(currentPID);
- if (uilock)
- uilock->setProcessName(processName);
-
- log::debug(
- "Waiting for {} process completion: {}",
- (originalHandle ? "spawned" : "usvfs"), processName);
-
- newHandle = false;
- }
+ HANDLE best_match = INVALID_HANDLE_VALUE;
+ bool best_match_hidden = true;
- // Wait for a an event on the handle, a key press, mouse click or timeout
- res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON);
- if (res == WAIT_FAILED) {
- log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError());
- break;
- }
+ for (auto handle : handles) {
+ const QString pname = env::getProcessName(handle);
- // keep processing events so the app doesn't appear dead
- QCoreApplication::sendPostedEvents();
- QCoreApplication::processEvents();
+ bool phidden = false;
+ for (auto h : hidden)
+ if (pname.contains(h, Qt::CaseInsensitive))
+ phidden = true;
- if (uilock && uilock->unlockForced()) {
- uiunlocked = true;
- break;
- }
+ bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid;
- if (res == WAIT_OBJECT_0) {
- // process we were waiting on has completed
- if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode))
- log::warn("Failed getting exit code of complete process: {}", GetLastError());
- CloseHandle(handle);
- handle = INVALID_HANDLE_VALUE;
- originalHandle = false;
- // if the previous process spawned a child process and immediately exits we may miss it if we check immediately
- waitForChildUntil = GetTickCount64() + 800;
+ if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) {
+ best_match = handle;
+ best_match_hidden = phidden;
}
- // search for another process to wait on if either:
- // 1. we just completed waiting for a process and need to find/wait for an inject child
- // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to wait on
- bool firstIteration = true;
- while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil)
- || (waitingOnHidden && GetTickCount64() >= nextHiddenCheck))
- {
- if (firstIteration)
- firstIteration = false;
- else {
- QThread::msleep(200);
- QCoreApplication::sendPostedEvents();
- QCoreApplication::processEvents();
- }
-
- // search if there is another usvfs process active
- handle = findAndOpenAUSVFSProcess(hiddenList, currentPID);
- waitingOnHidden = false;
- newHandle = handle != INVALID_HANDLE_VALUE;
- if (newHandle) {
- currentPID = GetProcessId(handle);
- processName = env::getProcessName(handle);
- for (QString hide : hiddenList)
- if (processName.contains(hide, Qt::CaseInsensitive))
- waitingOnHidden = true;
- }
- if (waitingOnHidden) {
- nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay;
- nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64) 2000);
- }
- else {
- nextHiddenCheck = GetTickCount64();
- nextHiddenCheckDelay = 200;
- }
- }
+ if (!phidden && pprefered)
+ return best_match;
}
- if (res == WAIT_OBJECT_0)
- log::debug("Waiting for process completion successfull");
- else if (uiunlocked)
- log::debug("Waiting for process completion aborted by UI");
- else
- log::debug("Waiting for process completion not successfull: {}", res);
-
- if (handle != INVALID_HANDLE_VALUE)
- ::CloseHandle(handle);
-
- return res == WAIT_OBJECT_0;
+ return best_match;
}
-HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid) {
- // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics
- // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid)
- constexpr size_t querySize = 100;
- DWORD pids[querySize];
- size_t found = querySize;
- if (!::GetVFSProcessList(&found, pids)) {
- log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!");
- return INVALID_HANDLE_VALUE;
- }
-
- HANDLE best_match = INVALID_HANDLE_VALUE;
- bool best_match_hidden = true;
- for (size_t i = 0; i < found; ++i) {
- if (pids[i] == GetCurrentProcessId())
- continue; // obviously don't wait for MO process
+bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
+{
+ // Certain process names we wish to "hide" for aesthetic reason:
+ std::vector hiddenList;
+ hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
- HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]);
- if (handle == INVALID_HANDLE_VALUE) {
- log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError());
- continue;
+ for (;;) {
+ const auto handles = getRunningUSVFSProcesses();
+ if (handles.empty()) {
+ break;
}
- QString pname = env::getProcessName(handle);
- bool phidden = false;
- for (auto hide : hiddenList)
- if (pname.contains(hide, Qt::CaseInsensitive))
- phidden = true;
+ const auto interesting = getInterestingProcess(
+ handles, hiddenList, GetCurrentProcessId());
- bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid;
+ if (uilock) {
+ const DWORD pid = ::GetProcessId(interesting);
+ const QString processName = QString("%1 (%2)")
+ .arg(env::getProcessName(interesting))
+ .arg(pid);
- if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) {
- if (best_match != INVALID_HANDLE_VALUE)
- CloseHandle(best_match);
- best_match = handle;
- best_match_hidden = phidden;
+ uilock->setProcessName(processName);
}
- else
- CloseHandle(handle);
- if (!phidden && pprefered)
- return best_match;
+ const auto r = spawn::waitForProcess(interesting, nullptr, uilock);
+
+ switch (r)
+ {
+ case spawn::WaitResults::Completed:
+ // this process is completed, check for others
+ break;
+
+ case spawn::WaitResults::Unlocked:
+ // force unlocked
+ log::debug("waiting for process completion aborted by UI");
+ return true;
+
+ case spawn::WaitResults::Error: // fall-through
+ default:
+ log::debug("waiting for process completion not successful");
+ return false;
+ }
}
- return best_match;
+ log::debug("Waiting for process completion successful");
+ return true;
}
bool OrganizerCore::onAboutToRun(
diff --git a/src/spawn.cpp b/src/spawn.cpp
index f0b3b2c7..1003024f 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -1094,7 +1094,8 @@ FileExecutionContext getFileExecutionContext(
return {{}, {}, FileExecutionTypes::Other};
}
-WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock)
+WaitResults waitForProcess(
+ HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock)
{
if (handle == INVALID_HANDLE_VALUE) {
return WaitResults::Error;
@@ -1108,37 +1109,62 @@ WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProc
if (uilock)
uilock->setProcessName(processName);
- constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1;
- DWORD res = WAIT_TIMEOUT;
-
log::debug(
"waiting for process completion '{}' ({})",
processName, pid);
+ std::vector handles;
+ handles.push_back(handle);
+
+ std::vector exitCodes;
+
+ const auto r = waitForProcesses(handles, exitCodes, uilock);
+ if (exitCode && !exitCodes.empty()) {
+ *exitCode = exitCodes[0];
+ }
+
+ return r;
+}
+
+WaitResults waitForProcesses(
+ const std::vector& handles, std::vector& exitCodes,
+ ILockedWaitingForProcess* uilock)
+{
+ if (handles.empty()) {
+ return WaitResults::Completed;
+ }
+
+ const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size());
+
for (;;) {
// Wait for a an event on the handle, a key press, mouse click or timeout
const auto res = MsgWaitForMultipleObjects(
- 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON);
+ static_cast(handles.size()), &handles[0],
+ TRUE, 50, QS_KEY | QS_MOUSEBUTTON);
if (res == WAIT_FAILED) {
// error
const auto e = ::GetLastError();
log::error(
- "failed waiting for process completion '{}' ({}), {}",
- processName, pid, formatSystemMessage(e));
+ "failed waiting for process completion, {}", formatSystemMessage(e));
return WaitResults::Error;
- } else if (res == WAIT_OBJECT_0) {
+ } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) {
// completed
- log::debug("process '{}' ({}) completed", processName, pid);
+ exitCodes.resize(handles.size());
+ std::fill(exitCodes.begin(), exitCodes.end(), 0);
+
+ for (std::size_t i=0; iunlockForced()) {
- log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid);
return WaitResults::Unlocked;
}
}
diff --git a/src/spawn.h b/src/spawn.h
index 441cad2c..6b947f2f 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -136,6 +136,10 @@ enum class WaitResults
WaitResults waitForProcess(
HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock);
+WaitResults waitForProcesses(
+ const std::vector& handles, std::vector& exitCodes,
+ ILockedWaitingForProcess* uilock);
+
} // namespace
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp
index 311c6dd3..3dba3efc 100644
--- a/src/usvfsconnector.cpp
+++ b/src/usvfsconnector.cpp
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see .
#include "usvfsconnector.h"
#include "settings.h"
#include "organizercore.h"
+#include "envmodule.h"
#include "shared/util.h"
#include
#include
@@ -256,3 +257,49 @@ void UsvfsConnector::updateForcedLibraries(const QList getRunningUSVFSProcesses()
+{
+ std::vector pids;
+
+ {
+ size_t count = 0;
+ DWORD* buffer = nullptr;
+ if (!::GetVFSProcessList2(&count, &buffer)) {
+ log::error("failed to get usvfs process list");
+ return {};
+ }
+
+ if (buffer) {
+ pids.assign(buffer, buffer + count);
+ std::free(buffer);
+ }
+ }
+
+ const auto thisPid = GetCurrentProcessId();
+ std::vector v;
+
+ for (auto&& pid : pids) {
+ if (pid == thisPid) {
+ continue; // obviously don't wait for MO process
+ }
+
+ HANDLE handle = ::OpenProcess(
+ PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ const auto e = GetLastError();
+
+ log::warn(
+ "failed to open usvfs process {}: {}",
+ pid, formatSystemMessage(e));
+
+ continue;
+ }
+
+ v.push_back(handle);
+ }
+
+ return v;
+}
diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h
index d0071678..5982778b 100644
--- a/src/usvfsconnector.h
+++ b/src/usvfsconnector.h
@@ -103,4 +103,6 @@ private:
CrashDumpsType crashDumpsType(int type);
+std::vector getRunningUSVFSProcesses();
+
#endif // USVFSCONNECTOR_H
--
cgit v1.3.1
From 8c72077febaea485200adcf1e9f615902e930def Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Tue, 29 Oct 2019 10:03:59 -0400
Subject: replaced uilock by a progress callback in spawn waiting for process
now gets the whole process tree to find an interesting process
---
src/env.h | 17 ------
src/envmodule.cpp | 107 +++++++++++++++++++++++++++++++++-
src/envmodule.h | 35 ++++++++++-
src/envsecurity.cpp | 1 +
src/envwindows.cpp | 1 +
src/ilockedwaitingforprocess.h | 1 +
src/lockeddialogbase.cpp | 7 ++-
src/lockeddialogbase.h | 4 +-
src/organizercore.cpp | 128 ++++++++++++++++++++++++++---------------
src/spawn.cpp | 29 ++++------
src/spawn.h | 7 +--
11 files changed, 246 insertions(+), 91 deletions(-)
(limited to 'src/spawn.cpp')
diff --git a/src/env.h b/src/env.h
index 1760c7fe..f95d1013 100644
--- a/src/env.h
+++ b/src/env.h
@@ -13,23 +13,6 @@ class WindowsInfo;
class Metrics;
-// used by HandlePtr, calls CloseHandle() as the deleter
-//
-struct HandleCloser
-{
- using pointer = HANDLE;
-
- void operator()(HANDLE h)
- {
- if (h != INVALID_HANDLE_VALUE) {
- ::CloseHandle(h);
- }
- }
-};
-
-using HandlePtr = std::unique_ptr;
-
-
// used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter
//
struct DesktopDCReleaser
diff --git a/src/envmodule.cpp b/src/envmodule.cpp
index 160a54fa..0e2e8ec7 100644
--- a/src/envmodule.cpp
+++ b/src/envmodule.cpp
@@ -320,19 +320,61 @@ QString Module::getMD5() const
}
-Process::Process(DWORD pid, QString name)
- : m_pid(pid), m_name(std::move(name))
+Process::Process()
+ : Process(0, 0, {})
{
}
+Process::Process(HANDLE h)
+ : Process(::GetProcessId(h), 0, {})
+{
+}
+
+Process::Process(DWORD pid, DWORD ppid, QString name)
+ : m_pid(pid), m_ppid(ppid), m_name(std::move(name))
+{
+}
+
+bool Process::isValid() const
+{
+ return (m_pid != 0);
+}
+
DWORD Process::pid() const
{
return m_pid;
}
+DWORD Process::ppid() const
+{
+ if (!m_ppid) {
+ m_ppid = getProcessParentID(m_pid);
+ }
+
+ return *m_ppid;
+}
+
const QString& Process::name() const
{
- return m_name;
+ if (!m_name) {
+ m_name = getProcessName(m_pid);
+ }
+
+ return *m_name;
+}
+
+HandlePtr Process::openHandleForWait() const
+{
+ HandlePtr h(OpenProcess(
+ PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, m_pid));
+
+ if (!h) {
+ const auto e = GetLastError();
+ log::error("can't get name of process {}, {}", m_pid, formatSystemMessage(e));
+ return {};
+ }
+
+ return h;
}
// whether this process can be accessed; fails if the current process doesn't
@@ -353,6 +395,16 @@ bool Process::canAccess() const
return true;
}
+void Process::addChild(Process p)
+{
+ m_children.push_back(p);
+}
+
+std::vector& Process::children()
+{
+ return m_children;
+}
+
std::vector getLoadedModules()
{
@@ -458,6 +510,7 @@ std::vector getRunningProcesses()
forEachRunningProcess([&](auto&& entry) {
v.push_back(Process(
entry.th32ProcessID,
+ entry.th32ParentProcessID,
QString::fromStdWString(entry.szExeFile)));
return true;
@@ -466,6 +519,54 @@ std::vector getRunningProcesses()
return v;
}
+void findChildren(Process& parent, const std::vector& processes)
+{
+ for (auto&& p : processes) {
+ if (p.ppid() == parent.pid()) {
+ Process child = p;
+ findChildren(child, processes);
+
+ parent.addChild(child);
+ }
+ }
+}
+
+Process getProcessTree(HANDLE parent)
+{
+ const auto parentPID = ::GetProcessId(parent);
+ const auto v = getRunningProcesses();
+
+ Process root;
+ for (auto&& p : v) {
+ if (p.pid() == parentPID) {
+ root = p;
+ break;
+ }
+ }
+
+ if (root.pid() == 0) {
+ log::error("process {} is not running", parentPID);
+ return {};
+ }
+
+ findChildren(root, v);
+
+ return root;
+}
+
+QString getProcessName(DWORD pid)
+{
+ HandlePtr h(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
+
+ if (!h) {
+ const auto e = GetLastError();
+ log::error("can't get name of process {}, {}", pid, formatSystemMessage(e));
+ return {};
+ }
+
+ return getProcessName(h.get());
+}
+
QString getProcessName(HANDLE process)
{
const QString badName = "unknown";
diff --git a/src/envmodule.h b/src/envmodule.h
index 6c0a028d..d152b840 100644
--- a/src/envmodule.h
+++ b/src/envmodule.h
@@ -7,6 +7,23 @@
namespace env
{
+// used by HandlePtr, calls CloseHandle() as the deleter
+//
+struct HandleCloser
+{
+ using pointer = HANDLE;
+
+ void operator()(HANDLE h)
+ {
+ if (h != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(h);
+ }
+ }
+};
+
+using HandlePtr = std::unique_ptr;
+
+
// represents one module
//
class Module
@@ -101,25 +118,39 @@ private:
class Process
{
public:
- Process(DWORD pid, QString name);
+ Process();
+ explicit Process(HANDLE h);
+ Process(DWORD pid, DWORD ppid, QString name);
+ bool isValid() const;
DWORD pid() const;
+ DWORD ppid() const;
const QString& name() const;
+ HandlePtr openHandleForWait() const;
+
// whether this process can be accessed; fails if the current process doesn't
// have the proper permissions
//
bool canAccess() const;
+ void addChild(Process p);
+ std::vector& children();
+
private:
DWORD m_pid;
- QString m_name;
+ mutable std::optional m_ppid;
+ mutable std::optional m_name;
+ std::vector m_children;
};
std::vector getRunningProcesses();
std::vector getLoadedModules();
+Process getProcessTree(HANDLE parent);
+
+QString getProcessName(DWORD pid);
QString getProcessName(HANDLE process);
DWORD getProcessParentID(DWORD pid);
diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp
index 786291c6..6d62728b 100644
--- a/src/envsecurity.cpp
+++ b/src/envsecurity.cpp
@@ -1,5 +1,6 @@
#include "envsecurity.h"
#include "env.h"
+#include "envmodule.h"
#include
#include
diff --git a/src/envwindows.cpp b/src/envwindows.cpp
index 3932a9b5..98e78a3e 100644
--- a/src/envwindows.cpp
+++ b/src/envwindows.cpp
@@ -1,5 +1,6 @@
#include "envwindows.h"
#include "env.h"
+#include "envmodule.h"
#include
#include
diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h
index 9475ddb9..4d1e786f 100644
--- a/src/ilockedwaitingforprocess.h
+++ b/src/ilockedwaitingforprocess.h
@@ -8,6 +8,7 @@ class ILockedWaitingForProcess
public:
virtual bool unlockForced() const = 0;
virtual void setProcessName(QString const &) = 0;
+ virtual void setProcessInformation(DWORD pid, const QString& name) = 0;
};
#endif // ILOCKEDWAITINGFORPROCESS_H
diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp
index b18f7429..0876a511 100644
--- a/src/lockeddialogbase.cpp
+++ b/src/lockeddialogbase.cpp
@@ -18,7 +18,7 @@ along with Mod Organizer. If not, see .
*/
#include "lockeddialogbase.h"
-
+#include "envmodule.h"
#include
#include
#include
@@ -63,6 +63,11 @@ bool LockedDialogBase::canceled() const {
return m_Canceled;
}
+void LockedDialogBase::setProcessInformation(DWORD pid, const QString& name)
+{
+ setProcessName(QString("%1 (%2)").arg(name).arg(pid));
+}
+
void LockedDialogBase::unlock() {
m_Unlocked = true;
}
diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h
index 3c974a38..4ebad4c7 100644
--- a/src/lockeddialogbase.h
+++ b/src/lockeddialogbase.h
@@ -30,7 +30,7 @@ class QWidget;
/**
* a small borderless dialog displayed while the Mod Organizer UI is locked
* The dialog contains only a label and a button to force the UI to be unlocked
- *
+ *
* The UI gets locked while running external applications since they may modify the
* data on which Mod Organizer works. After the UI is unlocked (manually or after the
* external application closed) MO will refresh all of its data sources
@@ -46,6 +46,8 @@ public:
virtual bool canceled() const;
+ void setProcessInformation(DWORD pid, const QString& name) override;
+
protected:
virtual void resizeEvent(QResizeEvent *event);
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 2a97b998..eda64c1d 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -34,6 +34,7 @@
#include "instancemanager.h"
#include
#include "previewdialog.h"
+#include "env.h"
#include "envmodule.h"
#include
@@ -85,6 +86,45 @@ QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
+env::Process* getInterestingProcess(std::vector& processes)
+{
+ if (processes.empty()) {
+ return nullptr;
+ }
+
+ // Certain process names we wish to "hide" for aesthetic reason:
+ const std::vector hiddenList = {
+ QFileInfo(QCoreApplication::applicationFilePath()).fileName()
+ };
+
+ auto isHidden = [&](auto&& p) {
+ for (auto h : hiddenList) {
+ if (p.name().contains(h, Qt::CaseInsensitive)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+
+ for (auto&& root : processes) {
+ if (!isHidden(root)) {
+ return &root;
+ }
+
+ for (auto&& child : root.children()) {
+ if (!isHidden(child)) {
+ return &child;
+ }
+ }
+ }
+
+
+ // everything is hidden, just pick the first one
+ return &processes[0];
+}
+
OrganizerCore::OrganizerCore(Settings &settings)
: m_MainWindow(nullptr)
@@ -1367,12 +1407,31 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
bool OrganizerCore::waitForProcessCompletion(
HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
{
- const auto r = spawn::waitForProcess(handle, exitCode, uilock);
+ const auto tree = env::getProcessTree(handle);
+ std::vector processes = {tree};
+
+ const auto* interesting = getInterestingProcess(processes);
+ if (!interesting) {
+ return true;
+ }
+
+ if (uilock) {
+ uilock->setProcessInformation(interesting->pid(), interesting->name());
+ }
+
+ auto interestingHandle = interesting->openHandleForWait();
+ if (!interestingHandle) {
+ return true;
+ }
+
+ auto progress = [&]{ return uilock->unlockForced(); };
+ const auto r = spawn::waitForProcess(
+ interestingHandle.get(), exitCode, progress);
switch (r)
{
case spawn::WaitResults::Completed: // fall-through
- case spawn::WaitResults::Unlocked:
+ case spawn::WaitResults::Cancelled:
return true;
case spawn::WaitResults::Error: // fall-through
@@ -1395,60 +1454,39 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock()
return r;
}
-HANDLE getInterestingProcess(
- const std::vector& handles,
- const std::vector& hidden, DWORD preferedParentPid)
-{
- HANDLE best_match = INVALID_HANDLE_VALUE;
- bool best_match_hidden = true;
-
- for (auto handle : handles) {
- const QString pname = env::getProcessName(handle);
-
- bool phidden = false;
- for (auto h : hidden)
- if (pname.contains(h, Qt::CaseInsensitive))
- phidden = true;
-
- bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid;
-
- if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) {
- best_match = handle;
- best_match_hidden = phidden;
- }
-
- if (!phidden && pprefered)
- return best_match;
- }
-
- return best_match;
-}
-
bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
{
- // Certain process names we wish to "hide" for aesthetic reason:
- std::vector hiddenList;
- hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
-
for (;;) {
const auto handles = getRunningUSVFSProcesses();
if (handles.empty()) {
break;
}
- const auto interesting = getInterestingProcess(
- handles, hiddenList, GetCurrentProcessId());
+ std::vector processes;
+ for (auto&& h : handles) {
+ auto p = env::getProcessTree(h);
+ if (p.isValid()) {
+ processes.emplace_back(std::move(p));
+ }
+ }
+
+ const auto* interesting = getInterestingProcess(processes);
+ if (!interesting) {
+ break;
+ }
if (uilock) {
- const DWORD pid = ::GetProcessId(interesting);
- const QString processName = QString("%1 (%2)")
- .arg(env::getProcessName(interesting))
- .arg(pid);
+ uilock->setProcessInformation(interesting->pid(), interesting->name());
+ }
- uilock->setProcessName(processName);
+ auto interestingHandle = interesting->openHandleForWait();
+ if (!interestingHandle) {
+ break;
}
- const auto r = spawn::waitForProcess(interesting, nullptr, uilock);
+ auto progress = [&]{ return uilock->unlockForced(); };
+ const auto r = spawn::waitForProcess(
+ interestingHandle.get(), nullptr, progress);
switch (r)
{
@@ -1456,7 +1494,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
// this process is completed, check for others
break;
- case spawn::WaitResults::Unlocked:
+ case spawn::WaitResults::Cancelled:
// force unlocked
log::debug("waiting for process completion aborted by UI");
return true;
@@ -1468,7 +1506,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
}
}
- log::debug("Waiting for process completion successful");
+ log::debug("waiting for process completion successful");
return true;
}
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 1003024f..0ea60641 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -1095,32 +1095,25 @@ FileExecutionContext getFileExecutionContext(
}
WaitResults waitForProcess(
- HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock)
+ HANDLE handle, DWORD* exitCode, std::function progress)
{
if (handle == INVALID_HANDLE_VALUE) {
return WaitResults::Error;
}
- const DWORD pid = ::GetProcessId(handle);
- const QString processName = QString("%1 (%2)")
- .arg(env::getProcessName(handle))
- .arg(pid);
-
- if (uilock)
- uilock->setProcessName(processName);
-
- log::debug(
- "waiting for process completion '{}' ({})",
- processName, pid);
+ log::debug("waiting for completion on pid {}", ::GetProcessId(handle));
std::vector handles;
handles.push_back(handle);
std::vector exitCodes;
- const auto r = waitForProcesses(handles, exitCodes, uilock);
- if (exitCode && !exitCodes.empty()) {
- *exitCode = exitCodes[0];
+ const auto r = waitForProcesses(handles, exitCodes, progress);
+
+ if (r == WaitResults::Completed) {
+ if (exitCode && !exitCodes.empty()) {
+ *exitCode = exitCodes[0];
+ }
}
return r;
@@ -1128,7 +1121,7 @@ WaitResults waitForProcess(
WaitResults waitForProcesses(
const std::vector& handles, std::vector& exitCodes,
- ILockedWaitingForProcess* uilock)
+ std::function progress)
{
if (handles.empty()) {
return WaitResults::Completed;
@@ -1175,8 +1168,8 @@ WaitResults waitForProcesses(
QCoreApplication::sendPostedEvents();
QCoreApplication::processEvents();
- if (uilock && uilock->unlockForced()) {
- return WaitResults::Unlocked;
+ if (progress && progress()) {
+ return WaitResults::Cancelled;
}
}
}
diff --git a/src/spawn.h b/src/spawn.h
index 6b947f2f..ad50bde6 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -27,7 +27,6 @@ along with Mod Organizer. If not, see .
#include
class Settings;
-class ILockedWaitingForProcess;
namespace MOBase { class IPluginGame; }
@@ -130,15 +129,15 @@ enum class WaitResults
{
Completed = 1,
Error,
- Unlocked
+ Cancelled
};
WaitResults waitForProcess(
- HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock);
+ HANDLE handle, DWORD* exitCode, std::function progress);
WaitResults waitForProcesses(
const std::vector& handles, std::vector& exitCodes,
- ILockedWaitingForProcess* uilock);
+ std::function progress);
} // namespace
--
cgit v1.3.1
From 4d269c2e1a625e6d50b7e6272b4f474a921c6bfa Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Tue, 29 Oct 2019 11:34:06 -0400
Subject: split to processrunner added IUserInterface::qtWidget() put back
IUserInterface in OrganizerCore now that there's a way to get the widget
---
src/CMakeLists.txt | 3 +
src/iuserinterface.h | 2 +
src/main.cpp | 4 +-
src/mainwindow.cpp | 13 +-
src/mainwindow.h | 5 +-
src/modinfodialogconflicts.cpp | 2 +-
src/organizercore.cpp | 601 ++++++++------------------------------
src/organizercore.h | 60 +---
src/organizerproxy.cpp | 4 +-
src/processrunner.cpp | 634 +++++++++++++++++++++++++++++++++++++++++
src/processrunner.h | 104 +++++++
src/spawn.cpp | 198 -------------
src/spawn.h | 48 ----
13 files changed, 892 insertions(+), 786 deletions(-)
create mode 100644 src/processrunner.cpp
create mode 100644 src/processrunner.h
(limited to 'src/spawn.cpp')
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 180422ef..5e909760 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -143,6 +143,7 @@ SET(organizer_SRCS
envwindows.cpp
colortable.cpp
sanitychecks.cpp
+ processrunner.cpp
shared/windows_error.cpp
shared/error_report.cpp
@@ -266,6 +267,7 @@ SET(organizer_HDRS
envshortcut.h
envwindows.h
colortable.h
+ processrunner.h
shared/windows_error.h
shared/error_report.h
@@ -348,6 +350,7 @@ set(core
organizercore
organizerproxy
apiuseraccount
+ processrunner
)
set(dialogs
diff --git a/src/iuserinterface.h b/src/iuserinterface.h
index a309ed9b..91487aee 100644
--- a/src/iuserinterface.h
+++ b/src/iuserinterface.h
@@ -33,6 +33,8 @@ public:
virtual ILockedWaitingForProcess* lock() = 0;
virtual void unlock() = 0;
+
+ virtual QWidget* qtWidget() = 0;
};
#endif // IUSERINTERFACE_H
diff --git a/src/main.cpp b/src/main.cpp
index 49ecc084..5ed7da5d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -632,7 +632,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
if (MOShortcut shortcut{ arguments.at(1) }) {
if (shortcut.hasExecutable()) {
try {
- organizer.runShortcut(shortcut);
+ organizer.processRunner().runShortcut(shortcut);
return 0;
}
catch (const std::exception &e) {
@@ -653,7 +653,7 @@ int runApplication(MOApplication &application, SingleInstance &instance,
arguments.removeFirst(); // remove binary name
// pass the remaining parameters to the binary
try {
- organizer.runExecutableOrExecutableFile(
+ organizer.processRunner().runExecutableOrExecutableFile(
exeName, arguments, QString(), QString());
return 0;
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index bbb63333..ce5280a6 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1312,7 +1312,7 @@ bool MainWindow::canExit()
}
m_exitAfterWait = true;
- m_OrganizerCore.waitForAllUSVFSProcessesWithLock();
+ m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock();
if (!m_exitAfterWait) { // if operation cancelled
return false;
}
@@ -1536,7 +1536,7 @@ void MainWindow::startExeAction()
}
action->setEnabled(false);
- m_OrganizerCore.runExecutable(*itor);
+ m_OrganizerCore.processRunner().runExecutable(*itor);
action->setEnabled(true);
}
@@ -2294,6 +2294,11 @@ void MainWindow::unlock()
}
}
+QWidget* MainWindow::qtWidget()
+{
+ return this;
+}
+
void MainWindow::on_btnRefreshData_clicked()
{
m_OrganizerCore.refreshDirectoryStructure();
@@ -2363,7 +2368,7 @@ void MainWindow::on_startButton_clicked()
forcedLibraries.clear();
}
- m_OrganizerCore.runExecutableFile(
+ m_OrganizerCore.processRunner().runExecutableFile(
selectedExecutable->binaryInfo(),
selectedExecutable->arguments(),
selectedExecutable->workingDirectory().length() != 0 ?
@@ -5453,7 +5458,7 @@ void MainWindow::openDataFile()
const QString path = m_ContextItem->data(0, Qt::UserRole).toString();
const QFileInfo targetInfo(path);
- m_OrganizerCore.runFile(this, targetInfo);
+ m_OrganizerCore.processRunner().runFile(this, targetInfo);
}
void MainWindow::openDataOriginExplorer_clicked()
diff --git a/src/mainwindow.h b/src/mainwindow.h
index dbbd0bd9..19723480 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -118,8 +118,9 @@ public:
void processUpdates(Settings& settings);
- virtual ILockedWaitingForProcess* lock() override;
- virtual void unlock() override;
+ ILockedWaitingForProcess* lock() override;
+ void unlock() override;
+ QWidget* qtWidget() override;
bool addProfile();
void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index 68b1be6b..758112cc 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree)
// the menu item is only shown for a single selection, but handle all of them
// in case this changes
for_each_in_selection(tree, [&](const ConflictItem* item) {
- core().runFile(parentWidget(), item->fileName());
+ core().processRunner().runFile(parentWidget(), item->fileName());
return true;
});
}
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index eda64c1d..0f767f46 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1,5 +1,4 @@
#include "organizercore.h"
-#include "mainwindow.h"
#include "delayedfilewriter.h"
#include "guessedvalue.h"
#include "imodinterface.h"
@@ -86,51 +85,13 @@ QStringList toStringList(InputIterator current, InputIterator end)
return result;
}
-env::Process* getInterestingProcess(std::vector& processes)
-{
- if (processes.empty()) {
- return nullptr;
- }
-
- // Certain process names we wish to "hide" for aesthetic reason:
- const std::vector hiddenList = {
- QFileInfo(QCoreApplication::applicationFilePath()).fileName()
- };
-
- auto isHidden = [&](auto&& p) {
- for (auto h : hiddenList) {
- if (p.name().contains(h, Qt::CaseInsensitive)) {
- return true;
- }
- }
-
- return false;
- };
-
-
- for (auto&& root : processes) {
- if (!isHidden(root)) {
- return &root;
- }
-
- for (auto&& child : root.children()) {
- if (!isHidden(child)) {
- return &child;
- }
- }
- }
-
-
- // everything is hidden, just pick the first one
- return &processes[0];
-}
-
OrganizerCore::OrganizerCore(Settings &settings)
- : m_MainWindow(nullptr)
+ : m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
, m_GameName()
, m_CurrentProfile(nullptr)
+ , m_Runner(*this)
, m_Settings(settings)
, m_Updater(NexusInterface::instance(m_PluginContainer))
, m_AboutToRun()
@@ -190,7 +151,7 @@ OrganizerCore::~OrganizerCore()
m_RefresherThread.exit();
m_RefresherThread.wait();
- prepareStart();
+ saveCurrentProfile();
// profile has to be cleaned up before the modinfo-buffer is cleared
delete m_CurrentProfile;
@@ -249,43 +210,49 @@ void OrganizerCore::updateExecutablesList()
m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(MainWindow* mainWindow)
+void OrganizerCore::setUserInterface(IUserInterface* ui)
{
storeSettings();
- m_MainWindow = mainWindow;
+ m_UserInterface = ui;
+
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
- if (m_MainWindow != nullptr) {
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow,
+ if (w) {
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w,
SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w,
SLOT(modlistChanged(QModelIndexList, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w,
SLOT(modRenamed(QString, QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), w,
SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow,
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), w,
SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow,
+ connect(&m_ModList, SIGNAL(clearOverwrite()), w,
SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w,
SLOT(displayColumnSelection(QPoint)));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow,
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w,
SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow,
+ connect(&m_ModList, SIGNAL(modorder_changed()), w,
SLOT(modorder_changed()));
- connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow,
+ connect(&m_PluginList, SIGNAL(writePluginsList()), w,
SLOT(esplist_changed()));
- connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow,
+ connect(&m_PluginList, SIGNAL(esplist_changed()), w,
SLOT(esplist_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow,
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
}
- m_InstallationManager.setParentWidget(m_MainWindow);
- m_Updater.setUserInterface(m_MainWindow);
+ m_InstallationManager.setParentWidget(w);
+ m_Updater.setUserInterface(w);
+ m_Runner.setUserInterface(ui);
checkForUpdates();
}
@@ -294,7 +261,7 @@ void OrganizerCore::checkForUpdates()
{
// this currently wouldn't work reliably if the ui isn't initialized yet to
// display the result
- if (m_MainWindow != nullptr) {
+ if (m_UserInterface != nullptr) {
m_Updater.testForUpdate(m_Settings);
}
}
@@ -390,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message)
{
if (MOShortcut moshortcut{ message } ) {
if(moshortcut.hasExecutable())
- runShortcut(moshortcut);
+ m_Runner.runShortcut(moshortcut);
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -754,13 +721,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
int modIndex = ModInfo::getIndex(modName);
if (modIndex != UINT_MAX) {
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- if (hasIniTweaks && (m_MainWindow != nullptr)
+ if (hasIniTweaks && (m_UserInterface != nullptr)
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
tr("This mod contains ini tweaks. Do you "
"want to configure them now?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)) {
- m_MainWindow->displayModInformation(
+ m_UserInterface->displayModInformation(
modInfo, modIndex, ModInfoTabIDs::IniFiles);
}
m_ModInstalled(modName);
@@ -821,13 +788,13 @@ void OrganizerCore::installDownload(int index)
ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
modInfo->addInstalledFile(modID, fileID);
- if (hasIniTweaks && m_MainWindow != nullptr
+ if (hasIniTweaks && m_UserInterface != nullptr
&& (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"),
tr("This mod contains ini tweaks. Do you "
"want to configure them now?"),
QMessageBox::Yes | QMessageBox::No)
== QMessageBox::Yes)) {
- m_MainWindow->displayModInformation(
+ m_UserInterface->displayModInformation(
modInfo, modIndex, ModInfoTabIDs::IniFiles);
}
@@ -1104,412 +1071,6 @@ bool OrganizerCore::previewFile(
return true;
}
-bool OrganizerCore::runFile(
- QWidget* parent, const QFileInfo& targetInfo)
-{
- const auto fec = spawn::getFileExecutionContext(parent, targetInfo);
-
- switch (fec.type)
- {
- case spawn::FileExecutionTypes::Executable:
- {
- runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir());
- return true;
- }
-
- case spawn::FileExecutionTypes::Other: // fall-through
- default:
- {
- auto r = shell::Open(targetInfo.absoluteFilePath());
- if (!r.success()) {
- return false;
- }
-
- // not all files will return a valid handle even if opening them was
- // successful, such as inproc handlers (like the photo viewer)
- if (r.processHandle() != INVALID_HANDLE_VALUE) {
- // steal because it gets closed after the wait
- return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr);
- }
-
- return true;
- }
- }
-}
-
-bool OrganizerCore::runExecutableFile(
- const QFileInfo &binary, const QString &arguments,
- const QDir ¤tDirectory, const QString &steamAppID,
- const QString &customOverwrite,
- const QList &forcedLibraries,
- bool refresh)
-{
- DWORD processExitCode = 0;
- HANDLE processHandle = spawnAndWait(
- binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID,
- customOverwrite, forcedLibraries, &processExitCode);
-
- if (processHandle == INVALID_HANDLE_VALUE) {
- // failed
- return false;
- }
-
- if (refresh) {
- refreshDirectoryStructure();
-
- // need to remove our stored load order because it may be outdated if a foreign tool changed the
- // file time. After removing that file, refreshESPList will use the file time as the order
- if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- log::debug("removing loadorder.txt");
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
-
- refreshDirectoryStructure();
-
- refreshESPList(true);
- savePluginList();
-
- //These callbacks should not fiddle with directoy structure and ESPs.
- m_FinishedRun(binary.absoluteFilePath(), processExitCode);
- }
-
- return true;
-}
-
-bool OrganizerCore::runExecutable(const Executable& exe, bool refresh)
-{
- const QString customOverwrite = m_CurrentProfile->setting(
- "custom_overwrites", exe.title()).toString();
-
- QList forcedLibraries;
-
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
-
- return runExecutableFile(
- exe.binaryInfo(),
- exe.arguments(),
- exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(),
- exe.steamAppID(),
- customOverwrite,
- forcedLibraries,
- refresh);
-}
-
-bool OrganizerCore::runShortcut(const MOShortcut& shortcut)
-{
- if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) {
- throw std::runtime_error(
- QString("Refusing to run executable from different instance %1:%2")
- .arg(shortcut.instance(),shortcut.executable())
- .toLocal8Bit().constData());
- }
-
- const Executable& exe = m_ExecutablesList.get(shortcut.executable());
- return runExecutable(exe, false);
-}
-
-HANDLE OrganizerCore::runExecutableOrExecutableFile(
- const QString &executable, const QStringList &args, const QString &cwd,
- const QString &profile, const QString &forcedCustomOverwrite,
- bool ignoreCustomOverwrite)
-{
- QString profileName = profile;
- if (profile == "") {
- if (m_CurrentProfile != nullptr) {
- profileName = m_CurrentProfile->name();
- } else {
- throw MyException(tr("No profile set"));
- }
- }
-
- QFileInfo binary;
- QString arguments = args.join(" ");
- QString currentDirectory = cwd;
- QString steamAppID;
- QString customOverwrite;
- QList forcedLibraries;
-
- if (executable.contains('\\') || executable.contains('/')) {
- // file path
-
- binary = QFileInfo(executable);
- if (binary.isRelative()) {
- // relative path, should be relative to game directory
- binary = managedGame()->gameDirectory().absoluteFilePath(executable);
- }
-
- if (currentDirectory == "") {
- currentDirectory = binary.absolutePath();
- }
-
- try {
- const Executable& exe = m_ExecutablesList.getByBinary(binary);
- steamAppID = exe.steamAppID();
- customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- } catch (const std::runtime_error &) {
- // nop
- }
- } else {
- // only a file name, search executables list
- try {
- const Executable &exe = m_ExecutablesList.get(executable);
- steamAppID = exe.steamAppID();
- customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
- }
- if (arguments == "") {
- arguments = exe.arguments();
- }
- binary = exe.binaryInfo();
- if (currentDirectory == "") {
- currentDirectory = exe.workingDirectory();
- }
- } catch (const std::runtime_error &) {
- log::warn("\"{}\" not set up as executable", executable);
- binary = QFileInfo(executable);
- }
- }
-
- if (!forcedCustomOverwrite.isEmpty())
- customOverwrite = forcedCustomOverwrite;
-
- if (ignoreCustomOverwrite)
- customOverwrite.clear();
-
- return spawnAndWait(binary,
- arguments,
- profileName,
- currentDirectory,
- steamAppID,
- customOverwrite,
- forcedLibraries);
-}
-
-HANDLE OrganizerCore::spawnAndWait(
- const QFileInfo &binary, const QString &arguments, const QString &profileName,
- const QDir ¤tDirectory, const QString &steamAppID,
- const QString &customOverwrite,
- const QList &forcedLibraries,
- LPDWORD exitCode)
-{
- spawn::SpawnParameters sp;
- sp.binary = binary;
- sp.arguments = arguments;
- sp.currentDirectory = currentDirectory;
- sp.steamAppID = steamAppID;
- sp.hooked = true;
-
- prepareStart();
-
- while (m_DirectoryUpdate) {
- ::Sleep(100);
- QCoreApplication::processEvents();
- }
-
- // need to make sure all data is saved before we start the application
- if (m_CurrentProfile != nullptr) {
- m_CurrentProfile->writeModlistNow(true);
- }
-
- // TODO: should also pass arguments
- if (!m_AboutToRun(binary.absoluteFilePath())) {
- log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath());
- return INVALID_HANDLE_VALUE;
- }
-
- try {
- m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
- m_USVFS.updateForcedLibraries(forcedLibraries);
-
- } catch (const UsvfsConnectorException &e) {
- log::debug(e.what());
- return INVALID_HANDLE_VALUE;
- } catch (const std::exception &e) {
- QMessageBox::warning(m_MainWindow, tr("Error"), e.what());
- return INVALID_HANDLE_VALUE;
- }
-
- HANDLE handle = spawn::Spawner()
- .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings)
- .releaseHandle();
-
- if (handle == INVALID_HANDLE_VALUE) {
- // failed
- return INVALID_HANDLE_VALUE;
- }
-
- waitForProcessCompletionWithLock(handle, exitCode);
- return handle;
-}
-
-void OrganizerCore::withLock(std::function f)
-{
- std::unique_ptr dlg;
- ILockedWaitingForProcess* uilock = nullptr;
-
- if (m_MainWindow != nullptr) {
- uilock = m_MainWindow->lock();
- }
- else {
- // i.e. when running command line shortcuts there is no user interface
- dlg.reset(new LockedDialog);
- dlg->show();
- dlg->setEnabled(true);
- uilock = dlg.get();
- }
-
- ON_BLOCK_EXIT([&]() {
- if (m_MainWindow != nullptr) {
- m_MainWindow->unlock();
- } });
-
- f(uilock);
-}
-
-bool OrganizerCore::waitForProcessCompletionWithLock(
- HANDLE handle, LPDWORD exitCode)
-{
- if (!Settings::instance().interface().lockGUI()) {
- return true;
- }
-
- bool r = false;
-
- withLock([&](auto* uilock) {
- DWORD ignoreExitCode;
- r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock);
- cycleDiagnostics();
- });
-
- return r;
-}
-
-bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
-{
- if (!Settings::instance().interface().lockGUI())
- return true;
-
- bool r = false;
-
- withLock([&](auto* uilock) {
- r = waitForProcessCompletion(handle, exitCode, uilock);
- });
-
- return r;
-}
-
-bool OrganizerCore::waitForProcessCompletion(
- HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
-{
- const auto tree = env::getProcessTree(handle);
- std::vector processes = {tree};
-
- const auto* interesting = getInterestingProcess(processes);
- if (!interesting) {
- return true;
- }
-
- if (uilock) {
- uilock->setProcessInformation(interesting->pid(), interesting->name());
- }
-
- auto interestingHandle = interesting->openHandleForWait();
- if (!interestingHandle) {
- return true;
- }
-
- auto progress = [&]{ return uilock->unlockForced(); };
- const auto r = spawn::waitForProcess(
- interestingHandle.get(), exitCode, progress);
-
- switch (r)
- {
- case spawn::WaitResults::Completed: // fall-through
- case spawn::WaitResults::Cancelled:
- return true;
-
- case spawn::WaitResults::Error: // fall-through
- default:
- return false;
- }
-}
-
-bool OrganizerCore::waitForAllUSVFSProcessesWithLock()
-{
- if (!Settings::instance().interface().lockGUI())
- return true;
-
- bool r = false;
-
- withLock([&](auto* uilock) {
- r = waitForAllUSVFSProcesses(uilock);
- });
-
- return r;
-}
-
-bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock)
-{
- for (;;) {
- const auto handles = getRunningUSVFSProcesses();
- if (handles.empty()) {
- break;
- }
-
- std::vector processes;
- for (auto&& h : handles) {
- auto p = env::getProcessTree(h);
- if (p.isValid()) {
- processes.emplace_back(std::move(p));
- }
- }
-
- const auto* interesting = getInterestingProcess(processes);
- if (!interesting) {
- break;
- }
-
- if (uilock) {
- uilock->setProcessInformation(interesting->pid(), interesting->name());
- }
-
- auto interestingHandle = interesting->openHandleForWait();
- if (!interestingHandle) {
- break;
- }
-
- auto progress = [&]{ return uilock->unlockForced(); };
- const auto r = spawn::waitForProcess(
- interestingHandle.get(), nullptr, progress);
-
- switch (r)
- {
- case spawn::WaitResults::Completed:
- // this process is completed, check for others
- break;
-
- case spawn::WaitResults::Cancelled:
- // force unlocked
- log::debug("waiting for process completion aborted by UI");
- return true;
-
- case spawn::WaitResults::Error: // fall-through
- default:
- log::debug("waiting for process completion not successful");
- return false;
- }
- }
-
- log::debug("waiting for process completion successful");
- return true;
-}
-
bool OrganizerCore::onAboutToRun(
const std::function &func)
{
@@ -1594,8 +1155,8 @@ void OrganizerCore::refreshBSAList()
m_ActiveArchives = m_DefaultArchives;
}
- if (m_MainWindow != nullptr) {
- m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives);
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives);
}
m_ArchivesInit = true;
@@ -1711,8 +1272,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap