[Backport release-11.x] When auth is down, launch into offline mode (#5729)
Some checks failed
Nix / Build (aarch64-darwin) (push) Has been cancelled
Nix / Build (x86_64-linux) (push) Has been cancelled
Nix / Build (aarch64-linux) (push) Has been cancelled

This commit is contained in:
Alexandru Ionut Tripon 2026-06-26 20:53:13 +03:00 committed by GitHub
commit 8ae894c7b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 38 additions and 9 deletions

View file

@ -40,6 +40,7 @@
#include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h" #include "minecraft/auth/AccountList.h"
#include "net/NetUtils.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/MSALoginDialog.h"
@ -225,13 +226,14 @@ bool LaunchController::askPlayDemo() const
return box.clickedButton() == demoButton; return box.clickedButton() == demoButton;
} }
QString LaunchController::askOfflineName(const QString& playerName, bool* ok) const QString LaunchController::askOfflineName(const QString& playerName, bool* ok)
{ {
if (ok != nullptr) { if (ok != nullptr) {
*ok = false; *ok = false;
} }
QString message; QString title, message;
title = tr("Player name");
switch (m_actualLaunchMode) { switch (m_actualLaunchMode) {
case LaunchMode::Normal: case LaunchMode::Normal:
Q_ASSERT(false); Q_ASSERT(false);
@ -241,7 +243,14 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co
break; break;
case LaunchMode::Offline: case LaunchMode::Offline:
if (m_wantedLaunchMode == LaunchMode::Normal) { 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"); message += tr("Choose your offline mode player name");
break; break;
@ -251,7 +260,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co
QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName;
ChooseOfflineNameDialog dialog(message, m_parentWidget); ChooseOfflineNameDialog dialog(message, m_parentWidget);
dialog.setWindowTitle(tr("Player name")); dialog.setWindowTitle(title);
dialog.setUsername(usedname); dialog.setUsername(usedname);
if (dialog.exec() != QDialog::Accepted) { if (dialog.exec() != QDialog::Accepted) {
return {}; return {};

View file

@ -78,7 +78,7 @@ class LaunchController : public Task {
void decideAccount(); void decideAccount();
LaunchDecision decideLaunchMode(); LaunchDecision decideLaunchMode();
bool askPlayDemo() const; 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); bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason);
private slots: private slots:

View file

@ -41,6 +41,7 @@
#include <QDateTime> #include <QDateTime>
#include <QMap> #include <QMap>
#include <QNetworkReply>
#include <QVariantMap> #include <QVariantMap>
enum class Validity { None, Assumed, Certain }; enum class Validity { None, Assumed, Certain };
@ -118,5 +119,6 @@ struct AccountData {
// runtime only information (not saved with the account) // runtime only information (not saved with the account)
QString internalId; QString internalId;
QString errorString; QString errorString;
QNetworkReply::NetworkError networkError = QNetworkReply::NoError;
AccountState accountState = AccountState::Unchecked; AccountState accountState = AccountState::Unchecked;
}; };

View file

@ -56,10 +56,11 @@ void LauncherLoginStep::onRequestDone(QByteArray* response)
qCDebug(authCredentials()) << *response; qCDebug(authCredentials()) << *response;
if (m_request->error() != QNetworkReply::NoError) { if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error(); 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, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); tr("Failed to get Minecraft access token: %1").arg(m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString()));
} }
return; return;

View file

@ -52,10 +52,11 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response)
qWarning() << " Response:"; qWarning() << " Response:";
qWarning() << QString::fromUtf8(*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, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, emit finished(AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
} }

View file

@ -60,13 +60,14 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response)
qCDebug(authCredentials()) << *response; qCDebug(authCredentials()) << *response;
if (m_request->error() != QNetworkReply::NoError) { if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error(); 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)) { if (processSTSError(*response)) {
return; return;
} }
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, emit finished(AccountTaskState::STATE_OFFLINE,
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
} }

View file

@ -56,9 +56,10 @@ void XboxUserStep::onRequestDone(QByteArray* response)
{ {
if (m_request->error() != QNetworkReply::NoError) { if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error(); 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())); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString()));
} }
return; return;

View file

@ -40,4 +40,18 @@ inline bool isApplicationError(QNetworkReply::NetworkError x)
QNetworkReply::UnknownContentError }; QNetworkReply::UnknownContentError };
return errors.contains(x); 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<QNetworkReply::NetworkError> 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 } // namespace Net