diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/createinstancedialog.cpp | 69 | ||||
| -rw-r--r-- | src/createinstancedialog.h | 37 | ||||
| -rw-r--r-- | src/createinstancedialogpages.cpp | 65 | ||||
| -rw-r--r-- | src/createinstancedialogpages.h | 32 | ||||
| -rw-r--r-- | src/envshortcut.cpp | 2 | ||||
| -rw-r--r-- | src/instancemanager.cpp | 707 | ||||
| -rw-r--r-- | src/instancemanager.h | 66 | ||||
| -rw-r--r-- | src/instancemanagerdialog.cpp | 50 | ||||
| -rw-r--r-- | src/instancemanagerdialog.h | 3 | ||||
| -rw-r--r-- | src/main.cpp | 263 | ||||
| -rw-r--r-- | src/organizercore.cpp | 8 | ||||
| -rw-r--r-- | src/processrunner.cpp | 13 | ||||
| -rw-r--r-- | src/shared/appconfig.inc | 1 | ||||
| -rw-r--r-- | src/statusbar.cpp | 7 |
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(); |
