diff options
| author | Mikaƫl Capelle <capelle.mikael@gmail.com> | 2021-01-18 21:16:40 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-18 21:16:40 +0100 |
| commit | ee44a38c986d953a19cbb37b0297590844cf54ac (patch) | |
| tree | e39b12f7cb7f37cd96b3c3f451d2b527a81d4578 /src | |
| parent | 59f055ba93381b965cdc04557ac1dce2df36bd07 (diff) | |
| parent | 6feab2ea8f96704e44a14c212a635dc17f4ba71e (diff) | |
Merge pull request #1354 from isanae/startup-rework
Startup rework
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/commandline.cpp | 364 | ||||
| -rw-r--r-- | src/commandline.h | 155 | ||||
| -rw-r--r-- | src/executableslist.cpp | 16 | ||||
| -rw-r--r-- | src/executableslist.h | 4 | ||||
| -rw-r--r-- | src/filetree.cpp | 4 | ||||
| -rw-r--r-- | src/instancemanager.cpp | 19 | ||||
| -rw-r--r-- | src/instancemanager.h | 4 | ||||
| -rw-r--r-- | src/loglist.cpp | 21 | ||||
| -rw-r--r-- | src/loglist.h | 1 | ||||
| -rw-r--r-- | src/main.cpp | 125 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 4 | ||||
| -rw-r--r-- | src/messagedialog.cpp | 22 | ||||
| -rw-r--r-- | src/moapplication.cpp | 252 | ||||
| -rw-r--r-- | src/moapplication.h | 47 | ||||
| -rw-r--r-- | src/organizercore.cpp | 29 | ||||
| -rw-r--r-- | src/organizercore.h | 1 | ||||
| -rw-r--r-- | src/organizerproxy.cpp | 2 | ||||
| -rw-r--r-- | src/processrunner.cpp | 38 | ||||
| -rw-r--r-- | src/processrunner.h | 22 | ||||
| -rw-r--r-- | src/profile.cpp | 1 | ||||
| -rw-r--r-- | src/shared/error_report.cpp | 64 | ||||
| -rw-r--r-- | src/shared/error_report.h | 44 | ||||
| -rw-r--r-- | src/shared/leaktrace.cpp | 68 | ||||
| -rw-r--r-- | src/shared/leaktrace.h | 24 | ||||
| -rw-r--r-- | src/shared/stackdata.cpp | 169 | ||||
| -rw-r--r-- | src/shared/stackdata.h | 50 | ||||
| -rw-r--r-- | src/spawn.cpp | 9 |
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(
|
