From 0a3adb7912101e0ba29e97a998e97a3e08146f1b Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 4 Jun 2026 21:17:03 +0200 Subject: [PATCH 1/4] on server errors, treat account as offline Signed-off-by: Tayou --- .../minecraft/auth/steps/LauncherLoginStep.cpp | 2 +- .../minecraft/auth/steps/MinecraftProfileStep.cpp | 2 +- .../minecraft/auth/steps/XboxAuthorizationStep.cpp | 2 +- launcher/minecraft/auth/steps/XboxUserStep.cpp | 2 +- launcher/net/NetUtils.h | 14 ++++++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 89293c22e..701261b52 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -56,7 +56,7 @@ void LauncherLoginStep::onRequestDone(QByteArray* response) qCDebug(authCredentials()) << *response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } else { diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 418c46a0e..7e089cf86 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -52,7 +52,7 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response) qWarning() << " Response:"; qWarning() << QString::fromUtf8(*response); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } else { diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 84320c7c1..4890f4da1 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -60,7 +60,7 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) qCDebug(authCredentials()) << *response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { if (processSTSError(*response)) { return; } diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 97544d09b..e2971dcd9 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -56,7 +56,7 @@ void XboxUserStep::onRequestDone(QByteArray* response) { if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } else { emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); diff --git a/launcher/net/NetUtils.h b/launcher/net/NetUtils.h index cd517bcca..67ebc9685 100644 --- a/launcher/net/NetUtils.h +++ b/launcher/net/NetUtils.h @@ -40,4 +40,18 @@ inline bool isApplicationError(QNetworkReply::NetworkError x) QNetworkReply::UnknownContentError }; return errors.contains(x); } + +// 500 class errors, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/500 +// microsoft may send these error codes when services (auth) are down. +// We treat this as a reason to launch in offline mode. +inline bool isServerError(QNetworkReply::NetworkError x) +{ + static QSet errors = { QNetworkReply::InternalServerError, + QNetworkReply::OperationNotImplementedError, + QNetworkReply::ServiceUnavailableError, // 503 | seen in logs in 2026 + //QNetworkReply::GatewayTimeoutError, // 504 | seen in logs in 2024 + // Qt doesn't have it mapped. Unknown covers it + QNetworkReply::UnknownServerError }; + return errors.contains(x); +} } // namespace Net From 768d12259b8ee76f4b71d312b48821682a355ec5 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 11 Jun 2026 14:35:07 +0200 Subject: [PATCH 2/4] add info & retry dialog Signed-off-by: Tayou --- launcher/LaunchController.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 30ee448a2..f817fd278 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -182,6 +182,33 @@ LaunchDecision LaunchController::decideLaunchMode() QString reauthReason; switch (state) { + case AccountState::Offline: { + if (m_wantedLaunchMode == LaunchMode::Normal) { + QMessageBox msg(m_parentWidget); + msg.setWindowTitle(tr("Auth servers offline")); + msg.setText(tr("The Minecraft authentication servers are currently unavailable.")); + msg.setIcon(QMessageBox::Warning); + + auto* launchOfflineButton = msg.addButton(tr("Launch Offline"), QMessageBox::AcceptRole); + auto* retryButton = msg.addButton(tr("Retry"), QMessageBox::ActionRole); + msg.addButton(tr("Cancel"), QMessageBox::RejectRole); + msg.setDefaultButton(launchOfflineButton); + + msg.exec(); + + if (msg.clickedButton() == launchOfflineButton) { + m_actualLaunchMode = LaunchMode::Offline; + return LaunchDecision::Continue; + } + if (msg.clickedButton() == retryButton) { + return LaunchDecision::Undecided; + } + return LaunchDecision::Abort; + } + + m_actualLaunchMode = LaunchMode::Offline; + return LaunchDecision::Continue; + } case AccountState::Errored: reauthReason = tr("An error occurred while refreshing '%1'").arg(accountToCheck->profileName()); break; From d71cfc33a2daa9811d7047d0ef5dd451232baad6 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 11 Jun 2026 17:24:30 +0200 Subject: [PATCH 3/4] show error code in dialog, change text for no internet Signed-off-by: Tayou --- launcher/LaunchController.cpp | 13 +++++++++++-- launcher/minecraft/auth/AccountData.h | 2 ++ launcher/minecraft/auth/steps/LauncherLoginStep.cpp | 1 + .../minecraft/auth/steps/MinecraftProfileStep.cpp | 1 + .../minecraft/auth/steps/XboxAuthorizationStep.cpp | 1 + launcher/minecraft/auth/steps/XboxUserStep.cpp | 1 + 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index f817fd278..86a5a61e1 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -41,6 +41,7 @@ #include "minecraft/auth/AccountList.h" #include "ui/InstanceWindow.h" +#include "net/NetUtils.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" @@ -185,8 +186,16 @@ LaunchDecision LaunchController::decideLaunchMode() case AccountState::Offline: { if (m_wantedLaunchMode == LaunchMode::Normal) { QMessageBox msg(m_parentWidget); - msg.setWindowTitle(tr("Auth servers offline")); - msg.setText(tr("The Minecraft authentication servers are currently unavailable.")); + + auto netErr = accountToCheck->accountData()->networkError; + if (Net::isServerError(netErr)) { + msg.setWindowTitle(tr("Auth servers offline")); + msg.setText(tr("The Minecraft authentication servers are currently unavailable.\n\n%1").arg(accountToCheck->lastError())); + } else { + msg.setWindowTitle(tr("No internet connection")); + msg.setText(tr("Unable to connect to the internet.\n\n%1").arg(accountToCheck->lastError())); + } + msg.setIcon(QMessageBox::Warning); auto* launchOfflineButton = msg.addButton(tr("Launch Offline"), QMessageBox::AcceptRole); diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 5fbe3213b..96ef94f30 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -41,6 +41,7 @@ #include #include +#include #include enum class Validity { None, Assumed, Certain }; @@ -118,5 +119,6 @@ struct AccountData { // runtime only information (not saved with the account) QString internalId; QString errorString; + QNetworkReply::NetworkError networkError = QNetworkReply::NoError; AccountState accountState = AccountState::Unchecked; }; diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 701261b52..4ceed8586 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -60,6 +60,7 @@ void LauncherLoginStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } return; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 7e089cf86..b95682b2b 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -56,6 +56,7 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 4890f4da1..422aa1a54 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -67,6 +67,7 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index e2971dcd9..382750218 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -59,6 +59,7 @@ void XboxUserStep::onRequestDone(QByteArray* response) if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } return; From 2425d2f8a3621acbe29cf191d47d56b5154ae028 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 11 Jun 2026 18:34:53 +0200 Subject: [PATCH 4/4] merge dialogs Signed-off-by: Tayou --- launcher/LaunchController.cpp | 53 +++++++++-------------------------- launcher/LaunchController.h | 2 +- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 86a5a61e1..2b882338c 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -40,8 +40,8 @@ #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountList.h" -#include "ui/InstanceWindow.h" #include "net/NetUtils.h" +#include "ui/InstanceWindow.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" @@ -183,41 +183,6 @@ LaunchDecision LaunchController::decideLaunchMode() QString reauthReason; switch (state) { - case AccountState::Offline: { - if (m_wantedLaunchMode == LaunchMode::Normal) { - QMessageBox msg(m_parentWidget); - - auto netErr = accountToCheck->accountData()->networkError; - if (Net::isServerError(netErr)) { - msg.setWindowTitle(tr("Auth servers offline")); - msg.setText(tr("The Minecraft authentication servers are currently unavailable.\n\n%1").arg(accountToCheck->lastError())); - } else { - msg.setWindowTitle(tr("No internet connection")); - msg.setText(tr("Unable to connect to the internet.\n\n%1").arg(accountToCheck->lastError())); - } - - msg.setIcon(QMessageBox::Warning); - - auto* launchOfflineButton = msg.addButton(tr("Launch Offline"), QMessageBox::AcceptRole); - auto* retryButton = msg.addButton(tr("Retry"), QMessageBox::ActionRole); - msg.addButton(tr("Cancel"), QMessageBox::RejectRole); - msg.setDefaultButton(launchOfflineButton); - - msg.exec(); - - if (msg.clickedButton() == launchOfflineButton) { - m_actualLaunchMode = LaunchMode::Offline; - return LaunchDecision::Continue; - } - if (msg.clickedButton() == retryButton) { - return LaunchDecision::Undecided; - } - return LaunchDecision::Abort; - } - - m_actualLaunchMode = LaunchMode::Offline; - return LaunchDecision::Continue; - } case AccountState::Errored: reauthReason = tr("An error occurred while refreshing '%1'").arg(accountToCheck->profileName()); break; @@ -261,13 +226,14 @@ bool LaunchController::askPlayDemo() const return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(const QString& playerName, bool* ok) const +QString LaunchController::askOfflineName(const QString& playerName, bool* ok) { if (ok != nullptr) { *ok = false; } - QString message; + QString title, message; + title = tr("Player name"); switch (m_actualLaunchMode) { case LaunchMode::Normal: Q_ASSERT(false); @@ -277,7 +243,14 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co break; case LaunchMode::Offline: if (m_wantedLaunchMode == LaunchMode::Normal) { - message = tr("You are not connected to the Internet, launching in offline mode\n\n"); + auto netErr = m_accountToUse->accountData()->networkError; + if (Net::isServerError(netErr)) { + title = tr("Auth servers offline"); + message = tr("The Minecraft authentication servers are currently unavailable, launching in offline mode.\n\n"); + } else { + title = tr("No internet connection"); + message = tr("You are not connected to the Internet, launching in offline mode.\n\n"); + } } message += tr("Choose your offline mode player name"); break; @@ -287,7 +260,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; ChooseOfflineNameDialog dialog(message, m_parentWidget); - dialog.setWindowTitle(tr("Player name")); + dialog.setWindowTitle(title); dialog.setUsername(usedname); if (dialog.exec() != QDialog::Accepted) { return {}; diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 742f20586..bc0d14e0f 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -78,7 +78,7 @@ class LaunchController : public Task { void decideAccount(); LaunchDecision decideLaunchMode(); bool askPlayDemo() const; - QString askOfflineName(const QString& playerName, bool* ok = nullptr) const; + QString askOfflineName(const QString& playerName, bool* ok = nullptr); bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason); private slots: