From 8990841c8b71e3b8b9cbe21594e6623228b7b867 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Mon, 25 Nov 2024 17:42:38 -0500 Subject: [PATCH] Add dialog to select profile if multiple availableProfiles Resolves https://github.com/unmojang/FjordLauncher/issues/29 and obsoletes https://github.com/unmojang/FjordLauncher/pull/34. This implementation follows the authlib-injector specification: https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#%E8%B4%A6%E6%88%B7%E7%9A%84%E6%B7%BB%E5%8A%A0 ([Google translate to English](https://github-com.translate.goog/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en-US)) --- .../minecraft/auth/steps/YggdrasilStep.cpp | 66 +++++++++++++++---- launcher/minecraft/auth/steps/YggdrasilStep.h | 1 + 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp index 6548646e7..eb195e9a0 100644 --- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp +++ b/launcher/minecraft/auth/steps/YggdrasilStep.cpp @@ -1,4 +1,5 @@ #include "YggdrasilStep.h" +#include #include "Application.h" #include "net/RawHeaderProxy.h" @@ -48,10 +49,9 @@ void YggdrasilStep::login(QString password) req.insert("username", m_data->userName()); req.insert("password", password); req.insert("requestUser", false); - // + // If we already have a client token, give it to the server. // Otherwise, let the server give us one. - m_data->generateClientTokenIfMissing(); req.insert("clientToken", m_data->clientToken()); @@ -79,19 +79,23 @@ void YggdrasilStep::refresh() /* * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } + * "clientToken": "client identifier", + * "accessToken": "current access token to be refreshed", + * "selectedProfile": { + * "id": "profile ID", + * "name": "profile name" + * }, * "requestUser": true/false // request the user structure * } */ + QJsonObject selectedProfile; + selectedProfile.insert("id", m_data->profileId()); + selectedProfile.insert("name", m_data->profileName()); + QJsonObject req; req.insert("clientToken", m_data->clientToken()); req.insert("accessToken", m_data->accessToken()); + req.insert("selectedProfile", selectedProfile); req.insert("requestUser", false); QJsonDocument doc(req); @@ -238,17 +242,49 @@ void YggdrasilStep::processResponse(QJsonObject responseData) // Get UUID here since we need it for later // FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided auto profile = responseData.value("selectedProfile"); - if (!profile.isObject()) { + if (profile.isObject()) { + m_didSelectProfile = false; + } else { + if (m_didSelectProfile) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't save our selected profile.")); + } auto profiles = responseData.value("availableProfiles"); if (!profiles.isArray()) { emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles.")); return; } else { - if (profiles.toArray().isEmpty()) { + const auto& profilesArray = profiles.toArray(); + if (profilesArray.isEmpty()) { emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile.")); return; - } else { + } else if (profilesArray.size() == 1) { profile = profiles.toArray().first(); + } else { + std::map profileMap; + QStringList profileNames; + + const auto& invalidProfileMessage = tr("Authentication server sent an invalid available profile."); + for (const auto& profileValue : profilesArray) { + if (!profileValue.isObject()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, invalidProfileMessage); + } + const auto& profileNameValue = profileValue.toObject().value("name"); + if (!profileNameValue.isString()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, invalidProfileMessage); + } + const auto& profileName = profileNameValue.toString(); + profileMap.insert({ profileName, profileValue }); + profileNames.append(profileName); + } + bool ok; + const auto& profileName = + QInputDialog::getItem(nullptr, "Select a player", "Select a player:", profileNames, 0, false, &ok); + + if (!ok || profileName.isEmpty()) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication cancelled.")); + } + profile = profileMap[profileName]; + m_didSelectProfile = true; } } } @@ -267,6 +303,12 @@ void YggdrasilStep::processResponse(QJsonObject responseData) return; } + if (m_didSelectProfile) { + // The authlib-injector specification requires that we refresh immediately after the user has selected a profile: + // https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#%E8%B4%A6%E6%88%B7%E7%9A%84%E6%B7%BB%E5%8A%A0 + return refresh(); + } + emit finished(AccountTaskState::STATE_WORKING, "Logged in"); } diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.h b/launcher/minecraft/auth/steps/YggdrasilStep.h index 70f4f40f8..db3ce4e42 100644 --- a/launcher/minecraft/auth/steps/YggdrasilStep.h +++ b/launcher/minecraft/auth/steps/YggdrasilStep.h @@ -33,4 +33,5 @@ class YggdrasilStep : public AuthStep { std::shared_ptr m_response; Net::Upload::Ptr m_request; NetJob::Ptr m_task; + bool m_didSelectProfile = false; };