From 3a53349e332599221bc325f7fac9dc7927194bc2 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Mon, 26 Jul 2021 21:44:11 +0200 Subject: GH-3392 dirty initial MSA support that shares logic with Mojang flows Both act as the first step of AuthContext. --- CMakeLists.txt | 3 + buildconfig/BuildConfig.cpp.in | 1 + buildconfig/BuildConfig.h | 6 +- launcher/BaseInstance.h | 2 +- launcher/CMakeLists.txt | 39 +- launcher/Env.cpp | 1 - launcher/LaunchController.cpp | 171 ++-- launcher/MainWindow.cpp | 158 ++-- launcher/MainWindow.h | 5 +- launcher/MultiMC.cpp | 4 +- launcher/MultiMC.h | 6 +- launcher/SkinUtils.cpp | 4 +- launcher/dialogs/LoginDialog.cpp | 9 +- launcher/dialogs/LoginDialog.h | 6 +- launcher/dialogs/LoginDialog.ui | 12 +- launcher/dialogs/MSALoginDialog.cpp | 96 +++ launcher/dialogs/MSALoginDialog.h | 55 ++ launcher/dialogs/MSALoginDialog.ui | 60 ++ launcher/dialogs/ProfileSelectDialog.cpp | 32 +- launcher/dialogs/ProfileSelectDialog.h | 8 +- launcher/dialogs/SkinUploadDialog.cpp | 2 +- launcher/dialogs/SkinUploadDialog.h | 6 +- launcher/minecraft/MinecraftInstance.cpp | 16 +- launcher/minecraft/auth-msa/BuildConfig.cpp.in | 9 - launcher/minecraft/auth-msa/BuildConfig.h | 11 - launcher/minecraft/auth-msa/CMakeLists.txt | 28 - launcher/minecraft/auth-msa/context.cpp | 938 --------------------- launcher/minecraft/auth-msa/context.h | 128 --- launcher/minecraft/auth-msa/main.cpp | 100 --- launcher/minecraft/auth-msa/mainwindow.cpp | 97 --- launcher/minecraft/auth-msa/mainwindow.h | 34 - launcher/minecraft/auth-msa/mainwindow.ui | 72 -- launcher/minecraft/auth/AccountData.cpp | 387 +++++++++ launcher/minecraft/auth/AccountData.h | 73 ++ launcher/minecraft/auth/AccountList.cpp | 519 ++++++++++++ launcher/minecraft/auth/AccountList.h | 118 +++ launcher/minecraft/auth/AccountTask.cpp | 69 ++ launcher/minecraft/auth/AccountTask.h | 103 +++ launcher/minecraft/auth/AuthSession.cpp | 2 + launcher/minecraft/auth/AuthSession.h | 13 +- launcher/minecraft/auth/MinecraftAccount.cpp | 303 +++++++ launcher/minecraft/auth/MinecraftAccount.h | 174 ++++ launcher/minecraft/auth/MojangAccount.cpp | 315 ------- launcher/minecraft/auth/MojangAccount.h | 180 ---- launcher/minecraft/auth/MojangAccountList.cpp | 468 ---------- launcher/minecraft/auth/MojangAccountList.h | 199 ----- launcher/minecraft/auth/YggdrasilTask.cpp | 255 ------ launcher/minecraft/auth/YggdrasilTask.h | 151 ---- launcher/minecraft/auth/flows/AuthContext.cpp | 752 +++++++++++++++++ launcher/minecraft/auth/flows/AuthContext.h | 94 +++ launcher/minecraft/auth/flows/AuthenticateTask.cpp | 202 ----- launcher/minecraft/auth/flows/AuthenticateTask.h | 46 - launcher/minecraft/auth/flows/MSAHelper.txt | 51 ++ launcher/minecraft/auth/flows/MSAInteractive.cpp | 20 + launcher/minecraft/auth/flows/MSAInteractive.h | 10 + launcher/minecraft/auth/flows/MSASilent.cpp | 16 + launcher/minecraft/auth/flows/MSASilent.h | 10 + launcher/minecraft/auth/flows/MojangLogin.cpp | 14 + launcher/minecraft/auth/flows/MojangLogin.h | 13 + launcher/minecraft/auth/flows/MojangRefresh.cpp | 14 + launcher/minecraft/auth/flows/MojangRefresh.h | 10 + launcher/minecraft/auth/flows/RefreshTask.cpp | 144 ---- launcher/minecraft/auth/flows/RefreshTask.h | 44 - launcher/minecraft/auth/flows/ValidateTask.cpp | 61 -- launcher/minecraft/auth/flows/ValidateTask.h | 47 -- launcher/minecraft/auth/flows/Yggdrasil.cpp | 337 ++++++++ launcher/minecraft/auth/flows/Yggdrasil.h | 82 ++ launcher/minecraft/launch/ClaimAccount.h | 4 +- launcher/pages/global/AccountListPage.cpp | 80 +- launcher/pages/global/AccountListPage.h | 8 +- launcher/pages/global/AccountListPage.ui | 30 +- launcher/pages/instance/VersionPage.cpp | 2 +- 72 files changed, 3702 insertions(+), 3837 deletions(-) create mode 100644 launcher/dialogs/MSALoginDialog.cpp create mode 100644 launcher/dialogs/MSALoginDialog.h create mode 100644 launcher/dialogs/MSALoginDialog.ui delete mode 100644 launcher/minecraft/auth-msa/BuildConfig.cpp.in delete mode 100644 launcher/minecraft/auth-msa/BuildConfig.h delete mode 100644 launcher/minecraft/auth-msa/CMakeLists.txt delete mode 100644 launcher/minecraft/auth-msa/context.cpp delete mode 100644 launcher/minecraft/auth-msa/context.h delete mode 100644 launcher/minecraft/auth-msa/main.cpp delete mode 100644 launcher/minecraft/auth-msa/mainwindow.cpp delete mode 100644 launcher/minecraft/auth-msa/mainwindow.h delete mode 100644 launcher/minecraft/auth-msa/mainwindow.ui create mode 100644 launcher/minecraft/auth/AccountData.cpp create mode 100644 launcher/minecraft/auth/AccountData.h create mode 100644 launcher/minecraft/auth/AccountList.cpp create mode 100644 launcher/minecraft/auth/AccountList.h create mode 100644 launcher/minecraft/auth/AccountTask.cpp create mode 100644 launcher/minecraft/auth/AccountTask.h create mode 100644 launcher/minecraft/auth/MinecraftAccount.cpp create mode 100644 launcher/minecraft/auth/MinecraftAccount.h delete mode 100644 launcher/minecraft/auth/MojangAccount.cpp delete mode 100644 launcher/minecraft/auth/MojangAccount.h delete mode 100644 launcher/minecraft/auth/MojangAccountList.cpp delete mode 100644 launcher/minecraft/auth/MojangAccountList.h delete mode 100644 launcher/minecraft/auth/YggdrasilTask.cpp delete mode 100644 launcher/minecraft/auth/YggdrasilTask.h create mode 100644 launcher/minecraft/auth/flows/AuthContext.cpp create mode 100644 launcher/minecraft/auth/flows/AuthContext.h delete mode 100644 launcher/minecraft/auth/flows/AuthenticateTask.cpp delete mode 100644 launcher/minecraft/auth/flows/AuthenticateTask.h create mode 100644 launcher/minecraft/auth/flows/MSAHelper.txt create mode 100644 launcher/minecraft/auth/flows/MSAInteractive.cpp create mode 100644 launcher/minecraft/auth/flows/MSAInteractive.h create mode 100644 launcher/minecraft/auth/flows/MSASilent.cpp create mode 100644 launcher/minecraft/auth/flows/MSASilent.h create mode 100644 launcher/minecraft/auth/flows/MojangLogin.cpp create mode 100644 launcher/minecraft/auth/flows/MojangLogin.h create mode 100644 launcher/minecraft/auth/flows/MojangRefresh.cpp create mode 100644 launcher/minecraft/auth/flows/MojangRefresh.h delete mode 100644 launcher/minecraft/auth/flows/RefreshTask.cpp delete mode 100644 launcher/minecraft/auth/flows/RefreshTask.h delete mode 100644 launcher/minecraft/auth/flows/ValidateTask.cpp delete mode 100644 launcher/minecraft/auth/flows/ValidateTask.h create mode 100644 launcher/minecraft/auth/flows/Yggdrasil.cpp create mode 100644 launcher/minecraft/auth/flows/Yggdrasil.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 521fdaad..817b4cfc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,9 @@ set(MultiMC_DISCORD_URL "" CACHE STRING "URL for the Discord guild.") # Subreddit URL set(MultiMC_SUBREDDIT_URL "" CACHE STRING "URL for the subreddit.") +# MSA Client ID +set(MultiMC_MSA_CLIENT_ID "" CACHE STRING "Client ID used for MSA authentication") + #### Check the current Git commit and branch include(GetGitRevisionDescription) get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 60d417a6..9d4771b4 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -35,6 +35,7 @@ Config::Config() PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@"; IMGUR_CLIENT_ID = "@MultiMC_IMGUR_CLIENT_ID@"; META_URL = "@MultiMC_META_URL@"; + MSA_CLIENT_ID = "@MultiMC_MSA_CLIENT_ID@"; BUG_TRACKER_URL = "@MultiMC_BUG_TRACKER_URL@"; DISCORD_URL = "@MultiMC_DISCORD_URL@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 185bebad..71880109 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -75,13 +75,17 @@ public: */ QString META_URL; + /** + * MSA client ID - registered with Azure / Microsoft, needs correct setup on MS side. + */ + QString MSA_CLIENT_ID; + QString BUG_TRACKER_URL; QString DISCORD_URL; QString SUBREDDIT_URL; QString RESOURCE_BASE = "https://resources.download.minecraft.net/"; QString LIBRARY_BASE = "https://libraries.minecraft.net/"; - QString SKINS_BASE = "https://crafatar.com/skins/"; QString AUTH_BASE = "https://authserver.mojang.com/"; QString MOJANG_STATUS_URL = "https://status.mojang.com/check"; QString IMGUR_BASE_URL = "https://api.imgur.com/3/"; diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 833646c0..8c08dc05 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -26,7 +26,7 @@ #include "settings/INIFile.h" #include "BaseVersionList.h" -#include "minecraft/auth/MojangAccount.h" +#include "minecraft/auth/MinecraftAccount.h" #include "MessageLevel.h" #include "pathmatcher/IPathMatcher.h" diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 37f5d3a1..3c140ede 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -203,20 +203,31 @@ set(STATUS_SOURCES # Support for Minecraft instances and launch set(MINECRAFT_SOURCES # Minecraft support + minecraft/auth/AccountData.h + minecraft/auth/AccountData.cpp + minecraft/auth/AccountTask.h + minecraft/auth/AccountTask.cpp minecraft/auth/AuthSession.h minecraft/auth/AuthSession.cpp - minecraft/auth/MojangAccountList.h - minecraft/auth/MojangAccountList.cpp - minecraft/auth/MojangAccount.h - minecraft/auth/MojangAccount.cpp - minecraft/auth/YggdrasilTask.h - minecraft/auth/YggdrasilTask.cpp - minecraft/auth/flows/AuthenticateTask.h - minecraft/auth/flows/AuthenticateTask.cpp - minecraft/auth/flows/RefreshTask.cpp - minecraft/auth/flows/RefreshTask.cpp - minecraft/auth/flows/ValidateTask.h - minecraft/auth/flows/ValidateTask.cpp + minecraft/auth/AccountList.h + minecraft/auth/AccountList.cpp + minecraft/auth/MinecraftAccount.h + minecraft/auth/MinecraftAccount.cpp + minecraft/auth/flows/AuthContext.h + minecraft/auth/flows/AuthContext.cpp + + minecraft/auth/flows/MSAInteractive.h + minecraft/auth/flows/MSAInteractive.cpp + minecraft/auth/flows/MSASilent.h + minecraft/auth/flows/MSASilent.cpp + + minecraft/auth/flows/MojangLogin.h + minecraft/auth/flows/MojangLogin.cpp + minecraft/auth/flows/MojangRefresh.h + minecraft/auth/flows/MojangRefresh.cpp + + minecraft/auth/flows/Yggdrasil.h + minecraft/auth/flows/Yggdrasil.cpp minecraft/gameoptions/GameOptions.h minecraft/gameoptions/GameOptions.cpp @@ -732,6 +743,8 @@ SET(MULTIMC_SOURCES dialogs/IconPickerDialog.h dialogs/LoginDialog.cpp dialogs/LoginDialog.h + dialogs/MSALoginDialog.cpp + dialogs/MSALoginDialog.h dialogs/NewComponentDialog.cpp dialogs/NewComponentDialog.h dialogs/NewInstanceDialog.cpp @@ -850,6 +863,7 @@ SET(MULTIMC_UIS dialogs/EditAccountDialog.ui dialogs/ExportInstanceDialog.ui dialogs/LoginDialog.ui + dialogs/MSALoginDialog.ui dialogs/UpdateDialog.ui dialogs/NotificationDialog.ui dialogs/SkinUploadDialog.ui @@ -892,6 +906,7 @@ target_link_libraries(MultiMC_logic optional-bare tomlc99 BuildConfig + Katabasis ) target_link_libraries(MultiMC_logic Qt5::Core diff --git a/launcher/Env.cpp b/launcher/Env.cpp index 71b49d95..abf9f58c 100644 --- a/launcher/Env.cpp +++ b/launcher/Env.cpp @@ -101,7 +101,6 @@ void Env::initHttpMetaCache() m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath()); m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); - m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index ee764082..11780625 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -1,6 +1,6 @@ #include "LaunchController.h" #include "MainWindow.h" -#include +#include #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/ProfileSelectDialog.h" @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -35,22 +35,23 @@ void LaunchController::executeTask() } // FIXME: minecraft specific -void LaunchController::login() -{ +void LaunchController::login() { JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget); // Find an account to use. - std::shared_ptr accounts = MMC->accounts(); - MojangAccountPtr account = accounts->activeAccount(); + std::shared_ptr accounts = MMC->accounts(); if (accounts->count() <= 0) { // Tell the user they need to log in at least one account in order to play. auto reply = CustomMessageBox::selectable( - m_parentWidget, tr("No Accounts"), + m_parentWidget, + tr("No Accounts"), tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " "account logged in to MultiMC." "Would you like to open the account manager to add an account now?"), - QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)->exec(); + QMessageBox::Information, + QMessageBox::Yes | QMessageBox::No + )->exec(); if (reply == QMessageBox::Yes) { @@ -58,11 +59,16 @@ void LaunchController::login() MMC->ShowGlobalSettings(m_parentWidget, "accounts"); } } - else if (account.get() == nullptr) + + MinecraftAccountPtr account = accounts->activeAccount(); + if (account.get() == nullptr) { // If no default account is set, ask the user which one to use. - ProfileSelectDialog selectDialog(tr("Which profile would you like to use?"), - ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); + ProfileSelectDialog selectDialog( + tr("Which account would you like to use?"), + ProfileSelectDialog::GlobalDefaultCheckbox, + m_parentWidget + ); selectDialog.exec(); @@ -70,8 +76,9 @@ void LaunchController::login() account = selectDialog.selectedAccount(); // If the user said to use the account as default, do that. - if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) - accounts->setActiveAccount(account->username()); + if (selectDialog.useAsGlobalDefault() && account.get() != nullptr) { + accounts->setActiveAccount(account->profileId()); + } } // if no account is selected, we bail @@ -93,7 +100,13 @@ void LaunchController::login() { m_session = std::make_shared(); m_session->wants_online = m_online; - auto task = account->login(m_session, password); + std::shared_ptr task; + if(!password.isNull()) { + task = account->login(m_session, password); + } + else { + task = account->refresh(m_session); + } if (task) { // We'll need to validate the access token to make sure the account @@ -107,9 +120,9 @@ void LaunchController::login() if (!task->wasSuccessful()) { auto failReasonNew = task->failReason(); - if(failReasonNew == "Invalid token.") + if(failReasonNew == "Invalid token." || failReasonNew == "Invalid Signature") { - account->invalidateClientToken(); + // account->invalidateClientToken(); failReason = needLoginAgain; } else failReason = failReasonNew; @@ -117,72 +130,82 @@ void LaunchController::login() } switch (m_session->status) { - case AuthSession::Undetermined: - { - qCritical() << "Received undetermined session status during login. Bye."; - tryagain = false; - emitFailed(tr("Received undetermined session status during login.")); - break; - } - case AuthSession::RequiresPassword: - { - EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField); - auto username = m_session->username; - auto chopN = [](QString toChop, int N) -> QString - { - if(toChop.size() > N) + case AuthSession::Undetermined: { + qCritical() << "Received undetermined session status during login. Bye."; + tryagain = false; + emitFailed(tr("Received undetermined session status during login.")); + return; + } + case AuthSession::RequiresPassword: { + // FIXME: this needs to understand MSA + EditAccountDialog passDialog(failReason, m_parentWidget, EditAccountDialog::PasswordField); + auto username = m_session->username; + auto chopN = [](QString toChop, int N) -> QString { - auto left = toChop.left(N); - left += QString("\u25CF").repeated(toChop.size() - N); - return left; - } - return toChop; - }; + if(toChop.size() > N) + { + auto left = toChop.left(N); + left += QString("\u25CF").repeated(toChop.size() - N); + return left; + } + return toChop; + }; - if(username.contains('@')) - { - auto parts = username.split('@'); - auto mailbox = chopN(parts[0],3); - QString domain = chopN(parts[1], 3); - username = mailbox + '@' + domain; - } - passDialog.setUsername(username); - if (passDialog.exec() == QDialog::Accepted) - { - password = passDialog.password(); + if(username.contains('@')) + { + auto parts = username.split('@'); + auto mailbox = chopN(parts[0],3); + QString domain = chopN(parts[1], 3); + username = mailbox + '@' + domain; + } + passDialog.setUsername(username); + if (passDialog.exec() == QDialog::Accepted) + { + password = passDialog.password(); + } + else + { + tryagain = false; + emitFailed(tr("Received undetermined session status during login.")); + } + break; } - else - { + case AuthSession::RequiresOAuth: { + // FIXME: add UI for expired / broken MS accounts tryagain = false; + emitFailed(tr("Microsoft account has expired and needs to be logged into again.")); + return; } - break; - } - case AuthSession::PlayableOffline: - { - // we ask the user for a player name - bool ok = false; - QString usedname = m_session->player_name; - QString name = QInputDialog::getText(m_parentWidget, tr("Player name"), - tr("Choose your offline mode player name."), - QLineEdit::Normal, m_session->player_name, &ok); - if (!ok) - { - tryagain = false; - break; + case AuthSession::PlayableOffline: { + // we ask the user for a player name + bool ok = false; + QString usedname = m_session->player_name; + QString name = QInputDialog::getText( + m_parentWidget, + tr("Player name"), + tr("Choose your offline mode player name."), + QLineEdit::Normal, + m_session->player_name, + &ok + ); + if (!ok) + { + tryagain = false; + break; + } + if (name.length()) + { + usedname = name; + } + m_session->MakeOffline(usedname); + // offline flavored game from here :3 } - if (name.length()) + case AuthSession::PlayableOnline: { - usedname = name; + launchInstance(); + tryagain = false; + return; } - m_session->MakeOffline(usedname); - // offline flavored game from here :3 - } - case AuthSession::PlayableOnline: - { - launchInstance(); - tryagain = false; - return; - } } } emitFailed(tr("Failed to launch.")); diff --git a/launcher/MainWindow.cpp b/launcher/MainWindow.cpp index 9225193e..182b22e9 100644 --- a/launcher/MainWindow.cpp +++ b/launcher/MainWindow.cpp @@ -54,7 +54,7 @@ #include #include #include -#include +#include #include #include #include @@ -90,6 +90,20 @@ #include "KonamiCode.h" #include +namespace { +QString profileInUseFilter(const QString & profile, bool used) +{ + if(used) + { + return QObject::tr("%1 (in use)").arg(profile); + } + else + { + return profile; + } +} +} + // WHY: to hold the pre-translation strings together with the T pointer, so it can be retranslated without a lot of ugly code template class Translated @@ -753,49 +767,27 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow // Update the menu when the active account changes. // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. // Template hell sucks... - connect(MMC->accounts().get(), &MojangAccountList::activeAccountChanged, [this] - { - activeAccountChanged(); - }); - connect(MMC->accounts().get(), &MojangAccountList::listChanged, [this] - { - repopulateAccountsMenu(); - }); + connect( + MMC->accounts().get(), + &AccountList::activeAccountChanged, + [this] { + activeAccountChanged(); + } + ); + connect( + MMC->accounts().get(), + &AccountList::listChanged, + [this] + { + repopulateAccountsMenu(); + } + ); // Show initial account activeAccountChanged(); - auto accounts = MMC->accounts(); - - QList skin_dls; - for (int i = 0; i < accounts->count(); i++) - { - auto account = accounts->at(i); - if (!account) - { - qWarning() << "Null account at index" << i; - continue; - } - for (auto profile : account->profiles()) - { - auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl(BuildConfig.SKINS_BASE + profile.id + ".png"), meta); - skin_dls.append(action); - meta->setStale(true); - } - } - if (!skin_dls.isEmpty()) - { - auto job = new NetJob("Startup player skins download"); - connect(job, &NetJob::succeeded, this, &MainWindow::skinJobFinished); - connect(job, &NetJob::failed, this, &MainWindow::skinJobFinished); - for (auto action : skin_dls) - { - job->addNetAction(action); - } - skin_download_job.reset(job); - job->start(); - } + // TODO: refresh accounts here? + // auto accounts = MMC->accounts(); // load the news { @@ -844,7 +836,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow void MainWindow::retranslateUi() { - accountMenuButton->setText(tr("Profiles")); + std::shared_ptr accounts = MMC->accounts(); + MinecraftAccountPtr active_account = accounts->activeAccount(); + if(active_account) { + auto profileLabel = profileInUseFilter(active_account->profileName(), active_account->isInUse()); + accountMenuButton->setText(profileLabel); + } + else { + accountMenuButton->setText(tr("Profiles")); + } if (m_selectedInstance) { m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); @@ -872,12 +872,6 @@ void MainWindow::konamiTriggered() qDebug() << "Super Secret Mode ACTIVATED!"; } -void MainWindow::skinJobFinished() -{ - activeAccountChanged(); - skin_download_job.reset(); -} - void MainWindow::showInstanceContextMenu(const QPoint &pos) { QList actions; @@ -1018,34 +1012,21 @@ void MainWindow::updateToolsMenu() ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); } -QString profileInUseFilter(const QString & profile, bool used) -{ - if(used) - { - return profile + QObject::tr(" (in use)"); - } - else - { - return profile; - } -} - void MainWindow::repopulateAccountsMenu() { accountMenu->clear(); - std::shared_ptr accounts = MMC->accounts(); - MojangAccountPtr active_account = accounts->activeAccount(); + std::shared_ptr accounts = MMC->accounts(); + MinecraftAccountPtr active_account = accounts->activeAccount(); - QString active_username = ""; + QString active_profileId = ""; if (active_account != nullptr) { - active_username = active_account->username(); - const AccountProfile *profile = active_account->currentProfile(); + active_profileId = active_account->profileId(); // this can be called before accountMenuButton exists - if (profile != nullptr && accountMenuButton) + if (accountMenuButton) { - auto profileLabel = profileInUseFilter(profile->name, active_account->isInUse()); + auto profileLabel = profileInUseFilter(active_account->profileName(), active_account->isInUse()); accountMenuButton->setText(profileLabel); } } @@ -1061,22 +1042,19 @@ void MainWindow::repopulateAccountsMenu() // TODO: Nicer way to iterate? for (int i = 0; i < accounts->count(); i++) { - MojangAccountPtr account = accounts->at(i); - for (auto profile : account->profiles()) + MinecraftAccountPtr account = accounts->at(i); + auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); + QAction *action = new QAction(profileLabel, this); + action->setData(account->profileId()); + action->setCheckable(true); + if (active_profileId == account->profileId()) { - auto profileLabel = profileInUseFilter(profile.name, account->isInUse()); - QAction *action = new QAction(profileLabel, this); - action->setData(account->username()); - action->setCheckable(true); - if (active_username == account->username()) - { - action->setChecked(true); - } - - action->setIcon(SkinUtils::getFaceFromCache(profile.id)); - accountMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + action->setChecked(true); } + + action->setIcon(account->getFace()); + accountMenu->addAction(action); + connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); } } @@ -1086,8 +1064,7 @@ void MainWindow::repopulateAccountsMenu() action->setCheckable(true); action->setIcon(MMC->getThemedIcon("noaccount")); action->setData(""); - if (active_username.isEmpty()) - { + if (active_profileId.isEmpty()) { action->setChecked(true); } @@ -1134,18 +1111,15 @@ void MainWindow::activeAccountChanged() { repopulateAccountsMenu(); - MojangAccountPtr account = MMC->accounts()->activeAccount(); + MinecraftAccountPtr account = MMC->accounts()->activeAccount(); - if (account != nullptr && account->username() != "") + // FIXME: this needs adjustment for MSA + if (account != nullptr && account->profileName() != "") { - const AccountProfile *profile = account->currentProfile(); - if (profile != nullptr) - { - auto profileLabel = profileInUseFilter(profile->name, account->isInUse()); - accountMenuButton->setIcon(SkinUtils::getFaceFromCache(profile->id)); - accountMenuButton->setText(profileLabel); - return; - } + auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); + accountMenuButton->setText(profileLabel); + accountMenuButton->setIcon(account->getFace()); + return; } // Set the icon to the "no account" icon. diff --git a/launcher/MainWindow.h b/launcher/MainWindow.h index c992ab94..67dec8cf 100644 --- a/launcher/MainWindow.h +++ b/launcher/MainWindow.h @@ -22,7 +22,7 @@ #include #include "BaseInstance.h" -#include "minecraft/auth/MojangAccount.h" +#include "minecraft/auth/MinecraftAccount.h" #include "net/NetJob.h" #include "updater/GoUpdate.h" @@ -149,8 +149,6 @@ private slots: void updateToolsMenu(); - void skinJobFinished(); - void instanceActivated(QModelIndex); void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); @@ -214,7 +212,6 @@ private: QToolButton *accountMenuButton = nullptr; KonamiCode * secretEventFilter = nullptr; - unique_qobject_ptr skin_download_job; unique_qobject_ptr m_newsChecker; unique_qobject_ptr m_notificationChecker; diff --git a/launcher/MultiMC.cpp b/launcher/MultiMC.cpp index 932c7a76..5961a45d 100644 --- a/launcher/MultiMC.cpp +++ b/launcher/MultiMC.cpp @@ -42,7 +42,7 @@ #include "dialogs/CustomMessageBox.h" #include "InstanceList.h" -#include +#include #include "icons/IconList.h" #include "net/HttpMetaCache.h" #include "Env.h" @@ -745,7 +745,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) // and accounts { - m_accounts.reset(new MojangAccountList(this)); + m_accounts.reset(new AccountList(this)); qDebug() << "Loading accounts..."; m_accounts->setListFilePath("accounts.json", true); m_accounts->loadList(); diff --git a/launcher/MultiMC.h b/launcher/MultiMC.h index af2b41c1..59fd7345 100644 --- a/launcher/MultiMC.h +++ b/launcher/MultiMC.h @@ -24,7 +24,7 @@ class QFile; class HttpMetaCache; class SettingsObject; class InstanceList; -class MojangAccountList; +class AccountList; class IconList; class QNetworkAccessManager; class JavaInstallList; @@ -111,7 +111,7 @@ public: return m_mcedit.get(); } - std::shared_ptr accounts() const + std::shared_ptr accounts() const { return m_accounts; } @@ -188,7 +188,7 @@ private: FolderInstanceProvider * m_instanceFolder = nullptr; std::shared_ptr m_icons; std::shared_ptr m_updateChecker; - std::shared_ptr m_accounts; + std::shared_ptr m_accounts; std::shared_ptr m_javalist; std::shared_ptr m_translations; std::shared_ptr m_globalSettingsProvider; diff --git a/launcher/SkinUtils.cpp b/launcher/SkinUtils.cpp index ec969889..a196173e 100644 --- a/launcher/SkinUtils.cpp +++ b/launcher/SkinUtils.cpp @@ -30,9 +30,7 @@ namespace SkinUtils */ QPixmap getFaceFromCache(QString username, int height, int width) { - QFile fskin(ENV.metacache() - ->resolveEntry("skins", username + ".png") - ->getFullPath()); + QFile fskin(ENV.metacache()->resolveEntry("skins", username + ".png")->getFullPath()); if (fskin.exists()) { diff --git a/launcher/dialogs/LoginDialog.cpp b/launcher/dialogs/LoginDialog.cpp index 32f8a48f..1dee9920 100644 --- a/launcher/dialogs/LoginDialog.cpp +++ b/launcher/dialogs/LoginDialog.cpp @@ -16,7 +16,7 @@ #include "LoginDialog.h" #include "ui_LoginDialog.h" -#include "minecraft/auth/YggdrasilTask.h" +#include "minecraft/auth/AccountTask.h" #include @@ -42,11 +42,10 @@ void LoginDialog::accept() ui->progressBar->setVisible(true); // Setup the login task and start it - m_account = MojangAccount::createFromUsername(ui->userTextBox->text()); + m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); m_loginTask = m_account->login(nullptr, ui->passTextBox->text()); connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); - connect(m_loginTask.get(), &Task::succeeded, this, - &LoginDialog::onTaskSucceeded); + connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded); connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus); connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress); m_loginTask->start(); @@ -98,7 +97,7 @@ void LoginDialog::onTaskProgress(qint64 current, qint64 total) } // Public interface -MojangAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) +MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) { LoginDialog dlg(parent); dlg.ui->label->setText(msg); diff --git a/launcher/dialogs/LoginDialog.h b/launcher/dialogs/LoginDialog.h index 16bdddfb..13463640 100644 --- a/launcher/dialogs/LoginDialog.h +++ b/launcher/dialogs/LoginDialog.h @@ -18,7 +18,7 @@ #include #include -#include "minecraft/auth/MojangAccount.h" +#include "minecraft/auth/MinecraftAccount.h" namespace Ui { @@ -32,7 +32,7 @@ class LoginDialog : public QDialog public: ~LoginDialog(); - static MojangAccountPtr newAccount(QWidget *parent, QString message); + static MinecraftAccountPtr newAccount(QWidget *parent, QString message); private: explicit LoginDialog(QWidget *parent = 0); @@ -53,6 +53,6 @@ slots: private: Ui::LoginDialog *ui; - MojangAccountPtr m_account; + MinecraftAccountPtr m_account; std::shared_ptr m_loginTask; }; diff --git a/launcher/dialogs/LoginDialog.ui b/launcher/dialogs/LoginDialog.ui index dbdb3b93..8fa4a45d 100644 --- a/launcher/dialogs/LoginDialog.ui +++ b/launcher/dialogs/LoginDialog.ui @@ -7,7 +7,7 @@ 0 0 421 - 238 + 198 @@ -20,16 +20,6 @@ Add Account - - - - NOTICE: MultiMC does not currently support Microsoft accounts. This means that accounts created from December 2020 onwards cannot be used. - - - true - - - diff --git a/launcher/dialogs/MSALoginDialog.cpp b/launcher/dialogs/MSALoginDialog.cpp new file mode 100644 index 00000000..778b379d --- /dev/null +++ b/launcher/dialogs/MSALoginDialog.cpp @@ -0,0 +1,96 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MSALoginDialog.h" +#include "ui_MSALoginDialog.h" + +#include "minecraft/auth/AccountTask.h" + +#include + +MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MSALoginDialog) +{ + ui->setupUi(this); + ui->progressBar->setVisible(false); + // ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +int MSALoginDialog::exec() { + setUserInputsEnabled(false); + ui->progressBar->setVisible(true); + + // Setup the login task and start it + m_account = MinecraftAccount::createBlankMSA(); + m_loginTask = m_account->loginMSA(nullptr); + connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); + connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded); + connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); + connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress); + m_loginTask->start(); + + return QDialog::exec(); +} + + +MSALoginDialog::~MSALoginDialog() +{ + delete ui; +} + +void MSALoginDialog::setUserInputsEnabled(bool enable) +{ + ui->buttonBox->setEnabled(enable); +} + +void MSALoginDialog::onTaskFailed(const QString &reason) +{ + // Set message + ui->label->setText("" + reason + ""); + + // Re-enable user-interaction + setUserInputsEnabled(true); + ui->progressBar->setVisible(false); +} + +void MSALoginDialog::onTaskSucceeded() +{ + QDialog::accept(); +} + +void MSALoginDialog::onTaskStatus(const QString &status) +{ + ui->label->setText(status); +} + +void MSALoginDialog::onTaskProgress(qint64 current, qint64 total) +{ + ui->progressBar->setMaximum(total); + ui->progressBar->setValue(current); +} + +// Public interface +MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg) +{ + MSALoginDialog dlg(parent); + dlg.ui->label->setText(msg); + if (dlg.exec() == QDialog::Accepted) + { + return dlg.m_account; + } + return 0; +} diff --git a/launcher/dialogs/MSALoginDialog.h b/launcher/dialogs/MSALoginDialog.h new file mode 100644 index 00000000..402180ee --- /dev/null +++ b/launcher/dialogs/MSALoginDialog.h @@ -0,0 +1,55 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "minecraft/auth/MinecraftAccount.h" + +namespace Ui +{ +class MSALoginDialog; +} + +class MSALoginDialog : public QDialog +{ + Q_OBJECT + +public: + ~MSALoginDialog(); + + static MinecraftAccountPtr newAccount(QWidget *parent, QString message); + int exec() override; + +private: + explicit MSALoginDialog(QWidget *parent = 0); + + void setUserInputsEnabled(bool enable); + +protected +slots: + void onTaskFailed(const QString &reason); + void onTaskSucceeded(); + void onTaskStatus(const QString &status); + void onTaskProgress(qint64 current, qint64 total); + +private: + Ui::MSALoginDialog *ui; + MinecraftAccountPtr m_account; + std::shared_ptr m_loginTask; +}; + diff --git a/launcher/dialogs/MSALoginDialog.ui b/launcher/dialogs/MSALoginDialog.ui new file mode 100644 index 00000000..4ae8085a --- /dev/null +++ b/launcher/dialogs/MSALoginDialog.ui @@ -0,0 +1,60 @@ + + + MSALoginDialog + + + + 0 + 0 + 421 + 114 + + + + + 0 + 0 + + + + Add Microsoft Account + + + + + + Message label placeholder. + + + Qt::RichText + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + 24 + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + diff --git a/launcher/dialogs/ProfileSelectDialog.cpp b/launcher/dialogs/ProfileSelectDialog.cpp index ae34709f..e2ad73e4 100644 --- a/launcher/dialogs/ProfileSelectDialog.cpp +++ b/launcher/dialogs/ProfileSelectDialog.cpp @@ -33,9 +33,10 @@ ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWid m_accounts = MMC->accounts(); auto view = ui->listView; //view->setModel(m_accounts.get()); - //view->hideColumn(MojangAccountList::ActiveColumn); + //view->hideColumn(AccountList::ActiveColumn); view->setColumnCount(1); view->setRootIsDecorated(false); + // FIXME: use a real model, not this if(QTreeWidgetItem* header = view->headerItem()) { header->setText(0, tr("Name")); @@ -47,20 +48,19 @@ ProfileSelectDialog::ProfileSelectDialog(const QString &message, int flags, QWid QList items; for (int i = 0; i < m_accounts->count(); i++) { - MojangAccountPtr account = m_accounts->at(i); - for (auto profile : account->profiles()) - { - auto profileLabel = profile.name; - if(account->isInUse()) - { - profileLabel += tr(" (in use)"); - } - auto item = new QTreeWidgetItem(view); - item->setText(0, profileLabel); - item->setIcon(0, SkinUtils::getFaceFromCache(profile.id)); - item->setData(0, MojangAccountList::PointerRole, QVariant::fromValue(account)); - items.append(item); + MinecraftAccountPtr account = m_accounts->at(i); + QString profileLabel; + if(account->isInUse()) { + profileLabel = tr("%1 (in use)").arg(account->profileName()); } + else { + profileLabel = account->profileName(); + } + auto item = new QTreeWidgetItem(view); + item->setText(0, profileLabel); + item->setIcon(0, account->getFace()); + item->setData(0, AccountList::PointerRole, QVariant::fromValue(account)); + items.append(item); } view->addTopLevelItems(items); @@ -84,7 +84,7 @@ ProfileSelectDialog::~ProfileSelectDialog() delete ui; } -MojangAccountPtr ProfileSelectDialog::selectedAccount() const +MinecraftAccountPtr ProfileSelectDialog::selectedAccount() const { return m_selected; } @@ -105,7 +105,7 @@ void ProfileSelectDialog::on_buttonBox_accepted() if (selection.size() > 0) { QModelIndex selected = selection.first(); - m_selected = selected.data(MojangAccountList::PointerRole).value(); + m_selected = selected.data(AccountList::PointerRole).value(); } close(); } diff --git a/launcher/dialogs/ProfileSelectDialog.h b/launcher/dialogs/ProfileSelectDialog.h index 9f95830c..a4acd9a1 100644 --- a/launcher/dialogs/ProfileSelectDialog.h +++ b/launcher/dialogs/ProfileSelectDialog.h @@ -19,7 +19,7 @@ #include -#include "minecraft/auth/MojangAccountList.h" +#include "minecraft/auth/AccountList.h" namespace Ui { @@ -59,7 +59,7 @@ public: * Gets a pointer to the account that the user selected. * This is null if the user clicked cancel or hasn't clicked OK yet. */ - MojangAccountPtr selectedAccount() const; + MinecraftAccountPtr selectedAccount() const; /*! * Returns true if the user checked the "use as global default" checkbox. @@ -80,10 +80,10 @@ slots: void on_buttonBox_rejected(); protected: - std::shared_ptr m_accounts; + std::shared_ptr m_accounts; //! The account that was selected when the user clicked OK. - MojangAccountPtr m_selected; + MinecraftAccountPtr m_selected; private: Ui::ProfileSelectDialog *ui; diff --git a/launcher/dialogs/SkinUploadDialog.cpp b/launcher/dialogs/SkinUploadDialog.cpp index 56133529..3c62edac 100644 --- a/launcher/dialogs/SkinUploadDialog.cpp +++ b/launcher/dialogs/SkinUploadDialog.cpp @@ -107,7 +107,7 @@ void SkinUploadDialog::on_skinBrowseBtn_clicked() ui->skinPathTextBox->setText(cooked_path); } -SkinUploadDialog::SkinUploadDialog(MojangAccountPtr acct, QWidget *parent) +SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget *parent) :QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog) { ui->setupUi(this); diff --git a/launcher/dialogs/SkinUploadDialog.h b/launcher/dialogs/SkinUploadDialog.h index deb44eac..84d17dc6 100644 --- a/launcher/dialogs/SkinUploadDialog.h +++ b/launcher/dialogs/SkinUploadDialog.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace Ui { @@ -11,7 +11,7 @@ namespace Ui class SkinUploadDialog : public QDialog { Q_OBJECT public: - explicit SkinUploadDialog(MojangAccountPtr acct, QWidget *parent = 0); + explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget *parent = 0); virtual ~SkinUploadDialog() {}; public slots: @@ -22,7 +22,7 @@ public slots: void on_skinBrowseBtn_clicked(); protected: - MojangAccountPtr m_acct; + MinecraftAccountPtr m_acct; private: Ui::SkinUploadDialog *ui; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index dbf9f816..5f3c7244 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -423,7 +423,7 @@ QStringList MinecraftInstance::processMinecraftArgs( // yggdrasil! if(session) { - token_mapping["auth_username"] = session->username; + // token_mapping["auth_username"] = session->username; token_mapping["auth_session"] = session->session; token_mapping["auth_access_token"] = session->access_token; token_mapping["auth_player_name"] = session->player_name; @@ -691,19 +691,11 @@ QMap MinecraftInstance::createCensorFilterFromSession(AuthSess addToFilter(sessionRef.session, tr("")); } addToFilter(sessionRef.access_token, tr("")); - addToFilter(sessionRef.client_token, tr("")); + if(sessionRef.client_token.size()) { + addToFilter(sessionRef.client_token, tr("")); + } addToFilter(sessionRef.uuid, tr("")); - auto i = sessionRef.u.properties.begin(); - while (i != sessionRef.u.properties.end()) - { - if(i.value().length() <= 3) { - ++i; - continue; - } - addToFilter(i.value(), "<" + i.key().toUpper() + ">"); - ++i; - } return filter; } diff --git a/launcher/minecraft/auth-msa/BuildConfig.cpp.in b/launcher/minecraft/auth-msa/BuildConfig.cpp.in deleted file mode 100644 index 8f470e25..00000000 --- a/launcher/minecraft/auth-msa/BuildConfig.cpp.in +++ /dev/null @@ -1,9 +0,0 @@ -#include "BuildConfig.h" -#include - -const Config BuildConfig; - -Config::Config() -{ - CLIENT_ID = "@MOJANGDEMO_CLIENT_ID@"; -} diff --git a/launcher/minecraft/auth-msa/BuildConfig.h b/launcher/minecraft/auth-msa/BuildConfig.h deleted file mode 100644 index 7a01d704..00000000 --- a/launcher/minecraft/auth-msa/BuildConfig.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include - -class Config -{ -public: - Config(); - QString CLIENT_ID; -}; - -extern const Config BuildConfig; diff --git a/launcher/minecraft/auth-msa/CMakeLists.txt b/launcher/minecraft/auth-msa/CMakeLists.txt deleted file mode 100644 index 22777d1b..00000000 --- a/launcher/minecraft/auth-msa/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -find_package(Qt5 COMPONENTS Core Gui Network Widgets REQUIRED) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - - -set(MOJANGDEMO_CLIENT_ID "" CACHE STRING "Client ID used for OAuth2 in mojangdemo") - -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/BuildConfig.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp") - -set(mojang_SRCS - main.cpp - context.cpp - context.h - - mainwindow.cpp - mainwindow.h - mainwindow.ui - - ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp - BuildConfig.h -) - -add_executable( mojangdemo ${mojang_SRCS} ) -target_link_libraries( mojangdemo Katabasis Qt5::Gui Qt5::Widgets ) -target_include_directories(mojangdemo PRIVATE logic) diff --git a/launcher/minecraft/auth-msa/context.cpp b/launcher/minecraft/auth-msa/context.cpp deleted file mode 100644 index d7ecda30..00000000 --- a/launcher/minecraft/auth-msa/context.cpp +++ /dev/null @@ -1,938 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -#include "context.h" -#include "katabasis/Globals.h" -#include "katabasis/StoreQSettings.h" -#include "katabasis/Requestor.h" -#include "BuildConfig.h" - -using OAuth2 = Katabasis::OAuth2; -using Requestor = Katabasis::Requestor; -using Activity = Katabasis::Activity; - -Context::Context(QObject *parent) : - QObject(parent) -{ - mgr = new QNetworkAccessManager(this); - - Katabasis::OAuth2::Options opts; - opts.scope = "XboxLive.signin offline_access"; - opts.clientIdentifier = BuildConfig.CLIENT_ID; - opts.authorizationUrl = "https://login.live.com/oauth20_authorize.srf"; - opts.accessTokenUrl = "https://login.live.com/oauth20_token.srf"; - opts.listenerPorts = {28562, 28563, 28564, 28565, 28566}; - - oauth2 = new OAuth2(opts, m_account.msaToken, this, mgr); - - connect(oauth2, &OAuth2::linkingFailed, this, &Context::onLinkingFailed); - connect(oauth2, &OAuth2::linkingSucceeded, this, &Context::onLinkingSucceeded); - connect(oauth2, &OAuth2::openBrowser, this, &Context::onOpenBrowser); - connect(oauth2, &OAuth2::closeBrowser, this, &Context::onCloseBrowser); - connect(oauth2, &OAuth2::activityChanged, this, &Context::onOAuthActivityChanged); -} - -void Context::beginActivity(Activity activity) { - if(isBusy()) { - throw 0; - } - activity_ = activity; - emit activityChanged(activity_); -} - -void Context::finishActivity() { - if(!isBusy()) { - throw 0; - } - activity_ = Katabasis::Activity::Idle; - m_account.validity_ = m_account.minecraftProfile.validity; - emit activityChanged(activity_); -} - -QString Context::gameToken() { - return m_account.minecraftToken.token; -} - -QString Context::userId() { - return m_account.minecraftProfile.id; -} - -QString Context::userName() { - return m_account.minecraftProfile.name; -} - -bool Context::silentSignIn() { - if(isBusy()) { - return false; - } - beginActivity(Activity::Refreshing); - if(!oauth2->refresh()) { - finishActivity(); - return false; - } - - requestsDone = 0; - xboxProfileSucceeded = false; - mcAuthSucceeded = false; - - return true; -} - -bool Context::signIn() { - if(isBusy()) { - return false; - } - - requestsDone = 0; - xboxProfileSucceeded = false; - mcAuthSucceeded = false; - - beginActivity(Activity::LoggingIn); - oauth2->unlink(); - m_account = AccountData(); - oauth2->link(); - return true; -} - -bool Context::signOut() { - if(isBusy()) { - return false; - } - beginActivity(Activity::LoggingOut); - oauth2->unlink(); - m_account = AccountData(); - finishActivity(); - return true; -} - - -void Context::onOpenBrowser(const QUrl &url) { - QDesktopServices::openUrl(url); -} - -void Context::onCloseBrowser() { - -} - -void Context::onLinkingFailed() { - finishActivity(); -} - -void Context::onLinkingSucceeded() { - auto *o2t = qobject_cast(sender()); - if (!o2t->linked()) { - finishActivity(); - return; - } - QVariantMap extraTokens = o2t->extraTokens(); - if (!extraTokens.isEmpty()) { - qDebug() << "Extra tokens in response:"; - foreach (QString key, extraTokens.keys()) { - qDebug() << "\t" << key << ":" << extraTokens.value(key); - } - } - doUserAuth(); -} - -void Context::onOAuthActivityChanged(Katabasis::Activity activity) { - // respond to activity change here -} - -void Context::doUserAuth() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "AuthMethod": "RPS", - "SiteName": "user.auth.xboxlive.com", - "RpsTicket": "d=%1" - }, - "RelyingParty": "http://auth.xboxlive.com", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.msaToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - auto *requestor = new Katabasis::Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onUserAuthDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "First layer of XBox auth ... commencing."; -} - -namespace { -bool getDateTime(QJsonValue value, QDateTime & out) { - if(!value.isString()) { - return false; - } - out = QDateTime::fromString(value.toString(), Qt::ISODateWithMs); - return out.isValid(); -} - -bool getString(QJsonValue value, QString & out) { - if(!value.isString()) { - return false; - } - out = value.toString(); - return true; -} - -bool getNumber(QJsonValue value, double & out) { - if(!value.isDouble()) { - return false; - } - out = value.toDouble(); - return true; -} - -/* -{ - "IssueInstant":"2020-12-07T19:52:08.4463796Z", - "NotAfter":"2020-12-21T19:52:08.4463796Z", - "Token":"token", - "DisplayClaims":{ - "xui":[ - { - "uhs":"userhash" - } - ] - } - } -*/ -// TODO: handle error responses ... -/* -{ - "Identity":"0", - "XErr":2148916238, - "Message":"", - "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily" -} -// 2148916233 = missing XBox account -// 2148916238 = child account not linked to a family -*/ - -bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output) { - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if(jsonError.error) { - qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString(); - qDebug() << data; - return false; - } - - auto obj = doc.object(); - if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) { - qWarning() << "User IssueInstant is not a timestamp"; - qDebug() << data; - return false; - } - if(!getDateTime(obj.value("NotAfter"), output.notAfter)) { - qWarning() << "User NotAfter is not a timestamp"; - qDebug() << data; - return false; - } - if(!getString(obj.value("Token"), output.token)) { - qWarning() << "User Token is not a timestamp"; - qDebug() << data; - return false; - } - auto arrayVal = obj.value("DisplayClaims").toObject().value("xui"); - if(!arrayVal.isArray()) { - qWarning() << "Missing xui claims array"; - qDebug() << data; - return false; - } - bool foundUHS = false; - for(auto item: arrayVal.toArray()) { - if(!item.isObject()) { - continue; - } - auto obj = item.toObject(); - if(obj.contains("uhs")) { - foundUHS = true; - } else { - continue; - } - // consume all 'display claims' ... whatever that means - for(auto iter = obj.begin(); iter != obj.end(); iter++) { - QString claim; - if(!getString(obj.value(iter.key()), claim)) { - qWarning() << "display claim " << iter.key() << " is not a string..."; - qDebug() << data; - return false; - } - output.extra[iter.key()] = claim; - } - - break; - } - if(!foundUHS) { - qWarning() << "Missing uhs"; - qDebug() << data; - return false; - } - output.validity = Katabasis::Validity::Certain; - qDebug() << data; - return true; -} - -} - -void Context::onUserAuthDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse user authentication response..."; - finishActivity(); - return; - } - m_account.userToken = temp; - - doSTSAuthMinecraft(); - doSTSAuthGeneric(); -} -/* - url = "https://xsts.auth.xboxlive.com/xsts/authorize" - headers = {"x-xbl-contract-version": "1"} - data = { - "RelyingParty": relying_party, - "TokenType": "JWT", - "Properties": { - "UserTokens": [self.user_token.token], - "SandboxId": "RETAIL", - }, - } -*/ -void Context::doSTSAuthMinecraft() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - "%1" - ] - }, - "RelyingParty": "rp://api.minecraftservices.com/", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onSTSAuthMinecraftDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "Second layer of XBox auth ... commencing."; -} - -void Context::onSTSAuthMinecraftDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse authorization response for access to mojang services..."; - finishActivity(); - return; - } - - if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) { - qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING"; - qDebug() << replyData; - finishActivity(); - return; - } - m_account.mojangservicesToken = temp; - - doMinecraftAuth(); -} - -void Context::doSTSAuthGeneric() { - QString xbox_auth_template = R"XXX( -{ - "Properties": { - "SandboxId": "RETAIL", - "UserTokens": [ - "%1" - ] - }, - "RelyingParty": "http://xboxlive.com", - "TokenType": "JWT" -} -)XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token); - - QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - request.setRawHeader("Accept", "application/json"); - Requestor *requestor = new Requestor(mgr, oauth2, this); - requestor->setAddAccessTokenInQuery(false); - - connect(requestor, &Requestor::finished, this, &Context::onSTSAuthGenericDone); - requestor->post(request, xbox_auth_data.toUtf8()); - qDebug() << "Second layer of XBox auth ... commencing."; -} - -void Context::onSTSAuthGenericDone( - int requestId, - QNetworkReply::NetworkError error, - QByteArray replyData, - QList headers -) { - if (error != QNetworkReply::NoError) { - qWarning() << "Reply error:" << error; - finishActivity(); - return; - } - - Katabasis::Token temp; - if(!parseXTokenResponse(replyData, temp)) { - qWarning() << "Could not parse authorization response for access to xbox AP