summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/createinstancedialog.cpp69
-rw-r--r--src/createinstancedialog.h37
-rw-r--r--src/createinstancedialogpages.cpp65
-rw-r--r--src/createinstancedialogpages.h32
-rw-r--r--src/envshortcut.cpp2
-rw-r--r--src/instancemanager.cpp707
-rw-r--r--src/instancemanager.h66
-rw-r--r--src/instancemanagerdialog.cpp50
-rw-r--r--src/instancemanagerdialog.h3
-rw-r--r--src/main.cpp263
-rw-r--r--src/organizercore.cpp8
-rw-r--r--src/processrunner.cpp13
-rw-r--r--src/shared/appconfig.inc1
-rw-r--r--src/statusbar.cpp7
14 files changed, 695 insertions, 628 deletions
diff --git a/src/createinstancedialog.cpp b/src/createinstancedialog.cpp
index 0f62fcf6..d67e3451 100644
--- a/src/createinstancedialog.cpp
+++ b/src/createinstancedialog.cpp
@@ -13,7 +13,7 @@ using namespace MOBase;
CreateInstanceDialog::CreateInstanceDialog(
const PluginContainer& pc, Settings* s, QWidget *parent) :
QDialog(parent), ui(new Ui::CreateInstanceDialog), m_pc(pc), m_settings(s),
- m_switching(false)
+ m_switching(false), m_singlePage(false)
{
using namespace cid;
@@ -23,7 +23,7 @@ CreateInstanceDialog::CreateInstanceDialog(
m_pages.push_back(std::make_unique<IntroPage>(*this));
m_pages.push_back(std::make_unique<TypePage>(*this));
m_pages.push_back(std::make_unique<GamePage>(*this));
- m_pages.push_back(std::make_unique<EditionsPage>(*this));
+ m_pages.push_back(std::make_unique<VariantsPage>(*this));
m_pages.push_back(std::make_unique<NamePage>(*this));
m_pages.push_back(std::make_unique<PathsPage>(*this));
m_pages.push_back(std::make_unique<NexusPage>(*this));
@@ -32,6 +32,12 @@ CreateInstanceDialog::CreateInstanceDialog(
ui->pages->setCurrentIndex(0);
ui->launch->setChecked(true);
+ if (!m_settings)
+ {
+ // first run of MO, there are no instances yet, force launch
+ ui->launch->setEnabled(false);
+ }
+
if (m_pages[0]->skip()) {
next();
}
@@ -77,7 +83,12 @@ void CreateInstanceDialog::next()
const auto last = isOnLastPage();
if (last) {
- finish();
+ if (m_singlePage) {
+ // just close the dialog
+ accept();
+ } else {
+ finish();
+ }
} else {
changePage(+1);
}
@@ -88,6 +99,15 @@ void CreateInstanceDialog::back()
changePage(-1);
}
+void CreateInstanceDialog::setSinglePageImpl()
+{
+ m_singlePage = true;
+
+ if (m_pages[ui->pages->currentIndex()]->skip()) {
+ next();
+ }
+}
+
void CreateInstanceDialog::changePage(int d)
{
std::size_t i = static_cast<std::size_t>(ui->pages->currentIndex());
@@ -247,8 +267,8 @@ void CreateInstanceDialog::finish()
s.game().setName(ci.game->gameName());
s.game().setDirectory(ci.gameLocation);
- if (!ci.gameEdition.isEmpty()) {
- s.game().setEdition(ci.gameEdition);
+ if (!ci.gameVariant.isEmpty()) {
+ s.game().setEdition(ci.gameVariant);
}
if (ci.type == Global) {
@@ -307,7 +327,8 @@ void CreateInstanceDialog::finish()
}
if (ui->launch->isChecked()) {
- InstanceManager::instance().switchToInstance(ci.instanceName);
+ InstanceManager::instance().setCurrentInstance(ci.instanceName);
+ ExitModOrganizer(Exit::Restart);
m_switching = true;
} else {
accept();
@@ -345,8 +366,8 @@ void CreateInstanceDialog::updateNavigation()
const auto i = ui->pages->currentIndex();
const auto last = isOnLastPage();
- ui->next->setEnabled(m_pages[i]->ready());
- ui->back->setEnabled(i > 0);
+ ui->next->setEnabled(canNext());
+ ui->back->setEnabled(canBack());
if (last) {
ui->next->setText(tr("Finish"));
@@ -355,6 +376,32 @@ void CreateInstanceDialog::updateNavigation()
}
}
+bool CreateInstanceDialog::canNext() const
+{
+ const auto i = ui->pages->currentIndex();
+ return m_pages[i]->ready();
+}
+
+bool CreateInstanceDialog::canBack() const
+{
+ auto i = ui->pages->currentIndex();
+
+ for (;;)
+ {
+ if (i == 0) {
+ break;
+ }
+
+ --i;
+
+ if (!m_pages[i]->skip()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
CreateInstanceDialog::Types CreateInstanceDialog::instanceType() const
{
return getSelected(&cid::Page::selectedInstanceType);
@@ -370,9 +417,9 @@ QString CreateInstanceDialog::gameLocation() const
return getSelected(&cid::Page::selectedGameLocation);
}
-QString CreateInstanceDialog::gameEdition() const
+QString CreateInstanceDialog::gameVariant() const
{
- return getSelected(&cid::Page::selectedGameEdition);
+ return getSelected(&cid::Page::selectedGameVariant);
}
QString CreateInstanceDialog::instanceName() const
@@ -433,7 +480,7 @@ CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const
ci.type = getSelected(&cid::Page::selectedInstanceType);
ci.game = getSelected(&cid::Page::selectedGame);
ci.gameLocation = getSelected(&cid::Page::selectedGameLocation);
- ci.gameEdition = getSelected(&cid::Page::selectedGameEdition);
+ ci.gameVariant = getSelected(&cid::Page::selectedGameVariant);
ci.instanceName = getSelected(&cid::Page::selectedInstanceName);
ci.paths = getSelected(&cid::Page::selectedPaths);
ci.dataPath = dataPath();
diff --git a/src/createinstancedialog.h b/src/createinstancedialog.h
index 0841ef29..6947f2e2 100644
--- a/src/createinstancedialog.h
+++ b/src/createinstancedialog.h
@@ -39,7 +39,7 @@ public:
Types type;
MOBase::IPluginGame* game;
QString gameLocation;
- QString gameEdition;
+ QString gameVariant;
QString instanceName;
QString dataPath;
QString iniPath;
@@ -57,6 +57,32 @@ public:
const PluginContainer& pluginContainer();
Settings* settings();
+ template <class Page>
+ void setSinglePage()
+ {
+ for (auto&& p : m_pages) {
+ if (auto* tp=dynamic_cast<Page*>(p.get())) {
+ tp->setSkip(false);
+ } else {
+ p->setSkip(true);
+ }
+ }
+
+ setSinglePageImpl();
+ }
+
+ template <class Page>
+ Page* getPage()
+ {
+ for (auto&& p : m_pages) {
+ if (auto* tp=dynamic_cast<Page*>(p.get())) {
+ return tp;
+ }
+ }
+
+ return nullptr;
+ }
+
void next();
void back();
void selectPage(std::size_t i);
@@ -69,7 +95,7 @@ public:
Types instanceType() const;
MOBase::IPluginGame* game() const;
QString gameLocation() const;
- QString gameEdition() const;
+ QString gameVariant() const;
QString instanceName() const;
QString dataPath() const;
Paths paths() const;
@@ -84,6 +110,10 @@ private:
std::vector<std::unique_ptr<cid::Page>> m_pages;
QString m_originalNext;
bool m_switching;
+ bool m_singlePage;
+
+
+ void setSinglePageImpl();
template <class T>
T getSelected(T (cid::Page::*mf)() const) const
@@ -100,6 +130,9 @@ private:
void logCreation(const QString& s);
void logCreation(const std::wstring& s);
+
+ bool canNext() const;
+ bool canBack() const;
};
#endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED
diff --git a/src/createinstancedialogpages.cpp b/src/createinstancedialogpages.cpp
index d809079d..00d49b99 100644
--- a/src/createinstancedialogpages.cpp
+++ b/src/createinstancedialogpages.cpp
@@ -43,7 +43,7 @@ void PlaceholderLabel::setVisible(bool b)
Page::Page(CreateInstanceDialog& dlg)
- : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer())
+ : ui(dlg.getUI()), m_dlg(dlg), m_pc(dlg.pluginContainer()), m_skip(false)
{
}
@@ -54,7 +54,11 @@ bool Page::ready() const
bool Page::skip() const
{
- // no-op
+ return m_skip || doSkip();
+}
+
+bool Page::doSkip() const
+{
return false;
}
@@ -63,6 +67,11 @@ void Page::activated()
// no-op
}
+void Page::setSkip(bool b)
+{
+ m_skip = b;
+}
+
void Page::updateNavigation()
{
m_dlg.updateNavigation();
@@ -92,7 +101,7 @@ QString Page::selectedGameLocation() const
return {};
}
-QString Page::selectedGameEdition() const
+QString Page::selectedGameVariant() const
{
// no-op
return {};
@@ -116,7 +125,7 @@ IntroPage::IntroPage(CreateInstanceDialog& dlg)
{
}
-bool IntroPage::skip() const
+bool IntroPage::doSkip() const
{
return GlobalSettings::hideCreateInstanceIntro();
}
@@ -223,19 +232,24 @@ QString GamePage::selectedGameLocation() const
return QDir::toNativeSeparators(m_selection->dir);
}
-void GamePage::select(IPluginGame* game)
+void GamePage::select(IPluginGame* game, const QString& dir)
{
Game* checked = findGame(game);
if (checked) {
if (!checked->installed) {
- const auto path = QFileDialog::getExistingDirectory(
- &m_dlg, QObject::tr("Find game installation"));
+ if (dir.isEmpty()) {
+ const auto path = QFileDialog::getExistingDirectory(
+ &m_dlg, QObject::tr("Find game installation"));
- if (path.isEmpty()) {
- checked = nullptr;
+ if (path.isEmpty()) {
+ checked = nullptr;
+ } else {
+ checked = checkInstallation(path, checked);
+ }
} else {
- checked = checkInstallation(path, checked);
+ checked->dir = dir;
+ checked->installed = true;
}
}
}
@@ -417,7 +431,6 @@ void GamePage::fillList()
const bool showAll = ui->showAllGames->isChecked();
clearButtons();
- addButton(createCustomButton());
for (auto& g : m_games) {
g->button = nullptr;
@@ -434,6 +447,8 @@ void GamePage::fillList()
createGameButton(g.get());
addButton(g->button);
}
+
+ addButton(createCustomButton());
}
void GamePage::clearButtons()
@@ -584,17 +599,17 @@ IPluginGame* GamePage::confirmOtherGame(
}
-EditionsPage::EditionsPage(CreateInstanceDialog& dlg)
+VariantsPage::VariantsPage(CreateInstanceDialog& dlg)
: Page(dlg), m_previousGame(nullptr)
{
}
-bool EditionsPage::ready() const
+bool VariantsPage::ready() const
{
return !m_selection.isEmpty();
}
-bool EditionsPage::skip() const
+bool VariantsPage::doSkip() const
{
auto* g = m_dlg.game();
if (!g) {
@@ -606,7 +621,7 @@ bool EditionsPage::skip() const
return (variants.size() < 2);
}
-void EditionsPage::activated()
+void VariantsPage::activated()
{
auto* g = m_dlg.game();
@@ -617,7 +632,7 @@ void EditionsPage::activated()
}
}
-void EditionsPage::select(const QString& variant)
+void VariantsPage::select(const QString& variant)
{
for (auto* b : m_buttons) {
if (b->text() == variant) {
@@ -629,9 +644,13 @@ void EditionsPage::select(const QString& variant)
}
updateNavigation();
+
+ if (!m_selection.isEmpty()) {
+ next();
+ }
}
-QString EditionsPage::selectedGameEdition() const
+QString VariantsPage::selectedGameVariant() const
{
auto* g = m_dlg.game();
if (!g) {
@@ -647,7 +666,7 @@ QString EditionsPage::selectedGameEdition() const
}
}
-void EditionsPage::fillList()
+void VariantsPage::fillList()
{
ui->editions->clear();
m_buttons.clear();
@@ -665,7 +684,7 @@ void EditionsPage::fillList()
QObject::connect(b, &QAbstractButton::clicked, [v, this] {
select(v);
- });
+ });
ui->editions->addButton(b, QDialogButtonBox::AcceptRole);
m_buttons.push_back(b);
@@ -687,7 +706,7 @@ bool NamePage::ready() const
return m_okay;
}
-bool NamePage::skip() const
+bool NamePage::doSkip() const
{
return (m_dlg.instanceType() == CreateInstanceDialog::Portable);
}
@@ -983,7 +1002,7 @@ bool NexusPage::ready() const
return true;
}
-bool NexusPage::skip() const
+bool NexusPage::doSkip() const
{
return m_skip;
}
@@ -1047,8 +1066,8 @@ QString ConfirmationPage::makeReview() const
// game
QString name = m_dlg.game()->gameName();
- if (!m_dlg.gameEdition().isEmpty()) {
- name += " (" + m_dlg.gameEdition() + ")";
+ if (!m_dlg.gameVariant().isEmpty()) {
+ name += " (" + m_dlg.gameVariant() + ")";
}
lines.push_back(QObject::tr("Game: %1").arg(name));
diff --git a/src/createinstancedialogpages.h b/src/createinstancedialogpages.h
index 412f2d84..e239c196 100644
--- a/src/createinstancedialogpages.h
+++ b/src/createinstancedialogpages.h
@@ -37,16 +37,18 @@ public:
Page(CreateInstanceDialog& dlg);
virtual bool ready() const;
- virtual bool skip() const;
virtual void activated();
+ void setSkip(bool b);
+ bool skip() const;
+
void updateNavigation();
void next();
virtual CreateInstanceDialog::Types selectedInstanceType() const;
virtual MOBase::IPluginGame* selectedGame() const;
virtual QString selectedGameLocation() const;
- virtual QString selectedGameEdition() const;
+ virtual QString selectedGameVariant() const;
virtual QString selectedInstanceName() const;
virtual CreateInstanceDialog::Paths selectedPaths() const;
@@ -54,6 +56,9 @@ protected:
Ui::CreateInstanceDialog* ui;
CreateInstanceDialog& m_dlg;
const PluginContainer& m_pc;
+ bool m_skip;
+
+ virtual bool doSkip() const;
};
@@ -62,7 +67,8 @@ class IntroPage : public Page
public:
IntroPage(CreateInstanceDialog& dlg);
- bool skip() const override;
+protected:
+ bool doSkip() const override;
};
@@ -91,7 +97,7 @@ public:
MOBase::IPluginGame* selectedGame() const override;
QString selectedGameLocation() const override;
- void select(MOBase::IPluginGame* game);
+ void select(MOBase::IPluginGame* game, const QString& dir={});
void selectCustom();
void warnUnrecognized(const QString& path);
@@ -133,18 +139,20 @@ private:
};
-class EditionsPage : public Page
+class VariantsPage : public Page
{
public:
- EditionsPage(CreateInstanceDialog& dlg);
+ VariantsPage(CreateInstanceDialog& dlg);
bool ready() const override;
- bool skip() const override;
void activated() override;
- QString selectedGameEdition() const override;
+ QString selectedGameVariant() const override;
void select(const QString& variant);
+protected:
+ bool doSkip() const override;
+
private:
MOBase::IPluginGame* m_previousGame;
std::vector<QCommandLinkButton*> m_buttons;
@@ -160,10 +168,12 @@ public:
NamePage(CreateInstanceDialog& dlg);
bool ready() const override;
- bool skip() const override;
void activated() override;
QString selectedInstanceName() const override;
+protected:
+ bool doSkip() const override;
+
private:
mutable PlaceholderLabel m_label, m_exists, m_invalid;
bool m_modified;
@@ -212,9 +222,11 @@ public:
~NexusPage();
bool ready() const override;
- bool skip() const override;
void activated() override;
+protected:
+ bool doSkip() const override;
+
private:
std::unique_ptr<NexusConnectionUI> m_connectionUI;
bool m_skip;
diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp
index 99495c39..5222665b 100644
--- a/src/envshortcut.cpp
+++ b/src/envshortcut.cpp
@@ -149,7 +149,7 @@ Shortcut::Shortcut(const Executable& exe)
m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath();
m_arguments = QString("\"moshortcut://%1:%2\"")
- .arg(InstanceManager::instance().currentInstance())
+ .arg(InstanceManager::instance().currentInstance()->name())
.arg(exe.title());
m_description = QString("Run %1 with ModOrganizer").arg(exe.title());
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index 4ad099ed..c79c5254 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -38,257 +38,320 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
-InstanceManager::InstanceManager()
+Instance::Instance(QDir dir, bool portable, QString profileName) :
+ m_dir(std::move(dir)), m_portable(portable), m_plugin(nullptr),
+ m_profile(std::move(profileName))
{
- GlobalSettings::updateRegistryKey();
}
-InstanceManager &InstanceManager::instance()
+QString Instance::name() const
{
- static InstanceManager s_Instance;
- return s_Instance;
+ if (isPortable())
+ return QObject::tr("Portable");
+ else
+ return m_dir.dirName();
}
-void InstanceManager::overrideInstance(const QString& instanceName)
+QString Instance::gameName() const
{
- m_overrideInstanceName = instanceName;
- m_overrideInstance = true;
+ return m_gameName;
}
-void InstanceManager::overrideProfile(const QString& profileName)
+QString Instance::gameDirectory() const
{
- m_overrideProfileName = profileName;
- m_overrideProfile = true;
+ return m_gameDir;
}
-QString InstanceManager::currentInstance() const
+QDir Instance::directory() const
{
- if (m_overrideInstance)
- return m_overrideInstanceName;
- else
- return GlobalSettings::currentInstance();
+ return m_dir;
}
-void InstanceManager::clearCurrentInstance()
+MOBase::IPluginGame* Instance::gamePlugin() const
{
- setCurrentInstance("");
- m_Reset = true;
- m_overrideInstance = false;
+ return m_plugin;
}
-void InstanceManager::switchToInstance(const QString& instanceName)
+QString Instance::profileName() const
{
- setCurrentInstance(instanceName);
- ExitModOrganizer(Exit::Restart);
+ return m_profile;
}
-void InstanceManager::setCurrentInstance(const QString &name)
+QString Instance::iniPath() const
{
- GlobalSettings::setCurrentInstance(name);
+ return InstanceManager::iniPath(m_dir);
}
-bool InstanceManager::deleteLocalInstance(const QString& instanceId) const
+bool Instance::isPortable() const
{
- QString dir = instancePath(instanceId);
+ return m_portable;
+}
- const auto Recycle = QMessageBox::Save;
- const auto Delete = QMessageBox::Yes;
- const auto Cancel = QMessageBox::Cancel;
+Instance::SetupResults Instance::setup(PluginContainer& plugins)
+{
+ Settings s(iniPath());
- 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();
+ if (s.iniStatus() != QSettings::NoError) {
+ log::error("can't read ini {}", iniPath());
+ return SetupResults::BadIni;
+ }
- std::wstring error;
+ if (m_gameName.isEmpty()) {
+ if (auto v=s.game().name())
+ m_gameName = *v;
+ }
- switch (r)
- {
- case Recycle:
- {
- if (MOBase::shellDelete(QStringList(dir), true)) {
- return true;
- }
+ if (m_gameDir.isEmpty()) {
+ if (auto v=s.game().directory())
+ m_gameDir = *v;
+ }
- const auto e = GetLastError();
- error = formatSystemMessage(e);
- log::warn("failed to move to trash '{}', {}", dir, error);
+ const auto r = getGamePlugin(plugins);
+ if (r != SetupResults::Ok) {
+ return r;
+ }
- break;
+ if (m_gameVariant.isEmpty()) {
+ if (auto v=s.game().edition()) {
+ m_gameVariant = *v;
}
+ }
- case Delete:
- {
- if (MOBase::shellDelete(QStringList(dir), false)) {
- return true;
- }
+ if (m_gameVariant.isEmpty() && m_plugin->gameVariants().size() > 1) {
+ return SetupResults::MissingVariant;
+ } else {
+ m_plugin->setGameVariant(m_gameVariant);
+ }
- const auto e = GetLastError();
- error = formatSystemMessage(e);
- log::warn("failed to delete '{}', {}", dir, error);
+ getProfile(s);
- break;
- }
+ s.game().setName(m_gameName);
+ s.game().setDirectory(m_gameDir);
+ s.game().setSelectedProfileName(m_profile);
- default:
- {
- return true;
- }
- }
+ if (!m_gameVariant.isEmpty())
+ s.game().setEdition(m_gameVariant);
- QMessageBox::critical(
- nullptr, QObject::tr("Error"), QObject::tr(
- "Could not delete instance folder \"%1\".\n\n%2")
- .arg(dir).arg(error),
- QMessageBox::Ok);
+ m_plugin->setGamePath(m_gameDir);
- return false;
+ return SetupResults::Ok;
}
-QString InstanceManager::manageInstances(const QStringList &instanceList) const
+void Instance::setGame(const QString& name, const QString& dir)
{
- SelectionDialog selection(QString("<h3>%1</h3><br>%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.<br><br>"
- "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(instanceNames()));
- }
- else {
- QString choice = selection.getChoiceData().toString();
- deleteLocalInstance(choice);
- }
+ m_gameName = name;
+ m_gameDir = dir;
+}
- return(manageInstances(instanceNames()));
+void Instance::setVariant(const QString& name)
+{
+ m_gameVariant = name;
}
-QString InstanceManager::queryInstanceName(const QStringList &instanceList) const
+Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins)
{
- QString instanceId;
- QString dialogText;
- while (instanceId.isEmpty()) {
- QInputDialog dialog;
+ if (!m_gameName.isEmpty() && !m_gameDir.isEmpty())
+ {
+ // normal case: both the name and dir are in the ini
- dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance"));
- dialog.setLabelText(QObject::tr("Enter a new name or select one from the suggested list: \n"
- "(This is just a name for the Instance and can be whatever you wish,\n"
- " the actual game selection will happen on the next screen regardless of chosen name)"));
- // would be neat if we could take the names from the game plugins but
- // the required initialization order requires the ini file to be
- // available *before* we load plugins
- dialog.setComboBoxItems({ "NewName", "Fallout 4", "SkyrimSE", "Skyrim", "SkyrimVR", "Fallout 3",
- "Fallout NV", "TTW", "FO4VR", "Oblivion", "Morrowind", "Enderal" });
- dialog.setComboBoxEditable(true);
+ // find the plugin by name
+ for (IPluginGame* game : plugins.plugins<IPluginGame>()) {
+ if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) {
+ // plugin found, check if the game directory is valid
- if (dialog.exec() == QDialog::Rejected) {
- throw MOBase::MyException(QObject::tr("Canceled"));
- }
- dialogText = dialog.textValue();
- instanceId = sanitizeInstanceName(dialogText);
- if (instanceId != dialogText) {
- if (QMessageBox::question( nullptr,
- QObject::tr("Invalid instance name"),
- QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
- instanceId="";
- continue;
+ if (!game->looksValid(m_gameDir)) {
+ // the directory from the ini is not valid anymore
+ log::warn(
+ "game plugin {} says dir {} from ini {} is not valid",
+ game->gameName(), m_gameDir, iniPath());
+
+ // note that some plugins return true for isInstalled() if a path
+ // is found in the registry, but without actually checking if it's
+ // valid
+
+ if (game->isInstalled() && game->looksValid(game->gameDirectory())) {
+ // bad game directory but the plugin reports there's a valid one
+ // somewhere; take it instead
+ log::warn(
+ "game plugin {} found a game at {}, taking it",
+ game->gameName(), game->gameDirectory().absolutePath());
+
+ m_gameDir = game->gameDirectory().absolutePath();
+ } else {
+ // game seems to be gone completely
+ log::warn("game plugin {} found no game installation at all", game->gameName());
+ return SetupResults::GameGone;
+ }
}
+
+ m_plugin = game;
+ return SetupResults::Ok;
+ }
}
- bool alreadyExists=false;
- for (const QString &instance : instanceList) {
- if(instanceId==instance)
- alreadyExists=true;
+ log::warn("game plugin {} not found", m_gameName);
+ return SetupResults::PluginGone;
+ }
+ else if (m_gameName.isEmpty() && !m_gameDir.isEmpty())
+ {
+ // the name is missing, but there's a directory; find a plugin that can
+ // handle it
+
+ log::warn(
+ "game name is missing from ini {} but dir {} is available",
+ iniPath(), m_gameDir);
+
+ for (IPluginGame* game : plugins.plugins<IPluginGame>()) {
+ if (game->looksValid(m_gameDir)) {
+ // take it
+ log::warn("found plugin {} that can use dir {}", game->gameName(), m_gameDir);
+
+ m_plugin = game;
+ m_gameName = game->gameName();
+
+ return SetupResults::Ok;
+ }
}
- if(alreadyExists)
- {
- QMessageBox msgBox;
- msgBox.setText( QObject::tr("The instance \"%1\" already exists.").arg(instanceId) );
- msgBox.setInformativeText(QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId));
- msgBox.exec();
- instanceId="";
+
+ log::error("no plugins can use dir {}", m_gameDir);
+ return SetupResults::GameGone;
+ }
+ else if (!m_gameName.isEmpty() && m_gameDir.isEmpty())
+ {
+ // dir is missing, find a plugin with the correct name and use the install
+ // dir it detected
+
+ log::warn(
+ "game dir is missing from ini {} but name {} is available",
+ iniPath(), m_gameName);
+
+ for (IPluginGame* game : plugins.plugins<IPluginGame>()) {
+ if (m_gameName.compare(game->gameName(), Qt::CaseInsensitive) == 0) {
+ // plugin found, use its detected installation dir
+
+ if (game->isInstalled()) {
+ log::warn(
+ "found plugin {} that matches name in ini {}, using auto detected "
+ "game dir {}",
+ game->gameName(), iniPath(), game->gameDirectory().absolutePath());
+
+ m_plugin = game;
+ m_gameDir = game->gameDirectory().absolutePath();
+
+ return SetupResults::Ok;
+ } else {
+ log::warn(
+ "found plugin {} that matches name in ini {}, but no game install "
+ "detected by plugin",
+ game->gameName(), iniPath());
+
+ return SetupResults::GameGone;
+ }
+ }
}
+
+ // plugin seems to be gone
+ log::error("no plugin matches name {}", m_gameName);
+ return SetupResults::PluginGone;
+ }
+ else
+ {
+ // can't do anything with these two missing
+ log::error("both game name and dir are missing from ini {}", iniPath());
+ return SetupResults::IniMissingGame;
}
- return instanceId;
}
-QString InstanceManager::chooseInstance(const QStringList &instanceList) const
+void Instance::getProfile(const Settings& s)
{
- if (portableInstallIsLocked()) {
- return QString();
+ if (!m_profile.isEmpty()) {
+ // there's already a profile set up, probably an override
+ return;
}
- enum class Special : uint8_t {
- NewInstance,
- Portable,
- Manage
- };
-
- SelectionDialog selection(
- QString("<h3>%1</h3><br>%2")
- .arg(QObject::tr("Choose Instance"))
- .arg(QObject::tr(
- "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).")),
- nullptr);
- selection.disableCancel();
- for (const QString &instance : instanceList) {
- selection.addChoice(instance, "", instance);
+ if (auto name=s.game().selectedProfileName()) {
+ // use last profile
+ m_profile = *name;
+ return;
}
- selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"),
- QObject::tr("Create a new instance."),
- static_cast<uint8_t>(Special::NewInstance));
+ // profile missing from ini, use the default
+ m_profile = QString::fromStdWString(AppConfig::defaultProfileName());
- if (QFileInfo(qApp->applicationDirPath()).isWritable()) {
- selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"),
- QObject::tr("Use MO folder for data."),
- static_cast<uint8_t>(Special::Portable));
- }
+ log::warn(
+ "no profile found in ini {}, using default '{}'",
+ iniPath(), m_profile);
+}
- selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"),
- QObject::tr("Delete an Instance."),
- static_cast<uint8_t>(Special::Manage));
- selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint);
+InstanceManager::InstanceManager()
+{
+ GlobalSettings::updateRegistryKey();
+}
+
+InstanceManager &InstanceManager::instance()
+{
+ static InstanceManager s_Instance;
+ return s_Instance;
+}
- if (selection.exec() == QDialog::Rejected) {
- log::debug("rejected");
- throw MOBase::MyException(QObject::tr("Canceled"));
+void InstanceManager::overrideInstance(const QString& instanceName)
+{
+ m_overrideInstanceName = instanceName;
+ m_overrideInstance = true;
+}
+
+void InstanceManager::overrideProfile(const QString& profileName)
+{
+ m_overrideProfileName = profileName;
+ m_overrideProfile = true;
+}
+
+std::optional<Instance> InstanceManager::currentInstance() const
+{
+ const QString profile = m_overrideProfile ? m_overrideProfileName : "";
+
+ if (portableInstallIsLocked()) {
+ // force portable instance
+ return Instance(QDir(portablePath()), true, profile);
}
- QVariant choice = selection.getChoiceData();
+ QString name;
- if (choice.type() == QVariant::String) {
- return choice.toString();
- } else {
- switch (static_cast<Special>(choice.value<uint8_t>())) {
- case Special::NewInstance: return queryInstanceName(instanceList);
- case Special::Portable: return QString();
- case Special::Manage: {
+ if (m_overrideInstance)
+ name = m_overrideInstanceName;
+ else
+ name = GlobalSettings::currentInstance();
- return(manageInstances(instanceNames()));
- }
- default: throw std::runtime_error("invalid selection");
+ if (name.isEmpty()) {
+ if (portableInstanceExists()) {
+ // use portable
+ return Instance(QDir(portablePath()), true, profile);
+ } else {
+ // no instance set
+ return {};
}
}
+
+ QString path = instancePath(name);
+ if (!QFileInfo::exists(path)) {
+ // the previously used instance doesn't exist anymore
+ return {};
+ }
+
+ return Instance(QDir(path), false, profile);
+}
+
+void InstanceManager::clearCurrentInstance()
+{
+ setCurrentInstance("");
+ m_overrideInstance = false;
+}
+
+void InstanceManager::setCurrentInstance(const QString &name)
+{
+ GlobalSettings::setCurrentInstance(name);
}
QString InstanceManager::instancePath(const QString& instanceName) const
@@ -302,6 +365,11 @@ QString InstanceManager::instancesPath() const
QStandardPaths::writableLocation(QStandardPaths::DataLocation));
}
+QString InstanceManager::iniPath(const QDir& instanceDir)
+{
+ return instanceDir.filePath(QString::fromStdWString(AppConfig::iniFileName()));
+}
+
std::vector<QDir> InstanceManager::instancePaths() const
{
const std::set<QString> ignore = {
@@ -362,287 +430,10 @@ bool InstanceManager::allowedToChangeInstance() const
return !portableInstallIsLocked();
}
-
-void InstanceManager::createDataPath(const QString &dataPath) const
-{
- if (!QDir(dataPath).exists()) {
- if (!QDir().mkpath(dataPath)) {
- throw MOBase::MyException(
- QObject::tr("failed to create %1").arg(dataPath));
- } else {
- QMessageBox::information(
- nullptr, QObject::tr("Data directory created"),
- QObject::tr("New data directory created at %1. If you don't want to "
- "store a lot of data there, reconfigure the storage "
- "directories via settings.").arg(dataPath));
- }
- }
-}
-
-
-QString InstanceManager::determineDataPath()
-{
- QString instanceId = currentInstance();
- if (portableInstallIsLocked())
- {
- instanceId.clear();
- }
- if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstanceExists()))
- {
- // startup, apparently using portable mode before
- return qApp->applicationDirPath();
- }
-
- QString dataPath = QDir::fromNativeSeparators(
- QStandardPaths::writableLocation(QStandardPaths::DataLocation)
- + "/" + instanceId);
-
-
- if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) {
- instanceId = chooseInstance(instanceNames());
- setCurrentInstance(instanceId);
- if (!instanceId.isEmpty()) {
- dataPath = QDir::fromNativeSeparators(
- QStandardPaths::writableLocation(QStandardPaths::DataLocation)
- + "/" + instanceId);
- }
- }
-
- if (instanceId.isEmpty()) {
- return qApp->applicationDirPath();
- } else {
- createDataPath(dataPath);
-
- return dataPath;
- }
-}
-
-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<IPluginGame>()) {
- //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<IPluginGame *>(nullptr)));
-
- while (selection.exec() != QDialog::Rejected) {
- IPluginGame * game = selection.getChoiceData().value<IPluginGame *>();
- 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<IPluginGame *> possibleGames;
- for (IPluginGame * const game : plugins.plugins<IPluginGame>()) {
- //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<IPluginGame *>());
- } 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<IPluginGame>()) {
- supportedGames += "<li>" + game->gameName() + "</li>";
- }
-
- QString text = QObject::tr(
- "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>")
- .arg(gamePath)
- .arg(supportedGames);
-
- reportError(text);
- }
- }
- }
- }
-
- return nullptr;
-}
-
const MOBase::IPluginGame* InstanceManager::gamePluginForDirectory(
const QDir& instanceDir, const PluginContainer& plugins) const
{
- const QString ini =
- QDir(instanceDir).filePath(QString::fromStdWString(AppConfig::iniFileName()));
-
+ const QString ini = iniPath(instanceDir);
Settings s(ini);
diff --git a/src/instancemanager.h b/src/instancemanager.h
index c2d1e0f4..ddab4a2e 100644
--- a/src/instancemanager.h
+++ b/src/instancemanager.h
@@ -29,29 +29,61 @@ namespace MOBase { class IPluginGame; }
class Settings;
class PluginContainer;
+
+class Instance
+{
+public:
+ enum class SetupResults
+ {
+ Ok,
+ BadIni,
+ IniMissingGame,
+ PluginGone,
+ GameGone,
+ MissingVariant
+ };
+
+ Instance(QDir dir, bool portable, QString profileName={});
+
+ SetupResults setup(PluginContainer& plugins);
+
+ void setGame(const QString& name, const QString& dir);
+ void setVariant(const QString& name);
+
+ QString name() const;
+ QString gameName() const;
+ QString gameDirectory() const;
+ QDir directory() const;
+ MOBase::IPluginGame* gamePlugin() const;
+ QString profileName() const;
+ QString iniPath() const;
+ bool isPortable() const;
+
+private:
+ QDir m_dir;
+ bool m_portable;
+ QString m_gameName, m_gameDir, m_gameVariant;
+ MOBase::IPluginGame* m_plugin;
+ QString m_profile;
+
+ SetupResults getGamePlugin(PluginContainer& plugins);
+ void getProfile(const Settings& s);
+};
+
+
class InstanceManager
{
public:
static InstanceManager &instance();
- // restarts MO
- //
- void switchToInstance(const QString& instanceName);
-
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);
-
const MOBase::IPluginGame* gamePluginForDirectory(
const QDir& dir, const PluginContainer& plugins) const;
void clearCurrentInstance();
- QString currentInstance() const;
+ std::optional<Instance> currentInstance() const;
void setCurrentInstance(const QString &name);
bool allowedToChangeInstance() const;
@@ -68,23 +100,13 @@ public:
bool instanceExists(const QString& instanceName) const;
bool validInstanceName(const QString& instanceName) const;
QString instancePath(const QString& instanceName) const;
+ static QString iniPath(const QDir& instanceDir);
private:
-
InstanceManager();
-
- bool deleteLocalInstance(const QString &instanceId) const;
-
- QString manageInstances(const QStringList &instanceList) const;
-
- QString queryInstanceName(const QStringList &instanceList) const;
- QString chooseInstance(const QStringList &instanceList) const;
-
- void createDataPath(const QString &dataPath) const;
bool portableInstallIsLocked() const;
private:
- bool m_Reset {false};
bool m_overrideInstance{false};
QString m_overrideInstanceName;
bool m_overrideProfile{false};
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
index 545b5c71..f662082f 100644
--- a/src/instancemanagerdialog.cpp
+++ b/src/instancemanagerdialog.cpp
@@ -6,6 +6,7 @@
#include "selectiondialog.h"
#include "plugincontainer.h"
#include "shared/appconfig.h"
+#include "shared/util.h"
#include <utility.h>
#include <report.h>
#include <iplugingame.h>
@@ -20,11 +21,6 @@ void openInstanceManager(PluginContainer& pc, QWidget* parent)
dlg.exec();
}
-QString makeIniFile(const QDir& dir)
-{
- return dir.filePath(QString::fromStdWString(AppConfig::iniFileName()));
-}
-
class InstanceInfo
{
@@ -61,7 +57,7 @@ public:
void setDir(const QDir& dir)
{
m_dir = dir;
- m_settings.reset(new Settings(makeIniFile(dir)));
+ m_settings.reset(new Settings(InstanceManager::iniPath(dir)));
}
QString name() const
@@ -109,7 +105,7 @@ public:
QString iniFile() const
{
- return makeIniFile(m_dir);
+ return InstanceManager::iniPath(m_dir);
}
QIcon icon(const PluginContainer& plugins) const
@@ -134,10 +130,13 @@ public:
{
auto& m = InstanceManager::instance();
- if (m_portable && m.currentInstance() == "") {
- return true;
- } else if (m.currentInstance() == name()) {
- return true;
+ if (auto i=m.currentInstance())
+ {
+ if (m_portable) {
+ return i->isPortable();
+ } else {
+ return (i->name() == name());
+ }
}
return false;
@@ -316,7 +315,7 @@ InstanceManagerDialog::~InstanceManagerDialog() = default;
InstanceManagerDialog::InstanceManagerDialog(
const PluginContainer& pc, QWidget *parent) :
QDialog(parent), ui(new Ui::InstanceManagerDialog), m_pc(pc),
- m_model(nullptr)
+ m_model(nullptr), m_restartOnSelect(true)
{
ui->setupUi(this);
@@ -449,14 +448,16 @@ void InstanceManagerDialog::selectActiveInstance()
{
const auto active = InstanceManager::instance().currentInstance();
- for (std::size_t i=0; i<m_instances.size(); ++i) {
- if (m_instances[i]->name() == active) {
- select(i);
+ if (active) {
+ for (std::size_t i=0; i<m_instances.size(); ++i) {
+ if (m_instances[i]->name() == active->name()) {
+ select(i);
- ui->list->scrollTo(
- m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)));
+ ui->list->scrollTo(
+ m_filter.mapFromSource(m_filter.sourceModel()->index(i, 0)));
- return;
+ return;
+ }
}
}
@@ -470,7 +471,13 @@ void InstanceManagerDialog::openSelectedInstance()
return;
}
- InstanceManager::instance().switchToInstance(m_instances[i]->name());
+ InstanceManager::instance().setCurrentInstance(m_instances[i]->name());
+
+ if (m_restartOnSelect) {
+ ExitModOrganizer(Exit::Restart);
+ }
+
+ accept();
}
QString getInstanceName(
@@ -697,6 +704,11 @@ void InstanceManagerDialog::deleteInstance()
}
+void InstanceManagerDialog::setRestartOnSelect(bool b)
+{
+ m_restartOnSelect = b;
+}
+
bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle)
{
if (MOBase::shellDelete(files, recycle, this)) {
diff --git a/src/instancemanagerdialog.h b/src/instancemanagerdialog.h
index 477a7d01..5b08ffc2 100644
--- a/src/instancemanagerdialog.h
+++ b/src/instancemanagerdialog.h
@@ -34,6 +34,8 @@ public:
void openINI();
void deleteInstance();
+ void setRestartOnSelect(bool b);
+
private:
static const std::size_t NoSelection = -1;
@@ -42,6 +44,7 @@ private:
std::vector<std::unique_ptr<InstanceInfo>> m_instances;
MOBase::FilterWidget m_filter;
QStandardItemModel* m_model;
+ bool m_restartOnSelect;
void updateInstances();
diff --git a/src/main.cpp b/src/main.cpp
index fd5a47c9..2a5a3e81 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -27,6 +27,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "instancemanager.h"
#include "instancemanagerdialog.h"
#include "createinstancedialog.h"
+#include "createinstancedialogpages.h"
#include "organizercore.h"
#include "env.h"
#include "envmodule.h"
@@ -273,9 +274,166 @@ std::optional<int> handleCommandLine(
void openInstanceManager(PluginContainer& pc, QWidget* parent);
+std::optional<Instance> selectInstance()
+{
+ NexusInterface ni(nullptr);
+
+ PluginContainer pc(nullptr);
+ pc.loadPlugins();
+
+ InstanceManagerDialog dlg(pc);
+ dlg.setRestartOnSelect(false);
+
+ dlg.show();
+ dlg.activateWindow();
+ dlg.raise();
+
+ if (dlg.exec() != QDialog::Accepted) {
+ return {};
+ }
+
+ return InstanceManager::instance().currentInstance();
+}
+
+enum class SetupInstanceResults
+{
+ Ok,
+ TryAgain,
+ SelectAnother,
+ Exit
+};
+
+
+void criticalOnTop(const QString& message)
+{
+ QMessageBox mb(QMessageBox::Critical, QObject::tr("Mod Organizer"), message);
+
+ mb.show();
+ mb.activateWindow();
+ mb.raise();
+ mb.exec();
+}
+
+
+SetupInstanceResults setupInstance(Instance& instance, PluginContainer& pc)
+{
+ const auto setupResult = instance.setup(pc);
+
+ switch (setupResult)
+ {
+ case Instance::SetupResults::Ok:
+ {
+ return SetupInstanceResults::Ok;
+ }
+
+ case Instance::SetupResults::BadIni:
+ {
+ criticalOnTop(
+ QObject::tr("Cannot open instance '%1', failed to read INI file %2.")
+ .arg(instance.name()).arg(instance.iniPath()));
+
+ return SetupInstanceResults::SelectAnother;
+ }
+
+ case Instance::SetupResults::IniMissingGame:
+ {
+ criticalOnTop(
+ QObject::tr(
+ "Cannot open instance '%1', the managed game was not found in the INI "
+ "file %2. Select the game managed by this instance.")
+ .arg(instance.name()).arg(instance.iniPath()));
+
+ CreateInstanceDialog dlg(pc, nullptr);
+ dlg.setSinglePage<cid::GamePage>();
+
+ dlg.show();
+ dlg.activateWindow();
+ dlg.raise();
+
+ if (dlg.exec() != QDialog::Accepted) {
+ return SetupInstanceResults::Exit;
+ }
+
+ instance.setGame(
+ dlg.creationInfo().game->gameName(),
+ dlg.creationInfo().game->gameDirectory().absolutePath());
+
+ return SetupInstanceResults::TryAgain;
+ }
+
+ case Instance::SetupResults::PluginGone:
+ {
+ criticalOnTop(
+ QObject::tr(
+ "Cannot open instance '%1', the game plugin '%2' doesn't exist. It "
+ "may have been deleted by an antivirus. Select another instance.")
+ .arg(instance.name()).arg(instance.gameName()));
+
+ return SetupInstanceResults::SelectAnother;
+ }
+
+ case Instance::SetupResults::GameGone:
+ {
+ criticalOnTop(
+ QObject::tr(
+ "Cannot open instance '%1', the game directory '%2' doesn't exist or "
+ "the game plugin '%3' doesn't recognize it. Select the game managed "
+ "by this instance.")
+ .arg(instance.name())
+ .arg(instance.gameDirectory())
+ .arg(instance.gameName()));
+
+ CreateInstanceDialog dlg(pc, nullptr);
+ dlg.setSinglePage<cid::GamePage>();
+
+ dlg.show();
+ dlg.activateWindow();
+ dlg.raise();
+
+ if (dlg.exec() != QDialog::Accepted) {
+ return SetupInstanceResults::Exit;
+ }
+
+ instance.setGame(
+ dlg.creationInfo().game->gameName(),
+ dlg.creationInfo().game->gameDirectory().absolutePath());
+
+ return SetupInstanceResults::TryAgain;
+ }
+
+ case Instance::SetupResults::MissingVariant:
+ {
+ CreateInstanceDialog dlg(pc, nullptr);
+
+ dlg.getPage<cid::GamePage>()->select(
+ instance.gamePlugin(), instance.gameDirectory());
+
+ dlg.setSinglePage<cid::VariantsPage>();
+
+ dlg.show();
+ dlg.activateWindow();
+ dlg.raise();
+
+ if (dlg.exec() != QDialog::Accepted) {
+ return SetupInstanceResults::Exit;
+ }
+
+ instance.setVariant(dlg.creationInfo().gameVariant);
+
+ return SetupInstanceResults::TryAgain;
+ }
+
+ default:
+ {
+ return SetupInstanceResults::Exit;
+ }
+ }
+}
+
int runApplication(
MOApplication &application, const cl::CommandLine& cl,
- SingleInstance &instance, const QString &dataPath)
+ SingleInstance &instance, const QString &dataPath,
+ Instance& currentInstance)
{
TimeThis tt("runApplication() to exec()");
@@ -345,57 +503,48 @@ int runApplication(
pluginContainer = std::make_unique<PluginContainer>(&organizer);
pluginContainer->loadPlugins();
- MOBase::IPluginGame* game = InstanceManager::instance()
- .determineCurrentGame(
- application.applicationDirPath(), settings, *pluginContainer);
-
- if (game == nullptr) {
- InstanceManager &instance = InstanceManager::instance();
- QString instanceName = instance.currentInstance();
+ for (;;)
+ {
+ const auto setupResult = setupInstance(currentInstance, *pluginContainer);
- if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) {
- instance.clearCurrentInstance();
+ if (setupResult == SetupInstanceResults::Ok) {
+ break;
+ } else if (setupResult == SetupInstanceResults::TryAgain) {
+ continue;
+ } else if (setupResult == SetupInstanceResults::SelectAnother) {
+ InstanceManager::instance().clearCurrentInstance();
return RestartExitCode;
+ } else {
+ return 1;
}
-
- return 1;
}
- checkPathsForSanity(*game, settings);
-
+ checkPathsForSanity(*currentInstance.gamePlugin(), settings);
- organizer.setManagedGame(game);
+ organizer.setManagedGame(currentInstance.gamePlugin());
organizer.createDefaultProfile();
- if (!InstanceManager::instance().determineGameEdition(settings, game)) {
- return 1;
- }
-
log::info(
"using game plugin '{}' ('{}', variant {}, steam id '{}') at {}",
- game->gameName(), game->gameShortName(),
+ currentInstance.gamePlugin()->gameName(),
+ currentInstance.gamePlugin()->gameShortName(),
(settings.game().edition().value_or("").isEmpty() ?
"(none)" : *settings.game().edition()),
- game->steamAPPId(), game->gameDirectory().absolutePath());
+ currentInstance.gamePlugin()->steamAPPId(),
+ currentInstance.gamePlugin()->gameDirectory().absolutePath());
+
CategoryFactory::instance().loadCategories();
organizer.updateExecutablesList();
organizer.updateModInfoFromDisc();
- if (cl.profile()) {
- InstanceManager::instance().overrideProfile(*cl.profile());
- }
-
- QString selectedProfileName = InstanceManager::instance()
- .determineProfile(settings);
-
- organizer.setCurrentProfile(selectedProfileName);
+ organizer.setCurrentProfile(currentInstance.profileName());
if (auto r=handleCommandLine(cl, organizer)) {
return *r;
}
- auto splash = createSplash(settings, dataPath, game);
+ auto splash = createSplash(settings, dataPath, currentInstance.gamePlugin());
QString apiKey;
if (GlobalSettings::nexusApiKey(apiKey)) {
@@ -438,11 +587,6 @@ int runApplication(
tt.stop();
- QTimer::singleShot(std::chrono::milliseconds(1), [&]
- {
- openInstanceManager(*pluginContainer, &mainWindow);
- });
-
res = application.exec();
mainWindow.close();
@@ -488,28 +632,6 @@ void resetForRestart(cl::CommandLine& cl)
cl.clear();
}
-QString determineDataPath(const cl::CommandLine& cl)
-{
- try
- {
- InstanceManager& instanceManager = InstanceManager::instance();
-
- if (cl.instance())
- instanceManager.overrideInstance(*cl.instance());
-
- 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());
- }
-
- return {};
- }
-}
-
-
int doOneRun(
cl::CommandLine& cl, MOApplication& application, SingleInstance& instance)
{
@@ -518,24 +640,25 @@ int doOneRun(
// resets things when MO is "restarted"
resetForRestart(cl);
+ if (cl.instance())
+ InstanceManager::instance().overrideInstance(*cl.instance());
- //{
- // NexusInterface ni(nullptr);
- //
- // PluginContainer pc(nullptr);
- // pc.loadPlugins();
- //
- // CreateInstanceDialog dlg(pc, nullptr);
- // dlg.exec();
- //}
+ if (cl.profile()) {
+ InstanceManager::instance().overrideProfile(*cl.profile());
+ }
+ auto currentInstance = InstanceManager::instance().currentInstance();
- const QString dataPath = determineDataPath(cl);
- if (dataPath.isEmpty()) {
- return 1;
+ if (!currentInstance)
+ {
+ currentInstance = selectInstance();
+ if (!currentInstance)
+ return 1;
}
+ const QString dataPath = currentInstance->directory().path();
application.setProperty("dataPath", dataPath);
+
setExceptionHandlers();
if (!setLogDirectory(dataPath)) {
@@ -548,7 +671,7 @@ int doOneRun(
tt.stop();
- return runApplication(application, cl, instance, dataPath);
+ return runApplication(application, cl, instance, dataPath, *currentInstance);
}
int main(int argc, char *argv[])
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 0d561581..550f61d4 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -513,9 +513,11 @@ bool OrganizerCore::bootstrap()
void OrganizerCore::createDefaultProfile()
{
QString profilesPath = settings().paths().profiles();
- if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size()
- == 0) {
- Profile newProf("Default", managedGame(), false);
+ if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) {
+ Profile newProf(
+ QString::fromStdWString(AppConfig::defaultProfileName()),
+ managedGame(), false);
+
m_ProfileCreated(&newProf);
}
}
diff --git a/src/processrunner.cpp b/src/processrunner.cpp
index a0e74f47..8ee0914b 100644
--- a/src/processrunner.cpp
+++ b/src/processrunner.cpp
@@ -588,11 +588,14 @@ ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut)
{
const auto currentInstance = InstanceManager::instance().currentInstance();
- if (shortcut.hasInstance() && shortcut.instance() != currentInstance) {
- throw std::runtime_error(
- QString("Refusing to run executable from different instance %1:%2")
- .arg(shortcut.instance(),shortcut.executable())
- .toLocal8Bit().constData());
+ if (currentInstance)
+ {
+ if (shortcut.hasInstance() && shortcut.instance() != currentInstance->name()) {
+ throw std::runtime_error(
+ QString("Refusing to run executable from different instance %1:%2")
+ .arg(shortcut.instance(),shortcut.executable())
+ .toLocal8Bit().constData());
+ }
}
const Executable& exe = m_core.executablesList()->get(shortcut.executable());
diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc
index 709c845d..807f1d69 100644
--- a/src/shared/appconfig.inc
+++ b/src/shared/appconfig.inc
@@ -9,6 +9,7 @@ APPPARAM(std::wstring, cachePath, L"webcache")
APPPARAM(std::wstring, tutorialsPath, L"tutorials")
APPPARAM(std::wstring, logPath, L"logs")
APPPARAM(std::wstring, dumpsDir, L"crashDumps")
+APPPARAM(std::wstring, defaultProfileName, L"Default")
APPPARAM(std::wstring, profileTweakIni, L"profile_tweaks.ini")
APPPARAM(std::wstring, logFileName, L"ModOrganizer.log")
APPPARAM(std::wstring, iniFileName, L"ModOrganizer.ini")
diff --git a/src/statusbar.cpp b/src/statusbar.cpp
index 5897b6bb..aefabc73 100644
--- a/src/statusbar.cpp
+++ b/src/statusbar.cpp
@@ -153,10 +153,9 @@ void StatusBar::updateNormalMessage(OrganizerCore& core)
game = tr("Unknown game");
}
- QString instance = InstanceManager::instance().currentInstance();
- if (instance.isEmpty()) {
- instance = tr("Portable");
- }
+ QString instance = "?";
+ if (auto i=InstanceManager::instance().currentInstance())
+ instance = i->name();
QString profile = core.profileName();