summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMikaƫl Capelle <capelle.mikael@gmail.com>2021-01-18 21:16:40 +0100
committerGitHub <noreply@github.com>2021-01-18 21:16:40 +0100
commitee44a38c986d953a19cbb37b0297590844cf54ac (patch)
treee39b12f7cb7f37cd96b3c3f451d2b527a81d4578 /src
parent59f055ba93381b965cdc04557ac1dce2df36bd07 (diff)
parent6feab2ea8f96704e44a14c212a635dc17f4ba71e (diff)
Merge pull request #1354 from isanae/startup-rework
Startup rework
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/commandline.cpp364
-rw-r--r--src/commandline.h155
-rw-r--r--src/executableslist.cpp16
-rw-r--r--src/executableslist.h4
-rw-r--r--src/filetree.cpp4
-rw-r--r--src/instancemanager.cpp19
-rw-r--r--src/instancemanager.h4
-rw-r--r--src/loglist.cpp21
-rw-r--r--src/loglist.h1
-rw-r--r--src/main.cpp125
-rw-r--r--src/mainwindow.cpp4
-rw-r--r--src/messagedialog.cpp22
-rw-r--r--src/moapplication.cpp252
-rw-r--r--src/moapplication.h47
-rw-r--r--src/organizercore.cpp29
-rw-r--r--src/organizercore.h1
-rw-r--r--src/organizerproxy.cpp2
-rw-r--r--src/processrunner.cpp38
-rw-r--r--src/processrunner.h22
-rw-r--r--src/profile.cpp1
-rw-r--r--src/shared/error_report.cpp64
-rw-r--r--src/shared/error_report.h44
-rw-r--r--src/shared/leaktrace.cpp68
-rw-r--r--src/shared/leaktrace.h24
-rw-r--r--src/shared/stackdata.cpp169
-rw-r--r--src/shared/stackdata.h50
-rw-r--r--src/spawn.cpp9
28 files changed, 847 insertions, 715 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a5d15740..e8c52721 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -212,12 +212,9 @@ add_filter(NAME src/utilities GROUPS
shared/appconfig
bbcode
csvbuilder
- shared/error_report
- shared/leaktrace
persistentcookiejar
serverinfo
spawn
- shared/stackdata
shared/util
usvfsconnector
shared/windows_error
diff --git a/src/commandline.cpp b/src/commandline.cpp
index 8c722990..477bfba1 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -1,7 +1,11 @@
#include "commandline.h"
#include "env.h"
#include "organizercore.h"
+#include "instancemanager.h"
+#include "multiprocess.h"
+#include "loglist.h"
#include "shared/util.h"
+#include "shared/appconfig.h"
#include <log.h>
#include <report.h>
@@ -49,15 +53,19 @@ std::string table(
CommandLine::CommandLine()
+ : m_command(nullptr)
{
createOptions();
- m_commands.push_back(std::make_unique<ExeCommand>());
- m_commands.push_back(std::make_unique<RunCommand>());
- m_commands.push_back(std::make_unique<CrashDumpCommand>());
- m_commands.push_back(std::make_unique<LaunchCommand>());
+
+ add<
+ RunCommand,
+ ReloadPluginCommand,
+ RefreshCommand,
+ CrashDumpCommand,
+ LaunchCommand>();
}
-std::optional<int> CommandLine::run(const std::wstring& line)
+std::optional<int> CommandLine::process(const std::wstring& line)
{
try
{
@@ -116,17 +124,22 @@ std::optional<int> CommandLine::run(const std::wstring& line)
parsed = parser.run();
po::store(parsed, m_vm);
- po::notify(m_vm);
if (m_vm.count("help")) {
env::Console console;
std::cout << usage(c.get()) << "\n";
return 0;
}
+
+ // must be below the help check because it throws if required
+ // positional arguments are missing
+ po::notify(m_vm);
}
- // run the command
- return c->run(line, m_vm, opts);
+ c->set(line, m_vm, opts);
+ m_command = c.get();
+
+ return runEarly();
}
catch(po::error& e)
{
@@ -154,6 +167,7 @@ std::optional<int> CommandLine::run(const std::wstring& line)
return 0;
}
+
if (!opts.empty()) {
const auto qs = QString::fromStdWString(opts[0]);
@@ -166,7 +180,7 @@ std::optional<int> CommandLine::run(const std::wstring& line)
return 1;
}
- // try as an moshorcut://
+ // try as an moshortcut://
m_shortcut = qs;
if (!m_shortcut.isValid()) {
@@ -201,16 +215,76 @@ std::optional<int> CommandLine::run(const std::wstring& line)
}
}
-std::optional<int> CommandLine::setupCore(OrganizerCore& organizer) const
+bool CommandLine::forwardToPrimary(MOMultiProcess& multiProcess)
+{
+ if (m_shortcut.isValid()) {
+ multiProcess.sendMessage(m_shortcut.toString());
+ } else if (m_nxmLink) {
+ multiProcess.sendMessage(*m_nxmLink);
+ } else if (m_command && m_command->canForwardToPrimary()) {
+ multiProcess.sendMessage(QString::fromWCharArray(GetCommandLineW()));
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+std::optional<int> CommandLine::runEarly()
+{
+ if (m_vm.count("logs")) {
+ // in loglist.h
+ logToStdout(true);
+ }
+
+ if (m_command) {
+ return m_command->runEarly();
+ }
+
+ return {};
+}
+
+std::optional<int> CommandLine::runPostApplication(MOApplication& a)
+{
+ // handle -i with no arguments
+ if (m_vm.count("instance") && m_vm["instance"].as<std::string>() == "") {
+ env::Console c;
+
+ if (auto i=InstanceManager::singleton().currentInstance()) {
+ std::cout << i->name().toStdString() << "\n";
+ } else {
+ std::cout << "no instance configured\n";
+ }
+
+ return 0;
+ }
+
+ if (m_command) {
+ return m_command->runPostApplication(a);
+ }
+
+ return {};
+}
+
+std::optional<int> CommandLine::runPostMultiProcess(MOMultiProcess& mp)
+{
+ if (m_command) {
+ return m_command->runPostMultiProcess(mp);
+ }
+
+ return {};
+}
+
+std::optional<int> CommandLine::runPostOrganizer(OrganizerCore& core)
{
if (m_shortcut.isValid()) {
if (m_shortcut.hasExecutable()) {
try {
// make sure MO doesn't exit even if locking is disabled, ForceWait and
// PreventExit will do that
- organizer.processRunner()
+ core.processRunner()
.setFromShortcut(m_shortcut)
- .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit)
+ .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit)
.run();
return 0;
@@ -223,7 +297,7 @@ std::optional<int> CommandLine::setupCore(OrganizerCore& organizer) const
}
} else if (m_nxmLink) {
log::debug("starting download from command line: {}", *m_nxmLink);
- organizer.externalMessage(*m_nxmLink);
+ core.downloadRequestedNXM(*m_nxmLink);
} else if (m_executable) {
const QString exeName = *m_executable;
log::debug("starting {} from command line", exeName);
@@ -234,9 +308,9 @@ std::optional<int> CommandLine::setupCore(OrganizerCore& organizer) const
//
// make sure MO doesn't exit even if locking is disabled, ForceWait and
// PreventExit will do that
- organizer.processRunner()
+ core.processRunner()
.setFromFileOrExecutable(exeName, m_untouched)
- .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::PreventExit)
+ .setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit)
.run();
return 0;
@@ -247,6 +321,8 @@ std::optional<int> CommandLine::setupCore(OrganizerCore& organizer) const
QObject::tr("failed to start application: %1").arg(e.what()));
return 1;
}
+ } else if (m_command) {
+ return m_command->runPostOrganizer(core);
}
return {};
@@ -262,10 +338,23 @@ void CommandLine::clear()
void CommandLine::createOptions()
{
m_visibleOptions.add_options()
- ("help", "show this message")
- ("multiple", "allow multiple MO processes to run; see below")
- ("instance,i", po::value<std::string>(), "use the given instance (defaults to last used)")
- ("profile,p", po::value<std::string>(), "use the given profile (defaults to last used)");
+ ("help",
+ "show this message")
+
+ ("multiple",
+ "allow multiple MO processes to run; see below")
+
+ ("logs",
+ "duplicates the logs to stdout")
+
+ ("instance,i",
+ po::value<std::string>()->implicit_value(""),
+ "use the given instance (defaults to last used)")
+
+ ("profile,p",
+ po::value<std::string>(),
+ "use the given profile (defaults to last used)");
+
po::options_description options;
options.add_options()
@@ -291,8 +380,14 @@ std::string CommandLine::usage(const Command* c) const
if (c) {
oss
- << " ModOrganizer.exe [global-options] " << c->usageLine() << "\n"
- << "\n"
+ << " ModOrganizer.exe [global-options] " << c->usageLine() << "\n\n";
+
+ const std::string more = c->moreInfo();
+ if (!more.empty()) {
+ oss << more << "\n\n";
+ }
+
+ oss
<< "Command options:\n"
<< c->visibleOptions() << "\n";
} else {
@@ -304,6 +399,11 @@ std::string CommandLine::usage(const Command* c) const
// name and description for all commands
std::vector<std::pair<std::string, std::string>> v;
for (auto&& c : m_commands) {
+ // don't show legacy commands
+ if (c->legacy()) {
+ continue;
+ }
+
v.push_back({c->name(), c->description()});
}
@@ -395,6 +495,11 @@ std::string CommandLine::more() const
}
+Command::Meta::Meta(std::string n, std::string d, std::string u, std::string m)
+ : name(n), description(d), usage(u), more(m)
+{
+}
+
std::string Command::name() const
{
return meta().name;
@@ -407,7 +512,12 @@ std::string Command::description() const
std::string Command::usageLine() const
{
- return name() + " " + getUsageLine();
+ return name() + " " + meta().usage;
+}
+
+std::string Command::moreInfo() const
+{
+ return meta().more;
}
po::options_description Command::allOptions() const
@@ -437,12 +547,7 @@ po::positional_options_description Command::positional() const
bool Command::legacy() const
{
- return true;
-}
-
-std::string Command::getUsageLine() const
-{
- return "[options]";
+ return false;
}
po::options_description Command::getVisibleOptions() const
@@ -463,8 +568,7 @@ po::positional_options_description Command::getPositional() const
return {};
}
-
-std::optional<int> Command::run(
+void Command::set(
const std::wstring& originalLine,
po::variables_map vm,
std::vector<std::wstring> untouched)
@@ -472,8 +576,31 @@ std::optional<int> Command::run(
m_original = originalLine;
m_vm = vm;
m_untouched = untouched;
+}
+
+std::optional<int> Command::runEarly()
+{
+ return {};
+}
+
+std::optional<int> Command::runPostApplication(MOApplication& a)
+{
+ return {};
+}
+
+std::optional<int> Command::runPostMultiProcess(MOMultiProcess&)
+{
+ return {};
+}
+
+std::optional<int> Command::runPostOrganizer(OrganizerCore&)
+{
+ return {};
+}
- return doRun();
+bool Command::canForwardToPrimary() const
+{
+ return false;
}
const std::wstring& Command::originalCmd() const
@@ -504,10 +631,15 @@ po::options_description CrashDumpCommand::getVisibleOptions() const
Command::Meta CrashDumpCommand::meta() const
{
- return {"crashdump", "writes a crashdump for a running process of MO"};
+ return {
+ "crashdump",
+ "writes a crashdump for a running process of MO",
+ "[options]",
+ ""
+ };
}
-std::optional<int> CrashDumpCommand::doRun()
+std::optional<int> CrashDumpCommand::runEarly()
{
env::Console console;
@@ -529,7 +661,7 @@ std::optional<int> CrashDumpCommand::doRun()
Command::Meta LaunchCommand::meta() const
{
- return {"launch", "(internal, do not use)"};
+ return {"launch", "(internal, do not use)", "", ""};
}
bool LaunchCommand::legacy() const
@@ -537,7 +669,7 @@ bool LaunchCommand::legacy() const
return true;
}
-std::optional<int> LaunchCommand::doRun()
+std::optional<int> LaunchCommand::runEarly()
{
// needs at least the working directory and process name
if (untouched().size() < 2) {
@@ -603,71 +735,189 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments(
}
-std::string ExeCommand::getUsageLine() const
+Command::Meta RunCommand::meta() const
{
- return "[options] exe-name";
+ return {
+ "run",
+ "runs a program, file or a configured executable",
+ "[options] NAME",
+
+ "Runs a program or a file with the virtual filesystem. If NAME is a path\n"
+ "to a non-executable file, the program that is associated with the file\n"
+ "extension is run instead. With -e, NAME must refer to the name of an\n"
+ "executable in the instance (for example, \"SKSE\")."
+ };
}
-po::options_description ExeCommand::getVisibleOptions() const
+po::options_description RunCommand::getVisibleOptions() const
{
po::options_description d;
d.add_options()
- ("arguments,a", po::value<std::string>()->default_value(""), "override arguments")
- ("cwd,c", po::value<std::string>()->default_value(""), "override working directory");
+ ("executable,e", po::value<bool>()->default_value(false)->zero_tokens(), "the name is a configured executable name")
+ ("arguments,a", po::value<std::string>(), "override arguments")
+ ("cwd,c", po::value<std::string>(), "override working directory");
return d;
}
-po::options_description ExeCommand::getInternalOptions() const
+po::options_description RunCommand::getInternalOptions() const
{
po::options_description d;
d.add_options()
- ("exe-name", po::value<std::string>()->required(), "executable name");
+ ("NAME", po::value<std::string>()->required(), "program or executable name");
return d;
}
-po::positional_options_description ExeCommand::getPositional() const
+po::positional_options_description RunCommand::getPositional() const
{
po::positional_options_description d;
- d.add("exe-name", 1);
+ d.add("NAME", 1);
return d;
}
-Command::Meta ExeCommand::meta() const
+bool RunCommand::canForwardToPrimary() const
+{
+ return true;
+}
+
+std::optional<int> RunCommand::runPostOrganizer(OrganizerCore& core)
+{
+ const auto program = QString::fromStdString(vm()["NAME"].as<std::string>());
+
+ try
+ {
+ // make sure MO doesn't exit even if locking is disabled, ForceWait and
+ // PreventExit will do that
+ auto p = core.processRunner();
+
+ if (vm()["executable"].as<bool>()) {
+ const auto& exes = *core.executablesList();
+
+ // case sensitive
+ auto itor = exes.find(program, true);
+ if (itor == exes.end()) {
+ // case insensitive
+ itor = exes.find(program, false);
+
+ if (itor == exes.end()) {
+ // not found
+ reportError(
+ QObject::tr("Executable '%1' not found in instance '%2'.")
+ .arg(program)
+ .arg(InstanceManager::singleton().currentInstance()->name()));
+
+ return 1;
+ }
+ }
+
+ p.setFromExecutable(*itor);
+ } else {
+ p.setFromFile(nullptr, QFileInfo(program));
+ }
+
+ if (vm().count("arguments")) {
+ p.setArguments(QString::fromStdString(vm()["arguments"].as<std::string>()));
+ }
+
+ if (vm().count("cwd")) {
+ p.setCurrentDirectory(QString::fromStdString(vm()["cwd"].as<std::string>()));
+ }
+
+ p.setWaitForCompletion(ProcessRunner::ForCommandLine, UILocker::PreventExit);
+
+ const auto r = p.run();
+ if (r == ProcessRunner::Error) {
+ reportError(
+ QObject::tr("Failed to run '%1'. The logs might have more information.").arg(program));
+
+ return 1;
+ }
+
+ return 0;
+ }
+ catch (const std::exception &e) {
+ reportError(
+ QObject::tr("Failed to run '%1'. The logs might have more information. %2")
+ .arg(program).arg(e.what()));
+
+ return 1;
+ }
+}
+
+
+
+Command::Meta ReloadPluginCommand::meta() const
{
- return {"exe", "launches a configured executable"};
+ return {
+ "reload-plugin",
+ "reloads the given plugin",
+ "PLUGIN",
+ ""
+ };
}
-std::optional<int> ExeCommand::doRun()
+po::options_description ReloadPluginCommand::getInternalOptions() const
{
- const auto exe = vm()["exe-name"].as<std::string>();
- const auto args = vm()["arguments"].as<std::string>();
- const auto cwd = vm()["cwd"].as<std::string>();
+ po::options_description d;
- std::cout << "not implemented\n";
+ d.add_options()
+ ("PLUGIN", po::value<std::string>()->required(), "plugin name");
- return 0;
+ return d;
}
+po::positional_options_description ReloadPluginCommand::getPositional() const
+{
+ po::positional_options_description d;
-po::options_description RunCommand::getOptions() const
+ d.add("PLUGIN", 1);
+
+ return d;
+}
+
+bool ReloadPluginCommand::canForwardToPrimary() const
{
+ return true;
+}
+
+std::optional<int> ReloadPluginCommand::runPostOrganizer(OrganizerCore& core)
+{
+ const QString name = QString::fromStdString(vm()["PLUGIN"].as<std::string>());
+
+ QString filepath = QDir(
+ qApp->applicationDirPath() + "/" +
+ ToQString(AppConfig::pluginPath()))
+ .absoluteFilePath(name);
+
+ log::debug("reloading plugin from {}", filepath);
+ core.pluginContainer().reloadPlugin(filepath);
+
return {};
}
-Command::Meta RunCommand::meta() const
+
+Command::Meta RefreshCommand::meta() const
{
- return {"run", "launches an arbitrary program"};
+ return {
+ "refresh",
+ "refreshes MO (same as F5)",
+ "", ""
+ };
+}
+
+bool RefreshCommand::canForwardToPrimary() const
+{
+ return true;
}
-std::optional<int> RunCommand::doRun()
+std::optional<int> RefreshCommand::runPostOrganizer(OrganizerCore& core)
{
- std::cout << "not implemented\n";
+ core.refresh();
return {};
}
diff --git a/src/commandline.h b/src/commandline.h
index 09c1bb8e..6bd57132 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -5,6 +5,8 @@
#include <memory>
class OrganizerCore;
+class MOApplication;
+class MOMultiProcess;
namespace cl
{
@@ -27,10 +29,14 @@ public:
//
std::string description() const;
- // usage line, puts together the name and whatever getUsageLine() returns
+ // usage line, puts together the name and whatever meta().usage is
//
std::string usageLine() const;
+ // shown after the usage line; this is meta().more
+ //
+ std::string moreInfo() const;
+
// returns all options for this command, including hidden ones; used to parse
// the command line
@@ -50,24 +56,65 @@ public:
//
virtual bool legacy() const;
- // runs this command, eventually calls doRun()
+ // remembers the given values
//
- std::optional<int> run(
+ void set(
const std::wstring& originalLine,
po::variables_map vm,
std::vector<std::wstring> untouched);
+
+ // called as soon as the command line has been parsed; return something to
+ // exit immediately
+ //
+ virtual std::optional<int> runEarly();
+
+ // called as soon as the MOApplication has been created, which is also the
+ // first time where Qt stuff is available
+ //
+ virtual std::optional<int> runPostApplication(MOApplication& a);
+
+ // called as soon as the multi process checks have confirmed that this is
+ // a primary instance; return something to exit immediately
+ //
+ virtual std::optional<int> runPostMultiProcess(MOMultiProcess& mp);
+
+ // called after the OrganizerCore has been initialized; return something to
+ // exit immediatley
+ //
+ virtual std::optional<int> runPostOrganizer(OrganizerCore& core);
+
+ // whether this command can be forwarded to the primary instance if one is
+ // already running
+ //
+ // if an instance is already running and this returns true, the whole command
+ // line is sent as a message to the primary instance, which will construct a
+ // new CommandLine and call runPostOrganizer() with it
+ //
+ // if an instance is already running and this returns false, the "an instance
+ // is already running" error dialog will be shown
+ //
+ // if this is the primary instance, this function is not called
+ //
+ virtual bool canForwardToPrimary() const;
+
protected:
// meta information about this command, returned by derived classes
//
struct Meta
{
- std::string name, description;
+ std::string name, description, usage, more;
+
+ Meta(
+ std::string name, std::string description,
+ std::string usage, std::string more);
};
- // returns the usage line for this command, not including the name
+
+ // meta
//
- virtual std::string getUsageLine() const;
+ virtual Meta meta() const = 0;
+
// returns visible options specific to this command
//
@@ -82,15 +129,6 @@ protected:
virtual po::positional_options_description getPositional() const;
- // meta
- //
- virtual Meta meta() const = 0;
-
- // runs the command
- //
- virtual std::optional<int> doRun() = 0;
-
-
// returns the original command line
//
const std::wstring& originalCmd() const;
@@ -117,7 +155,7 @@ class CrashDumpCommand : public Command
protected:
po::options_description getVisibleOptions() const override;
Meta meta() const override;
- std::optional<int> doRun() override;
+ std::optional<int> runEarly() override;
};
@@ -140,7 +178,7 @@ public:
protected:
Meta meta() const override;
- std::optional<int> doRun() override;
+ std::optional<int> runEarly() override;
int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine);
@@ -149,31 +187,49 @@ protected:
};
-// runs a configured executable
+// runs a program or an executable
//
-class ExeCommand : public Command
+class RunCommand : public Command
{
protected:
- std::string getUsageLine() const override;
+ Meta meta() const override;
+
po::options_description getVisibleOptions() const override;
po::options_description getInternalOptions() const override;
po::positional_options_description getPositional() const override;
+
+ bool canForwardToPrimary() const override;
+ std::optional<int> runPostOrganizer(OrganizerCore& core) override;
+};
+
+
+// reloads the given plugin
+//
+class ReloadPluginCommand : public Command
+{
+protected:
Meta meta() const override;
- std::optional<int> doRun() override;
+
+ po::options_description getInternalOptions() const override;
+ po::positional_options_description getPositional() const override;
+
+ bool canForwardToPrimary() const override;
+ std::optional<int> runPostOrganizer(OrganizerCore& core) override;
};
-// runs an arbitrary executable
+// refreshes mo
//
-class RunCommand : public Command
+class RefreshCommand : public Command
{
protected:
- po::options_description getOptions() const;
Meta meta() const override;
- std::optional<int> doRun() override;
+ bool canForwardToPrimary() const override;
+ std::optional<int> runPostOrganizer(OrganizerCore& core) override;
};
+
// parses the command line and runs any given command
//
// the command line used to support a few commands but with no real conventions;
@@ -212,20 +268,45 @@ class CommandLine
public:
CommandLine();
- // parses the given command line and executes the appropriate command, if
- // any
+ // parses the given command line and calls runEarly() on the appropriate
+ // command, if any
//
// returns an empty optional if execution should continue, or a return code
// if MO must quit
//
- std::optional<int> run(const std::wstring& line);
+ std::optional<int> process(const std::wstring& line);
- // handles moshortcut, nxm links and starting processes
+ // called as soon as the MOApplication has been created; this handles a few
+ // global actions and forwards to the command, if any
//
- // returns an empty optional if execution should continue, or a return code
- // if MO must quit
+ std::optional<int> runPostApplication(MOApplication& a);
+
+ // calls Command::runPostMultiProcess() on the command, if any
//
- std::optional<int> setupCore(OrganizerCore& organizer) const;
+ std::optional<int> runPostMultiProcess(MOMultiProcess& mp);
+
+ // calls Command::runPostOrganizer() on the command, if any
+ //
+ // if MO wasn't invoked with a valid command, this also handles moshortcut,
+ // nxm links and starting processes
+ //
+ std::optional<int> runPostOrganizer(OrganizerCore& core);
+
+ // called when this instance is not the primary one
+ //
+ // if a command was executed and the command returns true for
+ // canForwardToPrimary(), this forwards the command line as an external
+ // message and returns true
+ //
+ // if the command returns false for canForwardToPrimary(), returns false
+ //
+ // if there was no valid command on the command line, this also forwards
+ // moshortcut and nxm links
+ //
+ // if there was no command, moshortcut or nxm links, this returns false, which
+ // will end up displaying an error message about an instance already running
+ //
+ bool forwardToPrimary(MOMultiProcess& multiProcess);
// clears parsed options, used when MO is "restarted" so the options aren't
@@ -259,6 +340,7 @@ public:
std::optional<QString> nxmLink() const;
// returns the executable/binary, if any
+ //
std::optional<QString> executable() const;
// returns the list of arguments, excluding moshortcut or executable name;
@@ -275,9 +357,18 @@ private:
std::optional<QString> m_nxmLink;
std::optional<QString> m_executable;
QStringList m_untouched;
+ Command* m_command;
void createOptions();
std::string more() const;
+
+ template <class... Ts>
+ void add()
+ {
+ (m_commands.push_back(std::make_unique<Ts>()), ...);
+ }
+
+ std::optional<int> runEarly();
};
} // namespace
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index 75f29e8f..78aa981d 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -211,14 +211,22 @@ Executable &ExecutablesList::getByBinary(const QFileInfo &info)
throw std::runtime_error("invalid info");
}
-ExecutablesList::iterator ExecutablesList::find(const QString &title)
+ExecutablesList::iterator ExecutablesList::find(const QString &title, bool ci)
{
- return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; });
+ const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive;
+
+ return std::find_if(begin(), end(), [&](auto&& e) {
+ return (e.title().compare(title, cif) == 0);
+ });
}
-ExecutablesList::const_iterator ExecutablesList::find(const QString &title) const
+ExecutablesList::const_iterator ExecutablesList::find(const QString &title, bool ci) const
{
- return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; });
+ const auto cif = ci ? Qt::CaseInsensitive : Qt::CaseSensitive;
+
+ return std::find_if(begin(), end(), [&](auto&& e) {
+ return (e.title().compare(title, cif) == 0);
+ });
}
bool ExecutablesList::titleExists(const QString &title) const
diff --git a/src/executableslist.h b/src/executableslist.h
index a18042db..97d39500 100644
--- a/src/executableslist.h
+++ b/src/executableslist.h
@@ -148,8 +148,8 @@ public:
/**
* @brief returns an iterator for the given executable by title, or end()
*/
- iterator find(const QString &title);
- const_iterator find(const QString &title) const;
+ iterator find(const QString &title, bool caseSensitive=true);
+ const_iterator find(const QString &title, bool caseSensitive=true) const;
/**
* @brief determine if an executable exists
diff --git a/src/filetree.cpp b/src/filetree.cpp
index f46216b5..7bc4d09c 100644
--- a/src/filetree.cpp
+++ b/src/filetree.cpp
@@ -202,7 +202,7 @@ void FileTree::open(FileTreeItem* item)
m_core.processRunner()
.setFromFile(m_tree->window(), targetInfo)
.setHooked(false)
- .setWaitForCompletion(ProcessRunner::Refresh)
+ .setWaitForCompletion(ProcessRunner::TriggerRefresh)
.run();
}
@@ -226,7 +226,7 @@ void FileTree::openHooked(FileTreeItem* item)
m_core.processRunner()
.setFromFile(m_tree->window(), targetInfo)
.setHooked(true)
- .setWaitForCompletion(ProcessRunner::Refresh)
+ .setWaitForCompletion(ProcessRunner::TriggerRefresh)
.run();
}
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 18604bf1..8add4af8 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -28,7 +28,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "createinstancedialogpages.h"
#include "shared/appconfig.h"
#include "shared/util.h"
-#include "shared/error_report.h"
#include <report.h>
#include <iplugingame.h>
#include <utility.h>
@@ -544,7 +543,7 @@ void InstanceManager::clearOverrides()
m_overrideProfileName = {};
}
-std::optional<Instance> InstanceManager::currentInstance() const
+std::unique_ptr<Instance> InstanceManager::currentInstance() const
{
const QString profile = m_overrideProfileName ?
*m_overrideProfileName : "";
@@ -555,20 +554,20 @@ std::optional<Instance> InstanceManager::currentInstance() const
if (!allowedToChangeInstance()) {
// force portable instance
- return Instance(portablePath(), true, profile);
+ return std::make_unique<Instance>(portablePath(), true, profile);
}
if (name.isEmpty()) {
if (portableInstanceExists()) {
// use portable
- return Instance(portablePath(), true, profile);
+ return std::make_unique<Instance>(portablePath(), true, profile);
} else {
// no instance set
return {};
}
}
- return Instance(instancePath(name), false, profile);
+ return std::make_unique<Instance>(instancePath(name), false, profile);
}
void InstanceManager::clearCurrentInstance()
@@ -772,7 +771,7 @@ bool InstanceManager::validInstanceName(const QString& instanceName) const
-std::optional<Instance> selectInstance()
+std::unique_ptr<Instance> selectInstance()
{
auto& m = InstanceManager::singleton();
@@ -896,7 +895,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
// unreadable ini, there's not much that can be done, select another
// instance
- MOShared::criticalOnTop(
+ reportError(
QObject::tr("Cannot open instance '%1', failed to read INI file %2.")
.arg(instance.name()).arg(instance.iniPath()));
@@ -911,7 +910,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
//
// ask the user for the game managed by this instance
- MOShared::criticalOnTop(
+ reportError(
QObject::tr(
"Cannot open instance '%1', the managed game was not found in the INI "
"file %2. Select the game managed by this instance.")
@@ -925,7 +924,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
// there is no plugin that can handle the game name/directory from the
// ini, so this instance is unusable
- MOShared::criticalOnTop(
+ reportError(
QObject::tr(
"Cannot open instance '%1', the game plugin '%2' doesn't exist. It "
"may have been deleted by an antivirus. Select another instance.")
@@ -939,7 +938,7 @@ SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
// the game directory doesn't exist or the plugin doesn't recognize it;
// ask the user for the game managed by this instance
- MOShared::criticalOnTop(
+ reportError(
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 "
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 73082da2..893b88e4 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -264,7 +264,7 @@ public:
// instance name in the registry is empty or non-existent and there is no
// portable instance set up
//
- std::optional<Instance> currentInstance() const;
+ std::unique_ptr<Instance> currentInstance() const;
// sets the instance name in the registry so the same instance is opened next
// time MO runs
@@ -359,7 +359,7 @@ enum class SetupInstanceResults
// instance manager dialog and returns the selected instance or empty if the
// user cancelled
//
-std::optional<Instance> selectInstance();
+std::unique_ptr<Instance> selectInstance();
// calls instance.setup() tries to handle problems by itself:
//
diff --git a/src/loglist.cpp b/src/loglist.cpp
index 7f8f05bd..bea7363f 100644
--- a/src/loglist.cpp
+++ b/src/loglist.cpp
@@ -20,12 +20,18 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "loglist.h"
#include "organizercore.h"
#include "copyeventfilter.h"
+#include "env.h"
using namespace MOBase;
static LogModel* g_instance = nullptr;
const std::size_t MaxLines = 1000;
+static std::unique_ptr<env::Console> m_console;
+static bool m_stdout = false;
+static std::mutex m_stdoutMutex;
+
+
LogModel::LogModel()
{
}
@@ -324,6 +330,21 @@ void qtLogCallback(
}
}
+void logToStdout(bool b)
+{
+ m_stdout = b;
+
+ // logging to stdout is already set up in uibase by log::createDefault(),
+ // all it needs is to redirect stdout to the console, which is done by
+ // creating an env::Console object
+
+ if (m_stdout) {
+ m_console.reset(new env::Console);
+ } else {
+ m_console.reset();
+ }
+}
+
void initLogging()
{
LogModel::create();
diff --git a/src/loglist.h b/src/loglist.h
index d502f193..df7c1467 100644
--- a/src/loglist.h
+++ b/src/loglist.h
@@ -87,6 +87,7 @@ private:
};
+void logToStdout(bool b);
void initLogging();
bool setLogDirectory(const QString& dir);
diff --git a/src/main.cpp b/src/main.cpp
index 877236e1..0cd9ba26 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,8 +4,10 @@
#include "organizercore.h"
#include "commandline.h"
#include "env.h"
+#include "instancemanager.h"
#include "thread_utils.h"
#include "shared/util.h"
+#include <report.h>
#include <log.h>
using namespace MOBase;
@@ -13,49 +15,136 @@ using namespace MOBase;
thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr;
thread_local std::terminate_handler g_prevTerminateHandler = nullptr;
-int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl);
+int run(int argc, char *argv[]);
int main(int argc, char *argv[])
{
+ const int r = run(argc, argv);
+ std::cout << "mod organizer done\n";
+ return r;
+}
+
+int run(int argc, char *argv[])
+{
MOShared::SetThisThreadName("main");
setExceptionHandlers();
cl::CommandLine cl;
- if (auto r=cl.run(GetCommandLineW())) {
+ if (auto r=cl.process(GetCommandLineW())) {
return *r;
}
initLogging();
// must be after logging
- TimeThis tt("main()");
+ TimeThis tt("main() multiprocess");
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- MOApplication app(cl, argc, argv);
+ MOApplication app(argc, argv);
- MOMultiProcess multiProcess(cl.multiple());
- if (multiProcess.ephemeral()) {
- return forwardToPrimary(multiProcess, cl);
+
+ // check if the command line wants to run something right now
+ if (auto r=cl.runPostApplication(app)) {
+ return *r;
}
- tt.stop();
- return app.run(multiProcess);
-}
+ // check if there's another process running
+ MOMultiProcess multiProcess(cl.multiple());
+
+ if (multiProcess.ephemeral()) {
+ // this is not the primary process
+
+ if (cl.forwardToPrimary(multiProcess)) {
+ // but there's something on the command line that could be forwarded to
+ // it, so just exit
+ return 0;
+ }
-int forwardToPrimary(MOMultiProcess& multiProcess, const cl::CommandLine& cl)
-{
- if (cl.shortcut().isValid()) {
- multiProcess.sendMessage(cl.shortcut().toString());
- } else if (cl.nxmLink()) {
- multiProcess.sendMessage(*cl.nxmLink());
- } else {
QMessageBox::information(
nullptr, QObject::tr("Mod Organizer"),
QObject::tr("An instance of Mod Organizer is already running"));
+
+ return 1;
}
- return 0;
+
+ // check if the command line wants to run something right now
+ if (auto r=cl.runPostMultiProcess(multiProcess)) {
+ return *r;
+ }
+
+ tt.stop();
+
+
+ // stuff that's done only once, even if MO restarts in the loop below
+ app.firstTimeSetup(multiProcess);
+
+
+ // MO runs in a loop because it can be restarted in several ways, such as
+ // when switching instances or changing some settings
+ for (;;)
+ {
+ try
+ {
+ auto& m = InstanceManager::singleton();
+
+ if (cl.instance()) {
+ m.overrideInstance(*cl.instance());
+ }
+
+ if (cl.profile()) {
+ m.overrideProfile(*cl.profile());
+ }
+
+
+ // set up plugins, OrganizerCore, etc.
+ {
+ const auto r = app.setup(multiProcess);
+
+ if (r == RestartExitCode) {
+ // resets things when MO is "restarted"
+ app.resetForRestart();
+
+ // don't reprocess command line
+ cl.clear();
+
+ continue;
+ } else if (r != 0) {
+ // something failed, quit
+ return r;
+ }
+ }
+
+
+ // check if the command line wants to run something right now
+ if (auto r=cl.runPostOrganizer(app.core())) {
+ return *r;
+ }
+
+
+ // run the main window
+ const auto r = app.run(multiProcess);
+
+
+ if (r == RestartExitCode) {
+ // resets things when MO is "restarted"
+ app.resetForRestart();
+
+ // don't reprocess command line
+ cl.clear();
+
+ continue;
+ }
+
+ return r;
+ }
+ catch (const std::exception &e)
+ {
+ reportError(e.what());
+ return 1;
+ }
+ }
}
LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs)
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index a2b12686..a79aca2f 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1486,7 +1486,7 @@ void MainWindow::startExeAction()
m_OrganizerCore.processRunner()
.setFromExecutable(*itor)
- .setWaitForCompletion(ProcessRunner::Refresh)
+ .setWaitForCompletion(ProcessRunner::TriggerRefresh)
.run();
}
@@ -2033,7 +2033,7 @@ void MainWindow::on_startButton_clicked()
m_OrganizerCore.processRunner()
.setFromExecutable(*selectedExecutable)
- .setWaitForCompletion(ProcessRunner::Refresh)
+ .setWaitForCompletion(ProcessRunner::TriggerRefresh)
.run();
}
diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp
index 78a5dd4d..1244e8c6 100644
--- a/src/messagedialog.cpp
+++ b/src/messagedialog.cpp
@@ -86,11 +86,23 @@ void MessageDialog::showMessage(const QString &text, QWidget *reference, bool br
{
log::debug("{}", text);
- if (reference != nullptr) {
- if (bringToFront || (qApp->activeWindow() != nullptr)) {
- MessageDialog *dialog = new MessageDialog(text, reference);
- dialog->show();
- reference->activateWindow();
+ if (!reference) {
+ for (QWidget* w : qApp->topLevelWidgets()) {
+ if (dynamic_cast<QMainWindow*>(w)) {
+ reference = w;
+ break;
+ }
}
}
+
+ if (!reference) {
+ return;
+ }
+
+ MessageDialog *dialog = new MessageDialog(text, reference);
+ dialog->show();
+
+ if (bringToFront) {
+ reference->activateWindow();
+ }
}
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index 9a77c17e..91cac2b1 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -31,7 +31,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorialmanager.h"
#include "sanitychecks.h"
#include "mainwindow.h"
-#include "shared/error_report.h"
+#include "messagedialog.h"
#include "shared/util.h"
#include <iplugingame.h>
#include <report.h>
@@ -155,81 +155,55 @@ void addDllsToPath()
}
-MOApplication::MOApplication(cl::CommandLine& cl, int& argc, char** argv)
- : QApplication(argc, argv), m_cl(cl)
+MOApplication::MOApplication(int& argc, char** argv)
+ : QApplication(argc, argv)
{
TimeThis tt("MOApplication()");
- connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){
+ connect(&m_styleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){
log::debug("style file '{}' changed, reloading", file);
updateStyle(file);
});
- m_DefaultStyle = style()->objectName();
+ m_defaultStyle = style()->objectName();
setStyle(new ProxyStyle(style()));
addDllsToPath();
}
-int MOApplication::run(MOMultiProcess& multiProcess)
+OrganizerCore& MOApplication::core()
{
- TimeThis tt("MOApplication run() to doOneRun()");
-
- auto& m = InstanceManager::singleton();
+ return *m_core;
+}
- if (m_cl.instance())
- m.overrideInstance(*m_cl.instance());
+void MOApplication::firstTimeSetup(MOMultiProcess& multiProcess)
+{
+ connect(
+ &multiProcess, &MOMultiProcess::messageSent, this,
+ [this](auto&& s){ externalMessage(s); },
+ Qt::QueuedConnection);
+}
- if (m_cl.profile()) {
- m.overrideProfile(*m_cl.profile());
- }
+int MOApplication::setup(MOMultiProcess& multiProcess)
+{
+ TimeThis tt("MOApplication setup()");
// makes plugin data path available to plugins, see
// IOrganizer::getPluginDataPath()
MOBase::details::setPluginDataPath(OrganizerCore::pluginDataPath());
- // MO runs in a loop because it can be restarted in several ways, such as
- // when switching instances or changing some settings
- for (;;)
- {
- try
- {
- tt.stop();
-
- const auto r = doOneRun(multiProcess);
-
- if (r == RestartExitCode) {
- // resets things when MO is "restarted"
- resetForRestart();
- continue;
- }
-
- return r;
- }
- catch (const std::exception &e)
- {
- reportError(e.what());
- return 1;
- }
- }
-}
-
-int MOApplication::doOneRun(MOMultiProcess& multiProcess)
-{
- TimeThis tt("MOApplication::doOneRun() instances");
-
// figuring out the current instance
- auto currentInstance = getCurrentInstance();
- if (!currentInstance) {
+ m_instance = getCurrentInstance();
+ if (!m_instance) {
return 1;
}
// first time the data path is available, set the global property and log
// directory, then log a bunch of debug stuff
- const QString dataPath = currentInstance->directory();
+ const QString dataPath = m_instance->directory();
setProperty("dataPath", dataPath);
if (!setLogDirectory(dataPath)) {
- reportError("Failed to create log folder");
+ reportError(tr("Failed to create log folder."));
InstanceManager::singleton().clearCurrentInstance();
return 1;
}
@@ -245,7 +219,7 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
log::debug("another instance of MO is running but --multiple was given");
}
- log::info("data path: {}", currentInstance->directory());
+ log::info("data path: {}", m_instance->directory());
log::info("working directory: {}", QDir::currentPath());
@@ -261,45 +235,42 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
// loading settings
- Settings settings(currentInstance->iniPath(), true);
- log::getDefault().setLevel(settings.diagnostics().logLevel());
- log::debug("using ini at '{}'", settings.filename());
+ m_settings.reset(new Settings(m_instance->iniPath(), true));
+ log::getDefault().setLevel(m_settings->diagnostics().logLevel());
+ log::debug("using ini at '{}'", m_settings->filename());
- OrganizerCore::setGlobalCoreDumpType(settings.diagnostics().coreDumpType());
+ OrganizerCore::setGlobalCoreDumpType(m_settings->diagnostics().coreDumpType());
tt.start("MOApplication::doOneRun() log and checks");
// logging and checking
env::Environment env;
- env.dump(settings);
- settings.dump();
+ env.dump(*m_settings);
+ m_settings->dump();
sanity::checkEnvironment(env);
- const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) {
+ m_modules = std::move(env.onModuleLoaded(qApp, [](auto&& m) {
if (m.interesting()) {
log::debug("loaded module {}", m.toString());
}
sanity::checkIncompatibleModule(m);
- });
-
+ }));
- // this must outlive `organizer`
- std::unique_ptr<PluginContainer> pluginContainer;
// nexus interface
tt.start("MOApplication::doOneRun() NexusInterface");
log::debug("initializing nexus interface");
- NexusInterface ni(&settings);
+ m_nexus.reset(new NexusInterface(m_settings.get()));
// organizer core
tt.start("MOApplication::doOneRun() OrganizerCore");
log::debug("initializing core");
- OrganizerCore organizer(settings);
- if (!organizer.bootstrap()) {
- reportError("failed to set up data paths");
+ m_core.reset(new OrganizerCore(*m_settings));
+ if (!m_core->bootstrap()) {
+ reportError(tr("Failed to set up data paths."));
InstanceManager::singleton().clearCurrentInstance();
return 1;
}
@@ -308,58 +279,59 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
tt.start("MOApplication::doOneRun() plugins");
log::debug("initializing plugins");
- pluginContainer = std::make_unique<PluginContainer>(&organizer);
- pluginContainer->loadPlugins();
+ m_plugins = std::make_unique<PluginContainer>(m_core.get());
+ m_plugins->loadPlugins();
// instance
- if (auto r=setupInstanceLoop(*currentInstance, *pluginContainer)) {
+ if (auto r=setupInstanceLoop(*m_instance, *m_plugins)) {
return *r;
}
- if (currentInstance->isPortable()) {
+ if (m_instance->isPortable()) {
log::debug("this is a portable instance");
}
tt.start("MOApplication::doOneRun() OrganizerCore setup");
- sanity::checkPaths(*currentInstance->gamePlugin(), settings);
+ sanity::checkPaths(*m_instance->gamePlugin(), *m_settings);
// setting up organizer core
- organizer.setManagedGame(currentInstance->gamePlugin());
- organizer.createDefaultProfile();
+ m_core->setManagedGame(m_instance->gamePlugin());
+ m_core->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());
+ m_instance->gamePlugin()->gameName(),
+ m_instance->gamePlugin()->gameShortName(),
+ (m_settings->game().edition().value_or("").isEmpty() ?
+ "(none)" : *m_settings->game().edition()),
+ m_instance->gamePlugin()->steamAPPId(),
+ m_instance->gamePlugin()->gameDirectory().absolutePath());
CategoryFactory::instance().loadCategories();
- organizer.updateExecutablesList();
- organizer.updateModInfoFromDisc();
- organizer.setCurrentProfile(currentInstance->profileName());
+ m_core->updateExecutablesList();
+ m_core->updateModInfoFromDisc();
+ m_core->setCurrentProfile(m_instance->profileName());
+
+ return 0;
+}
+int MOApplication::run(MOMultiProcess& multiProcess)
+{
// checking command line
- tt.start("MOApplication::doOneRun() command line");
- if (auto r=m_cl.setupCore(organizer)) {
- return *r;
- }
+ TimeThis tt("MOApplication::run()");
// show splash
tt.start("MOApplication::doOneRun() splash");
- MOSplash splash(
- settings, currentInstance->directory(), currentInstance->gamePlugin());
+ MOSplash splash(*m_settings, m_instance->directory(), m_instance->gamePlugin());
tt.start("MOApplication::doOneRun() finishing");
// start an api check
QString apiKey;
if (GlobalSettings::nexusApiKey(apiKey)) {
- ni.getAccessManager()->apiCheck(apiKey);
+ m_nexus->getAccessManager()->apiCheck(apiKey);
}
// tutorials
@@ -367,12 +339,12 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
TutorialManager::init(
qApp->applicationDirPath() + "/"
+ QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
- &organizer);
+ m_core.get());
// styling
- if (!setStyleFile(settings.interface().styleName().value_or(""))) {
+ if (!setStyleFile(m_settings->interface().styleName().value_or(""))) {
// disable invalid stylesheet
- settings.interface().setStyleName("");
+ m_settings->interface().setStyleName("");
}
@@ -380,17 +352,16 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
{
tt.start("MOApplication::doOneRun() MainWindow setup");
- MainWindow mainWindow(settings, organizer, *pluginContainer);
+ MainWindow mainWindow(*m_settings, *m_core, *m_plugins);
// the nexus interface can show dialogs, make sure they're parented to the
// main window
- ni.getAccessManager()->setTopLevelWidget(&mainWindow);
-
- QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), this,
- SLOT(setStyleFile(QString)));
+ m_nexus->getAccessManager()->setTopLevelWidget(&mainWindow);
- QObject::connect(&multiProcess, SIGNAL(messageSent(QString)), &organizer,
- SLOT(externalMessage(QString)));
+ connect(
+ &mainWindow, &MainWindow::styleChanged, this,
+ [this](auto&& file){ setStyleFile(file); },
+ Qt::QueuedConnection);
log::debug("displaying main window");
@@ -404,16 +375,71 @@ int MOApplication::doOneRun(MOMultiProcess& multiProcess)
mainWindow.close();
// main window is about to be destroyed
- ni.getAccessManager()->setTopLevelWidget(nullptr);
+ m_nexus->getAccessManager()->setTopLevelWidget(nullptr);
}
// reset geometry if the flag was set from the settings dialog
- settings.geometry().resetIfNeeded();
+ m_settings->geometry().resetIfNeeded();
return res;
}
-std::optional<Instance> MOApplication::getCurrentInstance()
+void MOApplication::externalMessage(const QString& message)
+{
+ log::debug("received external message '{}'", message);
+
+ MOShortcut moshortcut(message);
+
+ if (moshortcut.isValid()) {
+ if(moshortcut.hasExecutable()) {
+ m_core->processRunner()
+ .setFromShortcut(moshortcut)
+ .setWaitForCompletion(ProcessRunner::TriggerRefresh)
+ .run();
+ }
+ } else if (isNxmLink(message)) {
+ MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
+ m_core->downloadRequestedNXM(message);
+ } else {
+ cl::CommandLine cl;
+
+ if (auto r=cl.process(message.toStdWString())) {
+ log::debug(
+ "while processing external message, command line wants to "
+ "exit; ignoring");
+
+ return;
+ }
+
+ if (auto i=cl.instance()) {
+ const auto ci = InstanceManager::singleton().currentInstance();
+
+ if (*i != ci->name()) {
+ reportError(tr(
+ "This shortcut or command line is for instance '%1', but the current "
+ "instance is '%2'.")
+ .arg(*i).arg(ci->name()));
+
+ return;
+ }
+ }
+
+ if (auto p=cl.profile()) {
+ if (*p != m_core->profileName()) {
+ reportError(tr(
+ "This shortcut or command line is for profile '%1', but the current "
+ "profile is '%2'.")
+ .arg(*p).arg(m_core->profileName()));
+
+ return;
+ }
+ }
+
+ cl.runPostOrganizer(*m_core);
+ }
+}
+
+std::unique_ptr<Instance> MOApplication::getCurrentInstance()
{
auto& m = InstanceManager::singleton();
auto currentInstance = m.currentInstance();
@@ -422,8 +448,6 @@ std::optional<Instance> MOApplication::getCurrentInstance()
{
// clear any overrides that might have been given on the command line
m.clearOverrides();
- m_cl.clear();
-
currentInstance = selectInstance();
}
else
@@ -433,14 +457,13 @@ std::optional<Instance> MOApplication::getCurrentInstance()
// clear any overrides that might have been given on the command line
m.clearOverrides();
- m_cl.clear();
if (m.hasAnyInstances()) {
- MOShared::criticalOnTop(QObject::tr(
+ reportError(QObject::tr(
"Instance at '%1' not found. Select another instance.")
.arg(currentInstance->directory()));
} else {
- MOShared::criticalOnTop(QObject::tr(
+ reportError(QObject::tr(
"Instance at '%1' not found. You must create a new instance")
.arg(currentInstance->directory()));
}
@@ -496,31 +519,34 @@ void MOApplication::resetForRestart()
// the previous instance gets deleted
log::getDefault().setFile({});
- // don't reprocess command line
- m_cl.clear();
-
// clear instance and profile overrides
InstanceManager::singleton().clearOverrides();
+
+ m_core = {};
+ m_plugins = {};
+ m_nexus = {};
+ m_settings = {};
+ m_instance = {};
}
bool MOApplication::setStyleFile(const QString& styleName)
{
// remove all files from watch
- QStringList currentWatch = m_StyleWatcher.files();
+ QStringList currentWatch = m_styleWatcher.files();
if (currentWatch.count() != 0) {
- m_StyleWatcher.removePaths(currentWatch);
+ m_styleWatcher.removePaths(currentWatch);
}
// set new stylesheet or clear it
if (styleName.length() != 0) {
QString styleSheetName = applicationDirPath() + "/" + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + styleName;
if (QFile::exists(styleSheetName)) {
- m_StyleWatcher.addPath(styleSheetName);
+ m_styleWatcher.addPath(styleSheetName);
updateStyle(styleSheetName);
} else {
updateStyle(styleName);
}
} else {
- setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle)));
+ setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle)));
setStyleSheet("");
}
return true;
@@ -551,7 +577,7 @@ void MOApplication::updateStyle(const QString& fileName)
setStyleSheet("");
setStyle(new ProxyStyle(QStyleFactory::create(fileName)));
} else {
- setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle)));
+ setStyle(new ProxyStyle(QStyleFactory::create(m_defaultStyle)));
if (QFile::exists(fileName)) {
setStyleSheet(QString("file:///%1").arg(fileName));
} else {
diff --git a/src/moapplication.h b/src/moapplication.h
index 91b8023a..65180ece 100644
--- a/src/moapplication.h
+++ b/src/moapplication.h
@@ -27,22 +27,45 @@ class Settings;
class MOMultiProcess;
class Instance;
class PluginContainer;
+class OrganizerCore;
+class NexusInterface;
namespace MOBase { class IPluginGame; }
-namespace cl { class CommandLine; }
+namespace env { class ModuleNotification; }
class MOApplication : public QApplication
{
Q_OBJECT
public:
- MOApplication(cl::CommandLine& cl, int& argc, char** argv);
+ MOApplication(int& argc, char** argv);
- // sets up everything, creates the main window and runs it
+ // called from main() only once for stuff that persists across "restarts"
+ //
+ void firstTimeSetup(MOMultiProcess& multiProcess);
+
+ // called from main() each time MO "restarts", loads settings, plugins,
+ // OrganizerCore and the current instance
+ //
+ int setup(MOMultiProcess& multiProcess);
+
+ // shows splash, starts an api check, shows the main window and blocks until
+ // MO exits
//
int run(MOMultiProcess& multiProcess);
- virtual bool notify(QObject* receiver, QEvent* event);
+ // called from main() when MO "restarts", must clean up everything so setup()
+ // starts fresh
+ //
+ void resetForRestart();
+
+ // undefined if setup() wasn't called
+ //
+ OrganizerCore& core();
+
+ // wraps QApplication::notify() in a catch, reports errors and ignores them
+ //
+ bool notify(QObject* receiver, QEvent* event) override;
public slots:
bool setStyleFile(const QString& style);
@@ -51,16 +74,20 @@ private slots:
void updateStyle(const QString& fileName);
private:
- QFileSystemWatcher m_StyleWatcher;
- QString m_DefaultStyle;
- cl::CommandLine& m_cl;
+ QFileSystemWatcher m_styleWatcher;
+ QString m_defaultStyle;
+ std::unique_ptr<env::ModuleNotification> m_modules;
- int doOneRun(MOMultiProcess& multiProcess);
+ std::unique_ptr<Instance> m_instance;
+ std::unique_ptr<Settings> m_settings;
+ std::unique_ptr<NexusInterface> m_nexus;
+ std::unique_ptr<PluginContainer> m_plugins;
+ std::unique_ptr<OrganizerCore> m_core;
- std::optional<Instance> getCurrentInstance();
+ void externalMessage(const QString& message);
+ std::unique_ptr<Instance> getCurrentInstance();
std::optional<int> setupInstanceLoop(Instance& currentInstance, PluginContainer& pc);
void purgeOldFiles();
- void resetForRestart();
};
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index e95aa565..d3e817a5 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -346,25 +346,6 @@ void OrganizerCore::profileRemoved(QString const& profileName)
m_ProfileRemoved(profileName);
}
-
-void OrganizerCore::externalMessage(const QString &message)
-{
- MOShortcut moshortcut(message);
-
- if (moshortcut.isValid()) {
- if(moshortcut.hasExecutable()) {
- processRunner()
- .setFromShortcut(moshortcut)
- .setWaitForCompletion(ProcessRunner::Refresh)
- .run();
- }
- }
- else if (isNxmLink(message)) {
- MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
- downloadRequestedNXM(message);
- }
-}
-
void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, int modID,
const QString &fileName)
{
@@ -579,6 +560,10 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0));
log::error("picked profile '{}' instead", QDir(profileDir).dirName());
+
+ reportError(
+ tr("The selected profile '%1' does not exist. The profile '%2' will be used instead")
+ .arg(profileName).arg(QDir(profileDir).dirName()));
}
// Keep the old profile to emit signal-changed:
@@ -826,7 +811,7 @@ ModInfo::Ptr OrganizerCore::installDownload(int index, int priority)
}
}
} catch (const std::exception &e) {
- reportError(e.what());
+ reportError(QString(e.what()));
}
return nullptr;
@@ -1911,8 +1896,6 @@ bool OrganizerCore::beforeRun(
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
@@ -1927,7 +1910,7 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode)
savePluginList();
cycleDiagnostics();
- //These callbacks should not fiddle with directoy structure and ESPs.
+ //These callbacks should not fiddle with directory structure and ESPs.
m_FinishedRun(binary.absoluteFilePath(), exitCode);
}
diff --git a/src/organizercore.h b/src/organizercore.h
index 8add5542..39a2c3cb 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -351,7 +351,6 @@ public: // IPluginDiagnose interface
public slots:
void profileRefresh();
- void externalMessage(const QString &message);
void syncOverwrite();
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp
index acfb8404..6a19986f 100644
--- a/src/organizerproxy.cpp
+++ b/src/organizerproxy.cpp
@@ -193,7 +193,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, bool refresh, LPDWORD exi
ProcessRunner::WaitFlags waitFlags = ProcessRunner::ForceWait;
if (refresh) {
- waitFlags |= ProcessRunner::Refresh;
+ waitFlags |= ProcessRunner::TriggerRefresh | ProcessRunner::WaitForRefresh;
}
const auto r = runner
diff --git a/src/processrunner.cpp b/src/processrunner.cpp
index cdcbfa16..c932ce44 100644
--- a/src/processrunner.cpp
+++ b/src/processrunner.cpp
@@ -521,6 +521,13 @@ ProcessRunner& ProcessRunner::setWaitForCompletion(
{
m_waitFlags = flags;
m_lockReason = reason;
+
+ if (m_waitFlags.testFlag(WaitForRefresh) && !m_waitFlags.testFlag(TriggerRefresh)) {
+ log::warn(
+ "process runner: WaitForRefresh without TriggerRefresh "
+ "makes no sense, will be ignored");
+ }
+
return *this;
}
@@ -825,8 +832,8 @@ bool ProcessRunner::shouldRefresh(Results r) const
// 2) the mod info dialog is not set up to deal with refreshes, so that
// it will crash because the old DirectoryEntry's are still being used
// in the list
- if (!m_waitFlags.testFlag(Refresh)) {
- log::debug("not refreshing because the flag isn't set");
+ if (!m_waitFlags.testFlag(TriggerRefresh)) {
+ log::debug("process runner: not refreshing because the flag isn't set");
return false;
}
@@ -834,13 +841,13 @@ bool ProcessRunner::shouldRefresh(Results r) const
{
case Completed:
{
- log::debug("refreshing because the process completed");
+ log::debug("process runner: refreshing because the process completed");
return true;
}
case ForceUnlocked:
{
- log::debug("refreshing because the ui was force unlocked");
+ log::debug("process runner: refreshing because the ui was force unlocked");
return true;
}
@@ -891,7 +898,10 @@ ProcessRunner::Results ProcessRunner::postRun()
if (!lockEnabled) {
// disabling locking is like clicking on unlock immediately
- log::debug("not waiting for process because locking is disabled");
+ log::debug(
+ "process runner: not waiting for process because "
+ "locking is disabled");
+
return ForceUnlocked;
}
}
@@ -917,8 +927,24 @@ ProcessRunner::Results ProcessRunner::postRun()
}
if (shouldRefresh(r)) {
+ QEventLoop loop;
+ const bool wait = m_waitFlags.testFlag(WaitForRefresh);
+
+ if (wait) {
+ QObject::connect(
+ &m_core, &OrganizerCore::directoryStructureReady,
+ &loop, &QEventLoop::quit,
+ Qt::ConnectionType::QueuedConnection);
+ }
+
m_core.afterRun(m_sp.binary, m_exitCode);
- }
+
+ if (wait) {
+ log::debug("process runner: waiting until refresh finishes");
+ loop.exec();
+ log::debug("process runner: refresh is done");
+ }
+}
return r;
}
diff --git a/src/processrunner.h b/src/processrunner.h
index d8ff0227..b8c8935c 100644
--- a/src/processrunner.h
+++ b/src/processrunner.h
@@ -39,12 +39,30 @@ public:
{
NoFlags = 0x00,
- // the ui will be refreshed once the process has completed
- Refresh = 0x01,
+ // the directory structure will be refreshed once the process has completed
+ TriggerRefresh = 0x01,
// the process will be waited for even if locking is disabled or the
// process is not hooked
ForceWait = 0x02,
+
+ // only valid with TriggerRefresh; run() will block until the refresh has
+ // completed
+ WaitForRefresh = 0x04,
+
+ // combination of flags used to run programs from the command line
+ //
+ // 1) TriggerRefresh: MO must refresh after running the program because
+ // programs can modify files behind its back; for example, external
+ // LOOT will modify loadorder.txt, so MO must read it back or it will
+ // write back the old order when exiting
+ //
+ // 2) WaitForRefresh: refreshing is asynchronous, so the refresh must
+ // complete before MO exits or stale data might be written to disk
+ //
+ // 3) ForceWait: MO must wait for the program to finish even if locking the
+ // ui is disabled
+ ForCommandLine = TriggerRefresh | WaitForRefresh | ForceWait
};
using WaitFlags = QFlags<WaitFlag>;
diff --git a/src/profile.cpp b/src/profile.cpp
index 3cc1a2a3..695cd3ae 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -22,7 +22,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfo.h"
#include "settings.h"
#include <utility.h>
-#include "shared/error_report.h"
#include "shared/appconfig.h"
#include <iplugingame.h>
#include <report.h>
diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp
deleted file mode 100644
index 09fdcb49..00000000
--- a/src/shared/error_report.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "error_report.h"
-#include <sstream>
-#include <stdio.h>
-
-namespace MOShared {
-
-void reportError(LPCSTR format, ...)
-{
- char buffer[1025];
- memset(buffer, 0, sizeof(char) * 1025);
-
- va_list argList;
- va_start(argList, format);
-
- vsnprintf(buffer, 1024, format, argList);
- va_end(argList);
-
- MessageBoxA(nullptr, buffer, "Error", MB_OK | MB_ICONERROR);
-}
-
-void reportError(LPCWSTR format, ...)
-{
- WCHAR buffer[1025];
- memset(buffer, 0, sizeof(WCHAR) * 1025);
-
- va_list argList;
- va_start(argList, format);
-
- _vsnwprintf_s(buffer, 1024, format, argList);
- va_end(argList);
-
- MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR);
-}
-
-void criticalOnTop(const QString& message)
-{
- QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message);
-
- mb.show();
- mb.activateWindow();
- mb.raise();
- mb.exec();
-}
-
-} // namespace MOShared
diff --git a/src/shared/error_report.h b/src/shared/error_report.h
deleted file mode 100644
index 9343d3da..00000000
--- a/src/shared/error_report.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED
-#define MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED
-
-#include <tchar.h>
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-#include <string>
-
-namespace MOShared
-{
-
-void reportError(LPCSTR format, ...);
-void reportError(LPCWSTR format, ...);
-
-// shows a critical message box that's raised to the top of the zorder, useful
-// for messages without a main window, which sometimes makes them pop up behind
-// all other windows
-//
-// the dialog is not topmost, it's just raised once when shown
-//
-void criticalOnTop(const QString& message);
-
-} // namespace MOShared
-
-#endif // MODORGANIZER_SHARED_ERROR_REPORT_INCLUDED
diff --git a/src/shared/leaktrace.cpp b/src/shared/leaktrace.cpp
deleted file mode 100644
index 466162a4..00000000
--- a/src/shared/leaktrace.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-//disable warning messages 4302 , 4311 and 4312
-#pragma warning( disable : 4302 )
-#pragma warning( disable : 4311 )
-#pragma warning( disable : 4312 )
-
-#include "leaktrace.h"
-#include "stackdata.h"
-#include <Windows.h>
-#include <DbgHelp.h>
-#include <set>
-#include <map>
-#include <vector>
-#include <sstream>
-#include <algorithm>
-
-
-using namespace MOShared;
-
-
-static struct __TraceData {
- void regTrace(void *pointer, const char *functionName, int line) {
- m_Traces[reinterpret_cast<unsigned long>(pointer)] = StackData(functionName, line);
- }
- void deregTrace(void *pointer) {
- auto iter = m_Traces.find(reinterpret_cast<unsigned long>(pointer));
- if (iter != m_Traces.end()) {
- m_Traces.erase(iter);
- }
- }
-
- ~__TraceData() {
- std::map<StackData, std::vector<unsigned long> > result;
- for (auto iter = m_Traces.begin(); iter != m_Traces.end(); ++iter) {
- result[iter->second].push_back(iter->first);
- }
- for (auto iter = result.begin(); iter != result.end(); ++iter) {
- printf("-----------------------------------\n"
- "%d objects not freed, allocated at:\n%s",
- static_cast<int>(iter->second.size()), iter->first.toString().c_str());
- printf("Addresses: ");
- for (int i = 0;
- i < (std::min<int>)(5, static_cast<int>(iter->second.size())); ++i) {
- printf("%p, ", reinterpret_cast<void *>(iter->second[i]));
- }
- printf("\n");
- }
- }
-
- std::map<unsigned long, StackData> m_Traces;
-
-} __trace;
-
-
-void LeakTrace::TraceAlloc(void *ptr, const char *functionName, int line)
-{
- __trace.regTrace(ptr, functionName, line);
-}
-
-void LeakTrace::TraceDealloc(void *ptr)
-{
- __trace.deregTrace(ptr);
-}
-
-//re-enable warning messages 4302 , 4311 and 4312
-#pragma warning( default : 4302 )
-#pragma warning( default : 4311 )
-#pragma warning( default : 4312 )
-
diff --git a/src/shared/leaktrace.h b/src/shared/leaktrace.h
deleted file mode 100644
index 4985925e..00000000
--- a/src/shared/leaktrace.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef LEAKTRACE_H
-#define LEAKTRACE_H
-
-
-namespace LeakTrace {
-
-void TraceAlloc(void *ptr, const char *functionName, int line);
-void TraceDealloc(void *ptr);
-
-};
-
-#ifdef TRACE_LEAKS
-
-#define LEAK_TRACE LeakTrace::TraceAlloc(this, __FUNCTION__, __LINE__)
-#define LEAK_UNTRACE LeakTrace::TraceDealloc(this)
-
-#else // TRACE_LEAKS
-
-#define LEAK_TRACE
-#define LEAK_UNTRACE
-
-#endif // TRACE_LEAKS
-
-#endif // LEAKTRACE_H
diff --git a/src/shared/stackdata.cpp b/src/shared/stackdata.cpp
deleted file mode 100644
index b336593a..00000000
--- a/src/shared/stackdata.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-#include "stackdata.h"
-
-#include "util.h"
-#include <DbgHelp.h>
-#include <sstream>
-#include <TlHelp32.h>
-#include <set>
-#include "error_report.h"
-#include <boost/predef.h>
-
-
-using namespace MOShared;
-
-#if defined _MSC_VER
-
-static void initDbgIfNecess()
-{
- HANDLE process = ::GetCurrentProcess();
- static std::set<DWORD> initialized;
- if (initialized.find(::GetCurrentProcessId()) == initialized.end()) {
- static bool firstCall = true;
- if (firstCall) {
- ::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);
- firstCall = false;
- }
- if (!::SymInitialize(process, NULL, TRUE)) {
- printf("failed to initialize symbols: %lu", ::GetLastError());
- }
- initialized.insert(::GetCurrentProcessId());
- }
-}
-
-
-
-StackData::StackData()
- : m_Count(0)
- , m_Function()
- , m_Line(-1)
-{
- initTrace();
-}
-
-StackData::StackData(const char *function, int line)
- : m_Count(0)
- , m_Function(function)
- , m_Line(line)
-{
-
-}
-
-std::string StackData::toString() const {
- initDbgIfNecess();
-
- char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
- PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;
- symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
- symbol->MaxNameLen = MAX_SYM_NAME;
-
- std::ostringstream stackStream;
-
- if (m_Function.length() > 0) {
- stackStream << "[" << m_Function << ":" << m_Line << "]\n";
- }
-
- for(unsigned int i = 0; i < m_Count; ++i) {
- DWORD64 displacement = 0;
- if (!::SymFromAddr(::GetCurrentProcess(), (DWORD64)m_Stack[i], &displacement, symbol)) {
- stackStream << m_Count - i - 1 << ": [" << m_Stack[i] << "]\n";
- } else {
- stackStream << m_Count - i - 1 << ": " << symbol->Name << "\n";
- }
- }
- return stackStream.str();
-}
-
-void StackData::load_modules(HANDLE process, DWORD processID) {
- HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
- if (snap == INVALID_HANDLE_VALUE)
- return;
-
- MODULEENTRY32 entry;
- entry.dwSize = sizeof(entry);
-
- if (Module32First(snap, &entry)) {
- do {
- std::string fileName = ToString(entry.szExePath, false);
- std::string moduleName = ToString(entry.szModule, false);
- SymLoadModule64(process, NULL, fileName.c_str(), moduleName.c_str(), (DWORD64) entry.modBaseAddr, entry.modBaseSize);
- } while (Module32Next(snap, &entry));
- }
- CloseHandle(snap);
-}
-
-#pragma warning( disable : 4748 )
-
-void StackData::initTrace() {
-#ifdef _X86_
- load_modules(::GetCurrentProcess(), ::GetCurrentProcessId());
- CONTEXT context;
- std::memset(&context, 0, sizeof(CONTEXT));
- context.ContextFlags = CONTEXT_CONTROL;
- //Why only for 64 bit?
-#if BOOST_ARCH_X86_64 || defined(__clang__)
- ::RtlCaptureContext(&context);
-#else
- __asm
- {
- Label:
- mov [context.Ebp], ebp;
- mov [context.Esp], esp;
- mov eax, [Label];
- mov [context.Eip], eax;
- }
-#endif
-
- STACKFRAME64 stackFrame;
- ::ZeroMemory(&stackFrame, sizeof(STACKFRAME64));
- stackFrame.AddrPC.Offset = context.Eip;
- stackFrame.AddrPC.Mode = AddrModeFlat;
- stackFrame.AddrFrame.Offset = context.Ebp;
- stackFrame.AddrFrame.Mode = AddrModeFlat;
- stackFrame.AddrStack.Offset = context.Esp;
- stackFrame.AddrStack.Mode = AddrModeFlat;
- m_Count = 0;
- while (m_Count < FRAMES_TO_CAPTURE) {
- if (!StackWalk64(IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess(),
- ::GetCurrentThread(), &stackFrame, &context, NULL,
- &SymFunctionTableAccess64, &SymGetModuleBase64, NULL)) {
- break;
- }
-
- if (stackFrame.AddrPC.Offset == 0) {
- continue;
- break;
- }
-
- m_Stack[m_Count++] = reinterpret_cast<void *>(stackFrame.AddrPC.Offset);
- }
-#endif
-}
-
-
-bool MOShared::operator==(const StackData &LHS, const StackData &RHS) {
- if (LHS.m_Count != RHS.m_Count) {
- return false;
- } else {
- for (int i = 0; i < LHS.m_Count; ++i) {
- if (LHS.m_Stack[i] != RHS.m_Stack[i]) {
- return false;
- }
- }
- }
- return true;
-}
-
-
-bool MOShared::operator<(const StackData &LHS, const StackData &RHS) {
- if (LHS.m_Count != RHS.m_Count) {
- return LHS.m_Count < RHS.m_Count;
- } else {
- for (int i = 0; i < LHS.m_Count; ++i) {
- if (LHS.m_Stack[i] != RHS.m_Stack[i]) {
- return LHS.m_Stack[i] < RHS.m_Stack[i];
- }
- }
- }
- return false;
-}
-#endif
diff --git a/src/shared/stackdata.h b/src/shared/stackdata.h
deleted file mode 100644
index 7151699c..00000000
--- a/src/shared/stackdata.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef STACKDATA_H
-#define STACKDATA_H
-
-
-#include <string>
-#include <Windows.h>
-
-
-namespace MOShared {
-
-
-class StackData {
- friend bool operator==(const StackData &LHS, const StackData &RHS);
- friend bool operator<(const StackData &LHS, const StackData &RHS);
-public:
-
- StackData();
- StackData(const char *function, int line);
-
- std::string toString() const;
-
-private:
-
- void load_modules(HANDLE process, DWORD processID);
-
- void initTrace();
-
-private:
-
- static const int FRAMES_TO_SKIP = 1;
- static const int FRAMES_TO_CAPTURE = 20;
-
-private:
-
- LPVOID m_Stack[FRAMES_TO_CAPTURE];
- USHORT m_Count;
- std::string m_Function;
- int m_Line;
-
-};
-
-
-bool operator==(const StackData &LHS, const StackData &RHS);
-
-bool operator<(const StackData &LHS, const StackData &RHS);
-
-} // namespace MOShared
-
-
-#endif // STACKDATA_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index a9ecb61e..68526165 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -164,9 +164,14 @@ QString makeContent(const SpawnParameters& sp, DWORD code)
} else if (code == ERROR_FILE_NOT_FOUND) {
return QObject::tr("The file '%1' does not exist.")
.arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath()));
- } else {
- return QString::fromStdWString(formatSystemMessage(code));
+ } else if (code == ERROR_DIRECTORY) {
+ if (!sp.currentDirectory.exists()) {
+ return QObject::tr("The working directory '%1' does not exist.")
+ .arg(QDir::toNativeSeparators(sp.currentDirectory.absolutePath()));
+ }
}
+
+ return QString::fromStdWString(formatSystemMessage(code));
}
QMessageBox::StandardButton badSteamReg(