diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 59028b60..76af0ac0 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -205,6 +205,18 @@ QVariant AccountList::data(const QModelIndex &index, int role) const return account->profileName(); } + case MigrationColumn: { + if(account->isMSA()) { + return tr("N/A", "Can Migrate?"); + } + if (account->canMigrate()) { + return tr("Yes", "Can Migrate?"); + } + else { + return tr("No", "Can Migrate?"); + } + } + default: return QVariant(); } @@ -238,6 +250,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r return tr("Account"); case TypeColumn: return tr("Type"); + case MigrationColumn: + return tr("Can Migrate?"); case ProfileNameColumn: return tr("Profile"); default: @@ -251,6 +265,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r return tr("User name of the account."); case TypeColumn: return tr("Type of the account - Mojang or MSA."); + case MigrationColumn: + return tr("Can this account migrate to Microsoft account?"); case ProfileNameColumn: return tr("Name of the Minecraft profile associated with the account."); default: diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h index ac3684ee..ed08bb1d 100644 --- a/launcher/minecraft/auth/AccountList.h +++ b/launcher/minecraft/auth/AccountList.h @@ -40,6 +40,7 @@ public: // TODO: Add icon column. NameColumn = 0, ProfileNameColumn, + MigrationColumn, TypeColumn, NUM_COLUMNS diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 72bb6bd4..5b0c1ec7 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -117,6 +117,14 @@ public: /* queries */ return data.profileName(); } + bool canMigrate() const { + return data.canMigrateToMSA; + } + + bool isMSA() const { + return data.type == AccountType::MSA; + } + QString typeString() const { switch(data.type) { case AccountType::Mojang: { diff --git a/launcher/minecraft/auth/flows/AuthContext.cpp b/launcher/minecraft/auth/flows/AuthContext.cpp index 9ae99453..5d7d858d 100644 --- a/launcher/minecraft/auth/flows/AuthContext.cpp +++ b/launcher/minecraft/auth/flows/AuthContext.cpp @@ -192,6 +192,15 @@ bool getNumber(QJsonValue value, double & out) { return true; } + +bool getBool(QJsonValue value, bool & out) { + if(!value.isBool()) { + return false; + } + out = value.toBool(); + return true; +} + /* { "IssueInstant":"2020-12-07T19:52:08.4463796Z", @@ -693,6 +702,63 @@ void AuthContext::onMinecraftProfileDone(int, QNetworkReply::NetworkError error, changeState(STATE_FAILED_HARD, tr("Minecraft Java profile response could not be parsed")); return; } + + if(m_data->type == AccountType::Mojang) { + doMigrationEligibilityCheck(); + } + else { + doGetSkin(); + } +} + +void AuthContext::doMigrationEligibilityCheck() { + setStage(AuthStage::MigrationEligibility); + changeState(STATE_WORKING, tr("Starting check for migration eligibility")); + + auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration"); + QNetworkRequest request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); + + Requestor *requestor = new Requestor(mgr, m_oauth2, this); + connect(requestor, &Requestor::finished, this, &AuthContext::onMigrationEligibilityCheckDone); + requestor->get(request); +} + +bool parseRolloutResponse(QByteArray & data, bool& result) { + qDebug() << "Parsing Rollout response..."; +#ifndef NDEBUG + qDebug() << data; +#endif + + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if(jsonError.error) { + qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: " << jsonError.errorString(); + return false; + } + + auto obj = doc.object(); + QString feature; + if(!getString(obj.value("feature"), feature)) { + qWarning() << "Rollout feature is not a string"; + return false; + } + if(feature != "msamigration") { + qWarning() << "Rollout feature is not what we expected (msamigration), but is instead \"" << feature << "\""; + return false; + } + if(!getBool(obj.value("rollout"), result)) { + qWarning() << "Rollout feature is not a string"; + return false; + } + return true; +} + +void AuthContext::onMigrationEligibilityCheckDone(int, QNetworkReply::NetworkError error, QByteArray data, QList headers) { + if (error == QNetworkReply::NoError) { + parseRolloutResponse(data, m_data->canMigrateToMSA); + } doGetSkin(); } @@ -742,6 +808,8 @@ QString AuthContext::getStateMessage() const { return tr("Logging in with XBox and Mojang services"); case AuthStage::MinecraftProfile: return tr("Getting Minecraft profile"); + case AuthStage::MigrationEligibility: + return tr("Checking for migration eligibility"); case AuthStage::Skin: return tr("Getting Minecraft skin"); case AuthStage::Complete: diff --git a/launcher/minecraft/auth/flows/AuthContext.h b/launcher/minecraft/auth/flows/AuthContext.h index 1d9f8f72..7bf69623 100644 --- a/launcher/minecraft/auth/flows/AuthContext.h +++ b/launcher/minecraft/auth/flows/AuthContext.h @@ -63,6 +63,9 @@ protected: void doMinecraftProfile(); Q_SLOT void onMinecraftProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList); + void doMigrationEligibilityCheck(); + Q_SLOT void onMigrationEligibilityCheckDone(int, QNetworkReply::NetworkError, QByteArray, QList); + void doGetSkin(); Q_SLOT void onSkinDone(int, QNetworkReply::NetworkError, QByteArray, QList); @@ -86,6 +89,7 @@ protected: UserAuth, XboxAuth, MinecraftProfile, + MigrationEligibility, Skin, Complete } m_stage = AuthStage::Initial; diff --git a/launcher/pages/global/AccountListPage.cpp b/launcher/pages/global/AccountListPage.cpp index 45b778de..6bb07b22 100644 --- a/launcher/pages/global/AccountListPage.cpp +++ b/launcher/pages/global/AccountListPage.cpp @@ -153,6 +153,22 @@ void AccountListPage::on_actionRemove_triggered() } } +void AccountListPage::on_actionRefresh_triggered() { + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() > 0) { + QModelIndex selected = selection.first(); + MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value(); + AuthSessionPtr session = std::make_shared(); + auto task = account->refresh(session); + if (task) { + ProgressDialog progDialog(this); + progDialog.execWithTask(task.get()); + // TODO: respond to results of the task + } + } +} + + void AccountListPage::on_actionSetDefault_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); @@ -178,6 +194,7 @@ void AccountListPage::updateButtonStates() ui->actionSetDefault->setEnabled(selection.size() > 0); ui->actionUploadSkin->setEnabled(selection.size() > 0); ui->actionDeleteSkin->setEnabled(selection.size() > 0); + ui->actionRefresh->setEnabled(selection.size() > 0); if(m_accounts->activeAccount().get() == nullptr) { ui->actionNoDefault->setEnabled(false); diff --git a/launcher/pages/global/AccountListPage.h b/launcher/pages/global/AccountListPage.h index 24bb96da..4474802e 100644 --- a/launcher/pages/global/AccountListPage.h +++ b/launcher/pages/global/AccountListPage.h @@ -63,6 +63,7 @@ public slots: void on_actionAddMojang_triggered(); void on_actionAddMicrosoft_triggered(); void on_actionRemove_triggered(); + void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); void on_actionNoDefault_triggered(); void on_actionUploadSkin_triggered(); diff --git a/launcher/pages/global/AccountListPage.ui b/launcher/pages/global/AccountListPage.ui index 887c3d48..8af23a2f 100644 --- a/launcher/pages/global/AccountListPage.ui +++ b/launcher/pages/global/AccountListPage.ui @@ -54,6 +54,7 @@ + @@ -102,6 +103,14 @@ Add Microsoft + + + Refresh + + + Refresh the account tokens + +