From 290ccebc2988bc094c8be793237e3469c6018323 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 30 Aug 2023 02:29:32 -0400 Subject: [PATCH 1/2] feature: Add support for authlibinjector Signed-off-by: Lenny McLennington --- launcher/Application.cpp | 1 + launcher/CMakeLists.txt | 6 ++ launcher/minecraft/MinecraftInstance.cpp | 12 +++ launcher/minecraft/MinecraftInstance.h | 1 + launcher/minecraft/auth/AccountData.cpp | 19 +++-- launcher/minecraft/auth/AccountData.h | 4 + launcher/minecraft/auth/AuthSession.h | 6 +- launcher/minecraft/auth/MinecraftAccount.cpp | 27 ++++++- launcher/minecraft/auth/MinecraftAccount.h | 6 ++ launcher/minecraft/auth/Yggdrasil.cpp | 24 +++++- launcher/minecraft/auth/Yggdrasil.h | 1 + .../minecraft/auth/flows/AuthlibInjector.cpp | 29 +++++++ .../minecraft/auth/flows/AuthlibInjector.h | 26 ++++++ .../auth/steps/AuthlibInjectorStep.cpp | 58 ++++++++++++++ .../auth/steps/AuthlibInjectorStep.h | 24 ++++++ .../auth/steps/MinecraftProfileStep.cpp | 18 ++++- .../auth/steps/MinecraftProfileStep.h | 3 + .../auth/steps/MinecraftProfileStepMojang.cpp | 20 ++++- .../auth/steps/MinecraftProfileStepMojang.h | 3 + .../launch/ConfigureAuthlibInjector.cpp | 80 +++++++++++++++++++ .../launch/ConfigureAuthlibInjector.h | 24 ++++++ launcher/ui/dialogs/LoginDialog.cpp | 17 ++-- launcher/ui/dialogs/LoginDialog.h | 9 ++- launcher/ui/dialogs/LoginDialog.ui | 7 ++ launcher/ui/pages/global/AccountListPage.cpp | 30 +++++++ launcher/ui/pages/global/AccountListPage.h | 1 + launcher/ui/pages/global/AccountListPage.ui | 6 ++ 27 files changed, 441 insertions(+), 21 deletions(-) create mode 100644 launcher/minecraft/auth/flows/AuthlibInjector.cpp create mode 100644 launcher/minecraft/auth/flows/AuthlibInjector.h create mode 100644 launcher/minecraft/auth/steps/AuthlibInjectorStep.cpp create mode 100644 launcher/minecraft/auth/steps/AuthlibInjectorStep.h create mode 100644 launcher/minecraft/launch/ConfigureAuthlibInjector.cpp create mode 100644 launcher/minecraft/launch/ConfigureAuthlibInjector.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 86ef3400..c66f20db 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -818,6 +818,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath()); + m_metacache->addBase("authlibinjector", QDir("cache/authlibinjector").absolutePath()); m_metacache->Load(); qDebug() << "<> Cache initialized."; } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3594e2e6..c71e53f6 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -205,11 +205,15 @@ set(MINECRAFT_SOURCES minecraft/auth/flows/AuthFlow.h minecraft/auth/flows/Mojang.cpp minecraft/auth/flows/Mojang.h + minecraft/auth/flows/AuthlibInjector.cpp + minecraft/auth/flows/AuthlibInjector.h minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.h minecraft/auth/flows/Offline.cpp minecraft/auth/flows/Offline.h + minecraft/auth/steps/AuthlibInjectorStep.cpp + minecraft/auth/steps/AuthlibInjectorStep.h minecraft/auth/steps/OfflineStep.cpp minecraft/auth/steps/OfflineStep.h minecraft/auth/steps/EntitlementsStep.cpp @@ -249,6 +253,8 @@ set(MINECRAFT_SOURCES minecraft/launch/ClaimAccount.cpp minecraft/launch/ClaimAccount.h + minecraft/launch/ConfigureAuthlibInjector.cpp + minecraft/launch/ConfigureAuthlibInjector.h minecraft/launch/CreateGameFolders.cpp minecraft/launch/CreateGameFolders.h minecraft/launch/ModMinecraftJar.cpp diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 22398b8e..dc041a2e 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -60,6 +60,7 @@ #include "launch/steps/QuitAfterGameStop.h" #include "minecraft/launch/LauncherPartLaunch.h" +#include "minecraft/launch/ConfigureAuthlibInjector.h" #include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.h" @@ -388,6 +389,11 @@ QStringList MinecraftInstance::javaArguments() { QStringList args; + if (!m_authlibinjector_javaagent->isNull()) + { + args.append(QString("-javaagent:%1").arg(*m_authlibinjector_javaagent)); + } + // custom args go first. we want to override them if we have our own here. args.append(extraArguments()); @@ -990,6 +996,12 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(step); } + *m_authlibinjector_javaagent = QString(); + if (!session->authlib_injector_base_url.isNull()) + { + process->appendStep(new ConfigureAuthlibInjector(pptr, session->authlib_injector_base_url, m_authlibinjector_javaagent)); + } + // if we aren't in offline mode,. if(session->status != AuthSession::PlayableOffline) { diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 1895d187..9fe79989 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -173,6 +173,7 @@ protected: // data mutable std::shared_ptr m_texture_pack_list; mutable std::shared_ptr m_world_list; mutable std::shared_ptr m_game_options; + mutable std::shared_ptr m_authlibinjector_javaagent = std::make_shared(); }; typedef std::shared_ptr MinecraftInstancePtr; diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 44f7e256..07428bb9 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -350,6 +350,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { type = AccountType::MSA; } else if (typeS == "Mojang") { type = AccountType::Mojang; + } else if (typeS == "Authlib-Injector") { + type = AccountType::AuthlibInjector; } else if (typeS == "Offline") { type = AccountType::Offline; } else { @@ -362,6 +364,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { canMigrateToMSA = data.value("canMigrateToMSA").toBool(false); } + if(type == AccountType::AuthlibInjector) { + authlibInjectorBaseUrl = data.value("authlibInjectorUrl").toString(); + } + if(type == AccountType::MSA) { auto clientIDV = data.value("msa-client-id"); if (clientIDV.isString()) { @@ -405,8 +411,10 @@ QJsonObject AccountData::saveState() const { tokenToJSONV3(output, userToken, "utoken"); tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); - } - else if (type == AccountType::Offline) { + } else if (type == AccountType::AuthlibInjector) { + output["type"] = "Authlib-Injector"; + output["authlibInjectorUrl"] = authlibInjectorBaseUrl; + } else if (type == AccountType::Offline) { output["type"] = "Offline"; } @@ -428,14 +436,14 @@ QString AccountData::accessToken() const { } QString AccountData::clientToken() const { - if(type != AccountType::Mojang) { + if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) { return QString(); } return yggdrasilToken.extra["clientToken"].toString(); } void AccountData::setClientToken(QString clientToken) { - if(type != AccountType::Mojang) { + if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) { return; } yggdrasilToken.extra["clientToken"] = clientToken; @@ -449,7 +457,7 @@ void AccountData::generateClientTokenIfMissing() { } void AccountData::invalidateClientToken() { - if(type != AccountType::Mojang) { + if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) { return; } yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]")); @@ -470,6 +478,7 @@ QString AccountData::profileName() const { QString AccountData::accountDisplayString() const { switch(type) { + case AccountType::AuthlibInjector: case AccountType::Mojang: { return userName(); } diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 366d7731..42894f87 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -74,6 +74,7 @@ struct MinecraftProfile { enum class AccountType { MSA, Mojang, + AuthlibInjector, Offline }; @@ -115,6 +116,9 @@ struct AccountData { QString lastError() const; AccountType type = AccountType::MSA; + QString authlibInjectorBaseUrl; + QString authlibInjectorApiLocation; + bool legacy = false; bool canMigrateToMSA = false; diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index a75df506..ed1ea100 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -38,8 +38,12 @@ struct AuthSession QString player_name; // profile ID QString uuid; - // 'legacy' or 'mojang', depending on account type + // 'legacy' or 'mojang' or 'authlib-injector', depending on account type QString user_type; + + // If not using authlib injector, this is blank. + QString authlib_injector_base_url; + // Did the auth server reply? bool auth_server_online = false; // Did the user request online mode? diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 73d570f1..700c3c0f 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -50,7 +50,9 @@ #include "flows/MSA.h" #include "flows/Mojang.h" +#include "flows/AuthlibInjector.h" #include "flows/Offline.h" +#include "minecraft/auth/AccountData.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); @@ -82,6 +84,16 @@ MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username return account; } +MinecraftAccountPtr MinecraftAccount::createAuthlibInjectorFromUsername(const QString &username, QString baseUrl) +{ + MinecraftAccountPtr account = createFromUsername(username); + account->data.type = AccountType::AuthlibInjector; + account->data.authlibInjectorBaseUrl = baseUrl; + account->data.minecraftEntitlement.ownsMinecraft = true; + account->data.minecraftEntitlement.canPlayMinecraft = true; + return account; +} + MinecraftAccountPtr MinecraftAccount::createBlankMSA() { MinecraftAccountPtr account(new MinecraftAccount()); @@ -132,7 +144,14 @@ QPixmap MinecraftAccount::getFace() const { shared_qobject_ptr MinecraftAccount::login(QString password) { Q_ASSERT(m_currentTask.get() == nullptr); - m_currentTask.reset(new MojangLogin(&data, password)); + if (data.type == AccountType::Mojang) + { + m_currentTask.reset(new MojangLogin(&data, password)); + } + else if (data.type == AccountType::AuthlibInjector) + { + m_currentTask.reset(new AuthlibInjectorLogin(&data, password)); + } connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); @@ -173,6 +192,9 @@ shared_qobject_ptr MinecraftAccount::refresh() { else if(data.type == AccountType::Offline) { m_currentTask.reset(new OfflineRefresh(&data)); } + else if(data.type == AccountType::AuthlibInjector) { + m_currentTask.reset(new AuthlibInjectorRefresh(&data)); + } else { m_currentTask.reset(new MojangRefresh(&data)); } @@ -300,8 +322,9 @@ void MinecraftAccount::fillSession(AuthSessionPtr session) session->player_name = data.profileName(); // profile ID session->uuid = data.profileId(); - // 'legacy' or 'mojang', depending on account type + // 'legacy' or 'mojang', or 'authlib-injector' depending on account type session->user_type = typeString(); + session->authlib_injector_base_url = data.authlibInjectorBaseUrl; if (!session->access_token.isEmpty()) { session->session = "token:" + data.accessToken() + ":" + data.profileId(); diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 7777f846..fd058ffa 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -91,6 +91,8 @@ public: /* construction */ static MinecraftAccountPtr createFromUsername(const QString &username); + static MinecraftAccountPtr createAuthlibInjectorFromUsername(const QString &username, QString baseUrl); + static MinecraftAccountPtr createBlankMSA(); static MinecraftAccountPtr createOffline(const QString &username); @@ -177,6 +179,10 @@ public: /* queries */ return "msa"; } break; + case AccountType::AuthlibInjector: { + return "authlib-injector"; + } + break; case AccountType::Offline: { return "offline"; } diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp index 490de5d7..e94bf49b 100644 --- a/launcher/minecraft/auth/Yggdrasil.cpp +++ b/launcher/minecraft/auth/Yggdrasil.cpp @@ -27,6 +27,25 @@ #include "Application.h" + +QString Yggdrasil::getBaseUrl() +{ + switch (m_data->type) + { + case AccountType::Mojang: { + return "https://authserver.mojang.com"; + } + case AccountType::AuthlibInjector: { + return m_data->authlibInjectorApiLocation + "/authserver"; + } + // Silence warnings about unhandled enum values for values we know shouldn't be handled. + case AccountType::MSA: + case AccountType::Offline: + break; + } + return ""; +} + Yggdrasil::Yggdrasil(AccountData *data, QObject *parent) : AccountTask(data, parent) { @@ -84,7 +103,7 @@ void Yggdrasil::refresh() { req.insert("requestUser", false); QJsonDocument doc(req); - QUrl reqUrl("https://authserver.mojang.com/refresh"); + QUrl reqUrl = getBaseUrl() + "/refresh"; QByteArray requestData = doc.toJson(); sendRequest(reqUrl, requestData); @@ -129,7 +148,8 @@ void Yggdrasil::login(QString password) { QJsonDocument doc(req); - QUrl reqUrl("https://authserver.mojang.com/authenticate"); + QUrl reqUrl = getBaseUrl() + "/authenticate"; + qDebug() << "baseurl = " << getBaseUrl() << "requrl = " << reqUrl; QNetworkRequest netRequest(reqUrl); QByteArray requestData = doc.toJson(); diff --git a/launcher/minecraft/auth/Yggdrasil.h b/launcher/minecraft/auth/Yggdrasil.h index 4f52a04c..4c5ba763 100644 --- a/launcher/minecraft/auth/Yggdrasil.h +++ b/launcher/minecraft/auth/Yggdrasil.h @@ -90,6 +90,7 @@ public slots: private: void sendRequest(QUrl endpoint, QByteArray content); + QString getBaseUrl(); protected: QNetworkReply *m_netReply = nullptr; diff --git a/launcher/minecraft/auth/flows/AuthlibInjector.cpp b/launcher/minecraft/auth/flows/AuthlibInjector.cpp new file mode 100644 index 00000000..840087d2 --- /dev/null +++ b/launcher/minecraft/auth/flows/AuthlibInjector.cpp @@ -0,0 +1,29 @@ +#include "AuthlibInjector.h" + +#include "minecraft/auth/steps/AuthlibInjectorStep.h" +#include "minecraft/auth/steps/MinecraftProfileStepMojang.h" +#include "minecraft/auth/steps/YggdrasilStep.h" +#include "minecraft/auth/steps/MinecraftProfileStep.h" +#include "minecraft/auth/steps/MigrationEligibilityStep.h" +#include "minecraft/auth/steps/GetSkinStep.h" + +AuthlibInjectorRefresh::AuthlibInjectorRefresh( + AccountData *data, + QObject *parent +) : AuthFlow(data, parent) { + m_steps.append(new AuthlibInjectorStep(m_data)); + m_steps.append(new YggdrasilStep(m_data, QString())); + m_steps.append(new MinecraftProfileStepMojang(m_data)); + m_steps.append(new GetSkinStep(m_data)); +} + +AuthlibInjectorLogin::AuthlibInjectorLogin( + AccountData *data, + QString password, + QObject *parent +): AuthFlow(data, parent), m_password(password) { + m_steps.append(new AuthlibInjectorStep(m_data)); + m_steps.append(new YggdrasilStep(m_data, m_password)); + m_steps.append(new MinecraftProfileStepMojang(m_data)); + m_steps.append(new GetSkinStep(m_data)); +} diff --git a/launcher/minecraft/auth/flows/AuthlibInjector.h b/launcher/minecraft/auth/flows/AuthlibInjector.h new file mode 100644 index 00000000..567eb1ee --- /dev/null +++ b/launcher/minecraft/auth/flows/AuthlibInjector.h @@ -0,0 +1,26 @@ +#pragma once +#include "AuthFlow.h" + +class AuthlibInjectorRefresh : public AuthFlow +{ + Q_OBJECT +public: + explicit AuthlibInjectorRefresh( + AccountData *data, + QObject *parent = 0 + ); +}; + +class AuthlibInjectorLogin : public AuthFlow +{ + Q_OBJECT +public: + explicit AuthlibInjectorLogin( + AccountData *data, + QString password, + QObject *parent = 0 + ); + +private: + QString m_password; +}; diff --git a/launcher/minecraft/auth/steps/AuthlibInjectorStep.cpp b/launcher/minecraft/auth/steps/AuthlibInjectorStep.cpp new file mode 100644 index 00000000..7e2e420b --- /dev/null +++ b/launcher/minecraft/auth/steps/AuthlibInjectorStep.cpp @@ -0,0 +1,58 @@ +#include "AuthlibInjectorStep.h" +#include "Application.h" + +#include + +#include +#include + +AuthlibInjectorStep::AuthlibInjectorStep(AccountData* data) : AuthStep(data) { +} + +AuthlibInjectorStep::~AuthlibInjectorStep() noexcept = default; + +QString AuthlibInjectorStep::describe() { + return tr("Fetching authlib injector API URL"); +} + + +void AuthlibInjectorStep::perform() { + // Default to the same as the base URL + QUrl url; + url.setScheme("https"); + url.setAuthority(m_data->authlibInjectorBaseUrl); + qDebug() << url << url.toString() << url.isLocalFile(); + m_data->authlibInjectorApiLocation = url.toString(); + QNetworkRequest request = QNetworkRequest(url); + m_reply.reset( APPLICATION->network()->get(request)); + connect(m_reply.get(), &QNetworkReply::finished, this, &AuthlibInjectorStep::onRequestDone); + qDebug() << "Fetching authlib injector API URL"; +} + +void AuthlibInjectorStep::rehydrate() { + // NOOP, for now. We only save bools and there's nothing to check. +} + +void AuthlibInjectorStep::onRequestDone() { + if (m_reply->hasRawHeader("x-authlib-injector-api-location")) + { + QString authlibInjectorApiLocationHeader = m_reply->rawHeader("x-authlib-injector-api-location"); + QUrl url = authlibInjectorApiLocationHeader; + if (!url.isValid()) + { + qDebug() << "Invalid Authlib Injector API URL specified by server: " << authlibInjectorApiLocationHeader; + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Invalid authlib injector API URL")); + } + else + { + m_data->authlibInjectorApiLocation = authlibInjectorApiLocationHeader; + qDebug() << "Authlib injector API URL: " << m_data->authlibInjectorApiLocation; + emit finished(AccountTaskState::STATE_WORKING, tr("Fetched authlib injector API URL")); + } + } + else + { + qDebug() << "Authlib injector API URL not found"; + emit finished(AccountTaskState::STATE_WORKING, tr("Authlib injector API URL not found, defaulting to the supplied base URL")); + } +} diff --git a/launcher/minecraft/auth/steps/AuthlibInjectorStep.h b/launcher/minecraft/auth/steps/AuthlibInjectorStep.h new file mode 100644 index 00000000..2abaf06b --- /dev/null +++ b/launcher/minecraft/auth/steps/AuthlibInjectorStep.h @@ -0,0 +1,24 @@ +#pragma once +#include + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + + +class AuthlibInjectorStep : public AuthStep { + Q_OBJECT + +public: + explicit AuthlibInjectorStep(AccountData *data); + virtual ~AuthlibInjectorStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + +private slots: + void onRequestDone(); +private: + std::unique_ptr m_reply; +}; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index b39b9326..e7651e46 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -7,7 +7,21 @@ #include "net/NetUtils.h" MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) { - + switch (m_data->type) + { + case AccountType::Mojang: { + baseUrl = "https://api.minecraftservices.com"; + break; + } + case AccountType::AuthlibInjector: { + baseUrl = m_data->authlibInjectorApiLocation + "/minecraftservices"; + break; + } + // Silence warnings about unhandled enum values for values we know shouldn't be handled. + case AccountType::MSA: + case AccountType::Offline: + break; + } } MinecraftProfileStep::~MinecraftProfileStep() noexcept = default; @@ -18,7 +32,7 @@ QString MinecraftProfileStep::describe() { void MinecraftProfileStep::perform() { - auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); + QUrl url = baseUrl + "/minecraft/profile"; QNetworkRequest request = QNetworkRequest(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.h b/launcher/minecraft/auth/steps/MinecraftProfileStep.h index 8ef3395c..9f008002 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.h +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.h @@ -19,4 +19,7 @@ public: private slots: void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + +private: + QString baseUrl; }; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp index 6a1eb7a0..ac38ca38 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp @@ -6,8 +6,24 @@ #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" -MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) { +MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {} +QString MinecraftProfileStepMojang::getBaseUrl() +{ + switch (m_data->type) + { + case AccountType::Mojang: { + return "https://sessionserver.mojang.com"; + } + case AccountType::AuthlibInjector: { + return m_data->authlibInjectorApiLocation + "/sessionserver"; + } + // Silence warnings about unhandled enum values for values we know shouldn't be handled. + case AccountType::MSA: + case AccountType::Offline: + break; + } + return ""; } MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default; @@ -24,7 +40,7 @@ void MinecraftProfileStepMojang::perform() { } // use session server instead of profile due to profile endpoint being locked for locked Mojang accounts - QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id); + QUrl url = getBaseUrl() + "/session/minecraft/profile/" + m_data->minecraftProfile.id; QNetworkRequest req = QNetworkRequest(url); AuthRequest *request = new AuthRequest(this); connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone); diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h index e06b30ab..04091fc1 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h +++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h @@ -19,4 +19,7 @@ public: private slots: void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList); + +private: + QString getBaseUrl(); }; diff --git a/launcher/minecraft/launch/ConfigureAuthlibInjector.cpp b/launcher/minecraft/launch/ConfigureAuthlibInjector.cpp new file mode 100644 index 00000000..ceb3d72f --- /dev/null +++ b/launcher/minecraft/launch/ConfigureAuthlibInjector.cpp @@ -0,0 +1,80 @@ +#include "ConfigureAuthlibInjector.h" +#include +#include +#include +#include +#include + +#include "Application.h" +#include "minecraft/auth/AccountList.h" +#include "net/ChecksumValidator.h" +#include "net/Download.h" +#include "net/HttpMetaCache.h" +#include "net/NetAction.h" + +ConfigureAuthlibInjector::ConfigureAuthlibInjector(LaunchTask* parent, + QString authlibinjector_base_url, + std::shared_ptr javaagent_arg) + : LaunchStep(parent), m_javaagent_arg{ javaagent_arg }, m_authlibinjector_base_url{ authlibinjector_base_url } +{} + +void ConfigureAuthlibInjector::executeTask() +{ + auto downloadFailed = [this] (QString reason) { + return emitFailed(QString("Download failed: %1").arg(reason)); + }; + auto entry = APPLICATION->metacache()->resolveEntry("authlibinjector", "latest.json"); + + entry->setStale(true); + m_job = std::make_unique("Download authlibinjector latest.json", APPLICATION->network()); + auto latestJsonDl = + Net::Download::makeCached(QUrl("https://authlib-injector.yushi.moe/artifact/latest.json"), entry, Net::Download::Option::NoOptions); + m_job->addNetAction(latestJsonDl); + connect(m_job.get(), &NetJob::succeeded, this, [this, entry, downloadFailed] { + QFile authlibInjectorLatestJson = entry->getFullPath(); + authlibInjectorLatestJson.open(QIODevice::ReadOnly); + if (!authlibInjectorLatestJson.isOpen()) + return emitFailed(QString("Failed to open authlib-injector info json: %1").arg(authlibInjectorLatestJson.errorString())); + + QJsonParseError json_parse_error; + QJsonDocument doc = QJsonDocument::fromJson(authlibInjectorLatestJson.readAll(), &json_parse_error); + if (json_parse_error.error != QJsonParseError::NoError) + return emitFailed(QString("Failed to parse authlib-injector info json: %1").arg(json_parse_error.errorString())); + + if (!doc.isObject()) + return emitFailed(QString("Failed to parse authlib-injector info json: not a json object")); + QJsonObject obj = doc.object(); + + QString authlibInjectorJarUrl = obj["download_url"].toString(); + if (authlibInjectorJarUrl.isNull()) + return emitFailed(QString("Failed to parse authlib-injector info json: download url missing")); + + QString sha256Sum = obj["checksums"].toObject()["sha256"].toString(); + if (sha256Sum.isNull()) + return emitFailed("Failed to parse authlib-injector info json: sha256 checksum missing"); + + auto sha256SumRaw = QByteArray::fromHex(sha256Sum.toLatin1()); + + QString filename = QFileInfo(authlibInjectorJarUrl).fileName(); + auto javaAgentEntry = APPLICATION->metacache()->resolveEntry("authlibinjector", filename); + m_job = std::make_unique("Download authlibinjector java agent", APPLICATION->network()); + auto javaAgentDl = Net::Download::makeCached(QUrl(authlibInjectorJarUrl), javaAgentEntry, Net::Download::Option::MakeEternal); + javaAgentDl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha256, sha256SumRaw)); + m_job->addNetAction(javaAgentDl); + connect(m_job.get(), &NetJob::succeeded, this, [this, javaAgentEntry] { + auto path = javaAgentEntry->getFullPath(); + qDebug() << path; + *m_javaagent_arg = QString("%1=%2").arg(path).arg(m_authlibinjector_base_url); + emitSucceeded(); + }); + connect(m_job.get(), &NetJob::failed, this, downloadFailed); + m_job->start(); + }, + // This slot can't run instantly because it needs to wait for the netjob's code to stop running + // Since it will destroy the old netjob by reassigning the unique_ptr + Qt::QueuedConnection); + connect(m_job.get(), &NetJob::failed, this, downloadFailed); + m_job->start(); +} + +void ConfigureAuthlibInjector::finalize() {} diff --git a/launcher/minecraft/launch/ConfigureAuthlibInjector.h b/launcher/minecraft/launch/ConfigureAuthlibInjector.h new file mode 100644 index 00000000..8bfc5748 --- /dev/null +++ b/launcher/minecraft/launch/ConfigureAuthlibInjector.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include "net/NetJob.h" + +class ConfigureAuthlibInjector: public LaunchStep +{ + Q_OBJECT +public: + explicit ConfigureAuthlibInjector(LaunchTask *parent, QString authlibinjector_base_url, std::shared_ptr javaagent_arg); + virtual ~ConfigureAuthlibInjector() {}; + + void executeTask() override; + void finalize() override; + bool canAbort() const override + { + return false; + } +private: + std::unique_ptr m_job; + std::shared_ptr m_javaagent_arg; + QString m_authlibinjector_base_url; +}; diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp index 30394b72..f9ddd188 100644 --- a/launcher/ui/dialogs/LoginDialog.cpp +++ b/launcher/ui/dialogs/LoginDialog.cpp @@ -20,9 +20,10 @@ #include -LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog) +LoginDialog::LoginDialog(QWidget *parent, AccountType type) : QDialog(parent), ui(new Ui::LoginDialog), m_accountType{type} { ui->setupUi(this); + ui->authlibInjectorBaseTextBox->setVisible(m_accountType == AccountType::AuthlibInjector); ui->progressBar->setVisible(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); @@ -42,7 +43,14 @@ void LoginDialog::accept() ui->progressBar->setVisible(true); // Setup the login task and start it - m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); + if (m_accountType == AccountType::Mojang) + { + m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); + } + else if (m_accountType == AccountType::AuthlibInjector) + { + m_account = MinecraftAccount::createAuthlibInjectorFromUsername(ui->userTextBox->text(), ui->authlibInjectorBaseTextBox->text()); + } m_loginTask = m_account->login(ui->passTextBox->text()); connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded); @@ -106,10 +114,9 @@ void LoginDialog::onTaskProgress(qint64 current, qint64 total) ui->progressBar->setValue(current); } -// Public interface -MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) +MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg, AccountType type) { - LoginDialog dlg(parent); + LoginDialog dlg(parent, type); dlg.ui->label->setText(msg); if (dlg.exec() == QDialog::Accepted) { diff --git a/launcher/ui/dialogs/LoginDialog.h b/launcher/ui/dialogs/LoginDialog.h index f8101ff5..0a15ce5b 100644 --- a/launcher/ui/dialogs/LoginDialog.h +++ b/launcher/ui/dialogs/LoginDialog.h @@ -18,6 +18,7 @@ #include #include +#include "minecraft/auth/AccountData.h" #include "minecraft/auth/MinecraftAccount.h" #include "tasks/Task.h" @@ -33,10 +34,13 @@ class LoginDialog : public QDialog public: ~LoginDialog(); - static MinecraftAccountPtr newAccount(QWidget *parent, QString message); + /* + * @param type: Mojang or Authlib + */ + static MinecraftAccountPtr newAccount(QWidget *parent, QString message, AccountType type = AccountType::Mojang); private: - explicit LoginDialog(QWidget *parent = 0); + explicit LoginDialog(QWidget *parent = 0, AccountType type = AccountType::Mojang); void setUserInputsEnabled(bool enable); @@ -56,4 +60,5 @@ private: Ui::LoginDialog *ui; MinecraftAccountPtr m_account; Task::Ptr m_loginTask; + AccountType m_accountType; }; diff --git a/launcher/ui/dialogs/LoginDialog.ui b/launcher/ui/dialogs/LoginDialog.ui index 8fa4a45d..1cbdfccd 100644 --- a/launcher/ui/dialogs/LoginDialog.ui +++ b/launcher/ui/dialogs/LoginDialog.ui @@ -33,6 +33,13 @@ + + + + AuthlibInjector base URL (e.g. ely.by) + + + diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 8359bb62..d0336d60 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -157,6 +157,36 @@ void AccountListPage::on_actionAddMojang_triggered() } } +void AccountListPage::on_actionAddAuthlibInjector_triggered() +{ + if (!m_accounts->drmCheck()) { + QMessageBox::warning( + this, + tr("Error"), + tr( + "You must add a Microsoft or Mojang account that owns Minecraft before you can add an Authlib Injector account." + "

" + "If you have lost your account you can contact Microsoft for support." + ) + ); + return; + } + + MinecraftAccountPtr account = LoginDialog::newAccount( + this, + tr("Please enter the AuthlibInjector base URL, and enter your account email and password to add your account."), + AccountType::AuthlibInjector + ); + + if (account) + { + m_accounts->addAccount(account); + if (m_accounts->count() == 1) { + m_accounts->setDefaultAccount(account); + } + } +} + void AccountListPage::on_actionAddMicrosoft_triggered() { if(BuildConfig.BUILD_PLATFORM == "osx64") { diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 9395e92b..e6003e20 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -83,6 +83,7 @@ public: public slots: void on_actionAddMojang_triggered(); + void on_actionAddAuthlibInjector_triggered(); void on_actionAddMicrosoft_triggered(); void on_actionAddOffline_triggered(); void on_actionRemove_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 469955b5..5b2efcbb 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -54,6 +54,7 @@ + @@ -68,6 +69,11 @@ Add &Mojang
+ + + Add Authlib-&Injector + + Remo&ve From 53a4395d5bc1e13091ec3e1a33c67eb9e3b5d39a Mon Sep 17 00:00:00 2001 From: Kaydax Date: Tue, 10 Oct 2023 20:14:11 -0400 Subject: [PATCH 2/2] Fix Yggdrasil error messages when not using Mojang Signed-off-by: Lenny McLennington --- .../minecraft/auth/steps/YggdrasilStep.cpp | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp index e1d33172..a8adfa66 100644 --- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp +++ b/launcher/minecraft/auth/steps/YggdrasilStep.cpp @@ -1,5 +1,6 @@ #include "YggdrasilStep.h" +#include "minecraft/auth/AccountData.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "minecraft/auth/Yggdrasil.h" @@ -15,7 +16,14 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat YggdrasilStep::~YggdrasilStep() noexcept = default; QString YggdrasilStep::describe() { - return tr("Logging in with Mojang account."); + switch(m_data->type) { + case(AccountType::Mojang): + return tr("Logging in with Mojang account."); + case AccountType::AuthlibInjector: + return tr("Logging in with %1 account.").arg(m_data->authlibInjectorBaseUrl); + default: + break; + } } void YggdrasilStep::rehydrate() { @@ -32,7 +40,9 @@ void YggdrasilStep::perform() { } void YggdrasilStep::onAuthSucceeded() { - emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang")); + emit m_data->type == AccountType::Mojang + ? finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang")) + : finished(AccountTaskState::STATE_WORKING, tr("Logged in with %1").arg(m_data->authlibInjectorBaseUrl)); } void YggdrasilStep::onAuthFailed() { @@ -41,12 +51,32 @@ void YggdrasilStep::onAuthFailed() { // m_aborted = m_yggdrasil->m_aborted; auto state = m_yggdrasil->taskState(); - QString errorMessage = tr("Mojang user authentication failed."); + QString errorMessage = m_data->type == AccountType::Mojang + ? tr("Mojang user authentication failed.") + : tr("%1 user authentication failed").arg(m_data->authlibInjectorBaseUrl); // NOTE: soft error in the first step means 'offline' if(state == AccountTaskState::STATE_FAILED_SOFT) { state = AccountTaskState::STATE_OFFLINE; - errorMessage = tr("Mojang user authentication ended with a network error."); + switch(m_data->type) { + case AccountType::Mojang: + { + errorMessage = tr("Mojang user authentication ended with a network error."); + break; + } + case AccountType::AuthlibInjector: + { + if(m_data->authlibInjectorBaseUrl.isEmpty()) + { + errorMessage = tr("User authentication ended with a network error, did specify a url?"); + } else { + errorMessage = tr("%1 user authentication ended with a network error").arg(m_data->authlibInjectorBaseUrl); + } + break; + } + default: + break; + } } emit finished(state, errorMessage); }