summaryrefslogtreecommitdiff
path: root/src/organizercore.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/organizercore.cpp')
-rw-r--r--src/organizercore.cpp1379
1 files changed, 269 insertions, 1110 deletions
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 81ec7f43..df505202 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -1,5 +1,4 @@
#include "organizercore.h"
-
#include "delayedfilewriter.h"
#include "guessedvalue.h"
#include "imodinterface.h"
@@ -14,7 +13,6 @@
#include "plugincontainer.h"
#include "pluginlistsortproxy.h"
#include "profile.h"
-#include "logbuffer.h"
#include "credentialsdialog.h"
#include "filedialogmemory.h"
#include "modinfodialog.h"
@@ -31,11 +29,11 @@
#include "appconfig.h"
#include <report.h>
#include <questionboxmemory.h>
-#include "lockeddialog.h"
#include "instancemanager.h"
#include <scriptextender.h>
-#include "helper.h"
#include "previewdialog.h"
+#include "env.h"
+#include "envmodule.h"
#include <QApplication>
#include <QCoreApplication>
@@ -76,108 +74,6 @@ using namespace MOBase;
//static
CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None;
-static bool isOnline()
-{
- QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
-
- bool connected = false;
- for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected;
- ++iter) {
- if ((iter->flags() & QNetworkInterface::IsUp)
- && (iter->flags() & QNetworkInterface::IsRunning)
- && !(iter->flags() & QNetworkInterface::IsLoopBack)) {
- auto addresses = iter->addressEntries();
- if (addresses.count() == 0) {
- continue;
- }
- qDebug("interface %s seems to be up (address: %s)",
- qUtf8Printable(iter->humanReadableName()),
- qUtf8Printable(addresses[0].ip().toString()));
- connected = true;
- }
- }
-
- return connected;
-}
-
-static bool renameFile(const QString &oldName, const QString &newName,
- bool overwrite = true)
-{
- if (overwrite && QFile::exists(newName)) {
- QFile::remove(newName);
- }
- return QFile::rename(oldName, newName);
-}
-
-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;
-}
-
-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().getSteamLogin(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 <typename InputIterator>
QStringList toStringList(InputIterator current, InputIterator end)
{
@@ -188,93 +84,13 @@ 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) {
- qWarning("failed to open service manager (query status) (error %d)", GetLastError());
- throw 1;
- }
-
- serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
- if (!serviceHandle) {
- qWarning("failed to open EventLog service (query status) (error %d)", GetLastError());
- throw 2;
- }
-
- if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- qWarning("failed to get size of service config (error %d)", GetLastError());
- throw 3;
- }
-
- DWORD serviceConfigSize = bytesNeeded;
- serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize);
- if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) {
- qWarning("failed to query service config (error %d)", GetLastError());
- throw 4;
- }
-
- if (serviceConfig->dwStartType == SERVICE_DISABLED) {
- qCritical("Windows Event Log service is disabled!");
- serviceRunning = false;
- }
-
- if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded)
- || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
- qWarning("failed to get size of service status (error %d)", 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)) {
- qWarning("failed to query service status (error %d)", GetLastError());
- throw 6;
- }
-
- if (serviceStatus->dwCurrentState != SERVICE_RUNNING) {
- qCritical("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(const QSettings &initSettings)
+OrganizerCore::OrganizerCore(Settings &settings)
: m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
, m_GameName()
, m_CurrentProfile(nullptr)
- , m_Settings(initSettings)
+ , m_Settings(settings)
, m_Updater(NexusInterface::instance(m_PluginContainer))
, m_AboutToRun()
, m_FinishedRun()
@@ -290,15 +106,13 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings)
, m_ArchivesInit(false)
, m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this))
{
- m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory());
- m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers());
-
- NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory());
+ m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads());
- MOBase::QuestionBoxMemory::init(initSettings.fileName());
+ NexusInterface::instance(m_PluginContainer)->setCacheDirectory(
+ m_Settings.paths().cache());
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
- m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory());
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
+ m_InstallationManager.setDownloadDirectory(m_Settings.paths().downloads());
connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this,
SLOT(downloadSpeed(QString, int)));
@@ -335,217 +149,119 @@ 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;
m_CurrentProfile = nullptr;
ModInfo::clear();
- LogBuffer::cleanQuit();
m_ModList.setProfile(nullptr);
// NexusInterface::instance()->cleanup();
delete m_DirectoryStructure;
}
-QString OrganizerCore::commitSettings(const QString &iniFile)
-{
- if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) {
- DWORD err = ::GetLastError();
- // make a second attempt using qt functions but if that fails print the
- // error from the first attempt
- if (!renameFile(iniFile + ".new", iniFile)) {
- return windowsErrorString(err);
- }
- }
- return QString();
-}
-
-QSettings::Status OrganizerCore::storeSettings(const QString &fileName)
+void OrganizerCore::storeSettings()
{
- QSettings settings(fileName, QSettings::IniFormat);
-
- if (m_UserInterface != nullptr) {
- m_UserInterface->storeSettings(settings);
- }
-
if (m_CurrentProfile != nullptr) {
- settings.setValue("selected_profile",
- m_CurrentProfile->name().toUtf8().constData());
+ m_Settings.game().setSelectedProfileName(m_CurrentProfile->name());
}
- m_ExecutablesList.store(settings);
+ m_ExecutablesList.store(m_Settings);
- FileDialogMemory::save(settings);
+ FileDialogMemory::save(m_Settings);
- settings.sync();
- return settings.status();
-}
+ const auto result = m_Settings.sync();
-void OrganizerCore::storeSettings()
-{
- QString iniFile = qApp->property("dataPath").toString() + "/"
- + QString::fromStdWString(AppConfig::iniFileName());
- if (QFileInfo(iniFile).exists()) {
- if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) {
- QMessageBox::critical(
- qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occurred trying to update MO settings to %1: %2")
- .arg(iniFile, windowsErrorString(::GetLastError())));
- return;
- }
- }
-
- QString writeTarget = iniFile + ".new";
-
- QSettings::Status result = storeSettings(writeTarget);
-
- if (result == QSettings::NoError) {
- QString errMsg = commitSettings(iniFile);
- if (!errMsg.isEmpty()) {
- qWarning("settings file not writable, may be locked by another "
- "application, trying direct write");
- writeTarget = iniFile;
- result = storeSettings(iniFile);
- }
- }
if (result != QSettings::NoError) {
- QString reason = result == QSettings::AccessError
- ? tr("File is write protected")
- : result == QSettings::FormatError
- ? tr("Invalid file format (probably a bug)")
- : tr("Unknown error %1").arg(result);
- QMessageBox::critical(
- qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occurred trying to write back MO settings to %1: %2")
- .arg(writeTarget, reason));
- }
-}
-
-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();
- qCritical("unable to get snapshot of processes (error %d)", lastError);
- return false;
- }
-
- // Retrieve information about the first process,
- // and exit if unsuccessful
- pe32.dwSize = sizeof(PROCESSENTRY32);
- if (!Process32First(hProcessSnap, &pe32)) {
- lastError = GetLastError();
- qCritical("unable to get first process (error %d)", 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;
+ QString reason;
- // 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;
+ if (result == QSettings::AccessError) {
+ reason = tr("File is write protected");
+ } else if (result == QSettings::FormatError) {
+ reason = tr("Invalid file format (probably a bug)");
+ } else {
+ reason = tr("Unknown error %1").arg(result);
}
-} while(Process32Next(hProcessSnap, &pe32));
-
-CloseHandle(hProcessSnap);
-return true;
-
+ QMessageBox::critical(
+ qApp->activeWindow(), tr("Failed to write settings"),
+ tr("An error occurred trying to write back MO settings to %1: %2")
+ .arg(m_Settings.filename(), reason));
+ }
}
-void OrganizerCore::updateExecutablesList(QSettings &settings)
+void OrganizerCore::updateExecutablesList()
{
if (m_PluginContainer == nullptr) {
- qCritical("can't update executables list now");
+ log::error("can't update executables list now");
return;
}
- m_ExecutablesList.load(managedGame(), settings);
+ m_ExecutablesList.load(managedGame(), m_Settings);
// TODO this has nothing to do with executables list move to an appropriate
// function!
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.displayForeign(), managedGame());
+ ModInfo::updateFromDisc(
+ m_Settings.paths().mods(), &m_DirectoryStructure,
+ m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(IUserInterface *userInterface,
- QWidget *widget)
+void OrganizerCore::setUserInterface(IUserInterface* ui)
{
storeSettings();
- m_UserInterface = userInterface;
+ m_UserInterface = ui;
- if (widget != nullptr) {
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget,
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
+
+ if (w) {
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w,
SLOT(modlistChanged(QModelIndex, int)));
- connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget,
+ connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w,
SLOT(modlistChanged(QModelIndexList, int)));
- connect(&m_ModList, SIGNAL(showMessage(QString)), widget,
+ connect(&m_ModList, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
- connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget,
+ connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w,
SLOT(modRenamed(QString, QString)));
- connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget,
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), w,
SLOT(modRemoved(QString)));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), widget,
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), w,
SLOT(removeMod_clicked()));
- connect(&m_ModList, SIGNAL(clearOverwrite()), widget,
+ connect(&m_ModList, SIGNAL(clearOverwrite()), w,
SLOT(clearOverwrite()));
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget,
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w,
SLOT(displayColumnSelection(QPoint)));
- connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget,
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w,
SLOT(fileMoved(QString, QString, QString)));
- connect(&m_ModList, SIGNAL(modorder_changed()), widget,
+ connect(&m_ModList, SIGNAL(modorder_changed()), w,
SLOT(modorder_changed()));
- connect(&m_PluginList, SIGNAL(writePluginsList()), widget,
+ connect(&m_PluginList, SIGNAL(writePluginsList()), w,
SLOT(esplist_changed()));
- connect(&m_PluginList, SIGNAL(esplist_changed()), widget,
+ connect(&m_PluginList, SIGNAL(esplist_changed()), w,
SLOT(esplist_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget,
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w,
SLOT(showMessage(QString)));
}
- m_InstallationManager.setParentWidget(widget);
- m_Updater.setUserInterface(widget);
+ m_InstallationManager.setParentWidget(w);
+ m_Updater.setUserInterface(w);
+ m_UILocker.setUserInterface(w);
+ m_DownloadManager.setParentWidget(w);
- if (userInterface != nullptr) {
- // this currently wouldn't work reliably if the ui isn't initialized yet to
- // display the result
- if (isOnline() && !m_Settings.offlineMode()) {
- m_Updater.testForUpdate();
- } else {
- qDebug("user doesn't seem to be connected to the internet");
- }
+ checkForUpdates();
+}
+
+void OrganizerCore::checkForUpdates()
+{
+ // this currently wouldn't work reliably if the ui isn't initialized yet to
+ // display the result
+ if (m_UserInterface != nullptr) {
+ m_Updater.testForUpdate(m_Settings);
}
}
@@ -575,7 +291,7 @@ void OrganizerCore::disconnectPlugins()
m_DownloadManager.setPluginContainer(nullptr);
m_ModList.setPluginContainer(nullptr);
- m_Settings.clearPlugins();
+ m_Settings.plugins().clearPlugins();
m_GamePlugin = nullptr;
m_PluginContainer = nullptr;
}
@@ -604,9 +320,9 @@ bool OrganizerCore::nexusApi(bool retry)
return false;
} else {
QString apiKey;
- if (m_Settings.getNexusApiKey(apiKey)) {
+ if (m_Settings.nexus().apiKey(apiKey)) {
// credentials stored or user entered them manually
- qDebug("attempt to verify nexus api key");
+ log::debug("attempt to verify nexus api key");
accessManager->apiCheck(apiKey);
return true;
} else {
@@ -628,7 +344,7 @@ void OrganizerCore::startMOUpdate()
void OrganizerCore::downloadRequestedNXM(const QString &url)
{
- qDebug("download requested: %s", qUtf8Printable(url));
+ log::debug("download requested: {}", url);
if (nexusApi()) {
m_PendingDownloads.append(url);
} else {
@@ -639,8 +355,12 @@ void OrganizerCore::downloadRequestedNXM(const QString &url)
void OrganizerCore::externalMessage(const QString &message)
{
if (MOShortcut moshortcut{ message } ) {
- if(moshortcut.hasExecutable())
- runShortcut(moshortcut);
+ if(moshortcut.hasExecutable()) {
+ processRunner()
+ .setFromShortcut(moshortcut)
+ .setWaitForCompletion(ProcessRunner::Refresh)
+ .run();
+ }
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -658,7 +378,7 @@ void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, in
}
} catch (const std::exception &e) {
MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow());
- qCritical("exception starting download: %s", e.what());
+ log::error("exception starting download: {}", e.what());
}
}
@@ -671,7 +391,7 @@ void OrganizerCore::removeOrigin(const QString &name)
void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond)
{
- m_Settings.setDownloadSpeed(serverName, bytesPerSecond);
+ m_Settings.network().setDownloadSpeed(serverName, bytesPerSecond);
}
InstallationManager *OrganizerCore::installationManager()
@@ -692,31 +412,35 @@ bool OrganizerCore::createDirectory(const QString &path) {
}
bool OrganizerCore::checkPathSymlinks() {
- bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() ||
- QFileInfo(m_Settings.getModDirectory()).isSymLink() ||
- QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink());
+ const bool hasSymlink = (
+ QFileInfo(m_Settings.paths().profiles()).isSymLink() ||
+ QFileInfo(m_Settings.paths().mods()).isSymLink() ||
+ QFileInfo(m_Settings.paths().overwrite()).isSymLink());
+
if (hasSymlink) {
- QMessageBox::critical(nullptr, QObject::tr("Error"),
- QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) "
- "is on a path containing a symbolic (or other) link. This is incompatible "
- "with MO2's VFS system."));
+ log::warn("{}", QObject::tr(
+ "One of the configured MO2 directories (profiles, mods, or overwrite) "
+ "is on a path containing a symbolic (or other) link. This is likely to "
+ "be incompatible with MO2's virtual filesystem."));
+
return false;
}
+
return true;
}
bool OrganizerCore::bootstrap() {
- return createDirectory(m_Settings.getProfileDirectory()) &&
- createDirectory(m_Settings.getModDirectory()) &&
- createDirectory(m_Settings.getDownloadDirectory()) &&
- createDirectory(m_Settings.getOverwriteDirectory()) &&
+ return createDirectory(m_Settings.paths().profiles()) &&
+ createDirectory(m_Settings.paths().mods()) &&
+ createDirectory(m_Settings.paths().downloads()) &&
+ createDirectory(m_Settings.paths().overwrite()) &&
createDirectory(QString::fromStdWString(crashDumpsPath())) &&
checkPathSymlinks() && cycleDiagnostics();
}
void OrganizerCore::createDefaultProfile()
{
- QString profilesPath = settings().getProfileDirectory();
+ QString profilesPath = settings().paths().profiles();
if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size()
== 0) {
Profile newProf("Default", managedGame(), false);
@@ -728,23 +452,47 @@ void OrganizerCore::prepareVFS()
m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString()));
}
-void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist) {
+void OrganizerCore::updateVFSParams(
+ log::Levels logLevel, CrashDumpsType crashDumpsType,
+ const QString& crashDumpsPath,
+ std::chrono::seconds spawnDelay, QString executableBlacklist)
+{
setGlobalCrashDumpsType(crashDumpsType);
- m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist);
+ m_USVFS.updateParams(
+ logLevel, crashDumpsType, crashDumpsPath, spawnDelay, executableBlacklist);
}
-bool OrganizerCore::cycleDiagnostics() {
- if (int maxDumps = settings().crashDumpsMax())
- removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
+void OrganizerCore::setLogLevel(log::Levels level)
+{
+ m_Settings.diagnostics().setLogLevel(level);
+
+ updateVFSParams(
+ m_Settings.diagnostics().logLevel(),
+ m_Settings.diagnostics().crashDumpsType(),
+ QString::fromStdWString(crashDumpsPath()),
+ m_Settings.diagnostics().spawnDelay(),
+ m_Settings.executablesBlacklist());
+
+ log::getDefault().setLevel(m_Settings.diagnostics().logLevel());
+}
+
+bool OrganizerCore::cycleDiagnostics()
+{
+ const auto maxDumps = settings().diagnostics().crashDumpsMax();
+ const auto path = QString::fromStdWString(crashDumpsPath());
+
+ if (maxDumps > 0) {
+ removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
+ }
+
return true;
}
-//static
-void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) {
- m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType);
+void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type)
+{
+ m_globalCrashDumpsType = type;
}
-//static
std::wstring OrganizerCore::crashDumpsPath() {
return (
qApp->property("dataPath").toString() + "/"
@@ -769,7 +517,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
return;
}
- QDir profileBaseDir(settings().getProfileDirectory());
+ QDir profileBaseDir(settings().paths().profiles());
QString profileDir = profileBaseDir.absoluteFilePath(profileName);
if (!QDir(profileDir).exists()) {
@@ -793,16 +541,11 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
m_CurrentProfile->deactivateInvalidation();
}
+ m_Settings.game().setSelectedProfileName(m_CurrentProfile->name());
+
connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint)));
connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList<uint>)), this, SLOT(modStatusChanged(QList<uint>)));
refreshDirectoryStructure();
-
- //This line is not actually needed and was only added to allow some
- //outside detection of Mo2 profile change. (like BaobobMiller utility)
- if (m_CurrentProfile != nullptr) {
- settings().directInterface().setValue("selected_profile",
- m_CurrentProfile->name().toUtf8().constData());
- }
}
MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const
@@ -830,22 +573,22 @@ QString OrganizerCore::profilePath() const
QString OrganizerCore::downloadsPath() const
{
- return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().downloads());
}
QString OrganizerCore::overwritePath() const
{
- return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().overwrite());
}
QString OrganizerCore::basePath() const
{
- return QDir::fromNativeSeparators(m_Settings.getBaseDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().base());
}
QString OrganizerCore::modsPath() const
{
- return QDir::fromNativeSeparators(m_Settings.getModDirectory());
+ return QDir::fromNativeSeparators(m_Settings.paths().mods());
}
MOBase::VersionInfo OrganizerCore::appVersion() const
@@ -875,10 +618,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name)
return nullptr;
}
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
QString targetDirectory
- = QDir::fromNativeSeparators(m_Settings.getModDirectory())
+ = QDir::fromNativeSeparators(m_Settings.paths().mods())
.append("/")
.append(name);
@@ -918,26 +661,26 @@ void OrganizerCore::modDataChanged(MOBase::IModInterface *)
QVariant OrganizerCore::pluginSetting(const QString &pluginName,
const QString &key) const
{
- return m_Settings.pluginSetting(pluginName, key);
+ return m_Settings.plugins().setting(pluginName, key);
}
void OrganizerCore::setPluginSetting(const QString &pluginName,
const QString &key, const QVariant &value)
{
- m_Settings.setPluginSetting(pluginName, key, value);
+ m_Settings.plugins().setSetting(pluginName, key, value);
}
QVariant OrganizerCore::persistent(const QString &pluginName,
const QString &key,
const QVariant &def) const
{
- return m_Settings.pluginPersistent(pluginName, key, def);
+ return m_Settings.plugins().persistent(pluginName, key, def);
}
void OrganizerCore::setPersistent(const QString &pluginName, const QString &key,
const QVariant &value, bool sync)
{
- m_Settings.setPluginPersistent(pluginName, key, value, sync);
+ m_Settings.plugins().setPersistent(pluginName, key, value, sync);
}
QString OrganizerCore::pluginDataPath() const
@@ -966,8 +709,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
modName.update(initModName, GUESS_USER);
}
m_CurrentProfile->writeModlistNow();
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
- if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
+ if (m_InstallationManager.install(fileName, modName, hasIniTweaks) == IPluginInstaller::RESULT_SUCCESS) {
MessageDialog::showMessage(tr("Installation successful"),
qApp->activeWindow());
refreshModList();
@@ -992,9 +735,12 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
reportError(tr("mod not found: %1").arg(qUtf8Printable(modName)));
}
} else if (m_InstallationManager.wasCancelled()) {
- QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"),
- tr("The mod was not installed completely."),
+ QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"),
+ tr("The installation was cancelled while extracting files. "
+ "If this was prior to a FOMOD setup, this warning may be ignored. "
+ "However, if this was during installation, the mod will likely be missing files."),
QMessageBox::Ok);
+ refreshModList();
}
return nullptr;
}
@@ -1031,8 +777,8 @@ void OrganizerCore::installDownload(int index)
m_CurrentProfile->writeModlistNow();
bool hasIniTweaks = false;
- m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
- if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
+ m_InstallationManager.setModsDirectory(m_Settings.paths().mods());
+ if (m_InstallationManager.install(fileName, modName, hasIniTweaks) == IPluginInstaller::RESULT_SUCCESS) {
MessageDialog::showMessage(tr("Installation successful"),
qApp->activeWindow());
refreshModList();
@@ -1060,9 +806,12 @@ void OrganizerCore::installDownload(int index)
emit modInstalled(modName);
} else if (m_InstallationManager.wasCancelled()) {
- QMessageBox::information(
- qApp->activeWindow(), tr("Installation cancelled"),
- tr("The mod was not installed completely."), QMessageBox::Ok);
+ QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"),
+ tr("The installation was cancelled while extracting files. "
+ "If this was prior to a FOMOD setup, this warning may be ignored. "
+ "However, if this was during installation, the mod will likely be missing files."),
+ QMessageBox::Ok);
+ refreshModList();
}
} catch (const std::exception &e) {
reportError(e.what());
@@ -1197,108 +946,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)) {
- qDebug("failed to determine binary type of \"%ls\": %lu", 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::executeFileVirtualized(
- QWidget* parent, const QFileInfo& targetInfo)
-{
- QFileInfo binaryInfo;
- QString arguments;
- FileExecutionTypes type;
-
- if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) {
- return false;
- }
-
- switch (type)
- {
- case FileExecutionTypes::Executable: {
- spawnBinaryDirect(
- binaryInfo, arguments, currentProfile()->name(),
- targetInfo.absolutePath(), "", "");
-
- return true;
- }
-
- case FileExecutionTypes::Other: {
- ::ShellExecuteW(nullptr, L"open",
- ToWString(targetInfo.absoluteFilePath()).c_str(),
- nullptr, nullptr, SW_SHOWNORMAL);
-
- return true;
- }
- }
-
- // nop
- return false;
-}
-
bool OrganizerCore::previewFileWithAlternatives(
QWidget* parent, QString fileName, int selectedOrigin)
{
@@ -1322,7 +969,7 @@ bool OrganizerCore::previewFileWithAlternatives(
else {
// crude: we search for the next slash after the base mod directory to skip
// everything up to the data-relative directory
- int offset = settings().getModDirectory().size() + 1;
+ int offset = settings().paths().mods().size() + 1;
offset = fileName.indexOf("/", offset);
fileName = fileName.mid(offset + 1);
}
@@ -1337,7 +984,8 @@ bool OrganizerCore::previewFileWithAlternatives(
}
// set up preview dialog
- PreviewDialog preview(fileName);
+ PreviewDialog preview(fileName, parent);
+
auto addFunc = [&](int originId) {
FilesOrigin &origin = directoryStructure()->getOriginByID(originId);
QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName;
@@ -1381,9 +1029,9 @@ bool OrganizerCore::previewFileWithAlternatives(
// sanity check, this shouldn't happen unless the caller passed an
// incorrect id
- qWarning().nospace()
- << "selected preview origin " << selectedOrigin << " not found in "
- << "list of alternatives";
+ log::warn(
+ "selected preview origin {} not found in list of alternatives",
+ selectedOrigin);
}
for (int id : origins) {
@@ -1392,16 +1040,7 @@ bool OrganizerCore::previewFileWithAlternatives(
}
if (preview.numVariants() > 0) {
- QSettings &s = settings().directInterface();
- QString key = QString("geometry/%1").arg(preview.objectName());
- if (s.contains(key)) {
- preview.restoreGeometry(s.value(key).toByteArray());
- }
-
preview.exec();
-
- s.setValue(key, preview.saveGeometry());
-
return true;
}
else {
@@ -1421,7 +1060,7 @@ bool OrganizerCore::previewFile(
return false;
}
- PreviewDialog preview(path);
+ PreviewDialog preview(path, parent);
QWidget *wid = m_PluginContainer->previewGenerator().genPreview(path);
if (wid == nullptr) {
@@ -1430,578 +1069,11 @@ bool OrganizerCore::previewFile(
}
preview.addVariant(originName, wid);
-
- QSettings &s = settings().directInterface();
- QString key = QString("geometry/%1").arg(preview.objectName());
- if (s.contains(key)) {
- preview.restoreGeometry(s.value(key).toByteArray());
- }
-
preview.exec();
- s.setValue(key, preview.saveGeometry());
-
return true;
}
-void OrganizerCore::spawnBinary(const QFileInfo &binary,
- const QString &arguments,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries)
-{
- DWORD processExitCode = 0;
- HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode);
- if (processHandle != INVALID_HANDLE_VALUE) {
- 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) {
- qDebug("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);
- }
-}
-
-HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
- const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries,
- LPDWORD exitCode)
-{
- HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries);
- if (Settings::instance().lockGUI() && processHandle != INVALID_HANDLE_VALUE) {
- std::unique_ptr<LockedDialog> dlg;
- ILockedWaitingForProcess* uilock = nullptr;
-
- if (m_UserInterface != nullptr) {
- uilock = m_UserInterface->lock();
- }
- else {
- // i.e. when running command line shortcuts there is no m_UserInterface
- dlg.reset(new LockedDialog);
- dlg->show();
- dlg->setEnabled(true);
- uilock = dlg.get();
- }
-
- ON_BLOCK_EXIT([&]() {
- if (m_UserInterface != nullptr) {
- m_UserInterface->unlock();
- } });
-
- DWORD ignoreExitCode;
- waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock);
- cycleDiagnostics();
- }
-
- return processHandle;
-}
-
-
-HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
- const QString &arguments,
- const QString &profileName,
- const QDir &currentDirectory,
- const QString &steamAppID,
- const QString &customOverwrite,
- const QList<MOBase::ExecutableForcedLoadSetting> &forcedLibraries)
-{
- 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.getSteamAppID()).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.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) {
-
- bool steamFound = true;
- bool steamAccess = true;
- if (!testForSteam(&steamFound, &steamAccess)) {
- qCritical("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)) {
- qCritical("unable to determine state of Steam");
- } else if (!steamFound) {
- qCritical("could not find Steam");
- }
-
- } else if (result == QDialogButtonBox::Cancel) {
- 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)) {
- qCritical("unable to get current directory (error %d)", GetLastError());
- cwd[0] = L'\0';
- }
- if (!Helper::adminLaunch(
- qApp->applicationDirPath().toStdWString(),
- qApp->applicationFilePath().toStdWString(),
- std::wstring(cwd))) {
- qCritical("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;
- }
- }
- }
-
- 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())) {
- try {
- m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
- m_USVFS.updateForcedLibraries(forcedLibraries);
-
- } catch (const UsvfsConnectorException &e) {
- qDebug(e.what());
- return INVALID_HANDLE_VALUE;
- } catch (const std::exception &e) {
- QMessageBox::warning(window, tr("Error"), e.what());
- 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;
- }
- }
-
- 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;
- }
- }
- }
-
- QString modsPath = settings().getModDirectory();
-
- // 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);
-
- qDebug() << "Spawning proxyed process <" << cmdline << ">";
-
- return startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
- cmdline, QCoreApplication::applicationDirPath(), true);
- } else {
- qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">";
- return startBinary(binary, arguments, currentDirectory, true);
- }
- } else {
- qDebug("start of \"%s\" canceled by plugin",
- qUtf8Printable(binary.absoluteFilePath()));
- return INVALID_HANDLE_VALUE;
- }
-}
-
-HANDLE 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());
-
- auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable());
- if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) {
- forcedLibaries.clear();
- }
-
- return spawnBinaryDirect(
- exe.binaryInfo(), exe.arguments(),
- m_CurrentProfile->name(),
- exe.workingDirectory().length() != 0
- ? exe.workingDirectory()
- : exe.binaryInfo().absolutePath(),
- exe.steamAppID(),
- "",
- forcedLibaries);
-}
-
-HANDLE OrganizerCore::startApplication(const QString &executable,
- const QStringList &args,
- const QString &cwd,
- const QString &profile,
- const QString &forcedCustomOverwrite,
- bool ignoreCustomOverwrite)
-{
- QFileInfo binary;
- QString arguments = args.join(" ");
- QString currentDirectory = cwd;
- QString profileName = profile;
- if (profile.length() == 0) {
- if (m_CurrentProfile != nullptr) {
- profileName = m_CurrentProfile->name();
- } else {
- throw MyException(tr("No profile set"));
- }
- }
- QString steamAppID;
- QString customOverwrite;
- QList<ExecutableForcedLoadSetting> forcedLibraries;
- if (executable.contains('\\') || executable.contains('/')) {
- // file path
-
- binary = QFileInfo(executable);
- if (binary.isRelative()) {
- // relative path, should be relative to game directory
- binary = QFileInfo(
- managedGame()->gameDirectory().absoluteFilePath(executable));
- }
- if (cwd.length() == 0) {
- 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 (cwd.length() == 0) {
- currentDirectory = exe.workingDirectory();
- }
- } catch (const std::runtime_error &) {
- qWarning("\"%s\" not set up as executable",
- qUtf8Printable(executable));
- binary = QFileInfo(executable);
- }
- }
-
- if (!forcedCustomOverwrite.isEmpty())
- customOverwrite = forcedCustomOverwrite;
- if (ignoreCustomOverwrite)
- customOverwrite.clear();
-
- return spawnBinaryDirect(binary,
- arguments,
- profileName,
- currentDirectory,
- steamAppID,
- customOverwrite,
- forcedLibraries);
-}
-
-bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
-{
- if (!Settings::instance().lockGUI())
- return true;
-
- ILockedWaitingForProcess* uilock = nullptr;
- if (m_UserInterface != nullptr) {
- uilock = m_UserInterface->lock();
- }
-
- ON_BLOCK_EXIT([&] () {
- if (m_UserInterface != nullptr) {
- m_UserInterface->unlock();
- } });
- return waitForProcessCompletion(handle, exitCode, uilock);
-}
-
-bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
-{
- bool originalHandle = true;
- bool newHandle = true;
- bool uiunlocked = false;
-
- DWORD currentPID = 0;
- QString processName;
- auto waitForChildUntil = GetTickCount64();
- if (handle != INVALID_HANDLE_VALUE) {
- currentPID = GetProcessId(handle);
- processName = QString::fromStdWString(getProcessName(handle));
- }
-
- // Certain process names we wish to "hide" for aesthetic reason:
- bool waitingOnHidden = false;
- std::vector<QString> 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"
- // 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);
- qDebug() << "Waiting for"
- << (originalHandle ? "spawned" : "usvfs")
- << "process completion :" << qUtf8Printable(processName);
- newHandle = false;
- }
-
- // 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) {
- qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError();
- break;
- }
-
- // keep processing events so the app doesn't appear dead
- QCoreApplication::sendPostedEvents();
- QCoreApplication::processEvents();
-
- if (uilock && uilock->unlockForced()) {
- uiunlocked = true;
- break;
- }
-
- if (res == WAIT_OBJECT_0) {
- // process we were waiting on has completed
- if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode))
- qWarning() << "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;
- }
-
- // 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 = QString::fromStdWString(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 (res == WAIT_OBJECT_0)
- qDebug() << "Waiting for process completion successfull";
- else if (uiunlocked)
- qDebug() << "Waiting for process completion aborted by UI";
- else
- qDebug() << "Waiting for process completion not successfull :" << res;
-
- if (handle != INVALID_HANDLE_VALUE)
- ::CloseHandle(handle);
-
- return res == WAIT_OBJECT_0;
-}
-
-HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& 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)) {
- qWarning() << "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
-
- HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]);
- if (handle == INVALID_HANDLE_VALUE) {
- qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError();
- continue;
- }
-
- QString pname = QString::fromStdWString(getProcessName(handle));
- bool phidden = false;
- for (auto hide : hiddenList)
- if (pname.contains(hide, Qt::CaseInsensitive))
- phidden = true;
-
- bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid;
-
- 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;
- }
- else
- CloseHandle(handle);
-
- if (!phidden && pprefered)
- return best_match;
- }
-
- return best_match;
-}
-
bool OrganizerCore::onAboutToRun(
const std::function<bool(const QString &)> &func)
{
@@ -2029,8 +1101,10 @@ void OrganizerCore::refreshModList(bool saveChanges)
if (saveChanges) {
m_CurrentProfile->writeModlistNow(true);
}
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.displayForeign(), managedGame());
+
+ ModInfo::updateFromDisc(
+ m_Settings.paths().mods(), &m_DirectoryStructure,
+ m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
m_CurrentProfile->refreshModStatus();
@@ -2118,7 +1192,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
dir.entryList(QStringList() << "*.esm", QDir::Files)) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm));
if (file.get() == nullptr) {
- qWarning("failed to activate %s", qUtf8Printable(esm));
+ log::warn("failed to activate {}", esm);
continue;
}
@@ -2134,7 +1208,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
dir.entryList(QStringList() << "*.esl", QDir::Files)) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl));
if (file.get() == nullptr) {
- qWarning("failed to activate %s", qUtf8Printable(esl));
+ log::warn("failed to activate {}", esl);
continue;
}
@@ -2150,7 +1224,7 @@ void OrganizerCore::updateModsActiveState(const QList<unsigned int> &modIndices,
for (const QString &esp : esps) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp));
if (file.get() == nullptr) {
- qWarning("failed to activate %s", qUtf8Printable(esp));
+ log::warn("failed to activate {}", esp);
continue;
}
@@ -2199,7 +1273,7 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap<unsigned int, ModInfo::P
updateModsActiveState(modInfo.keys(), true);
m_PluginList.blockSignals(false);
// now we need to refresh the bsa list and save it so there is no confusion
- // about what archives are avaiable and active
+ // about what archives are available and active
refreshBSAList();
if (m_UserInterface != nullptr) {
m_UserInterface->archivesWriter().writeImmediately(false);
@@ -2225,7 +1299,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function<void ()> f)
f();
} else {
QString apiKey;
- if (settings().getNexusApiKey(apiKey)) {
+ if (settings().nexus().apiKey(apiKey)) {
doAfterLogin([f]{ f(); });
NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey);
} else {
@@ -2364,8 +1438,10 @@ void OrganizerCore::profileRefresh()
{
// have to refresh mods twice (again in refreshModList), otherwise the refresh
// isn't complete. Not sure why
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
- m_PluginContainer, m_Settings.displayForeign(), managedGame());
+ ModInfo::updateFromDisc(
+ m_Settings.paths().mods(), &m_DirectoryStructure,
+ m_PluginContainer, m_Settings.interface().displayForeign(), managedGame());
+
m_CurrentProfile->refreshModStatus();
refreshModList();
@@ -2484,6 +1560,9 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary)
void OrganizerCore::loginFailed(const QString &message)
{
+ qCritical().nospace().noquote()
+ << "Nexus API validation failed: " << message;
+
if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"),
tr("Login failed, try again?"))
== QMessageBox::Yes) {
@@ -2529,7 +1608,7 @@ void OrganizerCore::syncOverwrite()
SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure,
qApp->activeWindow());
if (syncDialog.exec() == QDialog::Accepted) {
- syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory()));
+ syncDialog.apply(QDir::fromNativeSeparators(m_Settings.paths().mods()));
modInfo->testValid();
refreshDirectoryStructure();
}
@@ -2552,10 +1631,10 @@ std::vector<unsigned int> OrganizerCore::activeProblems() const
const auto& hookdll = oldMO1HookDll();
if (!hookdll.isEmpty()) {
// This warning will now be shown every time the problems are checked, which is a bit
- // of a "log spam". But since this is a sevre error which will most likely make the
+ // of a "log spam". But since this is a sever error which will most likely make the
// game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it
// easier for the user to notice the warning.
- qWarning("hook.dll found in game folder: %s", qUtf8Printable(hookdll));
+ log::warn("hook.dll found in game folder: {}", hookdll);
problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND);
}
return problems;
@@ -2601,7 +1680,7 @@ void OrganizerCore::startGuidedFix(unsigned int) const
bool OrganizerCore::saveCurrentLists()
{
if (m_DirectoryUpdate) {
- qWarning("not saving lists during directory update");
+ log::warn("not saving lists during directory update");
return false;
}
@@ -2628,22 +1707,102 @@ void OrganizerCore::savePluginList()
}
m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(),
m_CurrentProfile->getDeleterFileName(),
- m_Settings.hideUncheckedPlugins());
+ m_Settings.game().hideUncheckedPlugins());
m_PluginList.saveLoadOrder(*m_DirectoryStructure);
}
-void OrganizerCore::prepareStart()
+void OrganizerCore::saveCurrentProfile()
{
if (m_CurrentProfile == nullptr) {
return;
}
+
m_CurrentProfile->writeModlist();
m_CurrentProfile->createTweakedIniFile();
saveCurrentLists();
- m_Settings.setupLoadMechanism();
+ m_Settings.game().loadMechanism().activate(m_Settings.game().loadMechanismType());
storeSettings();
}
+ProcessRunner OrganizerCore::processRunner()
+{
+ return ProcessRunner(*this, m_UserInterface);
+}
+
+bool OrganizerCore::beforeRun(
+ const QFileInfo& binary, const QString& profileName,
+ const QString& customOverwrite,
+ const QList<MOBase::ExecutableForcedLoadSetting>& forcedLibraries)
+{
+ saveCurrentProfile();
+
+ 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 \"{}\" cancelled by plugin", binary.absoluteFilePath());
+ return false;
+ }
+
+ try
+ {
+ m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
+ m_USVFS.updateForcedLibraries(forcedLibraries);
+ }
+ catch (const UsvfsConnectorException &e)
+ {
+ log::debug(e.what());
+ return false;
+ }
+ catch (const std::exception &e)
+ {
+ QWidget* w = nullptr;
+ if (m_UserInterface) {
+ w = m_UserInterface->qtWidget();
+ }
+ QMessageBox::warning(w, tr("Error"), e.what());
+ return false;
+ }
+
+ return true;
+}
+
+void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
+{
+ 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();
+ cycleDiagnostics();
+
+ //These callbacks should not fiddle with directoy structure and ESPs.
+ m_FinishedRun(binary.absoluteFilePath(), exitCode);
+}
+
+ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses(
+ UILocker::Reasons reason)
+{
+ return processRunner().waitForAllUSVFSProcessesWithLock(reason);
+}
+
std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
const QString &customOverwrite)
{
@@ -2654,7 +1813,7 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
}
IPluginGame *game = qApp->property("managed_game").value<IPluginGame *>();
- Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName),
+ Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName),
game);
MappingType result;
@@ -2695,12 +1854,12 @@ std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
result.reserve(result.size() + saveMap.size());
result.insert(result.end(), saveMap.begin(), saveMap.end());
} else {
- qWarning("local save games not supported by this game plugin");
+ log::warn("local save games not supported by this game plugin");
}
}
result.insert(result.end(), {
- QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()),
+ QDir::toNativeSeparators(m_Settings.paths().overwrite()),
dataPath,
true,
customOverwrite.isEmpty()