diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 82c97ecf..190605fd 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -183,6 +183,19 @@ void LaunchController::login() { emitFailed(errorString); return; } + case AuthSession::GoneOrMigrated: { + auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to."); + QMessageBox::warning( + nullptr, + tr("Account gone"), + errorString, + QMessageBox::StandardButton::Ok, + QMessageBox::StandardButton::Ok + ); + tryagain = false; + emitFailed(errorString); + return; + } case AuthSession::PlayableOffline: { // we ask the user for a player name bool ok = false; diff --git a/launcher/minecraft/auth/AccountTask.cpp b/launcher/minecraft/auth/AccountTask.cpp index c06be42b..d400ce8d 100644 --- a/launcher/minecraft/auth/AccountTask.cpp +++ b/launcher/minecraft/auth/AccountTask.cpp @@ -49,6 +49,8 @@ QString AccountTask::getStateMessage() const return tr("Failed to contact the authentication server."); case STATE_FAILED_HARD: return tr("Failed to authenticate."); + case STATE_FAILED_GONE: + return tr("Failed to authenticate. The account no longer exists."); default: return tr("..."); } @@ -62,7 +64,7 @@ void AccountTask::changeState(AccountTask::State newState, QString reason) { emitSucceeded(); } - else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) + else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT || newState == STATE_FAILED_GONE) { emitFailed(reason); } diff --git a/launcher/minecraft/auth/AccountTask.h b/launcher/minecraft/auth/AccountTask.h index fc3488eb..4f3bd52a 100644 --- a/launcher/minecraft/auth/AccountTask.h +++ b/launcher/minecraft/auth/AccountTask.h @@ -76,6 +76,7 @@ public: STATE_WORKING, STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated STATE_FAILED_HARD, //!< hard failure. auth is invalid + STATE_FAILED_GONE, //!< hard failure. auth is invalid, and the account no longer exists STATE_SUCCEEDED } m_accountState = STATE_CREATED; diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index d77435b8..f609d5d3 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -18,7 +18,8 @@ struct AuthSession RequiresOAuth, RequiresPassword, PlayableOffline, - PlayableOnline + PlayableOnline, + GoneOrMigrated } status = Undetermined; // client token diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 4231d6b0..2d76f9ac 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -227,32 +227,60 @@ void MinecraftAccount::authFailed(QString reason) auto session = m_currentTask->getAssignedSession(); // This is emitted when the yggdrasil tasks time out or are cancelled. // -> we treat the error as no-op - if (m_currentTask->accountState() == AccountTask::STATE_FAILED_SOFT) - { - if (session) - { - session->status = accountStatus() == Verified ? AuthSession::PlayableOffline : AuthSession::RequiresPassword; - session->auth_server_online = false; - fillSession(session); + switch (m_currentTask->accountState()) { + case AccountTask::STATE_FAILED_SOFT: { + if (session) + { + if(accountStatus() == Verified) { + session->status = AuthSession::PlayableOffline; + } + else { + if(data.type == AccountType::MSA) { + session->status = AuthSession::RequiresOAuth; + } + else { + session->status = AuthSession::RequiresPassword; + } + } + session->auth_server_online = false; + fillSession(session); + } } - } - else - { - // FIXME: MSA ... - data.yggdrasilToken.token = QString(); - data.yggdrasilToken.validity = Katabasis::Validity::None; - data.validity_ = Katabasis::Validity::None; - emit changed(); - if (session) - { - if(data.type == AccountType::MSA) { - session->status = AuthSession::RequiresOAuth; + break; + case AccountTask::STATE_FAILED_HARD: { + // FIXME: MSA data clearing + data.yggdrasilToken.token = QString(); + data.yggdrasilToken.validity = Katabasis::Validity::None; + data.validity_ = Katabasis::Validity::None; + emit changed(); + if (session) + { + if(data.type == AccountType::MSA) { + session->status = AuthSession::RequiresOAuth; + } + else { + session->status = AuthSession::RequiresPassword; + } + session->auth_server_online = true; + fillSession(session); } - else { - session->status = AuthSession::RequiresPassword; + } + break; + case AccountTask::STATE_FAILED_GONE: { + data.validity_ = Katabasis::Validity::None; + emit changed(); + if (session) + { + session->status = AuthSession::GoneOrMigrated; + session->auth_server_online = true; + fillSession(session); } - session->auth_server_online = true; - fillSession(session); + } + break; + case AccountTask::STATE_CREATED: + case AccountTask::STATE_WORKING: + case AccountTask::STATE_SUCCEEDED: { + // Not reachable here, as they are not failures. } } m_currentTask.reset(); diff --git a/launcher/minecraft/auth/flows/Yggdrasil.cpp b/launcher/minecraft/auth/flows/Yggdrasil.cpp index 7cea059c..c2935d05 100644 --- a/launcher/minecraft/auth/flows/Yggdrasil.cpp +++ b/launcher/minecraft/auth/flows/Yggdrasil.cpp @@ -255,10 +255,17 @@ void Yggdrasil::processReply() case QNetworkReply::ContentAccessDenied: case QNetworkReply::ContentOperationNotPermittedError: break; + case QNetworkReply::ContentGoneError: { + changeState( + STATE_FAILED_GONE, + tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.") + ); + } default: - changeState(STATE_FAILED_SOFT, - tr("Authentication operation failed due to a network error: %1 (%2)") - .arg(m_netReply->errorString()).arg(m_netReply->error())); + changeState( + STATE_FAILED_SOFT, + tr("Authentication operation failed due to a network error: %1 (%2)").arg(m_netReply->errorString()).arg(m_netReply->error()) + ); return; } @@ -283,10 +290,10 @@ void Yggdrasil::processReply() } else { - changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " - "JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); + changeState( + STATE_FAILED_SOFT, + tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset) + ); qCritical() << replyData; } return; @@ -301,19 +308,18 @@ void Yggdrasil::processReply() // We were able to parse the server's response. Woo! // Call processError. If a subclass has overridden it then they'll handle their // stuff there. - qDebug() << "The request failed, but the server gave us an error message. " - "Processing error."; + qDebug() << "The request failed, but the server gave us an error message. Processing error."; processError(doc.object()); } else { // The server didn't say anything regarding the error. Give the user an unknown // error. - qDebug() - << "The request failed and the server gave no error message. Unknown error."; - changeState(STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the " - "authentication server: %1").arg(m_netReply->errorString())); + qDebug() << "The request failed and the server gave no error message. Unknown error."; + changeState( + STATE_FAILED_SOFT, + tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()) + ); } } @@ -325,8 +331,13 @@ void Yggdrasil::processError(QJsonObject responseData) if (errorVal.isString() && errorMessageValue.isString()) { - m_error = std::shared_ptr(new Error{ - errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); + m_error = std::shared_ptr( + new Error { + errorVal.toString(""), + errorMessageValue.toString(""), + causeVal.toString("") + } + ); changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); } else