From a9f3be9f45b9a151ee2d0d3ab1c090cdb7d30240 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 28 Jan 2026 00:59:03 +0500 Subject: [PATCH 1/5] refactor: LaunchController Signed-off-by: Octol1ttle --- launcher/Application.cpp | 16 +- launcher/Application.h | 5 +- launcher/CMakeLists.txt | 1 + launcher/LaunchController.cpp | 296 +++++++++---------- launcher/LaunchController.h | 15 +- launcher/LaunchMode.h | 25 ++ launcher/minecraft/MinecraftInstance.cpp | 16 +- launcher/minecraft/auth/AuthSession.cpp | 8 +- launcher/minecraft/auth/AuthSession.h | 23 +- launcher/minecraft/auth/MinecraftAccount.cpp | 9 - launcher/minecraft/launch/ClaimAccount.cpp | 4 +- launcher/ui/pages/instance/ServersPage.cpp | 2 +- launcher/ui/pages/instance/WorldListPage.cpp | 2 +- 13 files changed, 192 insertions(+), 230 deletions(-) create mode 100644 launcher/LaunchMode.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ac5cd8f4f..eb35b725c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -333,7 +333,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_worldToJoin = parser.value("world"); m_profileToUse = parser.value("profile"); if (parser.isSet("offline")) { - m_offline = true; + m_launchOffline = true; m_offlineName = parser.value("offline"); } m_liveCheck = parser.isSet("alive"); @@ -350,7 +350,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } // error if --launch is missing with --server or --profile - if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) && + if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_launchOffline) && m_instanceIdToLaunch.isEmpty()) { std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl; m_status = Application::Failed; @@ -478,7 +478,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) if (!m_profileToUse.isEmpty()) { launch.args["profile"] = m_profileToUse; } - if (m_offline) { + if (m_launchOffline) { launch.args["offline_enabled"] = "true"; launch.args["offline_name"] = m_offlineName; } @@ -1348,7 +1348,7 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName); + launch(inst, m_launchOffline ? LaunchMode::Offline : LaunchMode::Normal, targetToJoin, accountToUse, m_offlineName); return; } } @@ -1471,7 +1471,7 @@ void Application::messageReceived(const QByteArray& message) } } - launch(instance, !offline, false, serverObject, accountObject, offlineName); + launch(instance, offline ? LaunchMode::Offline : LaunchMode::Normal, serverObject, accountObject, offlineName); } else { qWarning() << "Received invalid message" << message; } @@ -1507,8 +1507,7 @@ bool Application::openJsonEditor(const QString& filename) } bool Application::launch(BaseInstance* instance, - bool online, - bool demo, + LaunchMode mode, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse, const QString& offlineName) @@ -1527,8 +1526,7 @@ bool Application::launch(BaseInstance* instance, auto& controller = extras.controller; controller.reset(new LaunchController()); controller->setInstance(instance); - controller->setOnline(online); - controller->setDemo(demo); + controller->setLaunchMode(mode); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get()); controller->setTargetToJoin(targetToJoin); controller->setAccountToUse(accountToUse); diff --git a/launcher/Application.h b/launcher/Application.h index b7ef77afd..cbbf55d26 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -216,8 +216,7 @@ class Application : public QApplication { public slots: bool launch(BaseInstance* instance, - bool online = true, - bool demo = false, + LaunchMode mode = LaunchMode::Normal, std::shared_ptr targetToJoin = nullptr, shared_qobject_ptr accountToUse = nullptr, const QString& offlineName = QString()); @@ -311,7 +310,7 @@ class Application : public QApplication { QString m_serverToJoin; QString m_worldToJoin; QString m_profileToUse; - bool m_offline = false; + bool m_launchOffline = false; QString m_offlineName; bool m_liveCheck = false; QList m_urlsToImport; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 837ff7234..cc9233a85 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -902,6 +902,7 @@ SET(LAUNCHER_SOURCES ui/themes/CatPainter.h # Processes + LaunchMode.h LaunchController.h LaunchController.cpp diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index bce6490cc..44d6f3af7 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -41,21 +41,16 @@ #include "minecraft/auth/AccountList.h" #include "ui/InstanceWindow.h" -#include "ui/MainWindow.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" #include "ui/dialogs/ProfileSetupDialog.h" #include "ui/dialogs/ProgressDialog.h" -#include -#include #include -#include #include #include #include -#include #include "BuildConfig.h" #include "JavaCommon.h" @@ -63,7 +58,7 @@ #include "tasks/Task.h" #include "ui/dialogs/ChooseOfflineNameDialog.h" -LaunchController::LaunchController() : Task() {} +LaunchController::LaunchController() = default; void LaunchController::executeTask() { @@ -88,7 +83,7 @@ void LaunchController::decideAccount() // Find an account to use. auto accounts = APPLICATION->accounts(); - if (accounts->count() <= 0 || !accounts->anyAccountIsValid()) { + if (!accounts->anyAccountIsValid()) { // Tell the user they need to log in at least one account in order to play. auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"), tr("In order to play Minecraft, you must have at least one Microsoft " @@ -132,6 +127,89 @@ void LaunchController::decideAccount() } } +bool LaunchController::decideLaunchMode() +{ + if (!m_accountToUse || m_wantedLaunchMode == LaunchMode::Demo) { + m_actualLaunchMode = LaunchMode::Demo; + return true; + } + + if (m_wantedLaunchMode == LaunchMode::Normal) { + if (m_accountToUse->shouldRefresh() || m_accountToUse->accountState() == AccountState::Offline) { + // Force account refresh on the account used to launch the instance updating the AccountState + // only on first try and if it is not meant to be offline + m_accountToUse->refresh(); + } + } + + MinecraftAccountPtr accountToCheck = nullptr; + + if (m_accountToUse->accountType() != AccountType::Offline) { + accountToCheck = m_accountToUse->ownsMinecraft() ? m_accountToUse : nullptr; + } else if (const MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); + defaultAccount && defaultAccount->ownsMinecraft()) { + accountToCheck = defaultAccount; + } else { + for (int i = 0; i < APPLICATION->accounts()->count(); i++) { + if (const MinecraftAccountPtr account = APPLICATION->accounts()->at(i); account->ownsMinecraft()) { + accountToCheck = account; + break; + } + } + } + + if (!accountToCheck) { + m_actualLaunchMode = LaunchMode::Demo; + return true; + } + + auto state = accountToCheck->accountState(); + if (state == AccountState::Unchecked || state == AccountState::Errored) { + accountToCheck->refresh(); + state = AccountState::Working; + } + + if (state == AccountState::Working) { + // refresh is in progress, we need to wait for it to finish to proceed. + ProgressDialog progDialog(m_parentWidget); + progDialog.setSkipButton(true, tr("Abort")); + + auto task = accountToCheck->currentTask(); + progDialog.execWithTask(task.get()); + + if (task->getState() == State::AbortedByUser) { + return false; + } + + state = accountToCheck->accountState(); + } + + QString reauthReason; + switch (state) { + case AccountState::Errored: + case AccountState::Expired: { + reauthReason = tr("'%1' has expired and needs to be reauthenticated").arg(accountToCheck->profileName()); + } break; + case AccountState::Disabled: { + reauthReason = tr("The launcher's client identification has changed"); + } break; + case AccountState::Gone: { + reauthReason = tr("'%1' no longer exists on the servers").arg(accountToCheck->profileName()); + } break; + default: { + m_actualLaunchMode = + state == AccountState::Online && m_wantedLaunchMode == LaunchMode::Normal ? LaunchMode::Normal : LaunchMode::Offline; + return true; // All good to go + } + } + + if (reauthenticateAccount(accountToCheck, reauthReason)) { + return decideLaunchMode(); + } + + return false; +} + bool LaunchController::askPlayDemo() { QMessageBox box(m_parentWidget); @@ -148,16 +226,26 @@ bool LaunchController::askPlayDemo() return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok) +QString LaunchController::askOfflineName(QString playerName, bool* ok) { if (ok != nullptr) { *ok = false; } - // we ask the user for a player name - QString message = tr("Choose your offline mode player name."); - if (demo) { - message = tr("Choose your demo mode player name."); + QString message; + switch (m_actualLaunchMode) { + case LaunchMode::Normal: + Q_ASSERT(false); + return ""; + case LaunchMode::Demo: + message = tr("Choose your demo mode player name"); + break; + case LaunchMode::Offline: + if (m_wantedLaunchMode == LaunchMode::Normal) { + message = tr("You are not connected to the Internet, launching in offline mode\n\n"); + } + message += tr("Choose your offline mode player name"); + break; } QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); @@ -170,8 +258,7 @@ QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok return {}; } - const QString name = dialog.getUsername(); - usedname = name; + usedname = dialog.getUsername(); APPLICATION->settings()->set("LastOfflinePlayerName", usedname); if (ok != nullptr) { @@ -184,15 +271,15 @@ void LaunchController::login() { decideAccount(); - if (!m_accountToUse) { - // if no account is selected, ask about demo - if (!m_demo) { - m_demo = askPlayDemo(); - } - if (m_demo) { - // we ask the user for a player name + if (!decideLaunchMode()) { + emitAborted(); + return; + } + + if (m_actualLaunchMode == LaunchMode::Demo) { + if (m_wantedLaunchMode == LaunchMode::Demo || askPlayDemo()) { bool ok = false; - auto name = askOfflineName("Player", m_demo, &ok); + auto name = askOfflineName("Player", &ok); if (ok) { m_session = std::make_shared(); static const QRegularExpression s_removeChars("[{}-]"); @@ -201,162 +288,45 @@ void LaunchController::login() return; } } - // if no account is selected, we bail - emitFailed(tr("No account selected for launch.")); + + emitFailed(tr("No account selected for launch")); return; } - // we loop until the user succeeds in logging in or gives up - bool tryagain = true; - unsigned int tries = 0; + m_session = std::make_shared(); + m_session->launchMode = m_actualLaunchMode; + m_accountToUse->fillSession(m_session); - if ((m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) || - m_accountToUse->shouldRefresh()) { - // Force account refresh on the account used to launch the instance updating the AccountState - // only on first try and if it is not meant to be offline - m_accountToUse->refresh(); - } - while (tryagain) { - if (tries > 0 && tries % 3 == 0) { - auto result = - QMessageBox::question(m_parentWidget, tr("Continue launch?"), - tr("It looks like we couldn't launch after %1 tries. Usually this can be fixed by logging out and " - "logging back in your Microsoft account. If that doesn't work, Minecraft authentication servers " - "may be having an outage or you may need a VPN in your region. Do you want to continue trying?") - .arg(tries)); - - if (result == QMessageBox::No) { + if (m_accountToUse->accountType() != AccountType::Offline) { + if (m_actualLaunchMode == LaunchMode::Normal && !m_accountToUse->hasProfile()) { + // Now handle setting up a profile name here... + if (ProfileSetupDialog dialog(m_accountToUse, m_parentWidget); dialog.exec() != QDialog::Accepted) { emitAborted(); return; } } - tries++; - m_session = std::make_shared(); - m_session->wants_online = m_online; - m_session->demo = m_demo; - m_accountToUse->fillSession(m_session); - MinecraftAccountPtr accountToCheck; - - if (m_accountToUse->ownsMinecraft()) - accountToCheck = m_accountToUse; - else if (const MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); - defaultAccount != nullptr && defaultAccount->ownsMinecraft()) { - accountToCheck = defaultAccount; - } else { - for (int i = 0; i < APPLICATION->accounts()->count(); i++) { - MinecraftAccountPtr account = APPLICATION->accounts()->at(i); - if (account->ownsMinecraft()) - accountToCheck = account; - } - } - - if (accountToCheck == nullptr) { - if (!m_session->demo) - m_session->demo = askPlayDemo(); - - if (m_session->demo) - launchInstance(); - else - emitFailed(tr("Launch cancelled - account does not own Minecraft.")); - - return; - } - - switch (accountToCheck->accountState()) { - case AccountState::Offline: { - m_session->wants_online = false; - } - /* fallthrough */ - case AccountState::Online: { - if (!m_session->wants_online && m_accountToUse->accountType() != AccountType::Offline) { - // we ask the user for a player name - bool ok = false; - QString name; - if (m_offlineName.isEmpty()) { - name = askOfflineName(m_session->player_name, m_session->demo, &ok); - if (!ok) { - tryagain = false; - break; - } - } else { - name = m_offlineName; - } - m_session->MakeOffline(name); - // offline flavored game from here :3 - } else if (m_accountToUse == accountToCheck && !m_accountToUse->hasProfile()) { - // Now handle setting up a profile name here... - ProfileSetupDialog dialog(m_accountToUse, m_parentWidget); - if (dialog.exec() == QDialog::Accepted) { - tryagain = true; - continue; - } else { - emitFailed(tr("Received undetermined session status during login.")); - return; - } + if (m_actualLaunchMode == LaunchMode::Offline && m_accountToUse->accountType() != AccountType::Offline) { + bool ok = false; + QString name = m_offlineName; + if (name.isEmpty()) { + name = askOfflineName(m_session->player_name, &ok); + if (!ok) { + emitAborted(); + return; } - - if (m_accountToUse->accountType() == AccountType::Offline) - m_session->wants_online = false; - - // we own Minecraft, there is a profile, it's all ready to go! - launchInstance(); - return; - } - case AccountState::Errored: - // This means some sort of soft error that we can fix with a refresh ... so let's refresh. - case AccountState::Unchecked: { - accountToCheck->refresh(); - } - /* fallthrough */ - case AccountState::Working: { - // refresh is in progress, we need to wait for it to finish to proceed. - ProgressDialog progDialog(m_parentWidget); - progDialog.setSkipButton(true, tr("Abort")); - - auto task = accountToCheck->currentTask(); - progDialog.execWithTask(task.get()); - - // don't retry if aborted - if (task->getState() == Task::State::AbortedByUser) - tryagain = false; - - continue; - } - case AccountState::Expired: { - if (reauthenticateAccount(accountToCheck)) - continue; - return; - } - case AccountState::Disabled: { - auto errorString = tr("The launcher's client identification has changed. Please remove '%1' and try again.") - .arg(accountToCheck->profileName()); - - QMessageBox::warning(m_parentWidget, tr("Client identification changed"), errorString, QMessageBox::StandardButton::Ok, - QMessageBox::StandardButton::Ok); - emitFailed(errorString); - return; - } - case AccountState::Gone: { - auto errorString = - tr("'%1' no longer exists on the servers. It may have been migrated, in which case please add the new account " - "you migrated this one to.") - .arg(accountToCheck->profileName()); - QMessageBox::warning(m_parentWidget, tr("Account gone"), errorString, QMessageBox::StandardButton::Ok, - QMessageBox::StandardButton::Ok); - emitFailed(errorString); - return; } + m_session->MakeOffline(name); } } - emitFailed(tr("Failed to launch.")); + + launchInstance(); } -bool LaunchController::reauthenticateAccount(MinecraftAccountPtr account) +bool LaunchController::reauthenticateAccount(MinecraftAccountPtr account, QString reason) { auto button = QMessageBox::warning( - m_parentWidget, tr("Account refresh failed"), - tr("'%1' has expired and needs to be reauthenticated. Do you want to reauthenticate this account?").arg(account->profileName()), + m_parentWidget, tr("Account refresh failed"), tr("%1. Do you want to reauthenticate this account?").arg(reason), QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes); if (button == QMessageBox::StandardButton::Yes) { auto accounts = APPLICATION->accounts(); @@ -413,7 +383,7 @@ void LaunchController::launchInstance() // Prepend Online and Auth Status QString online_mode; - if (m_session->wants_online) { + if (m_actualLaunchMode == LaunchMode::Normal) { online_mode = "online"; // Prepend Server Status @@ -421,7 +391,7 @@ void LaunchController::launchInstance() m_launcher->prependStep(makeShared(m_launcher, servers)); } else { - online_mode = m_demo ? "demo" : "offline"; + online_mode = m_actualLaunchMode == LaunchMode::Demo ? "demo" : "offline"; } m_launcher->prependStep(makeShared(m_launcher, "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index f421e1c86..f0e1dd03c 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -36,12 +36,12 @@ #pragma once #include #include -#include #include "minecraft/auth/MinecraftAccount.h" #include "minecraft/launch/MinecraftTarget.h" class InstanceWindow; + class LaunchController : public Task { Q_OBJECT public: @@ -54,12 +54,10 @@ class LaunchController : public Task { BaseInstance* instance() { return m_instance; } - void setOnline(bool online) { m_online = online; } + void setLaunchMode(const LaunchMode mode) { m_wantedLaunchMode = mode; } void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; } - void setDemo(bool demo) { m_demo = demo; } - void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; } void setParentWidget(QWidget* widget) { m_parentWidget = widget; } @@ -76,9 +74,10 @@ class LaunchController : public Task { void login(); void launchInstance(); void decideAccount(); + bool decideLaunchMode(); bool askPlayDemo(); - QString askOfflineName(QString playerName, bool demo, bool* ok = nullptr); - bool reauthenticateAccount(MinecraftAccountPtr account); + QString askOfflineName(QString playerName, bool* ok = nullptr); + bool reauthenticateAccount(MinecraftAccountPtr account, QString reason); private slots: void readyForLaunch(); @@ -88,10 +87,10 @@ class LaunchController : public Task { void onProgressRequested(Task* task); private: + LaunchMode m_wantedLaunchMode = LaunchMode::Normal; + LaunchMode m_actualLaunchMode = LaunchMode::Normal; BaseProfilerFactory* m_profiler = nullptr; - bool m_online = true; QString m_offlineName; - bool m_demo = false; BaseInstance* m_instance; QWidget* m_parentWidget = nullptr; InstanceWindow* m_console = nullptr; diff --git a/launcher/LaunchMode.h b/launcher/LaunchMode.h new file mode 100644 index 000000000..45cfe50ce --- /dev/null +++ b/launcher/LaunchMode.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2026 Octol1ttle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +enum class LaunchMode { + Normal, + Offline, + Demo, +}; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7218a8b6c..07fa44aee 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -313,8 +313,8 @@ void MinecraftInstance::populateLaunchMenu(QMenu* menu) normalLaunchDemo->setEnabled(supportsDemo()); connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(this); }); - connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(this, false, false); }); - connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(this, false, true); }); + connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(this, LaunchMode::Offline); }); + connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(this, LaunchMode::Demo); }); QString profilersTitle = tr("Profilers"); menu->addSeparator()->setText(profilersTitle); @@ -774,7 +774,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session, Mine tokenMapping["user_properties"] = session->serializeUserProperties(); tokenMapping["user_type"] = session->user_type; - if (session->demo) { + if (session->launchMode == LaunchMode::Demo) { args_pattern += " --demo"; } } @@ -1153,7 +1153,7 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf // load meta { - auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline; + auto mode = session->launchMode != LaunchMode::Offline ? Net::Mode::Online : Net::Mode::Offline; process->appendStep(makeShared(pptr, makeShared(this, mode))); } @@ -1170,11 +1170,9 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf process->appendStep(step); } - // if we aren't in offline mode,. - if (session->status != AuthSession::PlayableOffline) { - if (!session->demo) { - process->appendStep(makeShared(pptr, session)); - } + // if we aren't in offline mode + if (session->launchMode != LaunchMode::Offline) { + process->appendStep(makeShared(pptr, session)); for (auto t : createUpdateTask()) { process->appendStep(makeShared(pptr, t)); } diff --git a/launcher/minecraft/auth/AuthSession.cpp b/launcher/minecraft/auth/AuthSession.cpp index a74eabb7a..85d77be9c 100644 --- a/launcher/minecraft/auth/AuthSession.cpp +++ b/launcher/minecraft/auth/AuthSession.cpp @@ -20,23 +20,17 @@ QString AuthSession::serializeUserProperties() bool AuthSession::MakeOffline(QString offline_playername) { - if (status != PlayableOffline && status != PlayableOnline) { - return false; - } session = "-"; access_token = "0"; player_name = offline_playername; - status = PlayableOffline; return true; } void AuthSession::MakeDemo(QString name, QString u) { - wants_online = false; - demo = true; uuid = u; session = "-"; access_token = "0"; player_name = name; - status = PlayableOnline; // needs online to download the assets + launchMode = LaunchMode::Demo; }; diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index cbe604805..07db54213 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -3,6 +3,8 @@ #include #include +#include "LaunchMode.h" + class MinecraftAccount; struct AuthSession { @@ -11,16 +13,6 @@ struct AuthSession { QString serializeUserProperties(); - enum Status { - Undetermined, - RequiresOAuth, - RequiresPassword, - RequiresProfileSetup, - PlayableOffline, - PlayableOnline, - GoneOrMigrated - } status = Undetermined; - // combined session ID QString session; // volatile auth token @@ -29,15 +21,10 @@ struct AuthSession { QString player_name; // profile ID QString uuid; - // 'legacy' or 'mojang', depending on account type + // 'msa' or 'offline', depending on account type QString user_type; - // Did the auth server reply? - bool auth_server_online = false; - // Did the user request online mode? - bool wants_online = true; - - // Is this a demo session? - bool demo = false; + // the actual launch mode for this session + LaunchMode launchMode; }; using AuthSessionPtr = std::shared_ptr; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 3a7de8a19..8799b2c74 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -236,15 +236,6 @@ bool MinecraftAccount::shouldRefresh() const void MinecraftAccount::fillSession(AuthSessionPtr session) { static const QRegularExpression s_removeChars("[{}-]"); - if (ownsMinecraft() && !hasProfile()) { - session->status = AuthSession::RequiresProfileSetup; - } else { - if (session->wants_online) { - session->status = AuthSession::PlayableOnline; - } else { - session->status = AuthSession::PlayableOffline; - } - } // volatile auth token session->access_token = data.accessToken(); diff --git a/launcher/minecraft/launch/ClaimAccount.cpp b/launcher/minecraft/launch/ClaimAccount.cpp index eecc5282b..1b375abb3 100644 --- a/launcher/minecraft/launch/ClaimAccount.cpp +++ b/launcher/minecraft/launch/ClaimAccount.cpp @@ -6,7 +6,7 @@ ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session) : LaunchStep(parent) { - if (session->status == AuthSession::Status::PlayableOnline && !session->demo) { + if (session->launchMode == LaunchMode::Normal) { auto accounts = APPLICATION->accounts(); m_account = accounts->getAccountByProfileName(session->player_name); } @@ -16,8 +16,8 @@ void ClaimAccount::executeTask() { if (m_account) { lock.reset(new UseLock(m_account.get())); - emitSucceeded(); } + emitSucceeded(); } void ClaimAccount::finalize() diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 206658f63..9012ebd5b 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -757,7 +757,7 @@ void ServersPage::on_actionMove_Down_triggered() void ServersPage::on_actionJoin_triggered() { const auto& address = m_model->at(currentServer)->m_address; - APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(address, false))); + APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(address, false))); } void ServersPage::on_actionRefresh_triggered() diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index ecb48accb..e56e9c731 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -471,7 +471,7 @@ void WorldListPage::on_actionJoin_triggered() } auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); auto world = (World*)worldVariant.value(); - APPLICATION->launch(m_inst, true, false, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); + APPLICATION->launch(m_inst, LaunchMode::Normal, std::make_shared(MinecraftTarget::parse(world->folderName(), true))); } #include "WorldListPage.moc" From 5d0360ccece7b9c5f60abcd6d9861623f118a2c0 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 28 Jan 2026 16:23:32 +0500 Subject: [PATCH 2/5] style things Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 44d6f3af7..78da7cad6 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -142,16 +142,16 @@ bool LaunchController::decideLaunchMode() } } + const auto accounts = APPLICATION->accounts(); MinecraftAccountPtr accountToCheck = nullptr; if (m_accountToUse->accountType() != AccountType::Offline) { accountToCheck = m_accountToUse->ownsMinecraft() ? m_accountToUse : nullptr; - } else if (const MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); - defaultAccount && defaultAccount->ownsMinecraft()) { + } else if (const auto defaultAccount = accounts->defaultAccount(); defaultAccount && defaultAccount->ownsMinecraft()) { accountToCheck = defaultAccount; } else { - for (int i = 0; i < APPLICATION->accounts()->count(); i++) { - if (const MinecraftAccountPtr account = APPLICATION->accounts()->at(i); account->ownsMinecraft()) { + for (int i = 0; i < accounts->count(); i++) { + if (const auto account = accounts->at(i); account->ownsMinecraft()) { accountToCheck = account; break; } @@ -187,20 +187,19 @@ bool LaunchController::decideLaunchMode() QString reauthReason; switch (state) { case AccountState::Errored: - case AccountState::Expired: { + case AccountState::Expired: reauthReason = tr("'%1' has expired and needs to be reauthenticated").arg(accountToCheck->profileName()); - } break; - case AccountState::Disabled: { + break; + case AccountState::Disabled: reauthReason = tr("The launcher's client identification has changed"); - } break; - case AccountState::Gone: { + break; + case AccountState::Gone: reauthReason = tr("'%1' no longer exists on the servers").arg(accountToCheck->profileName()); - } break; - default: { + break; + default: m_actualLaunchMode = state == AccountState::Online && m_wantedLaunchMode == LaunchMode::Normal ? LaunchMode::Normal : LaunchMode::Offline; return true; // All good to go - } } if (reauthenticateAccount(accountToCheck, reauthReason)) { From f7deeb0db47d09f412c1961509e7ad3418c3f0a9 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 28 Jan 2026 17:06:23 +0500 Subject: [PATCH 3/5] no recursion Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 20 ++++++++++++-------- launcher/LaunchController.h | 4 +++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 78da7cad6..c4123038a 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -127,11 +127,11 @@ void LaunchController::decideAccount() } } -bool LaunchController::decideLaunchMode() +LaunchDecision LaunchController::decideLaunchMode() { if (!m_accountToUse || m_wantedLaunchMode == LaunchMode::Demo) { m_actualLaunchMode = LaunchMode::Demo; - return true; + return LaunchDecision::Continue; } if (m_wantedLaunchMode == LaunchMode::Normal) { @@ -160,7 +160,7 @@ bool LaunchController::decideLaunchMode() if (!accountToCheck) { m_actualLaunchMode = LaunchMode::Demo; - return true; + return LaunchDecision::Continue; } auto state = accountToCheck->accountState(); @@ -178,7 +178,7 @@ bool LaunchController::decideLaunchMode() progDialog.execWithTask(task.get()); if (task->getState() == State::AbortedByUser) { - return false; + return LaunchDecision::Abort; } state = accountToCheck->accountState(); @@ -199,14 +199,14 @@ bool LaunchController::decideLaunchMode() default: m_actualLaunchMode = state == AccountState::Online && m_wantedLaunchMode == LaunchMode::Normal ? LaunchMode::Normal : LaunchMode::Offline; - return true; // All good to go + return LaunchDecision::Continue; // All good to go } if (reauthenticateAccount(accountToCheck, reauthReason)) { - return decideLaunchMode(); + return LaunchDecision::Undecided; } - return false; + return LaunchDecision::Abort; } bool LaunchController::askPlayDemo() @@ -270,7 +270,11 @@ void LaunchController::login() { decideAccount(); - if (!decideLaunchMode()) { + LaunchDecision decision = decideLaunchMode(); + while (decision == LaunchDecision::Undecided) { + decision = decideLaunchMode(); + } + if (decision == LaunchDecision::Abort) { emitAborted(); return; } diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index f0e1dd03c..3966cc545 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -42,6 +42,8 @@ class InstanceWindow; +enum class LaunchDecision { Undecided, Continue, Abort }; + class LaunchController : public Task { Q_OBJECT public: @@ -74,7 +76,7 @@ class LaunchController : public Task { void login(); void launchInstance(); void decideAccount(); - bool decideLaunchMode(); + LaunchDecision decideLaunchMode(); bool askPlayDemo(); QString askOfflineName(QString playerName, bool* ok = nullptr); bool reauthenticateAccount(MinecraftAccountPtr account, QString reason); From 9f94b00925f0c46ab3acd67e7271c19d2ef4a256 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 5 Feb 2026 10:27:26 +0500 Subject: [PATCH 4/5] change: use different message when launching with no account selected Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index c4123038a..2221158fb 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -81,8 +81,16 @@ void LaunchController::decideAccount() return; } - // Find an account to use. + // Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used auto accounts = APPLICATION->accounts(); + auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString(); + auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId); + if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) { + m_accountToUse = accounts->defaultAccount(); + } else { + m_accountToUse = accounts->at(instanceAccountIndex); + } + if (!accounts->anyAccountIsValid()) { // Tell the user they need to log in at least one account in order to play. auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"), @@ -101,15 +109,6 @@ void LaunchController::decideAccount() } } - // Select the account to use. If the instance has a specific account set, that will be used. Otherwise, the default account will be used - auto instanceAccountId = m_instance->settings()->get("InstanceAccountId").toString(); - auto instanceAccountIndex = accounts->findAccountByProfileId(instanceAccountId); - if (instanceAccountIndex == -1 || instanceAccountId.isEmpty()) { - m_accountToUse = accounts->defaultAccount(); - } else { - m_accountToUse = accounts->at(instanceAccountIndex); - } - if (!m_accountToUse) { // If no default account is set, ask the user which one to use. ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox, @@ -213,9 +212,11 @@ bool LaunchController::askPlayDemo() { QMessageBox box(m_parentWidget); box.setWindowTitle(tr("Play demo?")); - box.setText( - tr("This account does not own Minecraft.\nYou need to purchase the game first to play it.\n\nDo you want to play " - "the demo?")); + QString text = m_accountToUse + ? tr("This account does not own Minecraft.\nYou need to purchase the game first to play the full version.") + : tr("No account was selected for launch."); + text += tr("\n\nDo you want to play the demo?"); + box.setText(text); box.setIcon(QMessageBox::Warning); auto demoButton = box.addButton(tr("Play Demo"), QMessageBox::ButtonRole::YesRole); auto cancelButton = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::NoRole); From 6f0e053f4d881e8a390b2e3613221f8f0a4cc144 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 5 Feb 2026 11:15:40 +0500 Subject: [PATCH 5/5] chore: add TODO for potential future race condition Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 2221158fb..d0cd64e51 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -173,6 +173,9 @@ LaunchDecision LaunchController::decideLaunchMode() ProgressDialog progDialog(m_parentWidget); progDialog.setSkipButton(true, tr("Abort")); + // TODO: this relies on tasks' synchronous signal dispatching nature + // TODO: meaning currentTask can't complete and become null while this code is running + // TODO: this code will produce a race condition when tasks become fully async auto task = accountToCheck->currentTask(); progDialog.execWithTask(task.get());