summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp747
1 files changed, 56 insertions, 691 deletions
diff --git a/src/main.cpp b/src/main.cpp
index e56197c9..554ed006 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,625 +1,53 @@
-/*
-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 <http://www.gnu.org/licenses/>.
-*/
-
-#include "mainwindow.h"
-#include "singleinstance.h"
+#include "multiprocess.h"
#include "loglist.h"
-#include "selectiondialog.h"
#include "moapplication.h"
-#include "tutorialmanager.h"
-#include "nxmaccessmanager.h"
-#include "instancemanager.h"
-#include "instancemanagerdialog.h"
-#include "createinstancedialog.h"
-#include "createinstancedialogpages.h"
#include "organizercore.h"
-#include "env.h"
-#include "envmodule.h"
#include "commandline.h"
+#include "env.h"
+#include "thread_utils.h"
#include "shared/util.h"
-#include "shared/appconfig.h"
-
-#include <imoinfo.h>
-#include <report.h>
-#include <usvfs.h>
#include <log.h>
-#include <utility.h>
-
-// see addDllsToPath() below
-#pragma comment(linker, "/manifestDependency:\"" \
- "name='dlls' " \
- "processorArchitecture='x86' " \
- "version='1.0.0.0' " \
- "type='win32' \"")
-
using namespace MOBase;
-using namespace MOShared;
-
-void sanityChecks(const env::Environment& env);
-int checkIncompatibleModule(const env::Module& m);
-int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s);
-
-void purgeOldFiles()
-{
- // remove the temporary backup directory in case we're restarting after an
- // update
- QString backupDirectory = qApp->applicationDirPath() + "/update_backup";
- if (QDir(backupDirectory).exists()) {
- shellDelete(QStringList(backupDirectory));
- }
-
- // cycle log file
- removeOldFiles(
- qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
- "usvfs*.log", 5, QDir::Name);
-}
thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr;
thread_local std::terminate_handler g_prevTerminateHandler = nullptr;
-LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs)
-{
- const std::wstring& dumpPath = OrganizerCore::crashDumpsPath();
-
- const int r = CreateMiniDump(
- ptrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str());
-
- if (r == 0) {
- log::error("ModOrganizer has crashed, crash dump created.");
- } else {
- log::error(
- "ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).",
- r, GetLastError());
- }
-
- if (g_prevExceptionFilter && ptrs)
- return g_prevExceptionFilter(ptrs);
- else
- return EXCEPTION_CONTINUE_SEARCH;
-}
-
-void onTerminate() noexcept
-{
- __try
- {
- // force an exception to get a valid stack trace for this thread
- *(int*)0 = 42;
- }
- __except
- (
- onUnhandledException(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER
- )
- {
- }
-
- if (g_prevTerminateHandler) {
- g_prevTerminateHandler();
- } else {
- std::abort();
- }
-}
-
-void setExceptionHandlers()
-{
- g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException);
- g_prevTerminateHandler = std::set_terminate(onTerminate);
-}
-
-// This adds the `dlls` directory to the path so the dlls can be found. How
-// MO is able to find dlls in there is a bit convoluted:
-//
-// Dependencies on DLLs can be baked into an executable by passing a
-// `manifestdependency` option to the linker. This can be done on the command
-// line or with a pragma. Typically, the dependency will not be a hardcoded
-// filename, but an assembly name, such as Microsoft.Windows.Common-Controls.
-//
-// When Windows loads the exe, it will look for this assembly in a variety of
-// places, such as in the WinSxS folder, but also in the program's folder. It
-// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try
-// to load that.
-//
-// If these files don't exist, then the loader gets creative and looks for
-// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest
-// file is just an XML file that can contain a list of DLLs to load for this
-// assembly.
-//
-// In MO's case, there's a `pragma` at the beginning of this file which adds
-// `dlls` as an "assembly" dependency. This is a bit of a hack to just force
-// the loader to eventually find `dlls/dlls.manifest`, which contains the list
-// of all the DLLs MO requires to load.
-//
-// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and
-// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note
-// that the useless and incorrect .qt5 extension is removed.
-//
-void addDllsToPath()
-{
- const auto dllsPath = QDir::toNativeSeparators(
- QCoreApplication::applicationDirPath() + "/dlls");
-
- QCoreApplication::setLibraryPaths(
- QStringList(dllsPath) + QCoreApplication::libraryPaths());
-
- env::prependToPath(dllsPath);
-}
-
-QString getSplashPath(
- const Settings& settings, const QString& dataPath,
- const MOBase::IPluginGame* game)
-{
- if (!settings.useSplash()) {
- return {};
- }
-
- // try splash from instance directory
- const QString splashPath = dataPath + "/splash.png";
- if (QFile::exists(dataPath + "/splash.png")) {
- QImage image(splashPath);
- if (!image.isNull()) {
- return splashPath;
- }
- }
-
- // try splash from plugin
- QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName());
- if (QFile::exists(pluginSplash)) {
- QImage image(pluginSplash);
- if (!image.isNull()) {
- image.save(splashPath);
- return pluginSplash;
- }
- }
-
- // try default splash from resource
- QString defaultSplash = ":/MO/gui/splash";
- if (QFile::exists(defaultSplash)) {
- QImage image(defaultSplash);
- if (!image.isNull()) {
- return defaultSplash;
- }
- }
-
- return splashPath;
-}
-
-std::unique_ptr<QSplashScreen> createSplash(
- const Settings& settings, const QString& dataPath,
- const MOBase::IPluginGame* game)
-{
- const auto splashPath = getSplashPath(settings, dataPath, game);
- if (splashPath.isEmpty()) {
- return {};
- }
-
- QPixmap image(splashPath);
- if (image.isNull()) {
- log::error("failed to load splash from {}", splashPath);
- return {};
- }
-
- auto splash = std::make_unique<QSplashScreen>(image);
- settings.geometry().centerOnMainWindowMonitor(splash.get());
-
- splash->show();
- splash->activateWindow();
-
- return splash;
-}
-
-std::optional<int> handleCommandLine(
- const cl::CommandLine& cl, OrganizerCore& organizer)
-{
- // if we have a command line parameter, it is either a nxm link or
- // a binary to start
- if (cl.shortcut().isValid()) {
- if (cl.shortcut().hasExecutable()) {
- try {
- organizer.processRunner()
- .setFromShortcut(cl.shortcut())
- .setWaitForCompletion()
- .run();
-
- return 0;
- }
- catch (const std::exception &e) {
- reportError(
- QObject::tr("failed to start shortcut: %1").arg(e.what()));
- return 1;
- }
- }
- } else if (cl.nxmLink()) {
- log::debug("starting download from command line: {}", *cl.nxmLink());
- organizer.externalMessage(*cl.nxmLink());
- } else if (cl.executable()) {
- const QString exeName = *cl.executable();
- log::debug("starting {} from command line", exeName);
-
- try
- {
- // pass the remaining parameters to the binary
- organizer.processRunner()
- .setFromFileOrExecutable(exeName, cl.untouched())
- .setWaitForCompletion()
- .run();
-
- return 0;
- }
- catch (const std::exception &e)
- {
- reportError(
- QObject::tr("failed to start application: %1").arg(e.what()));
- return 1;
- }
- }
-
- return {};
-}
-
-std::optional<Instance> selectInstance()
-{
- auto& m = InstanceManager::singleton();
-
- NexusInterface ni(nullptr);
- PluginContainer pc(nullptr);
- pc.loadPlugins();
-
- if (m.instancePaths().empty() && !m.portableInstanceExists()) {
- // no instances configured
- CreateInstanceDialog dlg(pc, nullptr);
- if (dlg.exec() != QDialog::Accepted) {
- return {};
- }
-
- return m.currentInstance();
- }
-
-
- InstanceManagerDialog dlg(pc);
- dlg.setRestartOnSelect(false);
-
- dlg.show();
- dlg.activateWindow();
- dlg.raise();
-
- if (dlg.exec() != QDialog::Accepted) {
- return {};
- }
-
- return m.currentInstance();
-}
-
-enum class SetupInstanceResults
-{
- Ok,
- TryAgain,
- SelectAnother,
- Exit
-};
-
+int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl);
-void criticalOnTop(const QString& message)
-{
- QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message);
-
- mb.show();
- mb.activateWindow();
- mb.raise();
- mb.exec();
-}
-
-
-SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
+int main(int argc, char *argv[])
{
- const auto setupResult = instance.setup(pc);
-
- switch (setupResult)
- {
- case Instance::SetupResults::Ok:
- {
- return SetupInstanceResults::Ok;
- }
-
- case Instance::SetupResults::BadIni:
- {
- criticalOnTop(
- QObject::tr("Cannot open instance '%1', failed to read INI file %2.")
- .arg(instance.name()).arg(instance.iniPath()));
-
- return SetupInstanceResults::SelectAnother;
- }
-
- case Instance::SetupResults::IniMissingGame:
- {
- criticalOnTop(
- QObject::tr(
- "Cannot open instance '%1', the managed game was not found in the INI "
- "file %2. Select the game managed by this instance.")
- .arg(instance.name()).arg(instance.iniPath()));
-
- CreateInstanceDialog dlg(pc, nullptr);
- dlg.setSinglePage<cid::GamePage>(instance.name());
-
- dlg.show();
- dlg.activateWindow();
- dlg.raise();
-
- if (dlg.exec() != QDialog::Accepted) {
- return SetupInstanceResults::Exit;
- }
-
- instance.setGame(
- dlg.creationInfo().game->gameName(),
- dlg.creationInfo().gameLocation);
+ TimeThis tt("main()");
- return SetupInstanceResults::TryAgain;
- }
-
- case Instance::SetupResults::PluginGone:
- {
- criticalOnTop(
- QObject::tr(
- "Cannot open instance '%1', the game plugin '%2' doesn't exist. It "
- "may have been deleted by an antivirus. Select another instance.")
- .arg(instance.name()).arg(instance.gameName()));
-
- return SetupInstanceResults::SelectAnother;
- }
-
- case Instance::SetupResults::GameGone:
- {
- criticalOnTop(
- QObject::tr(
- "Cannot open instance '%1', the game directory '%2' doesn't exist or "
- "the game plugin '%3' doesn't recognize it. Select the game managed "
- "by this instance.")
- .arg(instance.name())
- .arg(instance.gameDirectory())
- .arg(instance.gameName()));
-
- CreateInstanceDialog dlg(pc, nullptr);
- dlg.setSinglePage<cid::GamePage>(instance.name());
-
- dlg.show();
- dlg.activateWindow();
- dlg.raise();
-
- if (dlg.exec() != QDialog::Accepted) {
- return SetupInstanceResults::Exit;
- }
-
- instance.setGame(
- dlg.creationInfo().game->gameName(),
- dlg.creationInfo().gameLocation);
-
- return SetupInstanceResults::TryAgain;
- }
-
- case Instance::SetupResults::MissingVariant:
- {
- CreateInstanceDialog dlg(pc, nullptr);
-
- dlg.getPage<cid::GamePage>()->select(
- instance.gamePlugin(), instance.gameDirectory());
-
- dlg.setSinglePage<cid::VariantsPage>(instance.name());
-
- dlg.show();
- dlg.activateWindow();
- dlg.raise();
-
- if (dlg.exec() != QDialog::Accepted) {
- return SetupInstanceResults::Exit;
- }
-
- instance.setVariant(dlg.creationInfo().gameVariant);
-
- return SetupInstanceResults::TryAgain;
- }
+ MOShared::SetThisThreadName("main");
+ setExceptionHandlers();
- default:
- {
- return SetupInstanceResults::Exit;
- }
+ cl::CommandLine cl;
+ if (auto r=cl.run(GetCommandLineW())) {
+ return *r;
}
-}
-
-int runApplication(
- MOApplication &application, const cl::CommandLine& cl,
- SingleInstance &instance, const QString &dataPath,
- Instance& currentInstance)
-{
- TimeThis tt("runApplication() to exec()");
-
- log::info(
- "starting Mod Organizer version {} revision {} in {}, usvfs: {}",
- createVersionInfo().displayString(3), GITID,
- QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString());
- log::info("data path: {}", dataPath);
-
- if (InstanceManager::isPortablePath(dataPath)) {
- log::debug("this is a portable instance");
- }
+ initLogging();
- log::info("working directory: {}", QDir::currentPath());
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ MOApplication app(cl, argc, argv);
- if (!instance.secondary()) {
- purgeOldFiles();
+ MOMultiProcess multiProcess(cl.multiple());
+ if (multiProcess.ephemeral()) {
+ return forwardToPrimary(multiProcess, cl);
}
- QWindowsWindowFunctions::setWindowActivationBehavior(
- QWindowsWindowFunctions::AlwaysActivateWindow);
-
- try
- {
- Settings settings(
- dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()),
- true);
-
- log::getDefault().setLevel(settings.diagnostics().logLevel());
-
- log::debug("using ini at '{}'", settings.filename());
-
- if (instance.secondary()) {
- log::debug("another instance of MO is running but --multiple was given");
- }
-
- // global crashDumpType sits in OrganizerCore to make a bit less ugly to
- // update it when the settings are changed during runtime
- OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType());
-
- env::Environment env;
- env.dump(settings);
- settings.dump();
- sanityChecks(env);
-
- const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) {
- log::debug("loaded module {}", m.toString());
- checkIncompatibleModule(m);
- });
-
- // this must outlive `organizer`
- std::unique_ptr<PluginContainer> pluginContainer;
-
- log::debug("initializing nexus interface");
- NexusInterface ni(&settings);
-
- log::debug("initializing core");
- OrganizerCore organizer(settings);
- if (!organizer.bootstrap()) {
- reportError("failed to set up data paths");
- InstanceManager::singleton().clearCurrentInstance();
- return 1;
- }
-
- log::debug("initializing plugins");
- pluginContainer = std::make_unique<PluginContainer>(&organizer);
- pluginContainer->loadPlugins();
-
- for (;;)
- {
- const auto setupResult = setupInstance(currentInstance, *pluginContainer);
-
- if (setupResult == SetupInstanceResults::Ok) {
- break;
- } else if (setupResult == SetupInstanceResults::TryAgain) {
- continue;
- } else if (setupResult == SetupInstanceResults::SelectAnother) {
- InstanceManager::singleton().clearCurrentInstance();
- return RestartExitCode;
- } else {
- return 1;
- }
- }
-
- checkPathsForSanity(*currentInstance.gamePlugin(), settings);
-
- organizer.setManagedGame(currentInstance.gamePlugin());
- organizer.createDefaultProfile();
-
- log::info(
- "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}",
- currentInstance.gamePlugin()->gameName(),
- currentInstance.gamePlugin()->gameShortName(),
- (settings.game().edition().value_or("").isEmpty() ?
- "(none)" : *settings.game().edition()),
- currentInstance.gamePlugin()->steamAPPId(),
- currentInstance.gamePlugin()->gameDirectory().absolutePath());
-
-
- CategoryFactory::instance().loadCategories();
- organizer.updateExecutablesList();
- organizer.updateModInfoFromDisc();
-
- organizer.setCurrentProfile(currentInstance.profileName());
-
- if (auto r=handleCommandLine(cl, organizer)) {
- return *r;
- }
-
- auto splash = createSplash(settings, dataPath, currentInstance.gamePlugin());
-
- QString apiKey;
- if (GlobalSettings::nexusApiKey(apiKey)) {
- ni.getAccessManager()->apiCheck(apiKey);
- }
-
- log::debug("initializing tutorials");
- TutorialManager::init(
- qApp->applicationDirPath() + "/"
- + QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
- &organizer);
-
- if (!application.setStyleFile(settings.interface().styleName().value_or(""))) {
- // disable invalid stylesheet
- settings.interface().setStyleName("");
- }
-
- int res = 1;
-
- { // scope to control lifetime of mainwindow
- // set up main window and its data structures
- MainWindow mainWindow(settings, organizer, *pluginContainer);
-
- ni.getAccessManager()->setTopLevelWidget(&mainWindow);
-
- QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application,
- SLOT(setStyleFile(QString)));
- QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer,
- SLOT(externalMessage(QString)));
-
- log::debug("displaying main window");
- mainWindow.show();
- mainWindow.activateWindow();
-
- if (splash) {
- // don't pass mainwindow as it just waits half a second for it
- // instead of proceding
- splash->finish(nullptr);
- }
-
- tt.stop();
-
- res = application.exec();
- mainWindow.close();
-
- ni.getAccessManager()->setTopLevelWidget(nullptr);
- }
-
- settings.geometry().resetIfNeeded();
- return res;
- }
- catch (const std::exception &e)
- {
- reportError(e.what());
- }
+ tt.stop();
- return 1;
+ return app.run(multiProcess);
}
-int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl)
+int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl)
{
if (cl.shortcut().isValid()) {
- instance.sendMessage(cl.shortcut().toString());
+ multiProcess.sendMessage(cl.shortcut().toString());
} else if (cl.nxmLink()) {
- instance.sendMessage(*cl.nxmLink());
+ multiProcess.sendMessage(*cl.nxmLink());
} else {
QMessageBox::information(
nullptr, QObject::tr("Mod Organizer"),
@@ -629,118 +57,55 @@ int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl)
return 0;
}
-void resetForRestart(cl::CommandLine& cl)
+LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs)
{
- LogModel::instance().clear();
- ResetExitFlag();
+ const auto path = OrganizerCore::getGlobalCoreDumpPath();
+ const auto type = OrganizerCore::getGlobalCoreDumpType();
- // make sure the log file isn't locked in case MO was restarted and
- // the previous instance gets deleted
- log::getDefault().setFile({});
+ const auto r = env::coredump(path.empty() ? nullptr : path.c_str(), type);
- // don't reprocess command line
- cl.clear();
+ if (r) {
+ log::error("ModOrganizer has crashed, core dump created.");
+ } else {
+ log::error("ModOrganizer has crashed, core dump failed");
+ }
+
+ // g_prevExceptionFilter somehow sometimes point to this function, making this
+ // recurse and create hundreds of core dump, not sure why
+ if (g_prevExceptionFilter && ptrs && g_prevExceptionFilter != onUnhandledException)
+ return g_prevExceptionFilter(ptrs);
+ else
+ return EXCEPTION_CONTINUE_SEARCH;
}
-int doOneRun(
- cl::CommandLine& cl, MOApplication& application, SingleInstance& instance)
+void onTerminate() noexcept
{
- TimeThis tt("doOneRun() to runApplication()");
-
- // resets things when MO is "restarted"
- resetForRestart(cl);
-
- auto& m = InstanceManager::singleton();
- auto currentInstance = m.currentInstance();
-
- if (!currentInstance)
+ __try
{
- currentInstance = selectInstance();
- if (!currentInstance) {
- return 1;
- }
+ // force an exception to get a valid stack trace for this thread
+ *(int*)0 = 42;
}
- else
+ __except
+ (
+ onUnhandledException(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER
+ )
{
- if (!currentInstance->directory().exists()) {
- // the previously used instance doesn't exist anymore
-
- if (m.instanceNames().empty() && !m.portableInstanceExists()) {
- criticalOnTop(QObject::tr(
- "Instance at '%1' not found. You must create a new instance")
- .arg(currentInstance->directory().absolutePath()));
- } else {
- criticalOnTop(QObject::tr(
- "Instance at '%1' not found. Select another instance.")
- .arg(currentInstance->directory().absolutePath()));
- }
-
- currentInstance = selectInstance();
- if (!currentInstance) {
- return 1;
- }
- }
}
- const QString dataPath = currentInstance->directory().path();
- application.setProperty("dataPath", dataPath);
-
- setExceptionHandlers();
-
- if (!setLogDirectory(dataPath)) {
- reportError("Failed to create log folder");
- InstanceManager::singleton().clearCurrentInstance();
- return 1;
+ if (g_prevTerminateHandler) {
+ g_prevTerminateHandler();
+ } else {
+ std::abort();
}
-
- log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW()));
-
- tt.stop();
-
- return runApplication(application, cl, instance, dataPath, *currentInstance);
}
-int main(int argc, char *argv[])
+void setExceptionHandlers()
{
- cl::CommandLine cl;
-
- if (auto r=cl.run(GetCommandLineW())) {
- return *r;
- }
-
- TimeThis tt("main() to doOneRun()");
-
- SetThisThreadName("main");
-
- initLogging();
- auto application = MOApplication::create(argc, argv);
- addDllsToPath();
-
- SingleInstance instance(cl.multiple());
- if (instance.ephemeral()) {
- return forwardToPrimary(instance, cl);
+ if (g_prevExceptionFilter) {
+ // already called
+ return;
}
- tt.stop();
-
- if (cl.instance())
- InstanceManager::singleton().overrideInstance(*cl.instance());
-
- if (cl.profile()) {
- InstanceManager::singleton().overrideProfile(*cl.profile());
- }
-
- // makes plugin data path available to plugins, see
- // IOrganizer::getPluginDataPath()
- MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath());
-
- for (;;)
- {
- const auto r = doOneRun(cl, application, instance);
- if (r == RestartExitCode) {
- continue;
- }
-
- return r;
- }
+ g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException);
+ g_prevTerminateHandler = std::set_terminate(onTerminate);
}