From b103f297752b170ee4ade6e0e9085c6d31c020ca Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 00:35:26 -0400
Subject: added --multiple to allow launching multiple instances
---
src/main.cpp | 47 ++++++++++++++++++++++----------------
src/singleinstance.cpp | 23 +++++++++++--------
src/singleinstance.h | 62 ++++++++++++++++++++++++++++++++++----------------
3 files changed, 85 insertions(+), 47 deletions(-)
(limited to 'src')
diff --git a/src/main.cpp b/src/main.cpp
index a81ae962..7130d15c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -112,19 +112,19 @@ bool createAndMakeWritable(const std::wstring &subPath) {
}
}
-bool bootstrap()
+void purgeOldFiles()
{
- // remove the temporary backup directory in case we're restarting after an update
+ // remove the temporary backup directory in case we're restarting after an
+ // update
QString backupDirectory = qApp->applicationDirPath() + "/update_backup";
if (QDir(backupDirectory).exists()) {
shellDelete(QStringList(backupDirectory));
}
// cycle log file
- removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
- "usvfs*.log", 5, QDir::Name);
-
- return true;
+ removeOldFiles(
+ qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
+ "usvfs*.log", 5, QDir::Name);
}
thread_local LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
@@ -533,8 +533,9 @@ static QString getVersionDisplayString()
}
-int runApplication(MOApplication &application, SingleInstance &instance,
- const QString &splashPath)
+int runApplication(
+ MOApplication &application, QStringList& arguments,
+ SingleInstance &instance, const QString &splashPath)
{
TimeThis tt("runApplication() to exec()");
@@ -555,17 +556,13 @@ int runApplication(MOApplication &application, SingleInstance &instance,
log::debug("this is a portable instance");
}
- if (!bootstrap()) {
- reportError("failed to set up data paths");
- InstanceManager::instance().clearCurrentInstance();
- return 1;
+ if (!instance.secondary()) {
+ purgeOldFiles();
}
QWindowsWindowFunctions::setWindowActivationBehavior(
QWindowsWindowFunctions::AlwaysActivateWindow);
- QStringList arguments = application.arguments();
-
try {
log::info("working directory: {}", QDir::currentPath());
@@ -574,6 +571,11 @@ int runApplication(MOApplication &application, SingleInstance &instance,
log::debug("using ini at '{}'", settings.filename());
+ if (instance.secondary()) {
+ log::debug("another instance of MO is running but --multiple was given");
+ }
+
+
// global crashDumpType sits in OrganizerCore to make a bit less ugly to
// update it when the settings are changed during runtime
OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType());
@@ -929,16 +931,23 @@ int main(int argc, char *argv[])
setupPath();
- bool forcePrimary = false;
+
+ SingleInstance::Flags siFlags = SingleInstance::NoFlags;
+
if (arguments.contains("update")) {
arguments.removeAll("update");
- forcePrimary = true;
+ siFlags |= SingleInstance::ForcePrimary;
+ }
+
+ if (arguments.contains("--multiple")) {
+ arguments.removeAll("--multiple");
+ siFlags |= SingleInstance::AllowMultiple;
}
MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" };
- SingleInstance instance(forcePrimary);
- if (!instance.primaryInstance()) {
+ SingleInstance instance(siFlags);
+ if (instance.ephemeral()) {
if (moshortcut ||
arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1)))
{
@@ -998,7 +1007,7 @@ int main(int argc, char *argv[])
tt.stop();
- const int result = runApplication(application, instance, splash);
+ const int result = runApplication(application, arguments, instance, splash);
if (result != RestartExitCode) {
return result;
}
diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp
index aa62d40a..d38095df 100644
--- a/src/singleinstance.cpp
+++ b/src/singleinstance.cpp
@@ -28,33 +28,38 @@ static const int s_Timeout = 5000;
using MOBase::reportError;
-SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) :
- QObject(parent), m_PrimaryInstance(false)
+SingleInstance::SingleInstance(Flags flags, QObject *parent) :
+ QObject(parent), m_Ephemeral(false), m_OwnsSM(false)
{
m_SharedMem.setKey(s_Key);
+
if (!m_SharedMem.create(1)) {
- if (forcePrimary) {
+ if (flags.testFlag(ForcePrimary)) {
while (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
Sleep(500);
if (m_SharedMem.create(1)) {
- m_PrimaryInstance = true;
+ m_OwnsSM = true;
break;
}
}
}
if (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
- m_SharedMem.attach();
- m_PrimaryInstance = false;
+ if (!flags.testFlag(AllowMultiple)) {
+ m_SharedMem.attach();
+ m_Ephemeral = true;
+ }
}
+
if ((m_SharedMem.error() != QSharedMemory::NoError) &&
(m_SharedMem.error() != QSharedMemory::AlreadyExists)) {
throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString()));
}
} else {
- m_PrimaryInstance = true;
+ m_OwnsSM = true;
}
- if (m_PrimaryInstance) {
+
+ if (m_OwnsSM) {
connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()), Qt::QueuedConnection);
// has to be called before listen
m_Server.setSocketOptions(QLocalServer::WorldAccessOption);
@@ -65,7 +70,7 @@ SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) :
void SingleInstance::sendMessage(const QString &message)
{
- if (m_PrimaryInstance) {
+ if (m_OwnsSM) {
// nobody there to receive the message
return;
}
diff --git a/src/singleinstance.h b/src/singleinstance.h
index 14d49d6d..5c7cdf85 100644
--- a/src/singleinstance.h
+++ b/src/singleinstance.h
@@ -27,8 +27,8 @@ along with Mod Organizer. If not, see .
/**
* used to ensure only a single instance of Mod Organizer is started and to
- * allow secondary instances to send messages to the primary (visible) one. This way,
- * secondary instances can start a download in the primary one
+ * allow ephemeral instances to send messages to the primary (visible) one.
+ * This way, other instances can start a download in the primary one
**/
class SingleInstance : public QObject
{
@@ -36,24 +36,46 @@ class SingleInstance : public QObject
Q_OBJECT
public:
+ enum Flag
+ {
+ NoFlags = 0x00,
- /**
- * @brief constructor
- *
- * @param forcePrimary if true, this will be treated as the primary instance even
- * if another instance is running. This is used after an update since
- * the other instance is assumed to be in the process of quitting
- * @param parent parent object
- * @todo the forcePrimary parameter makes no sense. The second instance after an update
- * needs to delete the files from before the update so the first instance needs to quit
- * first anyway
- **/
- explicit SingleInstance(bool forcePrimary, QObject *parent = 0);
+
+ // when set, this will be treated as the primary instance even if
+ // another instance is running. This is used after an update since the
+ // other instance is assumed to be in the process of quitting
+ //
+ // todo: this makes no sense. The second instance after an update needs
+ // to delete the files from before the update so the first instance
+ // needs to quit first anyway
+ ForcePrimary = 0x01,
+
+ // if another instance is running, run this one disconnected from the
+ // shared memory
+ AllowMultiple = 0x02
+ };
+
+ using Flags = QFlags;
+
+
+ explicit SingleInstance(Flags flags, QObject *parent = 0);
/**
- * @return true if this is the primary instance (the one that gets to display a UI)
+ * @return true if this instance's job is to forward data to the primary
+ * instance through shared memory
**/
- bool primaryInstance() const { return m_PrimaryInstance; }
+ bool ephemeral() const
+ {
+ return m_Ephemeral;
+ }
+
+ // returns true if this is not the primary instance, but was allowed because
+ // of the AllowMultiple flag
+ //
+ bool secondary() const
+ {
+ return !m_Ephemeral && !m_OwnsSM;
+ }
/**
* send a message to the primary instance. This can be used to transmit download urls
@@ -65,7 +87,7 @@ public:
signals:
/**
- * @brief emitted when a secondary instance has sent a message (to us)
+ * @brief emitted when an ephemeral instance has sent a message (to us)
*
* @param message the message we received
**/
@@ -78,11 +100,13 @@ private slots:
void receiveMessage();
private:
-
- bool m_PrimaryInstance;
+ bool m_Ephemeral;
+ bool m_OwnsSM;
QSharedMemory m_SharedMem;
QLocalServer m_Server;
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(SingleInstance::Flags);
+
#endif // SINGLEINSTANCE_H
--
cgit v1.3.1
From 5e528fb4cf16ae208944d15d568c9140e3d741e4 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 02:24:44 -0400
Subject: new CommandLine class implemented crashdump as a command, fixed
dump_running_process.bat to use it attach to console if present instead of
always create one
---
dump_running_process.bat | 2 +-
src/CMakeLists.txt | 1 +
src/commandline.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++++
src/commandline.h | 62 ++++++++++++++
src/env.cpp | 42 +++++++++-
src/env.h | 4 +
src/main.cpp | 31 ++-----
src/pch.h | 1 +
8 files changed, 318 insertions(+), 30 deletions(-)
create mode 100644 src/commandline.cpp
create mode 100644 src/commandline.h
(limited to 'src')
diff --git a/dump_running_process.bat b/dump_running_process.bat
index 4697fe5e..1245c75e 100644
--- a/dump_running_process.bat
+++ b/dump_running_process.bat
@@ -1,2 +1,2 @@
pushd "%~dp0"
-start ModOrganizer.exe --crashdump
+start ModOrganizer.exe crashdump
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e95179b8..73254574 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -11,6 +11,7 @@ endif()
add_filter(NAME src/application GROUPS
iuserinterface
+ commandline
main
moapplication
moshortcut
diff --git a/src/commandline.cpp b/src/commandline.cpp
new file mode 100644
index 00000000..f0bdadf9
--- /dev/null
+++ b/src/commandline.cpp
@@ -0,0 +1,205 @@
+#include "commandline.h"
+#include "env.h"
+
+namespace cl
+{
+
+CommandLine::CommandLine()
+{
+ createOptions();
+ m_commands.push_back(std::make_unique());
+}
+
+int CommandLine::run(int argc, char** argv)
+{
+ try
+ {
+ po::variables_map vm;
+
+ auto parsed = po::command_line_parser(argc, argv)
+ .options(m_allOptions)
+ .positional(m_positional)
+ .allow_unregistered()
+ .run();
+
+ po::store(parsed, vm);
+
+ if (vm.count("command")) {
+ const auto cmd = vm["command"].as();
+
+ for (auto&& c : m_commands) {
+ if (c->name() == cmd) {
+ auto co = c->options();
+ co.add_options()
+ ("help", "shows this message");
+
+ auto opts = po::collect_unrecognized(
+ parsed.options, po::include_positional);
+
+ // remove the command name itself
+ opts.erase(opts.begin());
+
+ parsed = po::command_line_parser(opts)
+ .options(co)
+ .run();
+
+ po::store(parsed, vm);
+
+ if (vm.count("help")) {
+ env::Console console;
+ std::cout << usage(c.get()) << "\n";
+ return 0;
+ }
+
+ return c->run(vm);
+ }
+ }
+ }
+
+ if (vm.count("help")) {
+ env::Console console;
+ std::cout << usage() << "\n";
+ return 0;
+ }
+
+ return -1;
+ }
+ catch(po::error& e)
+ {
+ env::Console console;
+ std::cerr << e.what() << "\n";
+ std::cerr << usage() << "\n";
+ return 1;
+ }
+}
+
+void CommandLine::createOptions()
+{
+ m_visibleOptions.add_options()
+ ("help", "shows this message");
+
+ po::options_description options;
+ options.add_options()
+ ("command", po::value(), "command to execute");
+
+ m_positional
+ .add("command", 1)
+ .add("subargs", -1);
+
+ m_allOptions.add(m_visibleOptions);
+ m_allOptions.add(options);
+}
+
+std::string CommandLine::usage(const Command* c) const
+{
+ std::ostringstream oss;
+
+ oss
+ << "\n"
+ << "Usage:\n";
+
+ if (c) {
+ oss
+ << " ModOrganizer.exe [options] " << c->name() << " [command-options]\n"
+ << "\n"
+ << "Command options:\n"
+ << c->options() << "\n";
+ } else {
+ oss
+ << " ModOrganizer.exe [options] [[command] [command-options]]\n"
+ << "\n"
+ << "Commands:\n";
+
+ for (auto&& c : m_commands) {
+ oss << " " << c->name() << " " << c->description() << "\n";
+ }
+
+ oss << "\n";
+ }
+
+ oss
+ << "Global options:\n"
+ << m_visibleOptions << "\n";
+
+ return oss.str();
+}
+
+
+std::string Command::name() const
+{
+ return meta().name;
+}
+
+std::string Command::description() const
+{
+ return meta().description;
+}
+
+po::options_description Command::options() const
+{
+ return doOptions();
+}
+
+po::options_description Command::doOptions() const
+{
+ // no-op
+ return {};
+}
+
+std::string Command::usage() const
+{
+ std::ostringstream oss;
+
+ oss
+ << "\n"
+ << "Usage:\n"
+ << " ModOrganizer.exe [options] [[command] [command-options]]\n"
+ << "\n"
+ << "Options:\n"
+ << options() << "\n";
+
+ return oss.str();
+}
+
+int Command::run(po::variables_map& vm)
+{
+ return doRun(vm);
+}
+
+
+
+po::options_description CrashDumpCommand::doOptions() const
+{
+ po::options_description d;
+
+ d.add_options()
+ ("type", po::value()->default_value("mini"), "mini|data|full");
+
+ return d;
+}
+
+Command::Meta CrashDumpCommand::meta() const
+{
+ return {"crashdump", "writes a crashdump for a running process of MO"};
+}
+
+int CrashDumpCommand::doRun(po::variables_map& vm)
+{
+ env::Console console;
+
+ const auto typeString = vm["type"].as();
+ const auto type = env::coreDumpTypeFromString(typeString);
+
+ // dump
+ const auto b = env::coredumpOther(type);
+ if (!b) {
+ std::wcerr << L"\n>>>> a minidump file was not written\n\n";
+ }
+
+ std::wcerr << L"Press enter to continue...";
+ std::wcin.get();
+
+ return (b ? 0 : 1);
+}
+
+} // namespace
diff --git a/src/commandline.h b/src/commandline.h
new file mode 100644
index 00000000..d5ad5a32
--- /dev/null
+++ b/src/commandline.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include
+#include
+
+namespace cl
+{
+
+namespace po = boost::program_options;
+
+
+class Command
+{
+public:
+ virtual ~Command() = default;
+
+ std::string name() const;
+ std::string description() const;
+
+ po::options_description options() const;
+ std::string usage() const;
+
+ int run(po::variables_map& vm);
+
+protected:
+ struct Meta
+ {
+ std::string name, description;
+ };
+
+ virtual po::options_description doOptions() const;
+ virtual Meta meta() const = 0;
+ virtual int doRun(po::variables_map& vm) = 0;
+};
+
+
+class CrashDumpCommand : public Command
+{
+protected:
+ po::options_description doOptions() const;
+ Meta meta() const override;
+ int doRun(po::variables_map& vm) override;
+};
+
+
+class CommandLine
+{
+public:
+ CommandLine();
+
+ int run(int argc, char** argv);
+ std::string usage(const Command* c=nullptr) const;
+
+private:
+ po::options_description m_visibleOptions, m_allOptions;
+ po::positional_options_description m_positional;
+ std::vector> m_commands;
+
+ void createOptions();
+};
+
+} // namespace
diff --git a/src/env.cpp b/src/env.cpp
index 2862aa95..bf75c9fa 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -16,9 +16,15 @@ using namespace MOBase;
Console::Console()
: m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr)
{
- // open a console
- if (!AllocConsole()) {
- // failed, ignore
+ // try to attach to parent
+ if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
+ if (GetLastError() != ERROR_ACCESS_DENIED) {
+ // parent has no console, create one
+ if (!AllocConsole()) {
+ // failed, ignore
+ return;
+ }
+ }
}
m_hasConsole = true;
@@ -1032,6 +1038,36 @@ HandlePtr dumpFile()
return {};
}
+
+CoreDumpTypes coreDumpTypeFromString(const std::string& s)
+{
+ if (s == "data")
+ return env::CoreDumpTypes::Data;
+ else if (s == "full")
+ return env::CoreDumpTypes::Full;
+ else
+ return env::CoreDumpTypes::Mini;
+}
+
+std::string toString(CoreDumpTypes type)
+{
+ switch (type)
+ {
+ case CoreDumpTypes::Mini:
+ return "mini";
+
+ case CoreDumpTypes::Data:
+ return "data";
+
+ case CoreDumpTypes::Full:
+ return "full";
+
+ default:
+ return "?";
+ }
+}
+
+
bool createMiniDump(HANDLE process, CoreDumpTypes type)
{
const DWORD pid = GetProcessId(process);
diff --git a/src/env.h b/src/env.h
index 5c7492c6..9bec1713 100644
--- a/src/env.h
+++ b/src/env.h
@@ -307,6 +307,10 @@ enum class CoreDumpTypes
Full
};
+
+CoreDumpTypes coreDumpTypeFromString(const std::string& s);
+std::string toString(CoreDumpTypes type);
+
// creates a minidump file for this process
//
bool coredump(CoreDumpTypes type);
diff --git a/src/main.cpp b/src/main.cpp
index 7130d15c..8a006907 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -49,6 +49,7 @@ along with Mod Organizer. If not, see .
#include "env.h"
#include "envmodule.h"
#include "shared/util.h"
+#include "commandline.h"
#include
#include "shared/windows_error.h"
@@ -811,22 +812,6 @@ int runApplication(
return 1;
}
-int doCoreDump(env::CoreDumpTypes type)
-{
- env::Console c;
-
- // dump
- const auto b = env::coredumpOther(type);
- if (!b) {
- std::wcerr << L"\n>>>> a minidump file was not written\n\n";
- }
-
- std::wcerr << L"Press enter to continue...";
- std::wcin.get();
-
- return (b ? 0 : 1);
-}
-
log::Levels convertQtLevel(QtMsgType t)
{
switch (t)
@@ -895,17 +880,11 @@ void initLogging()
int main(int argc, char *argv[])
{
TimeThis tt("main to runApplication()");
+ cl::CommandLine cl;
- // handle --crashdump first
- for (int i=1; i= 0)
+ return r;
initLogging();
diff --git a/src/pch.h b/src/pch.h
index c66550be..02b6b1a2 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -64,6 +64,7 @@
#include
#include
#include
+#include
// openssl
#include
--
cgit v1.3.1
From f121d92602772110b80ce8ee89fef82c475190d3 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 03:38:11 -0400
Subject: implemented `launch` as a command
---
src/commandline.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++++++------
src/commandline.h | 51 +++++++++++++++--
src/main.cpp | 69 ++---------------------
3 files changed, 190 insertions(+), 85 deletions(-)
(limited to 'src')
diff --git a/src/commandline.cpp b/src/commandline.cpp
index f0bdadf9..588b5085 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -8,15 +8,22 @@ CommandLine::CommandLine()
{
createOptions();
m_commands.push_back(std::make_unique());
+ m_commands.push_back(std::make_unique());
}
-int CommandLine::run(int argc, char** argv)
+std::optional CommandLine::run(const std::wstring& line)
{
try
{
po::variables_map vm;
- auto parsed = po::command_line_parser(argc, argv)
+ auto args = po::split_winmain(line);
+ if (!args.empty()) {
+ // remove program name
+ args.erase(args.begin());
+ }
+
+ auto parsed = po::wcommand_line_parser(args)
.options(m_allOptions)
.positional(m_positional)
.allow_unregistered()
@@ -25,10 +32,10 @@ int CommandLine::run(int argc, char** argv)
po::store(parsed, vm);
if (vm.count("command")) {
- const auto cmd = vm["command"].as();
+ const auto commandName = vm["command"].as();
for (auto&& c : m_commands) {
- if (c->name() == cmd) {
+ if (c->name() == commandName) {
auto co = c->options();
co.add_options()
("help", "shows this message");
@@ -39,9 +46,14 @@ int CommandLine::run(int argc, char** argv)
// remove the command name itself
opts.erase(opts.begin());
- parsed = po::command_line_parser(opts)
- .options(co)
- .run();
+ po::wcommand_line_parser parser(opts);
+ parser.options(co);
+
+ if (c->allow_unregistered()) {
+ parser.allow_unregistered();
+ }
+
+ parsed = parser.run();
po::store(parsed, vm);
@@ -51,7 +63,7 @@ int CommandLine::run(int argc, char** argv)
return 0;
}
- return c->run(vm);
+ return c->run(line, vm, opts);
}
}
}
@@ -62,13 +74,16 @@ int CommandLine::run(int argc, char** argv)
return 0;
}
- return -1;
+ return {};
}
catch(po::error& e)
{
env::Console console;
- std::cerr << e.what() << "\n";
- std::cerr << usage() << "\n";
+
+ std::cerr
+ << e.what() << "\n"
+ << usage() << "\n";
+
return 1;
}
}
@@ -80,7 +95,8 @@ void CommandLine::createOptions()
po::options_description options;
options.add_options()
- ("command", po::value(), "command to execute");
+ ("command", po::value(), "command")
+ ("subargs", po::value >(), "args");
m_positional
.add("command", 1)
@@ -135,6 +151,11 @@ std::string Command::description() const
return meta().description;
}
+bool Command::allow_unregistered() const
+{
+ return false;
+}
+
po::options_description Command::options() const
{
return doOptions();
@@ -161,11 +182,32 @@ std::string Command::usage() const
return oss.str();
}
-int Command::run(po::variables_map& vm)
+std::optional Command::run(
+ const std::wstring& originalLine,
+ po::variables_map vm,
+ std::vector untouched)
+{
+ m_original = originalLine;
+ m_vm = vm;
+ m_untouched = untouched;
+
+ return doRun();
+}
+
+const std::wstring& Command::originalCmd() const
{
- return doRun(vm);
+ return m_original;
}
+const po::variables_map& Command::vm() const
+{
+ return m_vm;
+}
+
+const std::vector& Command::untouched() const
+{
+ return m_untouched;
+}
po::options_description CrashDumpCommand::doOptions() const
@@ -183,11 +225,11 @@ Command::Meta CrashDumpCommand::meta() const
return {"crashdump", "writes a crashdump for a running process of MO"};
}
-int CrashDumpCommand::doRun(po::variables_map& vm)
+std::optional CrashDumpCommand::doRun()
{
env::Console console;
- const auto typeString = vm["type"].as();
+ const auto typeString = vm()["type"].as();
const auto type = env::coreDumpTypeFromString(typeString);
// dump
@@ -202,4 +244,85 @@ int CrashDumpCommand::doRun(po::variables_map& vm)
return (b ? 0 : 1);
}
+
+bool LaunchCommand::allow_unregistered() const
+{
+ return true;
+}
+
+po::options_description LaunchCommand::doOptions() const
+{
+ return {};
+}
+
+Command::Meta LaunchCommand::meta() const
+{
+ return {"launch", ""};
+}
+
+std::optional LaunchCommand::doRun()
+{
+ // needs at least the working directory and process name
+ if (untouched().size() < 2) {
+ return 1;
+ }
+
+ std::vector arg;
+ auto args = UntouchedCommandLineArguments(2, arg);
+
+ return SpawnWaitProcess(arg[1].c_str(), args);
+}
+
+int LaunchCommand::SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine)
+{
+ PROCESS_INFORMATION pi{ 0 };
+ STARTUPINFO si{ 0 };
+ si.cb = sizeof(si);
+ std::wstring commandLineCopy = commandLine;
+
+ if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) {
+ // A bit of a problem where to log the error message here, at least this way you can get the message
+ // using a either DebugView or a live debugger:
+ std::wostringstream ost;
+ ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError();
+ OutputDebugStringW(ost.str().c_str());
+ return -1;
+ }
+
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ DWORD exitCode = (DWORD)-1;
+ ::GetExitCodeProcess(pi.hProcess, &exitCode);
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ return static_cast(exitCode);
+}
+
+// Parses the first parseArgCount arguments of the current process command line and returns
+// them in parsedArgs, the rest of the command line is returned untouched.
+LPCWSTR LaunchCommand::UntouchedCommandLineArguments(
+ int parseArgCount, std::vector& parsedArgs)
+{
+ LPCWSTR cmd = GetCommandLineW();
+ LPCWSTR arg = nullptr; // to skip executable name
+ for (; parseArgCount >= 0 && *cmd; ++cmd)
+ {
+ if (*cmd == '"') {
+ int escaped = 0;
+ for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd)
+ escaped = *cmd == '\\' ? escaped + 1 : 0;
+ }
+ if (*cmd == ' ') {
+ if (arg)
+ if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"')
+ parsedArgs.push_back(std::wstring(arg+1, cmd-1));
+ else
+ parsedArgs.push_back(std::wstring(arg, cmd));
+ arg = cmd + 1;
+ --parseArgCount;
+ }
+ }
+ return cmd;
+}
+
} // namespace
diff --git a/src/commandline.h b/src/commandline.h
index d5ad5a32..b45a86c6 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -20,7 +20,12 @@ public:
po::options_description options() const;
std::string usage() const;
- int run(po::variables_map& vm);
+ virtual bool allow_unregistered() const;
+
+ std::optional run(
+ const std::wstring& originalLine,
+ po::variables_map vm,
+ std::vector untouched);
protected:
struct Meta
@@ -30,7 +35,16 @@ protected:
virtual po::options_description doOptions() const;
virtual Meta meta() const = 0;
- virtual int doRun(po::variables_map& vm) = 0;
+ virtual std::optional doRun() = 0;
+
+ const std::wstring& originalCmd() const;
+ const po::variables_map& vm() const;
+ const std::vector& untouched() const;
+
+private:
+ std::wstring m_original;
+ po::variables_map m_vm;
+ std::vector m_untouched;
};
@@ -39,7 +53,36 @@ class CrashDumpCommand : public Command
protected:
po::options_description doOptions() const;
Meta meta() const override;
- int doRun(po::variables_map& vm) override;
+ std::optional doRun() override;
+};
+
+
+// this is the `launch` command used when starting a process from within the
+// virtualized directory, see processrunner.cpp
+//
+// it has its own parsing of the command line to extract the argument after
+// `launch` and use it as the cwd of the process, but pass the remaining
+// arguments verbatim
+//
+// this is very old code that should probably never be changed
+//
+// note that it's actually buggy; in particular, it doesn't handle multiple
+// whitespace between arguments
+//
+class LaunchCommand : public Command
+{
+public:
+ bool allow_unregistered() const override;
+
+protected:
+ po::options_description doOptions() const;
+ Meta meta() const override;
+ std::optional doRun() override;
+
+ int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine);
+
+ LPCWSTR UntouchedCommandLineArguments(
+ int parseArgCount, std::vector& parsedArgs);
};
@@ -48,7 +91,7 @@ class CommandLine
public:
CommandLine();
- int run(int argc, char** argv);
+ std::optional run(const std::wstring& line);
std::string usage(const Command* c=nullptr) const;
private:
diff --git a/src/main.cpp b/src/main.cpp
index 8a006907..68dcfe11 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -174,60 +174,6 @@ void setUnhandledExceptionHandler()
prevTerminateHandler = std::set_terminate(terminateHandler);
}
-// Parses the first parseArgCount arguments of the current process command line and returns
-// them in parsedArgs, the rest of the command line is returned untouched.
-LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs)
-{
- LPCWSTR cmd = GetCommandLineW();
- LPCWSTR arg = nullptr; // to skip executable name
- for (; parseArgCount >= 0 && *cmd; ++cmd)
- {
- if (*cmd == '"') {
- int escaped = 0;
- for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd)
- escaped = *cmd == '\\' ? escaped + 1 : 0;
- }
- if (*cmd == ' ') {
- if (arg)
- if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"')
- parsedArgs.push_back(std::wstring(arg+1, cmd-1));
- else
- parsedArgs.push_back(std::wstring(arg, cmd));
- arg = cmd + 1;
- --parseArgCount;
- }
- }
- return cmd;
-}
-
-
-static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) {
- PROCESS_INFORMATION pi{ 0 };
- STARTUPINFO si{ 0 };
- si.cb = sizeof(si);
- std::wstring commandLineCopy = commandLine;
-
- if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) {
- // A bit of a problem where to log the error message here, at least this way you can get the message
- // using a either DebugView or a live debugger:
- std::wostringstream ost;
- ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError();
- OutputDebugStringW(ost.str().c_str());
- return -1;
- }
-
- WaitForSingleObject(pi.hProcess, INFINITE);
-
- DWORD exitCode = (DWORD)-1;
- ::GetExitCodeProcess(pi.hProcess, &exitCode);
- CloseHandle(pi.hThread);
- CloseHandle(pi.hProcess);
- return static_cast(exitCode);
-}
-
-static DWORD WaitForProcess() {
-
-}
static bool HaveWriteAccess(const std::wstring &path)
{
@@ -879,13 +825,13 @@ void initLogging()
int main(int argc, char *argv[])
{
- TimeThis tt("main to runApplication()");
cl::CommandLine cl;
- const auto r = cl.run(argc, argv);
- if (r >= 0)
- return r;
+ const auto r = cl.run(GetCommandLineW());
+ if (r)
+ return *r;
+ TimeThis tt("main to runApplication()");
initLogging();
//Make sure the configured temp folder exists
@@ -896,13 +842,6 @@ int main(int argc, char *argv[])
//Should allow for better scaling of ui with higher resolution displays
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- if (argc >= 4) {
- std::vector arg;
- auto args = UntouchedCommandLineArguments(2, arg);
- if (arg[0] == L"launch")
- return SpawnWaitProcess(arg[1].c_str(), args);
- }
-
MOApplication application(argc, argv);
QStringList arguments = application.arguments();
--
cgit v1.3.1
From 75cc2ffead148ab2409cd1ef469613d2e9b80e17 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 04:06:18 -0400
Subject: removed unused `update` parameter moved --multiple to CommandLine
---
src/commandline.cpp | 44 ++++++++++++++++++++++++++++++++++----------
src/commandline.h | 4 ++++
src/main.cpp | 7 +------
src/singleinstance.cpp | 10 ----------
src/singleinstance.h | 12 +-----------
5 files changed, 40 insertions(+), 37 deletions(-)
(limited to 'src')
diff --git a/src/commandline.cpp b/src/commandline.cpp
index 588b5085..f84906e9 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -15,8 +15,6 @@ std::optional CommandLine::run(const std::wstring& line)
{
try
{
- po::variables_map vm;
-
auto args = po::split_winmain(line);
if (!args.empty()) {
// remove program name
@@ -29,10 +27,10 @@ std::optional CommandLine::run(const std::wstring& line)
.allow_unregistered()
.run();
- po::store(parsed, vm);
+ po::store(parsed, m_vm);
- if (vm.count("command")) {
- const auto commandName = vm["command"].as();
+ if (m_vm.count("command")) {
+ const auto commandName = m_vm["command"].as();
for (auto&& c : m_commands) {
if (c->name() == commandName) {
@@ -55,20 +53,20 @@ std::optional CommandLine::run(const std::wstring& line)
parsed = parser.run();
- po::store(parsed, vm);
+ po::store(parsed, m_vm);
- if (vm.count("help")) {
+ if (m_vm.count("help")) {
env::Console console;
std::cout << usage(c.get()) << "\n";
return 0;
}
- return c->run(line, vm, opts);
+ return c->run(line, m_vm, opts);
}
}
}
- if (vm.count("help")) {
+ if (m_vm.count("help")) {
env::Console console;
std::cout << usage() << "\n";
return 0;
@@ -91,7 +89,8 @@ std::optional CommandLine::run(const std::wstring& line)
void CommandLine::createOptions()
{
m_visibleOptions.add_options()
- ("help", "shows this message");
+ ("help", "shows this message")
+ ("multiple", "allows multiple instances of MO to run; see below");
po::options_description options;
options.add_options()
@@ -137,9 +136,34 @@ std::string CommandLine::usage(const Command* c) const
<< "Global options:\n"
<< m_visibleOptions << "\n";
+ if (!c) {
+ oss << "\n" << more() << "\n";
+ }
+
return oss.str();
}
+bool CommandLine::multiple() const
+{
+ return (m_vm.count("multiple") > 0);
+}
+
+std::string CommandLine::more() const
+{
+ return
+ "--multiple can be used to allow multiple instances of MO to run\n"
+ "simultaneously. This is unsupported and can create all sorts of weird\n"
+ "problems. To minimize the problems:\n"
+ "\n"
+ " 1) Never have multiple MO instances opened that manage the same game\n"
+ " instance.\n"
+ " 2) If an executable is launched from an instance, only this instance\n"
+ " may launch executables until all instances are closed.\n"
+ "\n"
+ "It is recommended to close _all_ instances of MO as soon as multiple\n"
+ "instances become unnecessary.";
+}
+
std::string Command::name() const
{
diff --git a/src/commandline.h b/src/commandline.h
index b45a86c6..deeb9923 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -94,12 +94,16 @@ public:
std::optional run(const std::wstring& line);
std::string usage(const Command* c=nullptr) const;
+ bool multiple() const;
+
private:
po::options_description m_visibleOptions, m_allOptions;
po::positional_options_description m_positional;
std::vector> m_commands;
+ po::variables_map m_vm;
void createOptions();
+ std::string more() const;
};
} // namespace
diff --git a/src/main.cpp b/src/main.cpp
index 68dcfe11..a1a4be01 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -852,12 +852,7 @@ int main(int argc, char *argv[])
SingleInstance::Flags siFlags = SingleInstance::NoFlags;
- if (arguments.contains("update")) {
- arguments.removeAll("update");
- siFlags |= SingleInstance::ForcePrimary;
- }
-
- if (arguments.contains("--multiple")) {
+ if (cl.multiple()) {
arguments.removeAll("--multiple");
siFlags |= SingleInstance::AllowMultiple;
}
diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp
index d38095df..e32c8de1 100644
--- a/src/singleinstance.cpp
+++ b/src/singleinstance.cpp
@@ -34,16 +34,6 @@ SingleInstance::SingleInstance(Flags flags, QObject *parent) :
m_SharedMem.setKey(s_Key);
if (!m_SharedMem.create(1)) {
- if (flags.testFlag(ForcePrimary)) {
- while (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
- Sleep(500);
- if (m_SharedMem.create(1)) {
- m_OwnsSM = true;
- break;
- }
- }
- }
-
if (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
if (!flags.testFlag(AllowMultiple)) {
m_SharedMem.attach();
diff --git a/src/singleinstance.h b/src/singleinstance.h
index 5c7cdf85..d500a056 100644
--- a/src/singleinstance.h
+++ b/src/singleinstance.h
@@ -40,19 +40,9 @@ public:
{
NoFlags = 0x00,
-
- // when set, this will be treated as the primary instance even if
- // another instance is running. This is used after an update since the
- // other instance is assumed to be in the process of quitting
- //
- // todo: this makes no sense. The second instance after an update needs
- // to delete the files from before the update so the first instance
- // needs to quit first anyway
- ForcePrimary = 0x01,
-
// if another instance is running, run this one disconnected from the
// shared memory
- AllowMultiple = 0x02
+ AllowMultiple = 0x01
};
using Flags = QFlags;
--
cgit v1.3.1
From ccab9eae8df3cf5367ce5cf164c98d1534ac13cb Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 05:07:32 -0400
Subject: add warning when profile given with -p doesn't exist implemented
moshortcut, nxm links and executable names as command line options
---
src/commandline.cpp | 98 +++++++++++++++++++++++++++++++++--------
src/commandline.h | 13 ++++++
src/main.cpp | 119 ++++++++++++++++++++++----------------------------
src/moshortcut.cpp | 9 ++++
src/moshortcut.h | 8 ++--
src/organizercore.cpp | 9 +++-
src/organizercore.h | 3 --
src/shared/util.cpp | 6 +++
src/shared/util.h | 2 +
9 files changed, 175 insertions(+), 92 deletions(-)
(limited to 'src')
diff --git a/src/commandline.cpp b/src/commandline.cpp
index f84906e9..f5c4df60 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -1,5 +1,6 @@
#include "commandline.h"
#include "env.h"
+#include "shared/util.h"
namespace cl
{
@@ -29,21 +30,22 @@ std::optional CommandLine::run(const std::wstring& line)
po::store(parsed, m_vm);
+ auto opts = po::collect_unrecognized(
+ parsed.options, po::include_positional);
+
+
if (m_vm.count("command")) {
const auto commandName = m_vm["command"].as();
for (auto&& c : m_commands) {
if (c->name() == commandName) {
+ // remove the command name itself
+ opts.erase(opts.begin());
+
auto co = c->options();
co.add_options()
("help", "shows this message");
- auto opts = po::collect_unrecognized(
- parsed.options, po::include_positional);
-
- // remove the command name itself
- opts.erase(opts.begin());
-
po::wcommand_line_parser parser(opts);
parser.options(co);
@@ -72,6 +74,26 @@ std::optional CommandLine::run(const std::wstring& line)
return 0;
}
+ if (!opts.empty()) {
+ const auto qs = QString::fromStdWString(opts[0]);
+ m_shortcut = qs;
+
+ if (!m_shortcut.isValid()) {
+ if (isNxmLink(qs)) {
+ m_nxmLink = qs;
+ } else {
+ m_executable = qs;
+ }
+ }
+
+ // remove the shortcut/nxm/executable
+ opts.erase(opts.begin());
+
+ for (auto&& o : opts) {
+ m_untouched.push_back(QString::fromStdWString(o));
+ }
+ }
+
return {};
}
catch(po::error& e)
@@ -86,11 +108,19 @@ std::optional CommandLine::run(const std::wstring& line)
}
}
+void CommandLine::clear()
+{
+ m_vm.clear();
+ m_shortcut = {};
+ m_nxmLink = {};
+}
+
void CommandLine::createOptions()
{
m_visibleOptions.add_options()
- ("help", "shows this message")
- ("multiple", "allows multiple instances of MO to run; see below");
+ ("help", "show this message")
+ ("multiple", "allow multiple instances of MO to run; see below")
+ ("profile,p", po::value(), "use the given profile (defaults to last used)");
po::options_description options;
options.add_options()
@@ -148,20 +178,50 @@ bool CommandLine::multiple() const
return (m_vm.count("multiple") > 0);
}
+std::optional CommandLine::profile() const
+{
+ if (m_vm.count("profile")) {
+ return QString::fromStdString(m_vm["profile"].as());
+ }
+
+ return {};
+}
+
+const MOShortcut& CommandLine::shortcut() const
+{
+ return m_shortcut;
+}
+
+std::optional CommandLine::nxmLink() const
+{
+ return m_nxmLink;
+}
+
+std::optional CommandLine::executable() const
+{
+ return m_executable;
+}
+
+const QStringList& CommandLine::untouched() const
+{
+ return m_untouched;
+}
+
std::string CommandLine::more() const
{
return
- "--multiple can be used to allow multiple instances of MO to run\n"
- "simultaneously. This is unsupported and can create all sorts of weird\n"
- "problems. To minimize the problems:\n"
- "\n"
- " 1) Never have multiple MO instances opened that manage the same game\n"
- " instance.\n"
- " 2) If an executable is launched from an instance, only this instance\n"
- " may launch executables until all instances are closed.\n"
- "\n"
- "It is recommended to close _all_ instances of MO as soon as multiple\n"
- "instances become unnecessary.";
+ "Multiple instances\n"
+ " --multiple can be used to allow multiple instances of MO to run\n"
+ " simultaneously. This is unsupported and can create all sorts of weird\n"
+ " problems. To minimize the problems:\n"
+ " \n"
+ " 1) Never have multiple MO instances opened that manage the same\n"
+ " game instance.\n"
+ " 2) If an executable is launched from an instance, only this\n"
+ " instance may launch executables until all instances are closed.\n"
+ " \n"
+ " It is recommended to close _all_ instances of MO as soon as multiple\n"
+ " instances become unnecessary.";
}
diff --git a/src/commandline.h b/src/commandline.h
index deeb9923..cdd1c917 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -1,5 +1,6 @@
#pragma once
+#include "moshortcut.h"
#include
#include
@@ -92,15 +93,27 @@ public:
CommandLine();
std::optional run(const std::wstring& line);
+ void clear();
+
std::string usage(const Command* c=nullptr) const;
bool multiple() const;
+ std::optional profile() const;
+ const MOShortcut& shortcut() const;
+ std::optional nxmLink() const;
+ std::optional executable() const;
+
+ const QStringList& untouched() const;
private:
po::options_description m_visibleOptions, m_allOptions;
po::positional_options_description m_positional;
std::vector> m_commands;
po::variables_map m_vm;
+ MOShortcut m_shortcut;
+ std::optional m_nxmLink;
+ std::optional m_executable;
+ QStringList m_untouched;
void createOptions();
std::string more() const;
diff --git a/src/main.cpp b/src/main.cpp
index a1a4be01..3c5bc92d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -225,18 +225,13 @@ static bool HaveWriteAccess(const std::wstring &path)
}
-QString determineProfile(QStringList &arguments, const Settings &settings)
+QString determineProfile(const cl::CommandLine& cl, const Settings &settings)
{
auto selectedProfileName = settings.game().selectedProfileName();
- { // see if there is a profile on the command line
- int profileIndex = arguments.indexOf("-p", 1);
- if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) {
- log::debug("profile overwritten on command line");
- selectedProfileName = arguments.at(profileIndex + 1);
- }
- arguments.removeAt(profileIndex);
- arguments.removeAt(profileIndex);
+ if (cl.profile()) {
+ log::debug("profile overwritten on command line");
+ selectedProfileName = *cl.profile();
}
if (!selectedProfileName) {
@@ -481,7 +476,7 @@ static QString getVersionDisplayString()
int runApplication(
- MOApplication &application, QStringList& arguments,
+ MOApplication &application, const cl::CommandLine& cl,
SingleInstance &instance, const QString &splashPath)
{
TimeThis tt("runApplication() to exec()");
@@ -635,57 +630,51 @@ int runApplication(
organizer.updateExecutablesList();
organizer.updateModInfoFromDisc();
- QString selectedProfileName = determineProfile(arguments, settings);
+ QString selectedProfileName = determineProfile(cl, settings);
organizer.setCurrentProfile(selectedProfileName);
// if we have a command line parameter, it is either a nxm link or
// a binary to start
- if (arguments.size() > 1) {
- if (MOShortcut shortcut{ arguments.at(1) }) {
- if (shortcut.hasExecutable()) {
- try {
- organizer.processRunner()
- .setFromShortcut(shortcut)
- .setWaitForCompletion()
- .run();
-
- return 0;
- }
- catch (const std::exception &e) {
- reportError(
- QObject::tr("failed to start shortcut: %1").arg(e.what()));
- return 1;
- }
- }
- }
- else if (OrganizerCore::isNxmLink(arguments.at(1))) {
- log::debug("starting download from command line: {}", arguments.at(1));
- organizer.externalMessage(arguments.at(1));
- }
- else {
- QString exeName = arguments.at(1);
- log::debug("starting {} from command line", exeName);
-
- arguments.removeFirst(); // remove application name (ModOrganizer.exe)
- arguments.removeFirst(); // remove binary name
-
- try
- {
- // pass the remaining parameters to the binary
- organizer.processRunner()
- .setFromFileOrExecutable(exeName, arguments)
+ if (cl.shortcut().isValid()) {
+ if (cl.shortcut().hasExecutable()) {
+ try {
+ organizer.processRunner()
+ .setFromShortcut(cl.shortcut())
.setWaitForCompletion()
.run();
- return 0;
- }
- catch (const std::exception &e)
- {
- reportError(
- QObject::tr("failed to start application: %1").arg(e.what()));
- return 1;
- }
- }
- }
+
+ return 0;
+ }
+ catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start shortcut: %1").arg(e.what()));
+ return 1;
+ }
+ }
+ } else if (cl.nxmLink()) {
+ log::debug("starting download from command line: {}", *cl.nxmLink());
+ organizer.externalMessage(*cl.nxmLink());
+ } else if (cl.executable()) {
+ const QString exeName = *cl.executable();
+ log::debug("starting {} from command line", exeName);
+
+ try
+ {
+ // pass the remaining parameters to the binary
+ organizer.processRunner()
+ .setFromFileOrExecutable(exeName, cl.untouched())
+ .setWaitForCompletion()
+ .run();
+
+ return 0;
+ }
+ catch (const std::exception &e)
+ {
+ reportError(
+ QObject::tr("failed to start application: %1").arg(e.what()));
+ return 1;
+ }
+ }
QPixmap pixmap;
@@ -857,15 +846,13 @@ int main(int argc, char *argv[])
siFlags |= SingleInstance::AllowMultiple;
}
- MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" };
-
SingleInstance instance(siFlags);
if (instance.ephemeral()) {
- if (moshortcut ||
- arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1)))
- {
- log::debug("not primary instance, sending shortcut/download message");
- instance.sendMessage(arguments.at(1));
+ if (cl.shortcut().isValid()) {
+ instance.sendMessage(cl.shortcut().toString());
+ return 0;
+ } else if (cl.nxmLink()) {
+ instance.sendMessage(*cl.nxmLink());
return 0;
} else if (arguments.size() == 1) {
QMessageBox::information(
@@ -887,8 +874,8 @@ int main(int argc, char *argv[])
try {
InstanceManager& instanceManager = InstanceManager::instance();
- if (moshortcut && moshortcut.hasInstance())
- instanceManager.overrideInstance(moshortcut.instance());
+ if (cl.shortcut().isValid() && cl.shortcut().hasInstance())
+ instanceManager.overrideInstance(cl.shortcut().instance());
dataPath = instanceManager.determineDataPath();
} catch (const std::exception &e) {
if (strcmp(e.what(),"Canceled"))
@@ -920,12 +907,12 @@ int main(int argc, char *argv[])
tt.stop();
- const int result = runApplication(application, arguments, instance, splash);
+ const int result = runApplication(application, cl, instance, splash);
if (result != RestartExitCode) {
return result;
}
argc = 1;
- moshortcut = MOShortcut("");
+ cl.clear();
} while (true);
}
diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp
index aad7380e..4efedbdb 100644
--- a/src/moshortcut.cpp
+++ b/src/moshortcut.cpp
@@ -40,3 +40,12 @@ MOShortcut::MOShortcut(const QString& link)
m_hasExecutable=true;
}
}
+
+QString MOShortcut::toString() const
+{
+ if (m_hasInstance) {
+ return "moshortcut://" + m_instance + ":" + m_executable;
+ } else {
+ return "moshortcut://" + m_executable;
+ }
+}
diff --git a/src/moshortcut.h b/src/moshortcut.h
index 2ce54910..0067b3bc 100644
--- a/src/moshortcut.h
+++ b/src/moshortcut.h
@@ -27,19 +27,21 @@ along with Mod Organizer. If not, see .
class MOShortcut {
public:
- MOShortcut(const QString& link);
+ MOShortcut(const QString& link={});
/// true iff intialized using a valid moshortcut link
- operator bool() const { return m_valid; }
+ bool isValid() const { return m_valid; }
bool hasInstance() const { return m_hasInstance; }
-
+
bool hasExecutable() const { return m_hasExecutable; }
const QString& instance() const { return m_instance; }
const QString& executable() const { return m_executable; }
+ QString toString() const;
+
private:
QString m_instance;
QString m_executable;
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 1c35720b..f9cafc95 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -38,6 +38,7 @@
#include "shared/directoryentry.h"
#include "shared/filesorigin.h"
#include "shared/fileentry.h"
+#include "shared/util.h"
#include
#include
@@ -395,7 +396,9 @@ void OrganizerCore::profileRemoved(QString const& profileName)
void OrganizerCore::externalMessage(const QString &message)
{
- if (MOShortcut moshortcut{ message } ) {
+ MOShortcut moshortcut(message);
+
+ if (moshortcut.isValid()) {
if(moshortcut.hasExecutable()) {
processRunner()
.setFromShortcut(moshortcut)
@@ -555,12 +558,16 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
QString profileDir = profileBaseDir.absoluteFilePath(profileName);
if (!QDir(profileDir).exists()) {
+ log::error("profile '{}' does not exist", profileName);
+
// selected profile doesn't exist. Ensure there is at least one profile,
// then pick any one
createDefaultProfile();
profileDir = profileBaseDir.absoluteFilePath(
profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0));
+
+ log::error("picked profile '{}' instead", QDir(profileDir).dirName());
}
// Keep the old profile to emit signal-changed:
diff --git a/src/organizercore.h b/src/organizercore.h
index 70ce94f5..1452bf08 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -191,9 +191,6 @@ public:
};
public:
-
- static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
-
OrganizerCore(Settings &settings);
~OrganizerCore();
diff --git a/src/shared/util.cpp b/src/shared/util.cpp
index ba500da9..f316549e 100644
--- a/src/shared/util.cpp
+++ b/src/shared/util.cpp
@@ -435,3 +435,9 @@ void ResetExitFlag()
{
g_exiting = false;
}
+
+
+bool isNxmLink(const QString& link)
+{
+ return link.startsWith("nxm://", Qt::CaseInsensitive);
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index 2761b64f..1688a931 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -83,4 +83,6 @@ bool ModOrganizerExiting();
bool ModOrganizerCanCloseNow();
void ResetExitFlag();
+bool isNxmLink(const QString& link);
+
#endif // UTIL_H
--
cgit v1.3.1
From f4ca82f798fa7e456bb904ed301ddc17db5410c8 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 05:30:57 -0400
Subject: fixed handling of profile names with different casing than on the
filesystem added --instance
---
src/commandline.cpp | 12 ++++++++++++
src/commandline.h | 2 ++
src/main.cpp | 6 ++++--
src/organizercore.cpp | 19 +++++++++++++++++--
4 files changed, 35 insertions(+), 4 deletions(-)
(limited to 'src')
diff --git a/src/commandline.cpp b/src/commandline.cpp
index f5c4df60..11f43dea 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -120,6 +120,7 @@ void CommandLine::createOptions()
m_visibleOptions.add_options()
("help", "show this message")
("multiple", "allow multiple instances of MO to run; see below")
+ ("instance,i", po::value(), "use the given instance (defaults to last used)")
("profile,p", po::value(), "use the given profile (defaults to last used)");
po::options_description options;
@@ -187,6 +188,17 @@ std::optional CommandLine::profile() const
return {};
}
+std::optional CommandLine::instance() const
+{
+ if (m_shortcut.isValid() && m_shortcut.hasInstance()) {
+ return m_shortcut.instance();
+ } else if (m_vm.count("instance")) {
+ return QString::fromStdString(m_vm["instance"].as());
+ }
+
+ return {};
+}
+
const MOShortcut& CommandLine::shortcut() const
{
return m_shortcut;
diff --git a/src/commandline.h b/src/commandline.h
index cdd1c917..ecc19c89 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -99,6 +99,8 @@ public:
bool multiple() const;
std::optional profile() const;
+ std::optional instance() const;
+
const MOShortcut& shortcut() const;
std::optional nxmLink() const;
std::optional executable() const;
diff --git a/src/main.cpp b/src/main.cpp
index 3c5bc92d..5ae8999c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -874,8 +874,10 @@ int main(int argc, char *argv[])
try {
InstanceManager& instanceManager = InstanceManager::instance();
- if (cl.shortcut().isValid() && cl.shortcut().hasInstance())
- instanceManager.overrideInstance(cl.shortcut().instance());
+
+ if (cl.instance())
+ instanceManager.overrideInstance(*cl.instance());
+
dataPath = instanceManager.determineDataPath();
} catch (const std::exception &e) {
if (strcmp(e.what(),"Canceled"))
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index f9cafc95..a9469e6e 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -555,9 +555,24 @@ void OrganizerCore::setCurrentProfile(const QString &profileName)
log::debug("selecting profile '{}'", profileName);
QDir profileBaseDir(settings().paths().profiles());
- QString profileDir = profileBaseDir.absoluteFilePath(profileName);
- if (!QDir(profileDir).exists()) {
+ const auto subdirs = profileBaseDir.entryList(
+ QDir::AllDirs | QDir::NoDotAndDotDot);
+
+ QString profileDir;
+
+ // the profile name may not have the correct case, which breaks other parts
+ // of the ui like the profile combobox, which walks directories on its own
+ //
+ // find the real name with the correct case by walking the directories
+ for (auto&& dirName : subdirs) {
+ if (QString::compare(dirName, profileName, Qt::CaseInsensitive) == 0) {
+ profileDir = profileBaseDir.absoluteFilePath(dirName);
+ break;
+ }
+ }
+
+ if (profileDir.isEmpty()) {
log::error("profile '{}' does not exist", profileName);
// selected profile doesn't exist. Ensure there is at least one profile,
--
cgit v1.3.1
From a8b6f227302f2264eea38ce95d82ddc5aebf1100 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Sat, 18 Jul 2020 13:17:31 -0400
Subject: formatting for command list added empty exe and run commands
---
src/commandline.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++--
src/commandline.h | 24 +++++++++++++++
2 files changed, 110 insertions(+), 3 deletions(-)
(limited to 'src')
diff --git a/src/commandline.cpp b/src/commandline.cpp
index 11f43dea..819ac1c6 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -5,9 +5,47 @@
namespace cl
{
+std::string pad_right(std::string s, std::size_t n, char c=' ')
+{
+ if (s.size() < n)
+ s.append(n - s.size() , c);
+
+ return s;
+}
+
+std::string table(
+ const std::vector>& v,
+ std::size_t indent, std::size_t spacing)
+{
+ std::size_t longest = 0;
+
+ for (auto&& p : v)
+ longest = std::max(longest, p.first.size());
+
+ std::string s;
+
+ for (auto&& p : v)
+ {
+ if (!s.empty())
+ s += "\n";
+
+ s +=
+ std::string(indent, ' ') +
+ pad_right(p.first, longest) + " " +
+ std::string(spacing, ' ') +
+ p.second;
+ }
+
+ return s;
+
+}
+
+
CommandLine::CommandLine()
{
createOptions();
+ m_commands.push_back(std::make_unique());
+ m_commands.push_back(std::make_unique());
m_commands.push_back(std::make_unique());
m_commands.push_back(std::make_unique());
}
@@ -156,11 +194,14 @@ std::string CommandLine::usage(const Command* c) const
<< "\n"
<< "Commands:\n";
+ std::vector> v;
for (auto&& c : m_commands) {
- oss << " " << c->name() << " " << c->description() << "\n";
+ v.push_back({c->name(), c->description()});
}
- oss << "\n";
+ oss
+ << table(v, 2, 4) << "\n"
+ << "\n";
}
oss
@@ -353,7 +394,7 @@ po::options_description LaunchCommand::doOptions() const
Command::Meta LaunchCommand::meta() const
{
- return {"launch", ""};
+ return {"launch", "(internal, do not use)"};
}
std::optional LaunchCommand::doRun()
@@ -421,4 +462,46 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments(
return cmd;
}
+
+bool ExeCommand::allow_unregistered() const
+{
+ return true;
+}
+
+po::options_description ExeCommand::doOptions() const
+{
+ return {};
+}
+
+Command::Meta ExeCommand::meta() const
+{
+ return {"exe", "launches a configured executable"};
+}
+
+std::optional ExeCommand::doRun()
+{
+ return {};
+}
+
+
+bool RunCommand::allow_unregistered() const
+{
+ return true;
+}
+
+po::options_description RunCommand::doOptions() const
+{
+ return {};
+}
+
+Command::Meta RunCommand::meta() const
+{
+ return {"run", "launches an arbitrary program"};
+}
+
+std::optional RunCommand::doRun()
+{
+ return {};
+}
+
} // namespace
diff --git a/src/commandline.h b/src/commandline.h
index ecc19c89..f633518b 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -87,6 +87,30 @@ protected:
};
+class ExeCommand : public Command
+{
+public:
+ bool allow_unregistered() const override;
+
+protected:
+ po::options_description doOptions() const;
+ Meta meta() const override;
+ std::optional doRun() override;
+};
+
+
+class RunCommand : public Command
+{
+public:
+ bool allow_unregistered() const override;
+
+protected:
+ po::options_description doOptions() const;
+ Meta meta() const override;
+ std::optional doRun() override;
+};
+
+
class CommandLine
{
public:
--
cgit v1.3.1
From cf289d7f95f6b5730cceaa8e6e22b4b33cf050c8 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Wed, 22 Jul 2020 22:17:58 -0400
Subject: command usage lines, exe options
---
src/commandline.cpp | 178 ++++++++++++++++++++++++++++++++++++++--------------
src/commandline.h | 27 ++++----
2 files changed, 147 insertions(+), 58 deletions(-)
(limited to 'src')
diff --git a/src/commandline.cpp b/src/commandline.cpp
index 819ac1c6..3f8a6b1a 100644
--- a/src/commandline.cpp
+++ b/src/commandline.cpp
@@ -67,6 +67,7 @@ std::optional CommandLine::run(const std::wstring& line)
.run();
po::store(parsed, m_vm);
+ po::notify(m_vm);
auto opts = po::collect_unrecognized(
parsed.options, po::include_positional);
@@ -80,28 +81,43 @@ std::optional CommandLine::run(const std::wstring& line)
// remove the command name itself
opts.erase(opts.begin());
- auto co = c->options();
- co.add_options()
- ("help", "shows this message");
+ try
+ {
+ po::wcommand_line_parser parser(opts);
- po::wcommand_line_parser parser(opts);
- parser.options(co);
+ auto co = c->allOptions();
+ parser.options(co);
- if (c->allow_unregistered()) {
- parser.allow_unregistered();
- }
+ if (c->allow_unregistered()) {
+ parser.allow_unregistered();
+ }
- parsed = parser.run();
+ auto pos = c->positional();
+ parser.positional(pos);
- po::store(parsed, m_vm);
+ parsed = parser.run();
- if (m_vm.count("help")) {
- env::Console console;
- std::cout << usage(c.get()) << "\n";
- return 0;
+ 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;
+ }
+
+ return c->run(line, m_vm, opts);
}
+ catch(po::error& e)
+ {
+ env::Console console;
- return c->run(line, m_vm, opts);
+ std::cerr
+ << e.what() << "\n"
+ << usage(c.get()) << "\n";
+
+ return 1;
+ }
}
}
}
@@ -157,7 +173,7 @@ void CommandLine::createOptions()
{
m_visibleOptions.add_options()
("help", "show this message")
- ("multiple", "allow multiple instances of MO to run; see below")
+ ("multiple", "allow multiple MO processes to run; see below")
("instance,i", po::value(), "use the given instance (defaults to last used)")
("profile,p", po::value(), "use the given profile (defaults to last used)");
@@ -184,10 +200,10 @@ std::string CommandLine::usage(const Command* c) const
if (c) {
oss
- << " ModOrganizer.exe [options] " << c->name() << " [command-options]\n"
+ << " ModOrganizer.exe [global-options] " << c->usageLine() << "\n"
<< "\n"
<< "Command options:\n"
- << c->options() << "\n";
+ << c->visibleOptions() << "\n";
} else {
oss
<< " ModOrganizer.exe [options] [[command] [command-options]]\n"
@@ -263,18 +279,24 @@ const QStringList& CommandLine::untouched() const
std::string CommandLine::more() const
{
return
- "Multiple instances\n"
- " --multiple can be used to allow multiple instances of MO to run\n"
+ "Multiple processes\n"
+ " A note on terminology: 'instance' can either mean an MO process\n"
+ " that's running on the system, or a set of mods and profiles managed\n"
+ " by MO. To avoid confusion, the term 'process' is used below for the\n"
+ " former.\n"
+ " \n"
+ " --multiple can be used to allow multiple MO processes to run\n"
" simultaneously. This is unsupported and can create all sorts of weird\n"
- " problems. To minimize the problems:\n"
+ " problems. To minimize these:\n"
" \n"
- " 1) Never have multiple MO instances opened that manage the same\n"
+ " 1) Never have multiple MO processes running that manage the same\n"
" game instance.\n"
- " 2) If an executable is launched from an instance, only this\n"
- " instance may launch executables until all instances are closed.\n"
+ " 2) If an executable is launched from an MO process, only this\n"
+ " process may launch executables until all processes are \n"
+ " terminated.\n"
" \n"
- " It is recommended to close _all_ instances of MO as soon as multiple\n"
- " instances become unnecessary.";
+ " It is recommended to close _all_ MO processes as soon as multiple\n"
+ " processes become unnecessary.";
}
@@ -288,22 +310,65 @@ std::string Command::description() const
return meta().description;
}
+std::string Command::usageLine() const
+{
+ return name() + " " + getUsageLine();
+}
+
bool Command::allow_unregistered() const
{
return false;
}
-po::options_description Command::options() const
+po::options_description Command::allOptions() const
+{
+ po::options_description d;
+
+ d.add(visibleOptions());
+ d.add(getInternalOptions());
+
+ return d;
+}
+
+po::options_description Command::visibleOptions() const
+{
+ po::options_description d(getVisibleOptions());
+
+ d.add_options()
+ ("help", "shows this message");
+
+ return d;
+}
+
+po::positional_options_description Command::positional() const
+{
+ return getPositional();
+}
+
+std::string Command::getUsageLine() const
+{
+ return "[options]";
+}
+
+po::options_description Command::getVisibleOptions() const
+{
+ // no-op
+ return {};
+}
+
+po::options_description Command::getInternalOptions() const
{
- return doOptions();
+ // no-op
+ return {};
}
-po::options_description Command::doOptions() const
+po::positional_options_description Command::getPositional() const
{
// no-op
return {};
}
+
std::string Command::usage() const
{
std::ostringstream oss;
@@ -314,7 +379,7 @@ std::string Command::usage() const
<< " ModOrganizer.exe [options] [[command] [command-options]]\n"
<< "\n"
<< "Options:\n"
- << options() << "\n";
+ << visibleOptions() << "\n";
return oss.str();
}
@@ -347,7 +412,7 @@ const std::vector& Command::untouched() const
}
-po::options_description CrashDumpCommand::doOptions() const
+po::options_description CrashDumpCommand::getVisibleOptions() const
{
po::options_description d;
@@ -387,11 +452,6 @@ bool LaunchCommand::allow_unregistered() const
return true;
}
-po::options_description LaunchCommand::doOptions() const
-{
- return {};
-}
-
Command::Meta LaunchCommand::meta() const
{
return {"launch", "(internal, do not use)"};
@@ -463,14 +523,39 @@ LPCWSTR LaunchCommand::UntouchedCommandLineArguments(
}
-bool ExeCommand::allow_unregistered() const
+std::string ExeCommand::getUsageLine() const
{
- return true;
+ return "[options] exe-name";
}
-po::options_description ExeCommand::doOptions() const
+po::options_description ExeCommand::getVisibleOptions() const
{
- return {};
+ po::options_description d;
+
+ d.add_options()
+ ("arguments,a", po::value()->default_value(""), "override arguments")
+ ("cwd,c", po::value()->default_value(""), "override working directory");
+
+ return d;
+}
+
+po::options_description ExeCommand::getInternalOptions() const
+{
+ po::options_description d;
+
+ d.add_options()
+ ("exe-name", po::value()->required(), "executable name");
+
+ return d;
+}
+
+po::positional_options_description ExeCommand::getPositional() const
+{
+ po::positional_options_description d;
+
+ d.add("exe-name", 1);
+
+ return d;
}
Command::Meta ExeCommand::meta() const
@@ -480,16 +565,17 @@ Command::Meta ExeCommand::meta() const
std::optional ExeCommand::doRun()
{
- return {};
-}
+ const auto exe = vm()["exe-name"].as();
+ const auto args = vm()["arguments"].as();
+ const auto cwd = vm()["cwd"].as();
-bool RunCommand::allow_unregistered() const
-{
- return true;
+
+ return 0;
}
-po::options_description RunCommand::doOptions() const
+
+po::options_description RunCommand::getOptions() const
{
return {};
}
diff --git a/src/commandline.h b/src/commandline.h
index f633518b..0e300327 100644
--- a/src/commandline.h
+++ b/src/commandline.h
@@ -17,8 +17,11 @@ public:
std::string name() const;
std::string description() const;
+ std::string usageLine() const;
- po::options_description options() const;
+ po::options_description allOptions() const;
+ po::options_description visibleOptions() const;
+ po::positional_options_description positional() const;
std::string usage() const;
virtual bool allow_unregistered() const;
@@ -34,7 +37,11 @@ protected:
std::string name, description;
};
- virtual po::options_description doOptions() const;
+ virtual std::string getUsageLine() const;
+ virtual po::options_description getVisibleOptions() const;
+ virtual po::options_description getInternalOptions() const;
+ virtual po::positional_options_description getPositional() const;
+
virtual Meta meta() const = 0;
virtual std::optional doRun() = 0;
@@ -52,7 +59,7 @@ private:
class CrashDumpCommand : public Command
{
protected:
- po::options_description doOptions() const;
+ po::options_description getVisibleOptions() const override;
Meta meta() const override;
std::optional doRun() override;
};
@@ -76,7 +83,6 @@ public:
bool allow_unregistered() const override;
protected:
- po::options_description doOptions() const;
Meta meta() const override;
std::optional doRun() override;
@@ -89,11 +95,11 @@ protected:
class ExeCommand : public Command
{
-public:
- bool allow_unregistered() const override;
-
protected:
- po::options_description doOptions() const;
+ std::string getUsageLine() const override;
+ po::options_description getVisibleOptions() const override;
+ po::options_description getInternalOptions() const override;
+ po::positional_options_description getPositional() const override;
Meta meta() const override;
std::optional doRun() override;
};
@@ -101,11 +107,8 @@ protected:
class RunCommand : public Command
{
-public:
- bool allow_unregistered() const override;
-
protected:
- po::options_description doOptions() const;
+ po::options_description getOptions() const;
Meta meta() const override;
std::optional doRun() override;
};
--
cgit v1.3.1
From 93d1c97fdaff456943ca398562ac04d6c7f4ed6a Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Wed, 22 Jul 2020 22:30:24 -0400
Subject: cleaned up includes
---
src/main.cpp | 61 +++++-------------------------------------------------------
1 file changed, 5 insertions(+), 56 deletions(-)
(limited to 'src')
diff --git a/src/main.cpp b/src/main.cpp
index 5ae8999c..23ec91da 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,83 +17,32 @@ You should have received a copy of the GNU General Public License
along with Mod Organizer. If not, see .
*/
-
-#ifdef LEAK_CHECK_WITH_VLD
-#include
-#include
-#endif // LEAK_CHECK_WITH_VLD
-
-#define WIN32_LEAN_AND_MEAN
-#include
-#include
-
-#include "shared/appconfig.h"
-#include
-#include
#include "mainwindow.h"
-#include
-#include "modlist.h"
-#include "profile.h"
-#include "spawn.h"
-#include "executableslist.h"
#include "singleinstance.h"
-#include "utility.h"
#include "loglist.h"
#include "selectiondialog.h"
#include "moapplication.h"
#include "tutorialmanager.h"
#include "nxmaccessmanager.h"
#include "instancemanager.h"
-#include "moshortcut.h"
#include "organizercore.h"
#include "env.h"
#include "envmodule.h"
-#include "shared/util.h"
#include "commandline.h"
-#include
-#include "shared/windows_error.h"
+#include "shared/util.h"
+#include "shared/appconfig.h"
+
+#include
#include
#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-#include
-
-#include
-#include
-#include
-#include
-
+#include
#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"")
-
using namespace MOBase;
using namespace MOShared;
-
void sanityChecks(const env::Environment& env);
int checkIncompatibleModule(const env::Module& m);
int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s);
--
cgit v1.3.1
From 3d1d9f111025879983147fbbe78f8af04693d066 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Wed, 22 Jul 2020 22:32:12 -0400
Subject: removed unused HaveWriteAccess() stop useless preloading of ssl dlls
---
src/main.cpp | 101 ++---------------------------------------------------------
1 file changed, 2 insertions(+), 99 deletions(-)
(limited to 'src')
diff --git a/src/main.cpp b/src/main.cpp
index 23ec91da..a335a37a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -123,57 +123,6 @@ void setUnhandledExceptionHandler()
prevTerminateHandler = std::set_terminate(terminateHandler);
}
-
-static bool HaveWriteAccess(const std::wstring &path)
-{
- bool writable = false;
-
- const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
-
- DWORD length = 0;
- if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, nullptr, 0UL, &length)
- && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
- std::string tempBuffer;
- tempBuffer.reserve(length);
- PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data();
- if (security
- && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) {
- HANDLE token = nullptr;
- const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ;
- if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) {
- if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) {
- throw std::runtime_error("Unable to get any thread or process token");
- }
- }
-
- HANDLE impersonatedToken = nullptr;
- if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) {
- GENERIC_MAPPING mapping = { 0xFFFFFFFF };
- mapping.GenericRead = FILE_GENERIC_READ;
- mapping.GenericWrite = FILE_GENERIC_WRITE;
- mapping.GenericExecute = FILE_GENERIC_EXECUTE;
- mapping.GenericAll = FILE_ALL_ACCESS;
-
- DWORD genericAccessRights = FILE_GENERIC_WRITE;
- ::MapGenericMask(&genericAccessRights, &mapping);
-
- PRIVILEGE_SET privileges = { 0 };
- DWORD grantedAccess = 0;
- DWORD privilegesLength = sizeof(privileges);
- BOOL result = 0;
- if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) {
- writable = result != 0;
- }
- ::CloseHandle(impersonatedToken);
- }
-
- ::CloseHandle(token);
- }
- }
- return writable;
-}
-
-
QString determineProfile(const cl::CommandLine& cl, const Settings &settings)
{
auto selectedProfileName = settings.game().selectedProfileName();
@@ -383,47 +332,6 @@ void setupPath()
::SetEnvironmentVariableW(L"PATH", newPath.c_str());
}
-void preloadDll(const QString& filename)
-{
- if (GetModuleHandleW(filename.toStdWString().c_str())) {
- // already loaded, this can happen when "restarting" MO by switching
- // instances, for example
- return;
- }
-
- const auto appPath = QDir::toNativeSeparators(
- QCoreApplication::applicationDirPath());
-
- const auto dllPath = appPath + "\\" + filename;
-
- if (!QFile::exists(dllPath)) {
- log::warn("{} not found", dllPath);
- return;
- }
-
- if (!LoadLibraryW(dllPath.toStdWString().c_str())) {
- const auto e = GetLastError();
- log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e));
- }
-}
-
-void preloadSsl()
-{
-#if Q_PROCESSOR_WORDSIZE == 8
- preloadDll("libcrypto-1_1-x64.dll");
- preloadDll("libssl-1_1-x64.dll");
-#elif Q_PROCESSOR_WORDSIZE == 4
- preloadDll("libcrypto-1_1.dll");
- preloadDll("libssl-1_1.dll");
-#endif
-}
-
-static QString getVersionDisplayString()
-{
- return createVersionInfo().displayString(3);
-}
-
-
int runApplication(
MOApplication &application, const cl::CommandLine& cl,
SingleInstance &instance, const QString &splashPath)
@@ -432,13 +340,8 @@ int runApplication(
log::info(
"starting Mod Organizer version {} revision {} in {}, usvfs: {}",
- getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath(),
- MOShared::getUsvfsVersionString());
-
- preloadSsl();
- if (!QSslSocket::supportsSsl()) {
- log::warn("no ssl support");
- }
+ createVersionInfo().displayString(3), GITID,
+ QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString());
const QString dataPath = application.property("dataPath").toString();
log::info("data path: {}", dataPath);
--
cgit v1.3.1
From f97e40f127441828eb4ffe11841ebf79191ac8a3 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Wed, 22 Jul 2020 22:35:40 -0400
Subject: moved initLogging() to loglist.cpp
---
src/loglist.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/loglist.h | 3 +++
src/main.cpp | 67 ++-------------------------------------------------------
3 files changed, 70 insertions(+), 65 deletions(-)
(limited to 'src')
diff --git a/src/loglist.cpp b/src/loglist.cpp
index a4868ed4..7a64ecad 100644
--- a/src/loglist.cpp
+++ b/src/loglist.cpp
@@ -262,3 +262,68 @@ void LogList::onContextMenu(const QPoint& pos)
auto* menu = createMenu(this);
menu->popup(viewport()->mapToGlobal(pos));
}
+
+
+log::Levels convertQtLevel(QtMsgType t)
+{
+ switch (t)
+ {
+ case QtDebugMsg:
+ return log::Debug;
+
+ case QtWarningMsg:
+ return log::Warning;
+
+ case QtCriticalMsg: // fall-through
+ case QtFatalMsg:
+ return log::Error;
+
+ case QtInfoMsg: // fall-through
+ default:
+ return log::Info;
+ }
+}
+
+void qtLogCallback(
+ QtMsgType type, const QMessageLogContext& context, const QString& message)
+{
+ std::string_view file = "";
+
+ if (type != QtDebugMsg) {
+ if (context.file) {
+ file = context.file;
+
+ const auto lastSep = file.find_last_of("/\\");
+ if (lastSep != std::string_view::npos) {
+ file = {context.file + lastSep + 1};
+ }
+ }
+ }
+
+ if (file.empty()) {
+ log::log(
+ convertQtLevel(type), "{}",
+ message.toStdString());
+ } else {
+ log::log(
+ convertQtLevel(type), "[{}:{}] {}",
+ file, context.line, message.toStdString());
+ }
+}
+
+void initLogging()
+{
+ LogModel::create();
+
+ log::LoggerConfiguration conf;
+ conf.maxLevel = MOBase::log::Debug;
+ conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$";
+ conf.utc = true;
+
+ log::createDefault(conf);
+
+ log::getDefault().setCallback(
+ [](log::Entry e){ LogModel::instance().add(e); });
+
+ qInstallMessageHandler(qtLogCallback);
+}
diff --git a/src/loglist.h b/src/loglist.h
index 6b3aa4d5..7387eb50 100644
--- a/src/loglist.h
+++ b/src/loglist.h
@@ -82,4 +82,7 @@ private:
void onNewEntry();
};
+
+void initLogging();
+
#endif // LOGBUFFER_H
diff --git a/src/main.cpp b/src/main.cpp
index a335a37a..e52b252b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -599,71 +599,6 @@ int runApplication(
return 1;
}
-log::Levels convertQtLevel(QtMsgType t)
-{
- switch (t)
- {
- case QtDebugMsg:
- return log::Debug;
-
- case QtWarningMsg:
- return log::Warning;
-
- case QtCriticalMsg: // fall-through
- case QtFatalMsg:
- return log::Error;
-
- case QtInfoMsg: // fall-through
- default:
- return log::Info;
- }
-}
-
-void qtLogCallback(
- QtMsgType type, const QMessageLogContext& context, const QString& message)
-{
- std::string_view file = "";
-
- if (type != QtDebugMsg) {
- if (context.file) {
- file = context.file;
-
- const auto lastSep = file.find_last_of("/\\");
- if (lastSep != std::string_view::npos) {
- file = {context.file + lastSep + 1};
- }
- }
- }
-
- if (file.empty()) {
- log::log(
- convertQtLevel(type), "{}",
- message.toStdString());
- } else {
- log::log(
- convertQtLevel(type), "[{}:{}] {}",
- file, context.line, message.toStdString());
- }
-}
-
-void initLogging()
-{
- LogModel::create();
-
- log::LoggerConfiguration conf;
- conf.maxLevel = MOBase::log::Debug;
- conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$";
- conf.utc = true;
-
- log::createDefault(conf);
-
- log::getDefault().setCallback(
- [](log::Entry e){ LogModel::instance().add(e); });
-
- qInstallMessageHandler(qtLogCallback);
-}
-
-
int main(int argc, char *argv[])
{
cl::CommandLine cl;
@@ -673,6 +608,8 @@ int main(int argc, char *argv[])
return *r;
TimeThis tt("main to runApplication()");
+
+ // in loglist.cpp
initLogging();
//Make sure the configured temp folder exists
--
cgit v1.3.1
From 9435202034cafb05ffc11aed48ff57536bce73f7 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Wed, 22 Jul 2020 23:27:01 -0400
Subject: removed flags from SingleInstance because there's only one left
refactoring in main.cpp: - moved stuff to loglist.cpp and moapplication.cpp
- split main() into a few functions
---
src/env.cpp | 13 ++-
src/env.h | 3 +-
src/loglist.cpp | 26 ++++++
src/loglist.h | 1 +
src/main.cpp | 225 ++++++++++++++++++++++++-------------------------
src/moapplication.cpp | 19 ++---
src/moapplication.h | 20 ++---
src/singleinstance.cpp | 4 +-
src/singleinstance.h | 18 +---
src/spawn.cpp | 2 +-
10 files changed, 174 insertions(+), 157 deletions(-)
(limited to 'src')
diff --git a/src/env.cpp b/src/env.cpp
index bf75c9fa..9f0acbbd 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -400,11 +400,18 @@ QString path()
return get("PATH");
}
-QString addPath(const QString& s)
+QString appendToPath(const QString& s)
{
auto old = path();
- set("PATH", get("PATH") + ";" + s);
- return old;
+ set("PATH", old + ";" + s);
+ return old;
+}
+
+QString prependToPath(const QString& s)
+{
+ auto old = path();
+ set("PATH", s + ";" + old);
+ return old;
}
QString setPath(const QString& s)
diff --git a/src/env.h b/src/env.h
index 9bec1713..a563f2c3 100644
--- a/src/env.h
+++ b/src/env.h
@@ -236,7 +236,8 @@ QString get(const QString& name);
QString set(const QString& name, const QString& value);
QString path();
-QString addPath(const QString& s);
+QString appendToPath(const QString& s);
+QString prependToPath(const QString& s);
QString setPath(const QString& s);
diff --git a/src/loglist.cpp b/src/loglist.cpp
index 7a64ecad..167b61ef 100644
--- a/src/loglist.cpp
+++ b/src/loglist.cpp
@@ -327,3 +327,29 @@ void initLogging()
qInstallMessageHandler(qtLogCallback);
}
+
+bool createAndMakeWritable(const std::wstring &subPath) {
+ QString const dataPath = qApp->property("dataPath").toString();
+ QString fullPath = dataPath + "/" + QString::fromStdWString(subPath);
+
+ if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("Failed to create \"%1\". Your user "
+ "account probably lacks permission.")
+ .arg(fullPath));
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool setLogDirectory(const QString& dir)
+{
+ const auto logFile = dir + "/logs/mo_interface.log";
+
+ if (!createAndMakeWritable(AppConfig::logPath())) {
+ return false;
+ }
+
+ log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString()));
+}
diff --git a/src/loglist.h b/src/loglist.h
index 7387eb50..0745ed3e 100644
--- a/src/loglist.h
+++ b/src/loglist.h
@@ -84,5 +84,6 @@ private:
void initLogging();
+bool setLogDirectory(const QString& dir);
#endif // LOGBUFFER_H
diff --git a/src/main.cpp b/src/main.cpp
index e52b252b..864d26a1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -38,7 +38,13 @@ along with Mod Organizer. If not, see .
#include
#include
-#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"")
+// see addDllsToPath() below
+#pragma comment(linker, "/manifestDependency:\"" \
+ "name='dlls' " \
+ "processorArchitecture='x86' " \
+ "version='1.0.0.0' " \
+ "type='win32' \"")
+
using namespace MOBase;
using namespace MOShared;
@@ -47,21 +53,6 @@ void sanityChecks(const env::Environment& env);
int checkIncompatibleModule(const env::Module& m);
int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s);
-bool createAndMakeWritable(const std::wstring &subPath) {
- QString const dataPath = qApp->property("dataPath").toString();
- QString fullPath = dataPath + "/" + QString::fromStdWString(subPath);
-
- if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) {
- QMessageBox::critical(nullptr, QObject::tr("Error"),
- QObject::tr("Failed to create \"%1\". Your user "
- "account probably lacks permission.")
- .arg(fullPath));
- return false;
- } else {
- return true;
- }
-}
-
void purgeOldFiles()
{
// remove the temporary backup directory in case we're restarting after an
@@ -305,36 +296,47 @@ MOBase::IPluginGame *determineCurrentGame(
}
-// extend path to include dll directory so plugins don't need a manifest
-// (using AddDllDirectory would be an alternative to this but it seems fairly
-// complicated esp.
-// since it isn't easily accessible on Windows < 8
-// SetDllDirectory replaces other search directories and this seems to
-// propagate to child processes)
-void setupPath()
+// This adds the `dlls` directory to the path so the dlls can be found. How
+// MO is able to find dlls in there is a bit convoluted:
+//
+// Dependencies on DLLs can be baked into an executable by passing a
+// `manifestdependency` option to the linker. This can be done on the command
+// line or with a pragma. Typically, the dependency will not be a hardcoded
+// filename, but an assembly name, such as Microsoft.Windows.Common-Controls.
+//
+// When Windows loads the exe, it will look for this assembly in a variety of
+// places, such as in the WinSxS folder, but also in the program's folder. It
+// will look for `assemblyname.dll` or `assemblyname/assemblyname.dll` and try
+// to load that.
+//
+// If these files don't exist, then the loader gets creative and looks for
+// `assemblyname.manifest` and `assemblyname/assemblyname.manifest`. A manifest
+// file is just an XML file that can contain a list of DLLs to load for this
+// assembly.
+//
+// In MO's case, there's a `pragma` at the beginning of this file which adds
+// `dlls` as an "assembly" dependency. This is a bit of a hack to just force
+// the loader to eventually find `dlls/dlls.manifest`, which contains the list
+// of all the DLLs MO requires to load.
+//
+// This file was handwritten in `modorganizer/src/dlls.manifest.qt5` and
+// is copied and renamed in CMakeLists.txt into `bin/dlls/dlls.manifest`. Note
+// that the useless and incorrect .qt5 extension is removed.
+//
+void addDllsToPath()
{
- static const int BUFSIZE = 4096;
-
- QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths());
+ const auto dllsPath = QDir::toNativeSeparators(
+ QCoreApplication::applicationDirPath() + "/dlls");
- boost::scoped_array oldPath(new TCHAR[BUFSIZE]);
- DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE);
- if (offset > BUFSIZE) {
- oldPath.reset(new TCHAR[offset]);
- ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset);
- }
-
- std::wstring newPath(ToWString(QDir::toNativeSeparators(
- QCoreApplication::applicationDirPath())) + L"\\dlls");
- newPath += L";";
- newPath += oldPath.get();
+ QCoreApplication::setLibraryPaths(
+ QStringList(dllsPath) + QCoreApplication::libraryPaths());
- ::SetEnvironmentVariableW(L"PATH", newPath.c_str());
+ env::prependToPath(dllsPath);
}
int runApplication(
MOApplication &application, const cl::CommandLine& cl,
- SingleInstance &instance, const QString &splashPath)
+ SingleInstance &instance, const QString &dataPath)
{
TimeThis tt("runApplication() to exec()");
@@ -343,7 +345,6 @@ int runApplication(
createVersionInfo().displayString(3), GITID,
QCoreApplication::applicationDirPath(), MOShared::getUsvfsVersionString());
- const QString dataPath = application.property("dataPath").toString();
log::info("data path: {}", dataPath);
if (InstanceManager::isPortablePath(dataPath)) {
@@ -429,8 +430,14 @@ int runApplication(
checkPathsForSanity(*game, settings);
bool useSplash = settings.useSplash();
+ QString splashPath;
if (useSplash) {
+ splashPath = dataPath + "/splash.png";
+ if (!QFile::exists(dataPath + "/splash.png")) {
+ splashPath = ":/MO/gui/splash";
+ }
+
if (splashPath.startsWith(':')) {
// currently using MO splash, see if the plugin contains one
QString pluginSplash
@@ -599,111 +606,101 @@ int runApplication(
return 1;
}
-int main(int argc, char *argv[])
+int forwardToPrimary(SingleInstance& instance, const cl::CommandLine& cl)
{
- cl::CommandLine cl;
-
- const auto r = cl.run(GetCommandLineW());
- if (r)
- return *r;
-
- TimeThis tt("main to runApplication()");
-
- // in loglist.cpp
- initLogging();
+ if (cl.shortcut().isValid()) {
+ instance.sendMessage(cl.shortcut().toString());
+ } else if (cl.nxmLink()) {
+ instance.sendMessage(*cl.nxmLink());
+ } else {
+ QMessageBox::information(
+ nullptr, QObject::tr("Mod Organizer"),
+ QObject::tr("An instance of Mod Organizer is already running"));
+ }
- //Make sure the configured temp folder exists
- QDir tempDir = QDir::temp();
- if (!tempDir.exists())
- tempDir.root().mkpath(tempDir.canonicalPath());
+ return 0;
+}
- //Should allow for better scaling of ui with higher resolution displays
- QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+void resetForRestart(cl::CommandLine& cl)
+{
+ LogModel::instance().clear();
+ ResetExitFlag();
- MOApplication application(argc, argv);
- QStringList arguments = application.arguments();
+ // make sure the log file isn't locked in case MO was restarted and
+ // the previous instance gets deleted
+ log::getDefault().setFile({});
- SetThisThreadName("main");
+ // don't reprocess command line
+ cl.clear();
+}
- setupPath();
+QString determineDataPath(const cl::CommandLine& cl)
+{
+ try
+ {
+ InstanceManager& instanceManager = InstanceManager::instance();
+ if (cl.instance())
+ instanceManager.overrideInstance(*cl.instance());
- SingleInstance::Flags siFlags = SingleInstance::NoFlags;
+ return instanceManager.determineDataPath();
+ }
+ catch (const std::exception &e)
+ {
+ if (strcmp(e.what(),"Canceled")) {
+ QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what());
+ }
- if (cl.multiple()) {
- arguments.removeAll("--multiple");
- siFlags |= SingleInstance::AllowMultiple;
+ return {};
}
+}
- SingleInstance instance(siFlags);
- if (instance.ephemeral()) {
- if (cl.shortcut().isValid()) {
- instance.sendMessage(cl.shortcut().toString());
- return 0;
- } else if (cl.nxmLink()) {
- instance.sendMessage(*cl.nxmLink());
- return 0;
- } else if (arguments.size() == 1) {
- QMessageBox::information(
- nullptr, QObject::tr("Mod Organizer"),
- QObject::tr("An instance of Mod Organizer is already running"));
- return 0;
- }
- } // we continue for the primary instance OR if MO was called with parameters
+int main(int argc, char *argv[])
+{
+ cl::CommandLine cl;
- do {
- LogModel::instance().clear();
- ResetExitFlag();
+ if (auto r=cl.run(GetCommandLineW())) {
+ return *r;
+ }
- // make sure the log file isn't locked in case MO was restarted and
- // the previous instance gets deleted
- log::getDefault().setFile({});
+ TimeThis tt("main to runApplication()");
+ SetThisThreadName("main");
- QString dataPath;
+ initLogging();
+ auto application = MOApplication::create(argc, argv);
+ addDllsToPath();
- try {
- InstanceManager& instanceManager = InstanceManager::instance();
+ SingleInstance instance(cl.multiple());
+ if (instance.ephemeral()) {
+ return forwardToPrimary(instance, cl);
+ }
- if (cl.instance())
- instanceManager.overrideInstance(*cl.instance());
+ for (;;)
+ {
+ // resets things when MO is "restarted"
+ resetForRestart(cl);
- dataPath = instanceManager.determineDataPath();
- } catch (const std::exception &e) {
- if (strcmp(e.what(),"Canceled"))
- QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what());
+ const QString dataPath = determineDataPath(cl);
+ if (dataPath.isEmpty()) {
return 1;
}
- application.setProperty("dataPath", dataPath);
-
- // initialize dump collection only after "dataPath" since the crashes are stored under it
- setUnhandledExceptionHandler();
- const auto logFile =
- qApp->property("dataPath").toString() + "/logs/mo_interface.log";
+ application.setProperty("dataPath", dataPath);
+ setExceptionHandler();
- if (!createAndMakeWritable(AppConfig::logPath())) {
+ if (!setLogDirectory(dataPath)) {
reportError("Failed to create log folder");
InstanceManager::instance().clearCurrentInstance();
return 1;
}
- log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString()));
-
log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW()));
- QString splash = dataPath + "/splash.png";
- if (!QFile::exists(dataPath + "/splash.png")) {
- splash = ":/MO/gui/splash";
- }
-
tt.stop();
- const int result = runApplication(application, cl, instance, splash);
+ const int result = runApplication(application, cl, instance, dataPath);
if (result != RestartExitCode) {
return result;
}
-
- argc = 1;
- cl.clear();
- } while (true);
+ }
}
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index dd49bf53..d95d544a 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -24,16 +24,10 @@ along with Mod Organizer. If not, see .
#include "shared/appconfig.h"
#include
#include
-#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
-#include
-#include
-#endif
#include
#include
#include
#include
-
-
#include
@@ -77,7 +71,7 @@ public:
};
-MOApplication::MOApplication(int &argc, char **argv)
+MOApplication::MOApplication(int argc, char** argv)
: QApplication(argc, argv)
{
connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){
@@ -89,8 +83,13 @@ MOApplication::MOApplication(int &argc, char **argv)
setStyle(new ProxyStyle(style()));
}
+MOApplication MOApplication::create(int argc, char** argv)
+{
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ return MOApplication(argc, argv);
+}
-bool MOApplication::setStyleFile(const QString &styleName)
+bool MOApplication::setStyleFile(const QString& styleName)
{
// remove all files from watch
QStringList currentWatch = m_StyleWatcher.files();
@@ -114,7 +113,7 @@ bool MOApplication::setStyleFile(const QString &styleName)
}
-bool MOApplication::notify(QObject *receiver, QEvent *event)
+bool MOApplication::notify(QObject* receiver, QEvent* event)
{
try {
return QApplication::notify(receiver, event);
@@ -134,7 +133,7 @@ bool MOApplication::notify(QObject *receiver, QEvent *event)
}
-void MOApplication::updateStyle(const QString &fileName)
+void MOApplication::updateStyle(const QString& fileName)
{
if (QStyleFactory::keys().contains(fileName)) {
setStyle(QStyleFactory::create(fileName));
diff --git a/src/moapplication.h b/src/moapplication.h
index 9db130af..3ed71fb6 100644
--- a/src/moapplication.h
+++ b/src/moapplication.h
@@ -24,27 +24,25 @@ along with Mod Organizer. If not, see .
#include
-class MOApplication : public QApplication {
-Q_OBJECT
-public:
-
- MOApplication(int &argc, char **argv);
+class MOApplication : public QApplication
+{
+ Q_OBJECT
- virtual bool notify (QObject *receiver, QEvent *event);
+public:
+ static MOApplication create(int argc, char** argv);
+ virtual bool notify (QObject* receiver, QEvent* event);
public slots:
-
- bool setStyleFile(const QString &style);
+ bool setStyleFile(const QString& style);
private slots:
-
- void updateStyle(const QString &fileName);
+ void updateStyle(const QString& fileName);
private:
-
QFileSystemWatcher m_StyleWatcher;
QString m_DefaultStyle;
+ MOApplication(int argc, char** argv);
};
diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp
index e32c8de1..bd7ccc43 100644
--- a/src/singleinstance.cpp
+++ b/src/singleinstance.cpp
@@ -28,14 +28,14 @@ static const int s_Timeout = 5000;
using MOBase::reportError;
-SingleInstance::SingleInstance(Flags flags, QObject *parent) :
+SingleInstance::SingleInstance(bool allowMultiple, QObject *parent) :
QObject(parent), m_Ephemeral(false), m_OwnsSM(false)
{
m_SharedMem.setKey(s_Key);
if (!m_SharedMem.create(1)) {
if (m_SharedMem.error() == QSharedMemory::AlreadyExists) {
- if (!flags.testFlag(AllowMultiple)) {
+ if (!allowMultiple) {
m_SharedMem.attach();
m_Ephemeral = true;
}
diff --git a/src/singleinstance.h b/src/singleinstance.h
index d500a056..5f6c3633 100644
--- a/src/singleinstance.h
+++ b/src/singleinstance.h
@@ -36,19 +36,9 @@ class SingleInstance : public QObject
Q_OBJECT
public:
- enum Flag
- {
- NoFlags = 0x00,
-
- // if another instance is running, run this one disconnected from the
- // shared memory
- AllowMultiple = 0x01
- };
-
- using Flags = QFlags;
-
-
- explicit SingleInstance(Flags flags, QObject *parent = 0);
+ // `allowMultiple`: if another instance is running, run this one
+ // disconnected from the shared memory
+ explicit SingleInstance(bool allowMultiple, QObject *parent = 0);
/**
* @return true if this instance's job is to forward data to the primary
@@ -97,6 +87,4 @@ private:
};
-Q_DECLARE_OPERATORS_FOR_FLAGS(SingleInstance::Flags);
-
#endif // SINGLEINSTANCE_H
diff --git a/src/spawn.cpp b/src/spawn.cpp
index 2016bb23..a9ecb61e 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -488,7 +488,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle)
}
const QString moPath = QCoreApplication::applicationDirPath();
- const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath));
+ const auto oldPath = env::appendToPath(QDir::toNativeSeparators(moPath));
PROCESS_INFORMATION pi = {};
BOOL success = FALSE;
--
cgit v1.3.1
From ae118153fbedc0240ea183488834a32abe202839 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 00:38:03 -0400
Subject: more refactoring: - moved splash stuff together - moved stuff in
the main loop into doOneRun()
---
src/loglist.cpp | 2 +
src/main.cpp | 213 ++++++++++++++++++++++++++++----------------------
src/moapplication.cpp | 4 +-
src/moapplication.h | 4 +-
src/organizercore.cpp | 44 +++++++++--
src/settings.cpp | 2 +-
src/settings.h | 2 +-
7 files changed, 164 insertions(+), 107 deletions(-)
(limited to 'src')
diff --git a/src/loglist.cpp b/src/loglist.cpp
index 167b61ef..fad4678c 100644
--- a/src/loglist.cpp
+++ b/src/loglist.cpp
@@ -352,4 +352,6 @@ bool setLogDirectory(const QString& dir)
}
log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString()));
+
+ return true;
}
diff --git a/src/main.cpp b/src/main.cpp
index 864d26a1..52011ef4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -68,26 +68,31 @@ void purgeOldFiles()
"usvfs*.log", 5, QDir::Name);
}
-thread_local LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
-thread_local std::terminate_handler prevTerminateHandler = nullptr;
+thread_local LPTOP_LEVEL_EXCEPTION_FILTER g_prevExceptionFilter = nullptr;
+thread_local std::terminate_handler g_prevTerminateHandler = nullptr;
-LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs)
+LONG WINAPI onUnhandledException(_EXCEPTION_POINTERS* ptrs)
{
const std::wstring& dumpPath = OrganizerCore::crashDumpsPath();
- int dumpRes =
- CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str());
- if (!dumpRes)
+
+ const int r = CreateMiniDump(
+ ptrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str());
+
+ if (r == 0) {
log::error("ModOrganizer has crashed, crash dump created.");
- else
- log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError());
+ } else {
+ log::error(
+ "ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).",
+ r, GetLastError());
+ }
- if (prevUnhandledExceptionFilter && exceptionPtrs)
- return prevUnhandledExceptionFilter(exceptionPtrs);
+ if (g_prevExceptionFilter && ptrs)
+ return g_prevExceptionFilter(ptrs);
else
return EXCEPTION_CONTINUE_SEARCH;
}
-void terminateHandler() noexcept
+void onTerminate() noexcept
{
__try
{
@@ -96,22 +101,22 @@ void terminateHandler() noexcept
}
__except
(
- MyUnhandledExceptionFilter(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER
+ onUnhandledException(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER
)
{
}
- if (prevTerminateHandler) {
- prevTerminateHandler();
+ if (g_prevTerminateHandler) {
+ g_prevTerminateHandler();
} else {
std::abort();
}
}
-void setUnhandledExceptionHandler()
+void setExceptionHandlers()
{
- prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
- prevTerminateHandler = std::set_terminate(terminateHandler);
+ g_prevExceptionFilter = SetUnhandledExceptionFilter(onUnhandledException);
+ g_prevTerminateHandler = std::set_terminate(onTerminate);
}
QString determineProfile(const cl::CommandLine& cl, const Settings &settings)
@@ -334,6 +339,55 @@ void addDllsToPath()
env::prependToPath(dllsPath);
}
+QString getSplashPath(
+ const Settings& settings, const QString& dataPath,
+ const MOBase::IPluginGame* game)
+{
+ if (!settings.useSplash()) {
+ return {};
+ }
+
+ const QString splashPath = dataPath + "/splash.png";
+ if (QFile::exists(dataPath + "/splash.png")) {
+ return splashPath;
+ }
+
+ // currently using MO splash, see if the plugin contains one
+ QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName());
+ QImage image(pluginSplash);
+
+ if (image.isNull()) {
+ return {};
+ }
+
+ image.save(splashPath);
+ return splashPath;
+}
+
+std::unique_ptr createSplash(
+ const Settings& settings, const QString& dataPath,
+ const MOBase::IPluginGame* game)
+{
+ const auto splashPath = getSplashPath(settings, dataPath, game);
+ if (splashPath.isEmpty()) {
+ return {};
+ }
+
+ QPixmap image(splashPath);
+ if (image.isNull()) {
+ log::error("failed to load splash from {}", splashPath);
+ return {};
+ }
+
+ auto splash = std::make_unique(image);
+ settings.geometry().centerOnMainWindowMonitor(splash.get());
+
+ splash->show();
+ splash->activateWindow();
+
+ return splash;
+}
+
int runApplication(
MOApplication &application, const cl::CommandLine& cl,
SingleInstance &instance, const QString &dataPath)
@@ -351,6 +405,8 @@ int runApplication(
log::debug("this is a portable instance");
}
+ log::info("working directory: {}", QDir::currentPath());
+
if (!instance.secondary()) {
purgeOldFiles();
}
@@ -358,9 +414,8 @@ int runApplication(
QWindowsWindowFunctions::setWindowActivationBehavior(
QWindowsWindowFunctions::AlwaysActivateWindow);
- try {
- log::info("working directory: {}", QDir::currentPath());
-
+ try
+ {
Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()));
log::getDefault().setLevel(settings.diagnostics().logLevel());
@@ -370,7 +425,6 @@ int runApplication(
log::debug("another instance of MO is running but --multiple was given");
}
-
// global crashDumpType sits in OrganizerCore to make a bit less ugly to
// update it when the settings are changed during runtime
OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType());
@@ -385,8 +439,10 @@ int runApplication(
checkIncompatibleModule(m);
});
- log::debug("initializing core");
+ // this must outlive `organizer`
std::unique_ptr pluginContainer;
+
+ log::debug("initializing core");
OrganizerCore organizer(settings);
if (!organizer.bootstrap()) {
reportError("failed to set up data paths");
@@ -394,25 +450,11 @@ int runApplication(
return 1;
}
- {
- // log if there are any dmp files
- const auto hasCrashDumps =
- !QDir(QString::fromStdWString(organizer.crashDumpsPath()))
- .entryList({"*.dmp"}, QDir::Files)
- .empty();
-
- if (hasCrashDumps) {
- log::debug(
- "there are crash dumps in '{}'",
- QString::fromStdWString(organizer.crashDumpsPath()));
- }
- }
-
log::debug("initializing plugins");
pluginContainer = std::make_unique(&organizer);
pluginContainer->loadPlugins();
- MOBase::IPluginGame *game = determineCurrentGame(
+ MOBase::IPluginGame* game = determineCurrentGame(
application.applicationDirPath(), settings, *pluginContainer);
if (game == nullptr) {
@@ -429,25 +471,6 @@ int runApplication(
checkPathsForSanity(*game, settings);
- bool useSplash = settings.useSplash();
- QString splashPath;
-
- if (useSplash) {
- splashPath = dataPath + "/splash.png";
- if (!QFile::exists(dataPath + "/splash.png")) {
- splashPath = ":/MO/gui/splash";
- }
-
- if (splashPath.startsWith(':')) {
- // currently using MO splash, see if the plugin contains one
- QString pluginSplash
- = QString(":/%1/splash").arg(game->gameShortName());
- QImage image(pluginSplash);
- if (!image.isNull()) {
- image.save(dataPath + "/splash.png");
- }
- }
- }
organizer.setManagedGame(game);
organizer.createDefaultProfile();
@@ -535,18 +558,7 @@ int runApplication(
}
}
- QPixmap pixmap;
-
- QSplashScreen splash;
-
- if (useSplash) {
- pixmap = QPixmap(splashPath);
- splash.setPixmap(pixmap);
-
- settings.geometry().centerOnMainWindowMonitor(&splash);
- splash.show();
- splash.activateWindow();
- }
+ auto splash = createSplash(settings, dataPath, game);
QString apiKey;
if (settings.nexus().apiKey(apiKey)) {
@@ -582,10 +594,10 @@ int runApplication(
mainWindow.show();
mainWindow.activateWindow();
- if (useSplash) {
+ if (splash) {
// don't pass mainwindow as it just waits half a second for it
// instead of proceding
- splash.finish(nullptr);
+ splash->finish(nullptr);
}
tt.stop();
@@ -655,6 +667,35 @@ QString determineDataPath(const cl::CommandLine& cl)
}
}
+int doOneRun(
+ cl::CommandLine& cl, MOApplication& application, SingleInstance& instance)
+{
+ TimeThis tt("doOneRun() to runApplication()");
+
+ // resets things when MO is "restarted"
+ resetForRestart(cl);
+
+ const QString dataPath = determineDataPath(cl);
+ if (dataPath.isEmpty()) {
+ return 1;
+ }
+
+ application.setProperty("dataPath", dataPath);
+ setExceptionHandlers();
+
+ if (!setLogDirectory(dataPath)) {
+ reportError("Failed to create log folder");
+ InstanceManager::instance().clearCurrentInstance();
+ return 1;
+ }
+
+ log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW()));
+
+ tt.stop();
+
+ return runApplication(application, cl, instance, dataPath);
+}
+
int main(int argc, char *argv[])
{
cl::CommandLine cl;
@@ -663,7 +704,8 @@ int main(int argc, char *argv[])
return *r;
}
- TimeThis tt("main to runApplication()");
+ TimeThis tt("main() to doOneRun()");
+
SetThisThreadName("main");
initLogging();
@@ -675,32 +717,15 @@ int main(int argc, char *argv[])
return forwardToPrimary(instance, cl);
}
+ tt.stop();
+
for (;;)
{
- // resets things when MO is "restarted"
- resetForRestart(cl);
-
- const QString dataPath = determineDataPath(cl);
- if (dataPath.isEmpty()) {
- return 1;
- }
-
- application.setProperty("dataPath", dataPath);
- setExceptionHandler();
-
- if (!setLogDirectory(dataPath)) {
- reportError("Failed to create log folder");
- InstanceManager::instance().clearCurrentInstance();
- return 1;
+ const auto r = doOneRun(cl, application, instance);
+ if (r == RestartExitCode) {
+ continue;
}
- log::debug("command line: '{}'", QString::fromWCharArray(GetCommandLineW()));
-
- tt.stop();
-
- const int result = runApplication(application, cl, instance, dataPath);
- if (result != RestartExitCode) {
- return result;
- }
+ return r;
}
}
diff --git a/src/moapplication.cpp b/src/moapplication.cpp
index d95d544a..290666af 100644
--- a/src/moapplication.cpp
+++ b/src/moapplication.cpp
@@ -71,7 +71,7 @@ public:
};
-MOApplication::MOApplication(int argc, char** argv)
+MOApplication::MOApplication(int& argc, char** argv)
: QApplication(argc, argv)
{
connect(&m_StyleWatcher, &QFileSystemWatcher::fileChanged, [&](auto&& file){
@@ -83,7 +83,7 @@ MOApplication::MOApplication(int argc, char** argv)
setStyle(new ProxyStyle(style()));
}
-MOApplication MOApplication::create(int argc, char** argv)
+MOApplication MOApplication::create(int& argc, char** argv)
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
return MOApplication(argc, argv);
diff --git a/src/moapplication.h b/src/moapplication.h
index 3ed71fb6..c67c4622 100644
--- a/src/moapplication.h
+++ b/src/moapplication.h
@@ -29,7 +29,7 @@ class MOApplication : public QApplication
Q_OBJECT
public:
- static MOApplication create(int argc, char** argv);
+ static MOApplication create(int& argc, char** argv);
virtual bool notify (QObject* receiver, QEvent* event);
public slots:
@@ -42,7 +42,7 @@ private:
QFileSystemWatcher m_StyleWatcher;
QString m_DefaultStyle;
- MOApplication(int argc, char** argv);
+ MOApplication(int& argc, char** argv);
};
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index a9469e6e..0849d756 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -473,13 +473,43 @@ bool OrganizerCore::checkPathSymlinks() {
return true;
}
-bool OrganizerCore::bootstrap() {
- return createDirectory(m_Settings.paths().profiles()) &&
- createDirectory(m_Settings.paths().mods()) &&
- createDirectory(m_Settings.paths().downloads()) &&
- createDirectory(m_Settings.paths().overwrite()) &&
- createDirectory(QString::fromStdWString(crashDumpsPath())) &&
- checkPathSymlinks() && cycleDiagnostics();
+bool OrganizerCore::bootstrap()
+{
+ const auto dirs = {
+ m_Settings.paths().profiles(),
+ m_Settings.paths().mods(),
+ m_Settings.paths().downloads(),
+ m_Settings.paths().overwrite(),
+ QString::fromStdWString(crashDumpsPath())
+ };
+
+ for (auto&& dir : dirs) {
+ if (!createDirectory(dir)) {
+ return false;
+ }
+ }
+
+ if (!checkPathSymlinks()) {
+ return false;
+ }
+
+ if (!cycleDiagnostics()) {
+ return false;
+ }
+
+ // log if there are any dmp files
+ const auto hasCrashDumps =
+ !QDir(QString::fromStdWString(crashDumpsPath()))
+ .entryList({"*.dmp"}, QDir::Files)
+ .empty();
+
+ if (hasCrashDumps) {
+ log::debug(
+ "there are crash dumps in '{}'",
+ QString::fromStdWString(crashDumpsPath()));
+ }
+
+ return true;
}
void OrganizerCore::createDefaultProfile()
diff --git a/src/settings.cpp b/src/settings.cpp
index 534e67c8..d37f0d99 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -876,7 +876,7 @@ void GeometrySettings::setCenterDialogs(bool b)
set(m_Settings, "Settings", "center_dialogs", b);
}
-void GeometrySettings::centerOnMainWindowMonitor(QWidget* w)
+void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) const
{
const auto monitor = getOptional(
m_Settings, "Geometry", "MainWindow_monitor").value_or(-1);
diff --git a/src/settings.h b/src/settings.h
index 636719bb..a1b30462 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -169,7 +169,7 @@ public:
// assumes the given widget is a top-level
//
- void centerOnMainWindowMonitor(QWidget* w);
+ void centerOnMainWindowMonitor(QWidget* w) const;
// saves the monitor number of the given window
//
--
cgit v1.3.1
From 2c915429d84a9df0e9c512db248fb5c8ec6663f7 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 01:16:47 -0400
Subject: refactored stuff into determineGameEdition() and handleCommandLine()
---
src/main.cpp | 171 ++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 100 insertions(+), 71 deletions(-)
(limited to 'src')
diff --git a/src/main.cpp b/src/main.cpp
index 52011ef4..49f3c6c0 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -388,6 +388,94 @@ std::unique_ptr createSplash(
return splash;
}
+bool determineGameEdition(Settings& settings, IPluginGame* game)
+{
+ QString edition;
+
+ if (auto v=settings.game().edition()) {
+ edition = *v;
+ } else {
+ QStringList editions = game->gameVariants();
+ if (editions.size() < 2) {
+ edition = "";
+ return true;
+ }
+
+ SelectionDialog selection(
+ QObject::tr("Please select the game edition you have (MO can't "
+ "start the game correctly if this is set "
+ "incorrectly!)"),
+ nullptr);
+
+ selection.setWindowFlag(Qt::WindowStaysOnTopHint, true);
+
+ int index = 0;
+ for (const QString &edition : editions) {
+ selection.addChoice(edition, "", index++);
+ }
+
+ if (selection.exec() == QDialog::Rejected) {
+ return false;
+ }
+
+ edition = selection.getChoiceString();
+ settings.game().setEdition(edition);
+ }
+
+ game->setGameVariant(edition);
+
+ return true;
+}
+
+std::optional handleCommandLine(
+ const cl::CommandLine& cl, OrganizerCore& organizer)
+{
+ // if we have a command line parameter, it is either a nxm link or
+ // a binary to start
+ if (cl.shortcut().isValid()) {
+ if (cl.shortcut().hasExecutable()) {
+ try {
+ organizer.processRunner()
+ .setFromShortcut(cl.shortcut())
+ .setWaitForCompletion()
+ .run();
+
+ return 0;
+ }
+ catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start shortcut: %1").arg(e.what()));
+ return 1;
+ }
+ }
+ } else if (cl.nxmLink()) {
+ log::debug("starting download from command line: {}", *cl.nxmLink());
+ organizer.externalMessage(*cl.nxmLink());
+ } else if (cl.executable()) {
+ const QString exeName = *cl.executable();
+ log::debug("starting {} from command line", exeName);
+
+ try
+ {
+ // pass the remaining parameters to the binary
+ organizer.processRunner()
+ .setFromFileOrExecutable(exeName, cl.untouched())
+ .setWaitForCompletion()
+ .run();
+
+ return 0;
+ }
+ catch (const std::exception &e)
+ {
+ reportError(
+ QObject::tr("failed to start application: %1").arg(e.what()));
+ return 1;
+ }
+ }
+
+ return {};
+}
+
int runApplication(
MOApplication &application, const cl::CommandLine& cl,
SingleInstance &instance, const QString &dataPath)
@@ -475,38 +563,16 @@ int runApplication(
organizer.setManagedGame(game);
organizer.createDefaultProfile();
- QString edition;
-
- if (auto v=settings.game().edition()) {
- edition = *v;
- } else {
- QStringList editions = game->gameVariants();
- if (editions.size() > 1) {
- SelectionDialog selection(
- QObject::tr("Please select the game edition you have (MO can't "
- "start the game correctly if this is set "
- "incorrectly!)"),
- nullptr);
- selection.setWindowFlag(Qt::WindowStaysOnTopHint, true);
- int index = 0;
- for (const QString &edition : editions) {
- selection.addChoice(edition, "", index++);
- }
- if (selection.exec() == QDialog::Rejected) {
- return 1;
- } else {
- edition = selection.getChoiceString();
- settings.game().setEdition(edition);
- }
- }
+ if (!determineGameEdition(settings, game)) {
+ return 1;
}
- game->setGameVariant(edition);
-
log::info(
- "using game plugin '{}' ('{}', steam id '{}') at {}",
- game->gameName(), game->gameShortName(), game->steamAPPId(),
- game->gameDirectory().absolutePath());
+ "using game plugin '{}' ('{}', variant {}, steam id '{}') at {}",
+ game->gameName(), game->gameShortName(),
+ (settings.game().edition().value_or("").isEmpty() ?
+ "(none)" : *settings.game().edition()),
+ game->steamAPPId(), game->gameDirectory().absolutePath());
CategoryFactory::instance().loadCategories();
organizer.updateExecutablesList();
@@ -515,47 +581,8 @@ int runApplication(
QString selectedProfileName = determineProfile(cl, settings);
organizer.setCurrentProfile(selectedProfileName);
- // if we have a command line parameter, it is either a nxm link or
- // a binary to start
- if (cl.shortcut().isValid()) {
- if (cl.shortcut().hasExecutable()) {
- try {
- organizer.processRunner()
- .setFromShortcut(cl.shortcut())
- .setWaitForCompletion()
- .run();
-
- return 0;
- }
- catch (const std::exception &e) {
- reportError(
- QObject::tr("failed to start shortcut: %1").arg(e.what()));
- return 1;
- }
- }
- } else if (cl.nxmLink()) {
- log::debug("starting download from command line: {}", *cl.nxmLink());
- organizer.externalMessage(*cl.nxmLink());
- } else if (cl.executable()) {
- const QString exeName = *cl.executable();
- log::debug("starting {} from command line", exeName);
-
- try
- {
- // pass the remaining parameters to the binary
- organizer.processRunner()
- .setFromFileOrExecutable(exeName, cl.untouched())
- .setWaitForCompletion()
- .run();
-
- return 0;
- }
- catch (const std::exception &e)
- {
- reportError(
- QObject::tr("failed to start application: %1").arg(e.what()));
- return 1;
- }
+ if (auto r=handleCommandLine(cl, organizer)) {
+ return *r;
}
auto splash = createSplash(settings, dataPath, game);
@@ -611,7 +638,9 @@ int runApplication(
settings.geometry().resetIfNeeded();
return res;
- } catch (const std::exception &e) {
+ }
+ catch (const std::exception &e)
+ {
reportError(e.what());
}
--
cgit v1.3.1
From 5b13704275fc3cf02f6c3f4d4bd77d32425ad2c9 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 01:32:53 -0400
Subject: moved profile, current game and game edition checks to
InstanceManager trying to centralize all of this stuff
---
src/instancemanager.cpp | 232 ++++++++++++++++++++++++++++++++++++++++++++++-
src/instancemanager.h | 18 +++-
src/main.cpp | 234 +++---------------------------------------------
3 files changed, 255 insertions(+), 229 deletions(-)
(limited to 'src')
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 6c7f797e..9bcd0f3c 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -20,9 +20,14 @@ along with Mod Organizer. If not, see .
#include "instancemanager.h"
#include "selectiondialog.h"
+#include "settings.h"
+#include "shared/appconfig.h"
+#include "plugincontainer.h"
+#include
+#include
#include
#include
-#include "shared/appconfig.h"
+
#include
#include
#include
@@ -54,6 +59,11 @@ void InstanceManager::overrideInstance(const QString& instanceName)
m_overrideInstance = true;
}
+void InstanceManager::overrideProfile(const QString& profileName)
+{
+ m_overrideProfileName = profileName;
+ m_overrideProfile = true;
+}
QString InstanceManager::currentInstance() const
{
@@ -357,6 +367,226 @@ QString InstanceManager::determineDataPath()
}
}
+QString InstanceManager::determineProfile(const Settings &settings)
+{
+ auto selectedProfileName = settings.game().selectedProfileName();
+
+ if (m_overrideProfile) {
+ log::debug("profile overwritten on command line");
+ selectedProfileName = m_overrideProfileName;
+ }
+
+ if (!selectedProfileName) {
+ log::debug("no configured profile");
+ selectedProfileName = "Default";
+ }
+
+ return *selectedProfileName;
+}
+
+bool InstanceManager::determineGameEdition(
+ Settings& settings, IPluginGame* game)
+{
+ QString edition;
+
+ if (auto v=settings.game().edition()) {
+ edition = *v;
+ } else {
+ QStringList editions = game->gameVariants();
+ if (editions.size() < 2) {
+ edition = "";
+ return true;
+ }
+
+ SelectionDialog selection(
+ QObject::tr("Please select the game edition you have (MO can't "
+ "start the game correctly if this is set "
+ "incorrectly!)"),
+ nullptr);
+
+ selection.setWindowFlag(Qt::WindowStaysOnTopHint, true);
+
+ int index = 0;
+ for (const QString &edition : editions) {
+ selection.addChoice(edition, "", index++);
+ }
+
+ if (selection.exec() == QDialog::Rejected) {
+ return false;
+ }
+
+ edition = selection.getChoiceString();
+ settings.game().setEdition(edition);
+ }
+
+ game->setGameVariant(edition);
+
+ return true;
+}
+
+MOBase::IPluginGame *selectGame(
+ Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game)
+{
+ settings.game().setName(game->gameName());
+
+ QString gameDir = gamePath.absolutePath();
+ game->setGamePath(gameDir);
+
+ settings.game().setDirectory(gameDir);
+
+ return game;
+}
+
+MOBase::IPluginGame* InstanceManager::determineCurrentGame(
+ const QString& moPath, Settings& settings, const PluginContainer &plugins)
+{
+ //Determine what game we are running where. Be very paranoid in case the
+ //user has done something odd.
+
+ //If the game name has been set up, try to use that.
+ const auto gameName = settings.game().name();
+ const bool gameConfigured = (gameName.has_value() && *gameName != "");
+
+ if (gameConfigured) {
+ MOBase::IPluginGame *game = plugins.managedGame(*gameName);
+ if (game == nullptr) {
+ reportError(
+ QObject::tr("Plugin to handle %1 no longer installed. An antivirus might have deleted files.")
+ .arg(*gameName));
+
+ return nullptr;
+ }
+
+ auto gamePath = settings.game().directory();
+ if (!gamePath || *gamePath == "") {
+ gamePath = game->gameDirectory().absolutePath();
+ }
+
+ QDir gameDir(*gamePath);
+ QFileInfo directoryInfo(gameDir.path());
+
+ if (directoryInfo.isSymLink()) {
+ reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. "
+ "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath));
+ }
+
+ if (game->looksValid(gameDir)) {
+ return selectGame(settings, gameDir, game);
+ }
+ }
+
+ //If we've made it this far and the instance is already configured for a game, something has gone wrong.
+ //Tell the user about it.
+ if (gameConfigured) {
+ const auto gamePath = settings.game().directory();
+
+ reportError(
+ QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".")
+ .arg(*gameName).arg(gamePath ? *gamePath : ""));
+ }
+
+ SelectionDialog selection(gameConfigured ?
+ QObject::tr("Please select the installation of %1 to manage").arg(*gameName) :
+ QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32));
+
+ for (IPluginGame *game : plugins.plugins()) {
+ //If a game is already configured, skip any plugins that are not for that game
+ if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0)
+ continue;
+
+ //Only add games that are installed
+ if (game->isInstalled()) {
+ QString path = game->gameDirectory().absolutePath();
+ selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game));
+ }
+ }
+
+ selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr)));
+
+ while (selection.exec() != QDialog::Rejected) {
+ IPluginGame * game = selection.getChoiceData().value();
+ QString gamePath = selection.getChoiceDescription();
+ QFileInfo directoryInfo(gamePath);
+ if (directoryInfo.isSymLink()) {
+ reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. "
+ "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath));
+ }
+ if (game != nullptr) {
+ return selectGame(settings, game->gameDirectory(), game);
+ }
+
+ gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ?
+ QObject::tr("Please select the installation of %1 to manage").arg(*gameName) :
+ QObject::tr("Please select the game to manage"),
+ QString(), QFileDialog::ShowDirsOnly);
+
+ if (!gamePath.isEmpty()) {
+ QDir gameDir(gamePath);
+ QFileInfo directoryInfo(gamePath);
+ if (directoryInfo.isSymLink()) {
+ reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. "
+ "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath));
+ }
+ QList possibleGames;
+ for (IPluginGame * const game : plugins.plugins()) {
+ //If a game is already configured, skip any plugins that are not for that game
+ if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0)
+ continue;
+
+ //Only try plugins that look valid for this directory
+ if (game->looksValid(gameDir)) {
+ possibleGames.append(game);
+ }
+ }
+
+ if (possibleGames.count() > 1) {
+ SelectionDialog browseSelection(gameConfigured ?
+ QObject::tr("Please select the installation of %1 to manage").arg(*gameName) :
+ QObject::tr("Please select the game to manage"),
+ nullptr, QSize(32, 32));
+
+ for (IPluginGame *game : possibleGames) {
+ browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game));
+ }
+
+ if (browseSelection.exec() == QDialog::Accepted) {
+ return selectGame(settings, gameDir, browseSelection.getChoiceData().value());
+ } else {
+ reportError(gameConfigured ?
+ QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) :
+ QObject::tr("Canceled finding game in \"%1\".").arg(gamePath));
+ }
+ } else if(possibleGames.count() == 1) {
+ return selectGame(settings, gameDir, possibleGames[0]);
+ } else {
+ if (gameConfigured) {
+ reportError(
+ QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.")
+ .arg(*gameName).arg(gamePath));
+ } else {
+ QString supportedGames;
+
+ for (IPluginGame * const game : plugins.plugins()) {
+ supportedGames += "
" + game->gameName() + "
";
+ }
+
+ QString text = QObject::tr(
+ "No game identified in \"%1\". The directory is required to "
+ "contain the game binary.
"
+ "These are the games supported by Mod Organizer:"
+ "
%2
")
+ .arg(gamePath)
+ .arg(supportedGames);
+
+ reportError(text);
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
QString InstanceManager::sanitizeInstanceName(const QString &name) const
{
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 649c2195..2331ebfd 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -24,18 +24,26 @@ along with Mod Organizer. If not, see .
#include
#include
+namespace MOBase { class IPluginGame; }
+
+class Settings;
+class PluginContainer;
class InstanceManager {
public:
-
static InstanceManager &instance();
- QString determineDataPath();
- void clearCurrentInstance();
-
void overrideInstance(const QString& instanceName);
+ void overrideProfile(const QString& profileName);
+
+ QString determineDataPath();
+ QString determineProfile(const Settings &settings);
+ bool determineGameEdition(Settings& settings, MOBase::IPluginGame* game);
+ MOBase::IPluginGame* determineCurrentGame(
+ const QString& moPath, Settings& settings, const PluginContainer &plugins);
+ void clearCurrentInstance();
QString currentInstance() const;
bool allowedToChangeInstance() const;
@@ -69,4 +77,6 @@ private:
bool m_Reset {false};
bool m_overrideInstance{false};
QString m_overrideInstanceName;
+ bool m_overrideProfile{false};
+ QString m_overrideProfileName;
};
diff --git a/src/main.cpp b/src/main.cpp
index 49f3c6c0..5479d5a3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -119,188 +119,6 @@ void setExceptionHandlers()
g_prevTerminateHandler = std::set_terminate(onTerminate);
}
-QString determineProfile(const cl::CommandLine& cl, const Settings &settings)
-{
- auto selectedProfileName = settings.game().selectedProfileName();
-
- if (cl.profile()) {
- log::debug("profile overwritten on command line");
- selectedProfileName = *cl.profile();
- }
-
- if (!selectedProfileName) {
- log::debug("no configured profile");
- selectedProfileName = "Default";
- }
-
- return *selectedProfileName;
-}
-
-MOBase::IPluginGame *selectGame(
- Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game)
-{
- settings.game().setName(game->gameName());
-
- QString gameDir = gamePath.absolutePath();
- game->setGamePath(gameDir);
-
- settings.game().setDirectory(gameDir);
-
- return game;
-}
-
-
-MOBase::IPluginGame *determineCurrentGame(
- QString const &moPath, Settings &settings, PluginContainer const &plugins)
-{
- //Determine what game we are running where. Be very paranoid in case the
- //user has done something odd.
-
- //If the game name has been set up, try to use that.
- const auto gameName = settings.game().name();
- const bool gameConfigured = (gameName.has_value() && *gameName != "");
-
- if (gameConfigured) {
- MOBase::IPluginGame *game = plugins.managedGame(*gameName);
- if (game == nullptr) {
- reportError(
- QObject::tr("Plugin to handle %1 no longer installed. An antivirus might have deleted files.")
- .arg(*gameName));
-
- return nullptr;
- }
-
- auto gamePath = settings.game().directory();
- if (!gamePath || *gamePath == "") {
- gamePath = game->gameDirectory().absolutePath();
- }
-
- QDir gameDir(*gamePath);
- QFileInfo directoryInfo(gameDir.path());
-
- if (directoryInfo.isSymLink()) {
- reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. "
- "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath));
- }
-
- if (game->looksValid(gameDir)) {
- return selectGame(settings, gameDir, game);
- }
- }
-
- //If we've made it this far and the instance is already configured for a game, something has gone wrong.
- //Tell the user about it.
- if (gameConfigured) {
- const auto gamePath = settings.game().directory();
-
- reportError(
- QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".")
- .arg(*gameName).arg(gamePath ? *gamePath : ""));
- }
-
- SelectionDialog selection(gameConfigured ?
- QObject::tr("Please select the installation of %1 to manage").arg(*gameName) :
- QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32));
-
- for (IPluginGame *game : plugins.plugins()) {
- //If a game is already configured, skip any plugins that are not for that game
- if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0)
- continue;
-
- //Only add games that are installed
- if (game->isInstalled()) {
- QString path = game->gameDirectory().absolutePath();
- selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game));
- }
- }
-
- selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr)));
-
- while (selection.exec() != QDialog::Rejected) {
- IPluginGame * game = selection.getChoiceData().value();
- QString gamePath = selection.getChoiceDescription();
- QFileInfo directoryInfo(gamePath);
- if (directoryInfo.isSymLink()) {
- reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. "
- "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath));
- }
- if (game != nullptr) {
- return selectGame(settings, game->gameDirectory(), game);
- }
-
- gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ?
- QObject::tr("Please select the installation of %1 to manage").arg(*gameName) :
- QObject::tr("Please select the game to manage"),
- QString(), QFileDialog::ShowDirsOnly);
-
- if (!gamePath.isEmpty()) {
- QDir gameDir(gamePath);
- QFileInfo directoryInfo(gamePath);
- if (directoryInfo.isSymLink()) {
- reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. "
- "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath));
- }
- QList possibleGames;
- for (IPluginGame * const game : plugins.plugins()) {
- //If a game is already configured, skip any plugins that are not for that game
- if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0)
- continue;
-
- //Only try plugins that look valid for this directory
- if (game->looksValid(gameDir)) {
- possibleGames.append(game);
- }
- }
-
- if (possibleGames.count() > 1) {
- SelectionDialog browseSelection(gameConfigured ?
- QObject::tr("Please select the installation of %1 to manage").arg(*gameName) :
- QObject::tr("Please select the game to manage"),
- nullptr, QSize(32, 32));
-
- for (IPluginGame *game : possibleGames) {
- browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game));
- }
-
- if (browseSelection.exec() == QDialog::Accepted) {
- return selectGame(settings, gameDir, browseSelection.getChoiceData().value());
- } else {
- reportError(gameConfigured ?
- QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) :
- QObject::tr("Canceled finding game in \"%1\".").arg(gamePath));
- }
- } else if(possibleGames.count() == 1) {
- return selectGame(settings, gameDir, possibleGames[0]);
- } else {
- if (gameConfigured) {
- reportError(
- QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.")
- .arg(*gameName).arg(gamePath));
- } else {
- QString supportedGames;
-
- for (IPluginGame * const game : plugins.plugins()) {
- supportedGames += "
" + game->gameName() + "
";
- }
-
- QString text = QObject::tr(
- "No game identified in \"%1\". The directory is required to "
- "contain the game binary.
"
- "These are the games supported by Mod Organizer:"
- "
%2
")
- .arg(gamePath)
- .arg(supportedGames);
-
- reportError(text);
- }
- }
- }
- }
-
- return nullptr;
-}
-
-
// This adds the `dlls` directory to the path so the dlls can be found. How
// MO is able to find dlls in there is a bit convoluted:
//
@@ -388,45 +206,6 @@ std::unique_ptr createSplash(
return splash;
}
-bool determineGameEdition(Settings& settings, IPluginGame* game)
-{
- QString edition;
-
- if (auto v=settings.game().edition()) {
- edition = *v;
- } else {
- QStringList editions = game->gameVariants();
- if (editions.size() < 2) {
- edition = "";
- return true;
- }
-
- SelectionDialog selection(
- QObject::tr("Please select the game edition you have (MO can't "
- "start the game correctly if this is set "
- "incorrectly!)"),
- nullptr);
-
- selection.setWindowFlag(Qt::WindowStaysOnTopHint, true);
-
- int index = 0;
- for (const QString &edition : editions) {
- selection.addChoice(edition, "", index++);
- }
-
- if (selection.exec() == QDialog::Rejected) {
- return false;
- }
-
- edition = selection.getChoiceString();
- settings.game().setEdition(edition);
- }
-
- game->setGameVariant(edition);
-
- return true;
-}
-
std::optional handleCommandLine(
const cl::CommandLine& cl, OrganizerCore& organizer)
{
@@ -542,7 +321,8 @@ int runApplication(
pluginContainer = std::make_unique(&organizer);
pluginContainer->loadPlugins();
- MOBase::IPluginGame* game = determineCurrentGame(
+ MOBase::IPluginGame* game = InstanceManager::instance()
+ .determineCurrentGame(
application.applicationDirPath(), settings, *pluginContainer);
if (game == nullptr) {
@@ -563,7 +343,7 @@ int runApplication(
organizer.setManagedGame(game);
organizer.createDefaultProfile();
- if (!determineGameEdition(settings, game)) {
+ if (!InstanceManager::instance().determineGameEdition(settings, game)) {
return 1;
}
@@ -578,7 +358,13 @@ int runApplication(
organizer.updateExecutablesList();
organizer.updateModInfoFromDisc();
- QString selectedProfileName = determineProfile(cl, settings);
+ if (cl.profile()) {
+ InstanceManager::instance().overrideProfile(*cl.profile());
+ }
+
+ QString selectedProfileName = InstanceManager::instance()
+ .determineProfile(settings);
+
organizer.setCurrentProfile(selectedProfileName);
if (auto r=handleCommandLine(cl, organizer)) {
--
cgit v1.3.1
From 3c791b092054192fcff27b599728d3482688aec8 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 03:01:00 -0400
Subject: moved registry key from "Tannin" to "Mod Organizer Team"
---
src/env.cpp | 160 ++++++++++++++++++++++++++++++++++++++++++++++++
src/env.h | 9 +++
src/instancemanager.cpp | 31 ++++++++--
src/instancemanager.h | 2 +
4 files changed, 196 insertions(+), 6 deletions(-)
(limited to 'src')
diff --git a/src/env.cpp b/src/env.cpp
index 9f0acbbd..5bed84c1 100644
--- a/src/env.cpp
+++ b/src/env.cpp
@@ -846,6 +846,166 @@ Association getAssociation(const QFileInfo& targetInfo)
}
+struct RegistryKeyCloser
+{
+ using pointer = HKEY;
+
+ void operator()(HKEY key)
+ {
+ if (key != 0) {
+ ::RegCloseKey(key);
+ }
+ }
+};
+
+using RegistryKeyPtr = std::unique_ptr;
+
+RegistryKeyPtr openRegistryKey(HKEY parent, const wchar_t* name)
+{
+ HKEY subkey = 0;
+
+ auto r = ::RegOpenKeyExW(
+ parent, name,
+ 0, KEY_SET_VALUE|KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,
+ &subkey);
+
+ if (r != ERROR_SUCCESS) {
+ return {};
+ }
+
+ return RegistryKeyPtr(subkey);
+}
+
+bool keyHasValues(HKEY key)
+{
+ auto name = std::make_unique(1000 + 1);
+ DWORD nameSize = 1000;
+
+ // note that RegEnumValueW() also enumerates the default value if it exists
+ auto r = ::RegEnumValueW(
+ key, 0, name.get(), &nameSize, nullptr, nullptr, nullptr, nullptr);
+
+ if (r != ERROR_NO_MORE_ITEMS) {
+ return true;
+ }
+
+ // no values, no default, it's empty
+ return false;
+}
+
+bool forEachSubKey(HKEY key, std::function f)
+{
+ auto name = std::make_unique(1000 + 1);
+ DWORD nameSize = 1000;
+
+ DWORD i = 0;
+
+ // something would be really wrong if it had more than 100 keys
+ while (i < 100)
+ {
+ auto r = ::RegEnumKeyExW(
+ key, i, name.get(), &nameSize, nullptr, nullptr, nullptr, nullptr);
+
+ if (r == ERROR_NO_MORE_ITEMS) {
+ // no more subkeys
+ break;
+ }
+
+ if (r == ERROR_SUCCESS) {
+ // a subkey exists
+ auto subkey = openRegistryKey(key, name.get());
+
+ if (!subkey) {
+ // can't open it, stop
+ return false;
+ }
+
+ // fire callback
+ if (!f(name.get())) {
+ return false;
+ }
+ } else {
+ // something went wrong, stop
+ return false;
+ }
+
+ ++i;
+ }
+
+ return true;
+}
+
+bool isKeyEmpty(HKEY key)
+{
+ // check for any values
+ if (keyHasValues(key)) {
+ return false;
+ }
+
+ auto r = forEachSubKey(key, [&](const wchar_t* name) {
+ // a subkey exists, recursively check if it's empty
+ auto subkey = openRegistryKey(key, name);
+
+ if (!subkey) {
+ // can't open, stop
+ return false;
+ }
+
+ if (!isKeyEmpty(subkey.get())) {
+ // not empty, stop
+ return false;
+ }
+
+ // empty, go on
+ return true;
+ });
+
+ if (!r) {
+ // something went wrong or some subkey has values
+ return false;
+ }
+
+ // key has no values and has either no subkeys or all subkeys are empty
+ return true;
+}
+
+void deleteRegistryKeyIfEmpty(const QString& name)
+{
+ if (name.isEmpty()) {
+ return;
+ }
+
+ auto key = openRegistryKey(HKEY_CURRENT_USER, name.toStdWString().c_str());
+ if (!key) {
+ return;
+ }
+
+ if (!isKeyEmpty(key.get())) {
+ return;
+ }
+
+ ::RegDeleteTreeW(HKEY_CURRENT_USER, name.toStdWString().c_str());
+}
+
+bool registryValueExists(const QString& keyName, const QString& valueName)
+{
+ auto key = openRegistryKey(
+ HKEY_CURRENT_USER, keyName.toStdWString().c_str());
+
+ if (!key) {
+ return false;
+ }
+
+ DWORD type = 0;
+
+ auto r = ::RegQueryValueExW(
+ key.get(), valueName.toStdWString().c_str(),
+ nullptr, &type, nullptr, nullptr);
+
+ return (r == ERROR_SUCCESS);
+}
+
+
// returns the filename of the given process or the current one
//
std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE)
diff --git a/src/env.h b/src/env.h
index a563f2c3..dbbd5cf4 100644
--- a/src/env.h
+++ b/src/env.h
@@ -301,6 +301,15 @@ struct Association
Association getAssociation(const QFileInfo& file);
+// returns whether the given value exists
+//
+bool registryValueExists(const QString& key, const QString& value);
+
+// deletes a registry key if it's empty or only contains empty keys
+//
+void deleteRegistryKeyIfEmpty(const QString& name);
+
+
enum class CoreDumpTypes
{
Mini = 1,
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 9bcd0f3c..aa8f1d8c 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -23,6 +23,7 @@ along with Mod Organizer. If not, see .
#include "settings.h"
#include "shared/appconfig.h"
#include "plugincontainer.h"
+#include "env.h"
#include
#include
#include
@@ -37,14 +38,15 @@ along with Mod Organizer. If not, see .
using namespace MOBase;
-static const char COMPANY_NAME[] = "Tannin";
-static const char APPLICATION_NAME[] = "Mod Organizer";
-static const char INSTANCE_KEY[] = "CurrentInstance";
+const QString Organization = "Mod Organizer Team";
+const QString Application = "Mod Organizer";
+const QString InstanceValue = "CurrentInstance";
InstanceManager::InstanceManager()
- : m_AppSettings(COMPANY_NAME, APPLICATION_NAME)
+ : m_AppSettings(Organization, Application)
{
+ updateRegistryKey();
}
InstanceManager &InstanceManager::instance()
@@ -53,6 +55,23 @@ InstanceManager &InstanceManager::instance()
return s_Instance;
}
+void InstanceManager::updateRegistryKey()
+{
+ const QString OldOrganization = "Tannin";
+ const QString OldApplication = "Mod Organizer";
+ const QString OldInstanceValue = "CurrentInstance";
+
+ const QString OldRootKey = "Software\\" + OldOrganization;
+
+ if (env::registryValueExists(OldRootKey + "\\" + OldApplication, OldInstanceValue)) {
+ QSettings old(OldOrganization, OldApplication);
+ setCurrentInstance(old.value(OldInstanceValue).toString());
+ old.remove(OldInstanceValue);
+ }
+
+ env::deleteRegistryKeyIfEmpty(OldRootKey);
+}
+
void InstanceManager::overrideInstance(const QString& instanceName)
{
m_overrideInstanceName = instanceName;
@@ -70,7 +89,7 @@ QString InstanceManager::currentInstance() const
if (m_overrideInstance)
return m_overrideInstanceName;
else
- return m_AppSettings.value(INSTANCE_KEY, "").toString();
+ return m_AppSettings.value(InstanceValue, "").toString();
}
void InstanceManager::clearCurrentInstance()
@@ -82,7 +101,7 @@ void InstanceManager::clearCurrentInstance()
void InstanceManager::setCurrentInstance(const QString &name)
{
- m_AppSettings.setValue(INSTANCE_KEY, name);
+ m_AppSettings.setValue(InstanceValue, name);
}
bool InstanceManager::deleteLocalInstance(const QString &instanceId) const
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 2331ebfd..7f87045d 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -71,6 +71,8 @@ private:
bool portableInstall() const;
bool portableInstallIsLocked() const;
+ void updateRegistryKey();
+
private:
QSettings m_AppSettings;
--
cgit v1.3.1
From 3761ac84b586669b08657d67a6d170009a96795f Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 03:31:09 -0400
Subject: deleteLocalInstance() now uses TaskDialog
---
src/instancemanager.cpp | 87 +++++++++++++++++++++++++++++++++----------------
src/instancemanager.h | 3 +-
2 files changed, 61 insertions(+), 29 deletions(-)
(limited to 'src')
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index aa8f1d8c..3821495b 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -104,30 +104,67 @@ void InstanceManager::setCurrentInstance(const QString &name)
m_AppSettings.setValue(InstanceValue, name);
}
-bool InstanceManager::deleteLocalInstance(const QString &instanceId) const
+bool InstanceManager::deleteLocalInstance(const QString& instanceId) const
{
- bool result = true;
- QString instancePath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId);
+ QString dir = instancePath(instanceId);
- if (QMessageBox::warning(nullptr, QObject::tr("Deleting folder"),
- QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No ){
- return false;
- }
+ const auto Recycle = QMessageBox::Save;
+ const auto Delete = QMessageBox::Yes;
+ const auto Cancel = QMessageBox::Cancel;
+
+ const auto r = MOBase::TaskDialog()
+ .title(QObject::tr("Deleting instance folder"))
+ .main(QObject::tr("This will delete the instance folder."))
+ .content(dir)
+ .icon(QMessageBox::Warning)
+ .button({QObject::tr("Move the folder to the recycle bin"), Recycle})
+ .button({QObject::tr("Delete the folder permanently"), Delete})
+ .button({QObject::tr("Cancel"), Cancel})
+ .exec();
+
+ std::wstring error;
- if (!MOBase::shellDelete(QStringList(instancePath),true))
+ switch (r)
{
- log::warn(
- "Failed to shell-delete \"{}\" (errorcode {}), trying regular delete",
- instancePath, ::GetLastError());
+ case Recycle:
+ {
+ if (MOBase::shellDelete(QStringList(dir), true)) {
+ return true;
+ }
+
+ const auto e = GetLastError();
+ error = formatSystemMessage(e);
+ log::warn("failed to move to trash '{}', {}", dir, error);
+
+ break;
+ }
+
+ case Delete:
+ {
+ if (MOBase::shellDelete(QStringList(dir), false)) {
+ return true;
+ }
+
+ const auto e = GetLastError();
+ error = formatSystemMessage(e);
+ log::warn("failed to delete '{}', {}", dir, error);
+
+ break;
+ }
- if (!MOBase::removeDir(instancePath))
+ default:
{
- log::warn("regular delete failed too");
- result = false;
+ return true;
}
}
- return result;
+ QMessageBox::critical(
+ nullptr, QObject::tr("Error"), QObject::tr(
+ "Could not delete instance folder \"%1\".\n\n%2")
+ .arg(dir).arg(error),
+ QMessageBox::Ok);
+
+ return false;
}
QString InstanceManager::manageInstances(const QStringList &instanceList) const
@@ -147,17 +184,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const
}
else {
QString choice = selection.getChoiceData().toString();
- {
- if (QMessageBox::warning(nullptr, QObject::tr("Are you sure?"),
- QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?").arg(choice), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
- {
- if (!deleteLocalInstance(choice))
- {
- QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"),
- QObject::tr("Could not delete Instance \"%1\". \nIf the folder was still in use, restart MO and try again.").arg(choice), QMessageBox::Ok);
- }
- }
- }
+ deleteLocalInstance(choice);
}
return(manageInstances(instances()));
}
@@ -278,8 +305,12 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
}
}
+QString InstanceManager::instancePath(const QString& instanceName) const
+{
+ return QDir::fromNativeSeparators(instancesPath() + "/" + instanceName);
+}
-QString InstanceManager::instancePath() const
+QString InstanceManager::instancesPath() const
{
return QDir::fromNativeSeparators(
QStandardPaths::writableLocation(QStandardPaths::DataLocation));
@@ -292,7 +323,7 @@ QStringList InstanceManager::instances() const
"cache", "qtwebengine",
};
- const auto dirs = QDir(instancePath())
+ const auto dirs = QDir(instancesPath())
.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
QStringList list;
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 7f87045d..13754f89 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -53,7 +53,8 @@ private:
InstanceManager();
- QString instancePath() const;
+ QString instancesPath() const;
+ QString instancePath(const QString& instanceName) const;
QStringList instances() const;
--
cgit v1.3.1
From 555b2508a5a34798059af7e128f6f32fc34a4947 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 03:39:52 -0400
Subject: rewrote the delete instance dialog text
---
src/instancemanager.cpp | 39 +++++++++++++++++++++------------------
1 file changed, 21 insertions(+), 18 deletions(-)
(limited to 'src')
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 3821495b..46936444 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -169,24 +169,27 @@ bool InstanceManager::deleteLocalInstance(const QString& instanceId) const
QString InstanceManager::manageInstances(const QStringList &instanceList) const
{
- SelectionDialog selection(
- QString("
%1
%2")
- .arg(QObject::tr("Choose Instance to Delete"))
- .arg(QObject::tr("Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")),
- nullptr);
- for (const QString &instance : instanceList)
- {
- selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance);
- }
-
- if (selection.exec() == QDialog::Rejected) {
- return(chooseInstance(instances()));
- }
- else {
- QString choice = selection.getChoiceData().toString();
- deleteLocalInstance(choice);
- }
- return(manageInstances(instances()));
+ SelectionDialog selection(QString("
%1
%2")
+ .arg(QObject::tr("Select an instance to delete"))
+ .arg(QObject::tr(
+ "Deleting an instance will delete all the mods, downloads, profiles "
+ "(including profile-specific saves) and anything in the overwrite "
+ "folder.
"
+ "Custom paths outside of the instance folder will not be deleted.")));
+
+ for (const QString &instance : instanceList) {
+ selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance);
+ }
+
+ if (selection.exec() == QDialog::Rejected) {
+ return (chooseInstance(instances()));
+ }
+ else {
+ QString choice = selection.getChoiceData().toString();
+ deleteLocalInstance(choice);
+ }
+
+ return(manageInstances(instances()));
}
QString InstanceManager::queryInstanceName(const QStringList &instanceList) const
--
cgit v1.3.1
From e29f41565fce21cd72968ac188cd319e746b10e6 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 04:47:52 -0400
Subject: fixed splash not trying the default one
---
src/main.cpp | 26 ++++++++++++++++++++------
1 file changed, 20 insertions(+), 6 deletions(-)
(limited to 'src')
diff --git a/src/main.cpp b/src/main.cpp
index 5479d5a3..08d70dd9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -165,20 +165,34 @@ QString getSplashPath(
return {};
}
+ // try splash from instance directory
const QString splashPath = dataPath + "/splash.png";
if (QFile::exists(dataPath + "/splash.png")) {
- return splashPath;
+ QImage image(splashPath);
+ if (!image.isNull()) {
+ return splashPath;
+ }
}
- // currently using MO splash, see if the plugin contains one
+ // try splash from plugin
QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName());
- QImage image(pluginSplash);
+ if (QFile::exists(pluginSplash)) {
+ QImage image(pluginSplash);
+ if (!image.isNull()) {
+ image.save(splashPath);
+ return pluginSplash;
+ }
+ }
- if (image.isNull()) {
- return {};
+ // try default splash from resource
+ QString defaultSplash = ":/MO/gui/splash";
+ if (QFile::exists(defaultSplash)) {
+ QImage image(defaultSplash);
+ if (!image.isNull()) {
+ return defaultSplash;
+ }
}
- image.save(splashPath);
return splashPath;
}
--
cgit v1.3.1
From 5513b273c679b9edda951f418ef013a762ca587a Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 04:53:20 -0400
Subject: added empty instance manager dialog
---
src/CMakeLists.txt | 6 +-
src/instancemanager.cpp | 30 +-
src/instancemanager.h | 9 +-
src/instancemanagerdialog.cpp | 36 ++
src/instancemanagerdialog.h | 20 +
src/instancemanagerdialog.ui | 378 ++++++++++++++++++
src/organizer_en.ts | 885 +++++++++++++++++++++++-------------------
7 files changed, 940 insertions(+), 424 deletions(-)
create mode 100644 src/instancemanagerdialog.cpp
create mode 100644 src/instancemanagerdialog.h
create mode 100644 src/instancemanagerdialog.ui
(limited to 'src')
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 73254574..1f048be8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -29,7 +29,6 @@ add_filter(NAME src/core GROUPS
categories
archivefiletree
installationmanager
- instancemanager
loadmechanism
nexusinterface
nxmaccessmanager
@@ -84,6 +83,11 @@ add_filter(NAME src/executables GROUPS
editexecutablesdialog
)
+add_filter(NAME src/instances GROUPS
+ instancemanager
+ instancemanagerdialog
+)
+
add_filter(NAME src/loot GROUPS
loot
lootdialog
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 46936444..98edba47 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -182,14 +182,14 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const
}
if (selection.exec() == QDialog::Rejected) {
- return (chooseInstance(instances()));
+ return (chooseInstance(instanceNames()));
}
else {
QString choice = selection.getChoiceData().toString();
deleteLocalInstance(choice);
}
- return(manageInstances(instances()));
+ return(manageInstances(instanceNames()));
}
QString InstanceManager::queryInstanceName(const QStringList &instanceList) const
@@ -301,7 +301,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const
case Special::Portable: return QString();
case Special::Manage: {
- return(manageInstances(instances()));
+ return(manageInstances(instanceNames()));
}
default: throw std::runtime_error("invalid selection");
}
@@ -319,27 +319,37 @@ QString InstanceManager::instancesPath() const
QStandardPaths::writableLocation(QStandardPaths::DataLocation));
}
-
-QStringList InstanceManager::instances() const
+std::vector InstanceManager::instancePaths() const
{
const std::set ignore = {
"cache", "qtwebengine",
};
- const auto dirs = QDir(instancesPath())
- .entryList(QDir::Dirs | QDir::NoDotAndDotDot);
+ const QDir root(instancesPath());
+ const auto dirs = root.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
- QStringList list;
+ std::vector list;
for (auto&& d : dirs) {
if (!ignore.contains(QFileInfo(d).fileName().toLower())) {
- list.push_back(d);
+ list.push_back(root.filePath(d));
}
}
return list;
}
+QStringList InstanceManager::instanceNames() const
+{
+ QStringList list;
+
+ for (auto&& d : instancePaths()) {
+ list.push_back(d.dirName());
+ }
+
+ return list;
+}
+
bool InstanceManager::isPortablePath(const QString& dataPath)
{
@@ -402,7 +412,7 @@ QString InstanceManager::determineDataPath()
if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) {
- instanceId = chooseInstance(instances());
+ instanceId = chooseInstance(instanceNames());
setCurrentInstance(instanceId);
if (!instanceId.isEmpty()) {
dataPath = QDir::fromNativeSeparators(
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 13754f89..8bbbbee9 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -29,8 +29,8 @@ namespace MOBase { class IPluginGame; }
class Settings;
class PluginContainer;
-class InstanceManager {
-
+class InstanceManager
+{
public:
static InstanceManager &instance();
@@ -49,6 +49,9 @@ public:
bool allowedToChangeInstance() const;
static bool isPortablePath(const QString& dataPath);
+ QStringList instanceNames() const;
+ std::vector instancePaths() const;
+
private:
InstanceManager();
@@ -56,8 +59,6 @@ private:
QString instancesPath() const;
QString instancePath(const QString& instanceName) const;
- QStringList instances() const;
-
bool deleteLocalInstance(const QString &instanceId) const;
QString manageInstances(const QStringList &instanceList) const;
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
new file mode 100644
index 00000000..1b482099
--- /dev/null
+++ b/src/instancemanagerdialog.cpp
@@ -0,0 +1,36 @@
+#include "instancemanagerdialog.h"
+#include "ui_instancemanagerdialog.h"
+#include "instancemanager.h"
+
+class InstanceInfo
+{
+public:
+ InstanceInfo(QDir dir)
+ : m_dir(std::move(dir))
+ {
+ }
+
+ QString name() const
+ {
+ return m_dir.dirName();
+ }
+
+private:
+ QDir m_dir;
+};
+
+
+InstanceManagerDialog::InstanceManagerDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::InstanceManagerDialog)
+{
+ ui->setupUi(this);
+
+ auto& m = InstanceManager::instance();
+
+ for (auto&& d : m.instancePaths()) {
+ InstanceInfo i(d);
+ ui->list->addItem(i.name());
+ }
+}
+
+InstanceManagerDialog::~InstanceManagerDialog() = default;
diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h
new file mode 100644
index 00000000..ea1cfc48
--- /dev/null
+++ b/src/instancemanagerdialog.h
@@ -0,0 +1,20 @@
+#ifndef MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED
+#define MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED
+
+#include
+
+namespace Ui { class InstanceManagerDialog; };
+
+class InstanceManagerDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit InstanceManagerDialog(QWidget *parent = nullptr);
+ ~InstanceManagerDialog();
+
+private:
+ std::unique_ptr ui;
+};
+
+#endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED
diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui
new file mode 100644
index 00000000..b35d2e58
--- /dev/null
+++ b/src/instancemanagerdialog.ui
@@ -0,0 +1,378 @@
+
+
+ InstanceManagerDialog
+
+
+
+ 0
+ 0
+ 531
+ 413
+
+
+
+ Instance manager
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+
+
+ Create new instance
+
+
+
+ :/MO/gui/add:/MO/gui/add
+
+
+
+
+
+
+ Create portable instance
+
+
+
+ :/MO/gui/package:/MO/gui/package
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Qt::Horizontal
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ instance name
+
+
+ Qt::TextBrowserInteraction
+
+
+
+
+
+
+ Game:
+
+
+
+
+
+
+ Name:
+
+
+
+
+
+
+ Location:
+
+
+
+
+
+
+ Rename
+
+
+
+
+
+
+ location path
+
+
+ Qt::TextBrowserInteraction
+
+
+
+
+
+
+ Base folder:
+
+
+
+
+
+
+ base folder path
+
+
+ Qt::TextBrowserInteraction
+
+
+
+
+
+
+ Explore
+
+
+
+
+
+
+ Explore
+
+
+
+
+
+
+ game name
+
+
+ Qt::TextBrowserInteraction
+
+
+
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Convert to portable
+
+
+
+
+
+
+ Convert to global
+
+
+
+
+
+
+ Delete instance
+
+
+
+ :/MO/gui/remove:/MO/gui/remove
+
+
+
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ Switch to this instance
+
+
+
+ :/MO/gui/next:/MO/gui/next
+
+
+
+
+
+
+ Cancel
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/organizer_en.ts b/src/organizer_en.ts
index 1ad4650f..c65c946c 100644
--- a/src/organizer_en.ts
+++ b/src/organizer_en.ts
@@ -110,22 +110,22 @@
-
+ ...and all other contributors!
-
+ Other Supporters && Contributors
-
+ Tannin (Original Creator)
-
+ Close
@@ -178,17 +178,17 @@ p, li { white-space: pre-wrap; }
AdvancedConflictListModel
-
+ Overwrites
-
+ File
-
+ Overwritten By
@@ -1462,54 +1462,54 @@ Right now the only case I know of where this needs to be overwritten is for the
FileTreeModel
-
+ Name
-
+ Mod
-
+ Type
-
+ Size
-
+ Date modified
-
+ Directory
-
-
+
+ Virtual path
-
+ Real path
-
+ From
-
-
+
+ Also in
@@ -1868,6 +1868,85 @@ This is likely due to a corrupted or incompatible download or unrecognized archi
+
+ InstanceManagerDialog
+
+
+ Instance manager
+
+
+
+
+ Create new instance
+
+
+
+
+ Create portable instance
+
+
+
+
+ <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html>
+
+
+
+
+ Game:
+
+
+
+
+ Name:
+
+
+
+
+ Location:
+
+
+
+
+ Rename
+
+
+
+
+ Base folder:
+
+
+
+
+
+ Explore
+
+
+
+
+ Convert to portable
+
+
+
+
+ Convert to global
+
+
+
+
+ Delete instance
+
+
+
+
+ Switch to this instance
+
+
+
+
+ Cancel
+
+
+ListDialog
@@ -1999,12 +2078,12 @@ This is likely due to a corrupted or incompatible download or unrecognized archi
MOApplication
-
+ an error occurred: %1
-
+ an error occurred
@@ -2199,7 +2278,7 @@ p, li { white-space: pre-wrap; }
-
+ Create Backup
@@ -2353,7 +2432,7 @@ p, li { white-space: pre-wrap; }
-
+ Refresh
@@ -2677,7 +2756,7 @@ p, li { white-space: pre-wrap; }
-
+ Endorse Mod Organizer
@@ -2755,829 +2834,833 @@ p, li { white-space: pre-wrap; }
-
+ Toolbar and Menu
-
+ Desktop
-
+ Start Menu
-
+ There is no supported sort mechanism for this game. You will probably have to use a third-party tool.
-
+ Crash on exit
-
+ MO crashed while exiting. Some settings may not be saved.
Error: %1
-
+ There are notifications to read
-
+ There are no notifications
-
-
-
+
+
+ Endorse
-
+ Won't Endorse
-
+ Help on UI
-
+ Documentation
-
+ Chat on Discord
-
+ Report Issue
-
+ Tutorials
-
+ About
-
+ About Qt
-
+ Name
-
+ Please enter a name for the new profile
-
+ failed to create profile: %1
-
+ Show tutorial?
-
+ You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu.
-
+ Downloads in progress
-
+ There are still downloads in progress, do you really want to quit?
-
+ Plugin "%1" failed: %2
-
+ Plugin "%1" failed
-
+ Browse Mod Page
-
+ <Edit...>
-
+ (no executables)
-
+ This bsa is enabled in the ini file so it may be required!
-
+ Activating Network Proxy
-
+ Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues.
-
+ Choose Mod
-
+ Mod Archive
-
+ Start Tutorial?
-
+ You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue?
-
+ failed to change origin name: %1
-
+ failed to move "%1" from mod "%2" to "%3": %4
-
+ failed to rename mod: %1
-
+ Overwrite?
-
+ This will replace the existing mod "%1". Continue?
-
+ failed to remove mod "%1"
-
+ failed to rename "%1" to "%2"
-
-
-
-
-
+
+
+
+
+ Confirm
-
+ Remove the following mods?<br><ul>%1</ul>
-
+ failed to remove mod: %1
-
-
-
+
+
+ Failed
-
+ Installation file no longer exists
-
+ Mods installed with old versions of MO can't be reinstalled in this way.
-
+ Failed to create backup.
-
+ Endorsing multiple mods will take a while. Please wait...
-
+ Unendorsing multiple mods will take a while. Please wait...
-
+ Failed to display overwrite dialog: %1
-
+ Restore all hidden files in the following mods?<br><ul>%1</ul>
-
-
-
+
+ Are you sure?
-
+ About to restore all hidden files in:
-
+ Opening Nexus Links
-
+ You are trying to open %1 links to Nexus Mods. Are you sure you want to do this?
-
+ Nexus ID for this mod is unknown
-
-
+
+ Opening Web Pages
-
-
+
+ You are trying to open %1 Web Pages. Are you sure you want to do this?
-
+
+ No valid Web Page for this mod
+
+
+
+ <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods: </td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs: </td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups: </td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators: </td><td align=right>%9</td><td align=right>%10</td></tr></table>
-
+ <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table>
-
-
-
+
+
+ Create Mod...
-
+ This will create an empty mod.
Please enter a name:
-
-
+
+ A mod with this name already exists
-
+ Create Separator...
-
+ This will create a new separator.
Please enter a name:
-
+ A separator with this name already exists
-
+ This will move all files from overwrite into a new, regular mod.
Please enter a name:
-
+ Move successful.
-
+ About to recursively delete:
-
+ Continue?
-
+ The versioning scheme decides which version is considered newer than another.
This function will guess the versioning scheme under the assumption that the installed version is outdated.
-
+ Sorry
-
+ I don't know a versioning scheme where %1 is newer than %2.
-
+ Really enable all visible mods?
-
+ Really disable all visible mods?
-
+ Export to csv
-
+ CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.
You can also use online editors and converters instead.
-
+ Select what mods you want export:
-
+ All installed mods
-
+ Only active (checked) mods from your current profile
-
+ All currently visible mods in the mod list
-
+ Choose what Columns to export:
-
+ Mod_Priority
-
+ Mod_Name
-
+ Notes_column
-
+ Mod_Status
-
+ Primary_Category
-
+ Nexus_ID
-
+ Mod_Nexus_URL
-
+ Mod_Version
-
+ Install_Date
-
+ Download_File_Name
-
+ export failed: %1
-
+ Open Game folder
-
+ Open MyGames folder
-
+ Open INIs folder
-
+ Open Instance folder
-
+ Open Mods folder
-
+ Open Profile folder
-
+ Open Downloads folder
-
+ Open MO2 Install folder
-
+ Open MO2 Plugins folder
-
+ Open MO2 Stylesheets folder
-
+ Open MO2 Logs folder
-
+ Install Mod...
-
+ Create empty mod
-
+ Create Separator
-
+ Enable all visible
-
+ Disable all visible
-
+ Check for updates
-
+ Export to csv...
-
-
+
+ Send to
-
-
+
+ Top
-
-
+
+ Bottom
-
-
+
+ Priority...
-
+ Separator...
-
+ All Mods
-
+ Sync to Mods...
-
+ Move content to Mod...
-
+ Clear Overwrite...
-
-
-
+
+
+ Open in Explorer
-
+ Restore Backup
-
+ Remove Backup...
-
-
+
+ Ignore missing data
-
-
+
+ Mark as converted/working
-
-
+
+ Visit on Nexus
-
-
+
+ Visit on %1
-
-
+
+ Change Categories
-
-
+
+ Primary Category
-
+ Rename Separator...
-
+ Remove Separator...
-
-
+
+ Select Color...
-
-
+
+ Reset Color
-
+ Change versioning scheme
-
+ Force-check updates
-
+ Un-ignore update
-
+ Ignore update
-
-
+
+ Enable selected
-
-
+
+ Disable selected
-
+ Rename Mod...
-
+ Reinstall Mod
-
+ Remove Mod...
-
+ Restore hidden files
-
+ Un-Endorse
-
+ Won't endorse
-
+ Endorsement state unknown
-
+ Start tracking
-
+ Stop tracking
-
+ Tracked state unknown
-
+ Information...
-
-
+
+ Exception:
-
-
+
+ Unknown exception
-
+ %1 more
-
+ Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin.
@@ -3585,12 +3668,12 @@ You can also use online editors and converters instead.
-
+ Enable Mods...
-
+ Delete %n save(s)
@@ -3598,242 +3681,227 @@ You can also use online editors and converters instead.
-
+ Restart Mod Organizer
-
+ Mod Organizer must restart to finish configuration changes
-
+ Restart
-
+ Continue
-
+ Some things might be weird.
-
+ Can't change download directory while downloads are in progress!
-
-
+
+ Set Priority
-
+ Set the priority of the selected plugins
-
+ Update available
-
+ Do you want to endorse Mod Organizer on %1 now?
-
+ Abstain from Endorsing Mod Organizer
-
+ Are you sure you want to abstain from endorsing Mod Organizer 2?
You will have to visit the mod page on the %1 Nexus site to change your mind.
-
+ Thank you for endorsing MO2! :)
-
+ Please reconsider endorsing MO2 on Nexus!
-
+ Thank you!
-
+ Thank you for your endorsement!
-
+ Mod ID %1 no longer seems to be available on Nexus.
-
+ Request to Nexus failed: %1
-
-
+
+ failed to read %1: %2
-
+ Error
-
+ failed to extract %1 (errorcode %2)
-
+ Extract BSA
-
+ This archive contains invalid hashes. Some files may be broken.
-
+ Extract...
-
- This will restart MO, continue?
-
-
-
-
+ <Multiple>
-
+ Remove '%1' from the toolbar
-
+ Enable all
-
+ Disable all
-
+ Unlock load order
-
+ Lock load order
-
+ Open Origin in Explorer
-
+ Open Origin Info...
-
- Sorting plugins
-
-
-
-
- Are you sure you want to sort your plugins list?
-
-
-
-
+ Backup of load order created
-
+ Choose backup to restore
-
+ No Backups
-
+ There are no backups to restore
-
-
+
+ Restore failed
-
-
+
+ Failed to restore the backup. Errorcode: %1
-
+ Backup of mod list created
-
+ A file with the same name has already been downloaded. What would you like to do?
-
+ Overwrite
-
+ Rename new file
-
+ Ignore file
-
+ Set the priority of the selected mods
@@ -4701,7 +4769,7 @@ p, li { white-space: pre-wrap; }
NoConflictListModel
-
+ File
@@ -4709,211 +4777,211 @@ p, li { white-space: pre-wrap; }
OrganizerCore
-
+ File is write protected
-
+ Invalid file format (probably a bug)
-
+ Unknown error %1
-
+ Failed to write settings
-
+ An error occurred trying to write back MO settings to %1: %2
-
-
+
+ Download started
-
+ Download failed
-
-
+
+ Installation cancelled
-
-
+
+ Another installation is currently in progress.
-
-
+
+ Installation successful
-
-
+
+ Configure Mod
-
-
+
+ This mod contains ini tweaks. Do you want to configure them now?
-
-
+
+ mod not found: %1
-
-
+
+ Extraction cancelled
-
-
+
+ The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files.
-
+ file not found: %1
-
+ failed to generate preview for %1
-
+ Sorry
-
+ Sorry, can't preview anything. This function currently does not support extracting from bsas.
-
+ File '%1' not found.
-
+ Failed to generate preview for %1
-
+ Failed to refresh list of esps: %1
-
+ Multiple esps/esls activated, please check that they don't conflict.
-
+ You need to be logged in with Nexus
-
+ Download?
-
+ A download has been started but no installed page plugin recognizes it.
If you download anyway no information (i.e. version) will be associated with the download.
Continue?
-
-
+
+ failed to update mod list: %1
-
-
+
+ login successful
-
+ Login failed
-
+ Login failed, try again?
-
+ login failed: %1. Download will not be associated with an account
-
+ login failed: %1
-
+ login failed: %1. You need to log-in with Nexus to update MO.
-
+ MO1 "Script Extender" load mechanism has left hook.dll in your game folder
-
-
+
+ Description missing
-
+ <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn.
-
+ failed to save load order: %1
-
+ Error
-
+ The designated write target "%1" is not enabled.
@@ -4921,12 +4989,12 @@ Continue?
OverwriteConflictListModel
-
+ File
-
+ Overwritten Mods
@@ -5026,12 +5094,12 @@ Continue?
OverwrittenConflictListModel
-
+ File
-
+ Providing Mod
@@ -5653,9 +5721,10 @@ p, li { white-space: pre-wrap; }
+
+
-
-
+
@@ -5972,137 +6041,134 @@ Destination:
-
- Deleting folder
-
-
-
-
- I'm about to delete the following folder: "%1". Proceed?
+
+ Deleting instance folder
-
- Choose Instance to Delete
+
+ This will delete the instance folder.
+ The folder will be deleted: "%1"
-
- Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.
+
+ Move the folder to the recycle bin
-
- Are you sure?
+
+ Delete the folder permanently
-
- Are you really sure you want to delete the Instance "%1" with all its files?
+
+ Could not delete instance folder "%1".
+
+%2
-
- Failed to delete Instance
+
+ Select an instance to delete
-
- Could not delete Instance "%1".
-If the folder was still in use, restart MO and try again.
+
+ Deleting an instance will delete all the mods, downloads, profiles (including profile-specific saves) and anything in the overwrite folder.<br><br>Custom paths outside of the instance folder will not be deleted.
-
+ Enter a Name for the new Instance
-
+ Enter a new name or select one from the suggested list:
(This is just a name for the Instance and can be whatever you wish,
the actual game selection will happen on the next screen regardless of chosen name)
-
-
+
+ Canceled
-
+ Invalid instance name
-
+ The instance name "%1" is invalid. Use the name "%2" instead?
-
+ The instance "%1" already exists.
-
+ Please choose a different instance name, like: "%1 1" .
-
+ Choose Instance
-
+ Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). You can use multiple instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. If your MO folder is writable, you can also store a single instance locally (called a Portable install, and all the MO data files will be inside the installation folder).
-
+ New
-
+ Create a new instance.
-
+ Portable
-
+ Use MO folder for data.
-
+ Manage Instances
-
+ Delete an Instance.
-
+ failed to create %1
-
+ Data directory created
-
+ New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings.
@@ -6194,112 +6260,112 @@ If the folder was still in use, restart MO and try again.
-
-
+
+ Failed to create "%1". Your user account probably lacks permission.
-
+ Plugin to handle %1 no longer installed. An antivirus might have deleted files.Plugin to handle %1 no longer installed
-
-
-
+
+
+ The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly.
-
+ Could not use configuration settings for game "%1", path "%2".
-
-
-
+
+
+ Please select the installation of %1 to manage
-
-
-
+
+
+ Please select the game to manage
-
+ Canceled finding %1 in "%2".
-
+ Canceled finding game in "%1".
-
+ %1 not identified in "%2". The directory is required to contain the game binary.
-
+ No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul>
-
+ Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)
-
+ failed to start shortcut: %1
-
+ failed to start application: %1
-
+ Mod Organizer
-
+ An instance of Mod Organizer is already running
-
+ Failed to set up instance
-
+ <Unmanaged>
-
+ Please use "Help" from the toolbar to get usage instructions to all elements
-
-
+
+ <Manage...>
-
+ failed to parse profile %1: %2
@@ -6427,7 +6493,7 @@ If the folder was still in use, restart MO and try again.
-
+ One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem.
@@ -6590,6 +6656,7 @@ This program is known to cause issues with Mod Organizer, such as freezing or bl
+
@@ -7903,22 +7970,22 @@ programs you are intentionally running.
SingleInstance
-
+ SHM error: %1
-
+ failed to connect to running instance: %1
-
+ failed to communicate with running instance: %1
-
+ failed to receive data from secondary instance: %1
--
cgit v1.3.1
From 60b59ddf097fffa846a4d28e0d9256630da5149c Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 07:32:06 -0400
Subject: started create instance dialog allow for multiple instances of
Settings fill in information in instance manager
---
src/CMakeLists.txt | 1 +
src/createinstancedialog.cpp | 176 ++++++++++++
src/createinstancedialog.h | 30 ++
src/createinstancedialog.ui | 655 ++++++++++++++++++++++++++++++++++++++++++
src/instancemanager.h | 2 +-
src/instancemanagerdialog.cpp | 102 ++++++-
src/instancemanagerdialog.h | 16 +-
src/instancemanagerdialog.ui | 116 ++++----
src/main.cpp | 9 +
src/settings.cpp | 19 +-
src/settings.h | 1 +
11 files changed, 1064 insertions(+), 63 deletions(-)
create mode 100644 src/createinstancedialog.cpp
create mode 100644 src/createinstancedialog.h
create mode 100644 src/createinstancedialog.ui
(limited to 'src')
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1f048be8..af350584 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -84,6 +84,7 @@ add_filter(NAME src/executables GROUPS
)
add_filter(NAME src/instances GROUPS
+ createinstancedialog
instancemanager
instancemanagerdialog
)
diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp
new file mode 100644
index 00000000..d43fba1c
--- /dev/null
+++ b/src/createinstancedialog.cpp
@@ -0,0 +1,176 @@
+#include "createinstancedialog.h"
+#include "ui_createinstancedialog.h"
+#include "instancemanager.h"
+
+namespace cid
+{
+
+class Page
+{
+public:
+ Page(CreateInstanceDialog& dlg, std::size_t i)
+ : ui(dlg.getUI()), m_dlg(dlg), m_index(i)
+ {
+ }
+
+ virtual bool ready() const
+ {
+ return true;
+ }
+
+ void next()
+ {
+ m_dlg.next();
+ }
+
+protected:
+ Ui::CreateInstanceDialog* ui;
+
+private:
+ CreateInstanceDialog& m_dlg;
+ std::size_t m_index;
+};
+
+class TypePage : public Page
+{
+public:
+ TypePage(CreateInstanceDialog& dlg, std::size_t i)
+ : Page(dlg, i)
+ {
+ ui->createGlobal->setDescription(
+ ui->createGlobal->description()
+ .arg(InstanceManager::instance().instancesPath()));
+
+ ui->createPortable->setDescription(
+ ui->createPortable->description()
+ .arg(qApp->applicationDirPath()));
+
+ QObject::connect(
+ ui->createGlobal, &QAbstractButton::clicked, [&]{ global(); });
+
+ QObject::connect(
+ ui->createPortable, &QAbstractButton::clicked, [&]{ portable(); });
+ }
+
+ bool ready() const override
+ {
+ return m_global.has_value();
+ }
+
+ void global()
+ {
+ m_global = true;
+
+ ui->createGlobal->setChecked(true);
+ ui->createPortable->setChecked(false);
+
+ next();
+ }
+
+ void portable()
+ {
+ m_global = false;
+
+ ui->createGlobal->setChecked(false);
+ ui->createPortable->setChecked(true);
+
+ next();
+ }
+
+private:
+ std::optional m_global;
+};
+
+
+class GamePage : public Page
+{
+public:
+ GamePage(CreateInstanceDialog& dlg, std::size_t i)
+ : Page(dlg, i)
+ {
+ }
+};
+
+
+class NamePage : public Page
+{
+public:
+ NamePage(CreateInstanceDialog& dlg, std::size_t i)
+ : Page(dlg, i)
+ {
+ }
+};
+
+
+class PathsPage : public Page
+{
+public:
+ PathsPage(CreateInstanceDialog& dlg, std::size_t i)
+ : Page(dlg, i)
+ {
+ }
+};
+
+} // namespace
+
+
+CreateInstanceDialog::CreateInstanceDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::CreateInstanceDialog)
+{
+ using namespace cid;
+
+ ui->setupUi(this);
+
+ m_pages.push_back(std::make_unique(*this, 0));
+ m_pages.push_back(std::make_unique(*this, 1));
+ m_pages.push_back(std::make_unique(*this, 2));
+ m_pages.push_back(std::make_unique(*this, 3));
+
+ ui->pages->setCurrentIndex(0);
+
+ updateNavigationButtons();
+
+ connect(ui->next, &QPushButton::clicked, [&]{ next(); });
+ connect(ui->back, &QPushButton::clicked, [&]{ back(); });
+
+ //
+ //SelectionDialog games(tr("Select a game to manage."));
+ //
+ //for (auto* game : m_pc.plugins()) {
+ // if (game->isInstalled()) {
+ // games.addChoice(game->gameName(), game->gameDirectory().path(), {});
+ // } else {
+ // games.addChoice(game->gameName(), "", {});
+ // }
+ //}
+ //
+ //games.exec();
+}
+
+CreateInstanceDialog::~CreateInstanceDialog() = default;
+
+Ui::CreateInstanceDialog* CreateInstanceDialog::getUI()
+{
+ return ui.get();
+}
+
+void CreateInstanceDialog::next()
+{
+ ui->pages->setCurrentIndex(ui->pages->currentIndex() + 1);
+ updateNavigationButtons();
+}
+
+void CreateInstanceDialog::back()
+{
+ ui->pages->setCurrentIndex(ui->pages->currentIndex() - 1);
+ updateNavigationButtons();
+}
+
+void CreateInstanceDialog::updateNavigationButtons()
+{
+ const auto i = ui->pages->currentIndex();
+ const auto last = (i == (ui->pages->count() - 1));
+
+ ui->next->setEnabled(m_pages[i]->ready() && !last);
+ ui->back->setEnabled(i > 0);
+}
diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h
new file mode 100644
index 00000000..03e9de01
--- /dev/null
+++ b/src/createinstancedialog.h
@@ -0,0 +1,30 @@
+#ifndef MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED
+#define MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED
+
+#include
+
+namespace Ui { class CreateInstanceDialog; };
+namespace cid { class Page; }
+
+class CreateInstanceDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit CreateInstanceDialog(QWidget *parent = nullptr);
+
+ ~CreateInstanceDialog();
+
+ Ui::CreateInstanceDialog* getUI();
+
+ void next();
+ void back();
+
+private:
+ std::unique_ptr ui;
+ std::vector> m_pages;
+
+ void updateNavigationButtons();
+};
+
+#endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED
diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui
new file mode 100644
index 00000000..bcfddb20
--- /dev/null
+++ b/src/createinstancedialog.ui
@@ -0,0 +1,655 @@
+
+
+ CreateInstanceDialog
+
+
+
+ 0
+ 0
+ 493
+ 423
+
+
+
+ Creating an instance
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ <h2>Creating a new instance</h2>
+
+
+
+
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ <h3>Select the type of instance to create.</h3>
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Create a global instance
+
+
+ true
+
+
+ Global instances are stored in %1, but some paths can be changed to be on a different drive if necessary. A single installation of Mod Organizer can manage multiple global instances.
+
+
+
+
+
+
+ Create a portable instance
+
+
+ true
+
+
+ A portable instance stores everything in Mod Organizer's installation folder, currently %1. There can only be one portable instance per installation of Mod Organizer.
+
+
+
+
+
+
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ <h3>Select the game to manage.</h3>
+
+
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ Show all supported games
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ <h3>Pick a name for this instance.</h3>
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+ Instance name
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ <h3>Select a folder where the data should be stored.</h3>
+
+
+
+
+
+
+ This includes downloads, mods, profiles and overwrite. If there is enough space on this drive, you should use the default folder.
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ ...
+
+
+
+
+
+
+ Location
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Overwrite
+
+
+
+
+
+
+ Mods
+
+
+
+
+
+
+ Base directory
+
+
+
+
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ Profiles
+
+
+
+
+
+
+
+
+
+ Downloads
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Use <code>%BASE_DIR%</code> to refer to the Base Directory.
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 292
+ 20
+
+
+
+
+
+
+
+ Show advanced options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ < Back
+
+
+
+
+
+
+ Next >
+
+
+ true
+
+
+
+
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/instancemanager.h b/src/instancemanager.h
index 8bbbbee9..d53f4391 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -49,6 +49,7 @@ public:
bool allowedToChangeInstance() const;
static bool isPortablePath(const QString& dataPath);
+ QString instancesPath() const;
QStringList instanceNames() const;
std::vector instancePaths() const;
@@ -56,7 +57,6 @@ private:
InstanceManager();
- QString instancesPath() const;
QString instancePath(const QString& instanceName) const;
bool deleteLocalInstance(const QString &instanceId) const;
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
index 1b482099..c7042aa8 100644
--- a/src/instancemanagerdialog.cpp
+++ b/src/instancemanagerdialog.cpp
@@ -1,12 +1,19 @@
#include "instancemanagerdialog.h"
#include "ui_instancemanagerdialog.h"
#include "instancemanager.h"
+#include "createinstancedialog.h"
+#include "settings.h"
+#include "selectiondialog.h"
+#include "plugincontainer.h"
+#include "shared/appconfig.h"
+#include
class InstanceInfo
{
public:
- InstanceInfo(QDir dir)
- : m_dir(std::move(dir))
+ InstanceInfo(QDir dir) :
+ m_dir(std::move(dir)),
+ m_settings(dir.filePath(QString::fromStdWString(AppConfig::iniFileName())))
{
}
@@ -15,22 +22,105 @@ public:
return m_dir.dirName();
}
+ QString gameName() const
+ {
+ if (auto n=m_settings.game().name()) {
+ if (auto e=m_settings.game().edition()) {
+ if (!e->isEmpty()) {
+ return *n + " (" + *e + ")";
+ }
+ }
+
+ return *n;
+ } else {
+ return {};
+ }
+ }
+
+ QString gamePath() const
+ {
+ if (auto n=m_settings.game().directory()) {
+ return *n;
+ } else {
+ return {};
+ }
+ }
+
+ QString location() const
+ {
+ return m_dir.path();
+ }
+
+ QString baseDirectory() const
+ {
+ return m_settings.paths().base();
+ }
+
private:
QDir m_dir;
+ Settings m_settings;
};
-InstanceManagerDialog::InstanceManagerDialog(QWidget *parent)
- : QDialog(parent), ui(new Ui::InstanceManagerDialog)
+InstanceManagerDialog::InstanceManagerDialog(
+ const PluginContainer& pc, QWidget *parent)
+ : QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc)
{
ui->setupUi(this);
+ ui->splitter->setSizes({200, 1});
+ ui->splitter->setStretchFactor(0, 0);
+ ui->splitter->setStretchFactor(1, 1);
auto& m = InstanceManager::instance();
for (auto&& d : m.instancePaths()) {
- InstanceInfo i(d);
- ui->list->addItem(i.name());
+ auto ii = std::make_unique(d);
+
+ ui->list->addItem(ii->name());
+ m_instances.push_back(std::move(ii));
+ }
+
+ if (!m_instances.empty()) {
+ select(0);
}
+
+ connect(ui->createNew, &QPushButton::clicked, [&]{ createNew(); });
+ connect(ui->list, &QListWidget::itemSelectionChanged, [&]{ onSelection(); });
}
InstanceManagerDialog::~InstanceManagerDialog() = default;
+
+void InstanceManagerDialog::select(std::size_t i)
+{
+ if (i >= m_instances.size()) {
+ return;
+ }
+
+ const auto& ii = m_instances[i];
+ fill(*ii);
+}
+
+void InstanceManagerDialog::onSelection()
+{
+ const auto sel = ui->list->selectionModel()->selectedIndexes();
+ if (sel.size() != 1) {
+ return;
+ }
+
+ select(static_cast(sel[0].row()));
+}
+
+void InstanceManagerDialog::createNew()
+{
+ CreateInstanceDialog dlg(this);
+ dlg.exec();
+}
+
+void InstanceManagerDialog::fill(const InstanceInfo& ii)
+{
+ ui->name->setText(ii.name());
+ ui->location->setText(ii.location());
+ ui->baseDirectory->setText(ii.baseDirectory());
+ ui->gameName->setText(ii.gameName());
+ ui->gameDir->setText(ii.gamePath());
+}
diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h
index ea1cfc48..c3e947dd 100644
--- a/src/instancemanagerdialog.h
+++ b/src/instancemanagerdialog.h
@@ -5,16 +5,30 @@
namespace Ui { class InstanceManagerDialog; };
+class InstanceInfo;
+class PluginContainer;
+
class InstanceManagerDialog : public QDialog
{
Q_OBJECT
public:
- explicit InstanceManagerDialog(QWidget *parent = nullptr);
+ explicit InstanceManagerDialog(
+ const PluginContainer& pc, QWidget *parent = nullptr);
+
~InstanceManagerDialog();
+ void select(std::size_t i);
+
private:
std::unique_ptr ui;
+ const PluginContainer& m_pc;
+ std::vector> m_instances;
+
+ void onSelection();
+ void createNew();
+
+ void fill(const InstanceInfo& ii);
};
#endif // MODORGANIZER_INSTANCEMANAGERDIALOG_INCLUDED
diff --git a/src/instancemanagerdialog.ui b/src/instancemanagerdialog.ui
index b35d2e58..146b5fb5 100644
--- a/src/instancemanagerdialog.ui
+++ b/src/instancemanagerdialog.ui
@@ -6,7 +6,7 @@
00
- 531
+ 689413
@@ -40,17 +40,6 @@
-
-
-
- Create portable instance
-
-
-
- :/MO/gui/package:/MO/gui/package
-
-
-
@@ -65,9 +54,9 @@
-
+
- <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance"><span style=" text-decoration: underline; color:#0000ff;">What is an instance?</span></a></p></body></html>
+ <html><head/><body><p><a href="https://github.com/ModOrganizer2/modorganizer/wiki/Instance">What is an instance?</a></p></body></html>true
@@ -134,7 +123,7 @@
-
+ 0
@@ -148,33 +137,51 @@
0
-
+
+
+ true
+
+
+
+
+
- instance name
+ Name
-
- Qt::TextBrowserInteraction
+
+
+
+
+
+ Explore
-
+
- Game:
+ Game
-
-
-
- Name:
+
+
+
+ true
+
+
+
+
+
+
+ true
- Location:
+ Location
@@ -185,30 +192,24 @@
-
-
+
+
- location path
-
-
- Qt::TextBrowserInteraction
+ Game location
- Base folder:
+ Base folder
-
-
- base folder path
-
-
- Qt::TextBrowserInteraction
+
+
+ true
@@ -219,20 +220,17 @@
-
-
-
- Explore
+
+
+
+ true
-
-
+
+
- game name
-
-
- Qt::TextBrowserInteraction
+ Explore
@@ -292,6 +290,19 @@
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
@@ -371,6 +382,13 @@
+
+
+ LinkLabel
+ QLabel
+ linklabel.h
+
+
diff --git a/src/main.cpp b/src/main.cpp
index 08d70dd9..71fb0bbd 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -25,6 +25,7 @@ along with Mod Organizer. If not, see .
#include "tutorialmanager.h"
#include "nxmaccessmanager.h"
#include "instancemanager.h"
+#include "instancemanagerdialog.h"
#include "organizercore.h"
#include "env.h"
#include "envmodule.h"
@@ -298,6 +299,8 @@ int runApplication(
try
{
Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()));
+ settings.setGlobalInstance();
+
log::getDefault().setLevel(settings.diagnostics().logLevel());
log::debug("using ini at '{}'", settings.filename());
@@ -429,6 +432,12 @@ int runApplication(
tt.stop();
+ QTimer::singleShot(std::chrono::milliseconds(1), [&]
+ {
+ InstanceManagerDialog dlg(*pluginContainer, &mainWindow);
+ dlg.exec();
+ });
+
res = application.exec();
mainWindow.close();
diff --git a/src/settings.cpp b/src/settings.cpp
index d37f0d99..43e7a4fa 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -68,17 +68,24 @@ Settings::Settings(const QString& path) :
m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings),
m_Interface(m_Settings), m_Diagnostics(m_Settings)
{
- if (s_Instance != nullptr) {
- throw std::runtime_error("second instance of \"Settings\" created");
- } else {
- s_Instance = this;
- }
}
Settings::~Settings()
{
MOBase::QuestionBoxMemory::setCallbacks({}, {}, {});
- s_Instance = nullptr;
+
+ if (s_Instance == this) {
+ s_Instance = nullptr;
+ }
+}
+
+void Settings::setGlobalInstance()
+{
+ if (s_Instance != nullptr) {
+ throw std::runtime_error("second instance of \"Settings\" created");
+ } else {
+ s_Instance = this;
+ }
}
Settings &Settings::instance()
diff --git a/src/settings.h b/src/settings.h
index a1b30462..84102c72 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -681,6 +681,7 @@ public:
~Settings();
static Settings &instance();
+ void setGlobalInstance();
// name of the ini file
//
--
cgit v1.3.1
From 717be0c0483839ef4da22b366ed5bf8f07e379e2 Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 09:17:43 -0400
Subject: game page
---
src/createinstancedialog.cpp | 280 +++++++++++++++++++++++++++++++++++++++---
src/createinstancedialog.h | 7 +-
src/createinstancedialog.ui | 55 ++++++++-
src/instancemanagerdialog.cpp | 2 +-
4 files changed, 324 insertions(+), 20 deletions(-)
(limited to 'src')
diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp
index d43fba1c..21dcd8ec 100644
--- a/src/createinstancedialog.cpp
+++ b/src/createinstancedialog.cpp
@@ -1,6 +1,9 @@
#include "createinstancedialog.h"
#include "ui_createinstancedialog.h"
#include "instancemanager.h"
+#include "plugincontainer.h"
+#include
+#include
namespace cid
{
@@ -9,7 +12,7 @@ class Page
{
public:
Page(CreateInstanceDialog& dlg, std::size_t i)
- : ui(dlg.getUI()), m_dlg(dlg), m_index(i)
+ : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_index(i)
{
}
@@ -25,12 +28,14 @@ public:
protected:
Ui::CreateInstanceDialog* ui;
+ CreateInstanceDialog& m_dlg;
+ const PluginContainer& m_pc;
private:
- CreateInstanceDialog& m_dlg;
std::size_t m_index;
};
+
class TypePage : public Page
{
public:
@@ -88,6 +93,254 @@ public:
GamePage(CreateInstanceDialog& dlg, std::size_t i)
: Page(dlg, i)
{
+ createGames();
+ fillList();
+
+ QObject::connect(ui->showAllGames, &QCheckBox::clicked, [&]{ fillList(); });
+ }
+
+ void select(MOBase::IPluginGame* game)
+ {
+ Game* checked = findGame(game);
+ if (!checked) {
+ return;
+ }
+
+ if (!checked->installed) {
+ const auto path = QFileDialog::getExistingDirectory(
+ &m_dlg, QObject::tr("Find game installation"));
+
+ if (path.isEmpty()) {
+ checked = nullptr;
+ } else {
+ checked = checkInstallation(path, checked);
+ }
+ }
+
+ selectButton(checked);
+ }
+
+private:
+ struct Game
+ {
+ MOBase::IPluginGame* game = nullptr;
+ QCommandLinkButton* button = nullptr;
+ QString dir;
+ bool installed = false;
+
+ Game(MOBase::IPluginGame* g)
+ : game(g), installed(g->isInstalled())
+ {
+ if (installed) {
+ dir = game->gameDirectory().path();
+ }
+ }
+
+ Game(const Game&) = delete;
+ Game& operator=(const Game&) = delete;
+ };
+
+ std::vector> m_games;
+
+
+ Game* findGame(MOBase::IPluginGame* game)
+ {
+ for (auto& g : m_games) {
+ if (g->game == game) {
+ return g.get();
+ }
+ }
+
+ return nullptr;
+ }
+
+ void createGames()
+ {
+ m_games.clear();
+
+ for (auto* game : m_pc.plugins()) {
+ m_games.push_back(std::make_unique(game));
+ }
+ }
+
+ void createButton(Game* g)
+ {
+ g->button = new QCommandLinkButton;
+ g->button->setCheckable(true);
+
+ updateButton(g);
+
+ QObject::connect(g->button, &QAbstractButton::clicked, [g, this] {
+ select(g->game);
+ });
+ }
+
+ void updateButton(Game* g)
+ {
+ if (!g->button) {
+ return;
+ }
+
+ g->button->setText(g->game->gameName());
+
+ if (g->installed) {
+ g->button->setDescription(g->dir);
+ } else {
+ g->button->setDescription(QObject::tr("No installation found"));
+ }
+ }
+
+ void selectButton(Game* g)
+ {
+ for (const auto& gg : m_games) {
+ if (!gg->button) {
+ continue;
+ }
+
+ if (g) {
+ gg->button->setChecked(gg->game == g->game);
+ } else {
+ gg->button->setChecked(false);
+ }
+ }
+ }
+
+ void fillList()
+ {
+ const bool showAll = ui->showAllGames->isChecked();
+
+ ui->games->clear();
+
+ for (auto& g : m_games) {
+ g->button = nullptr;
+
+ if (!showAll && !g->installed) {
+ // not installed
+ continue;
+ }
+
+ createButton(g.get());
+ ui->games->addButton(g->button, QDialogButtonBox::AcceptRole);
+ }
+ }
+
+ Game* checkInstallation(const QString& path, Game* g)
+ {
+ if (g->game->looksValid(path)) {
+ // okay
+ return g;
+ }
+
+ // the selected game can't use that folder, find another one
+ auto* otherGame = findAnotherGame(path);
+ if (otherGame == g->game) {
+ // shouldn't happen, but okay
+ return g;
+ }
+
+ if (otherGame) {
+ auto* confirmedGame = confirmOtherGame(path, g->game, otherGame);
+
+ if (!confirmedGame) {
+ // cancelled
+ return nullptr;
+ }
+
+ // make it look like the user clicked that button instead
+ g = findGame(confirmedGame);
+ if (!g) {
+ return nullptr;
+ }
+ } else {
+ // nothing can manage this, but the user can override
+ if (!confirmUnknown(path, g->game)) {
+ // cancelled
+ return nullptr;
+ }
+ }
+
+ // remember this path
+ g->dir = path;
+ g->installed = true;
+
+ updateButton(g);
+
+ return g;
+ }
+
+ MOBase::IPluginGame* findAnotherGame(const QString& path)
+ {
+ for (auto* otherGame : m_pc.plugins()) {
+ if (otherGame->looksValid(path)) {
+ return otherGame;
+ }
+ }
+
+ return nullptr;
+ }
+
+ bool confirmUnknown(const QString& path, MOBase::IPluginGame* game)
+ {
+ const auto r = MOBase::TaskDialog(&m_dlg)
+ .title(QObject::tr("Unrecognized game"))
+ .main(QObject::tr("Unrecognized game"))
+ .content(QObject::tr(
+ "The folder %1 does not seem to contain installation for "
+ "%2 or "
+ "any other game Mod Organizer can manage.")
+ .arg(path)
+ .arg(game->gameName()))
+ .button({
+ QObject::tr("Use this folder for %1").arg(game->gameName()),
+ QObject::tr("I know what I'm doing"),
+ QMessageBox::Ignore})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ return (r == QMessageBox::Ignore);
+ }
+
+ MOBase::IPluginGame* confirmOtherGame(
+ const QString& path,
+ MOBase::IPluginGame* selectedGame, MOBase::IPluginGame* guessedGame)
+ {
+ const auto r = MOBase::TaskDialog(&m_dlg)
+ .title(QObject::tr("Incorrect game"))
+ .main(QObject::tr("Incorrect game"))
+ .content(QObject::tr(
+ "The folder %1 seems to contain an installation for "
+ "%2, "
+ "not "
+ "%3.")
+ .arg(path)
+ .arg(guessedGame->gameName())
+ .arg(selectedGame->gameName()))
+ .button({
+ QObject::tr("Manage %1 instead").arg(guessedGame->gameName()),
+ QMessageBox::Ok})
+ .button({
+ QObject::tr("Use this folder for %1").arg(selectedGame->gameName()),
+ QObject::tr("I know what I'm doing"),
+ QMessageBox::Ignore})
+ .button({
+ QObject::tr("Cancel"),
+ QMessageBox::Cancel})
+ .exec();
+
+ switch (r)
+ {
+ case QMessageBox::Ok:
+ return guessedGame;
+
+ case QMessageBox::Ignore:
+ return selectedGame;
+
+ case QMessageBox::Cancel:
+ default:
+ return nullptr;
+ }
}
};
@@ -114,8 +367,9 @@ public:
} // namespace
-CreateInstanceDialog::CreateInstanceDialog(QWidget *parent)
- : QDialog(parent), ui(new Ui::CreateInstanceDialog)
+CreateInstanceDialog::CreateInstanceDialog(
+ const PluginContainer& pc, QWidget *parent)
+ : QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc)
{
using namespace cid;
@@ -132,19 +386,6 @@ CreateInstanceDialog::CreateInstanceDialog(QWidget *parent)
connect(ui->next, &QPushButton::clicked, [&]{ next(); });
connect(ui->back, &QPushButton::clicked, [&]{ back(); });
-
- //
- //SelectionDialog games(tr("Select a game to manage."));
- //
- //for (auto* game : m_pc.plugins()) {
- // if (game->isInstalled()) {
- // games.addChoice(game->gameName(), game->gameDirectory().path(), {});
- // } else {
- // games.addChoice(game->gameName(), "", {});
- // }
- //}
- //
- //games.exec();
}
CreateInstanceDialog::~CreateInstanceDialog() = default;
@@ -154,6 +395,11 @@ Ui::CreateInstanceDialog* CreateInstanceDialog::getUI()
return ui.get();
}
+const PluginContainer& CreateInstanceDialog::pluginContainer()
+{
+ return m_pc;
+}
+
void CreateInstanceDialog::next()
{
ui->pages->setCurrentIndex(ui->pages->currentIndex() + 1);
diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h
index 03e9de01..df056f85 100644
--- a/src/createinstancedialog.h
+++ b/src/createinstancedialog.h
@@ -6,22 +6,27 @@
namespace Ui { class CreateInstanceDialog; };
namespace cid { class Page; }
+class PluginContainer;
+
class CreateInstanceDialog : public QDialog
{
Q_OBJECT
public:
- explicit CreateInstanceDialog(QWidget *parent = nullptr);
+ explicit CreateInstanceDialog(
+ const PluginContainer& pc, QWidget *parent = nullptr);
~CreateInstanceDialog();
Ui::CreateInstanceDialog* getUI();
+ const PluginContainer& pluginContainer();
void next();
void back();
private:
std::unique_ptr ui;
+ const PluginContainer& m_pc;
std::vector> m_pages;
void updateNavigationButtons();
diff --git a/src/createinstancedialog.ui b/src/createinstancedialog.ui
index bcfddb20..8cad959d 100644
--- a/src/createinstancedialog.ui
+++ b/src/createinstancedialog.ui
@@ -227,7 +227,60 @@
0
-
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 455
+ 287
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+ 0
+ 0
+
+
+
+ Qt::Vertical
+
+
+ QDialogButtonBox::NoButton
+
+
+
+
+
+
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
index c7042aa8..566f1aad 100644
--- a/src/instancemanagerdialog.cpp
+++ b/src/instancemanagerdialog.cpp
@@ -112,7 +112,7 @@ void InstanceManagerDialog::onSelection()
void InstanceManagerDialog::createNew()
{
- CreateInstanceDialog dlg(this);
+ CreateInstanceDialog dlg(m_pc, this);
dlg.exec();
}
--
cgit v1.3.1
From e0ed1c2153be0a9f922a619c527b3646b40377eb Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Thu, 23 Jul 2020 09:45:59 -0400
Subject: select custom game directory
---
src/createinstancedialog.cpp | 119 ++++++++++++++++++++++++++++++++++++-------
1 file changed, 100 insertions(+), 19 deletions(-)
(limited to 'src')
diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp
index 21dcd8ec..79eaa79a 100644
--- a/src/createinstancedialog.cpp
+++ b/src/createinstancedialog.cpp
@@ -91,7 +91,7 @@ class GamePage : public Page
{
public:
GamePage(CreateInstanceDialog& dlg, std::size_t i)
- : Page(dlg, i)
+ : Page(dlg, i), m_selection(nullptr)
{
createGames();
fillList();
@@ -117,9 +117,49 @@ public:
}
}
+ m_selection = checked;
selectButton(checked);
}
+ void selectCustom()
+ {
+ const auto path = QFileDialog::getExistingDirectory(
+ &m_dlg, QObject::tr("Find game installation"));
+
+ if (path.isEmpty()) {
+ selectButton(m_selection);
+ return;
+ }
+
+ for (auto& g : m_games) {
+ if (g->game->looksValid(path)) {
+ g->dir = path;
+ g->installed = true;
+ select(g->game);
+ updateButton(g.get());
+ return;
+ }
+ }
+
+ warnUnrecognized(path);
+ selectButton(m_selection);
+ }
+
+ void warnUnrecognized(const QString& path)
+ {
+ QString supportedGames;
+ for (auto* game : m_pc.plugins()) {
+ supportedGames += "
" + game->gameName() + "
";
+ }
+
+ QMessageBox::warning(&m_dlg,
+ QObject::tr("Unrecognized game"),
+ QObject::tr(
+ "The folder %1 does not seem to contain a game Mod Organizer can "
+ "manage.