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(-) 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