Merge pull request #1598 from Kaydax/authlib

Add support for authlibinjector
This commit is contained in:
Lenny McLennington 2023-11-25 02:03:22 +00:00 committed by GitHub
commit 6431265a73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 475 additions and 25 deletions

View File

@ -818,6 +818,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("meta", QDir("meta").absolutePath()); m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->addBase("authlibinjector", QDir("cache/authlibinjector").absolutePath());
m_metacache->Load(); m_metacache->Load();
qDebug() << "<> Cache initialized."; qDebug() << "<> Cache initialized.";
} }

View File

@ -205,11 +205,15 @@ set(MINECRAFT_SOURCES
minecraft/auth/flows/AuthFlow.h minecraft/auth/flows/AuthFlow.h
minecraft/auth/flows/Mojang.cpp minecraft/auth/flows/Mojang.cpp
minecraft/auth/flows/Mojang.h minecraft/auth/flows/Mojang.h
minecraft/auth/flows/AuthlibInjector.cpp
minecraft/auth/flows/AuthlibInjector.h
minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.cpp
minecraft/auth/flows/MSA.h minecraft/auth/flows/MSA.h
minecraft/auth/flows/Offline.cpp minecraft/auth/flows/Offline.cpp
minecraft/auth/flows/Offline.h minecraft/auth/flows/Offline.h
minecraft/auth/steps/AuthlibInjectorStep.cpp
minecraft/auth/steps/AuthlibInjectorStep.h
minecraft/auth/steps/OfflineStep.cpp minecraft/auth/steps/OfflineStep.cpp
minecraft/auth/steps/OfflineStep.h minecraft/auth/steps/OfflineStep.h
minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.cpp
@ -249,6 +253,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ClaimAccount.cpp minecraft/launch/ClaimAccount.cpp
minecraft/launch/ClaimAccount.h minecraft/launch/ClaimAccount.h
minecraft/launch/ConfigureAuthlibInjector.cpp
minecraft/launch/ConfigureAuthlibInjector.h
minecraft/launch/CreateGameFolders.cpp minecraft/launch/CreateGameFolders.cpp
minecraft/launch/CreateGameFolders.h minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp minecraft/launch/ModMinecraftJar.cpp

View File

@ -60,6 +60,7 @@
#include "launch/steps/QuitAfterGameStop.h" #include "launch/steps/QuitAfterGameStop.h"
#include "minecraft/launch/LauncherPartLaunch.h" #include "minecraft/launch/LauncherPartLaunch.h"
#include "minecraft/launch/ConfigureAuthlibInjector.h"
#include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/DirectJavaLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ClaimAccount.h" #include "minecraft/launch/ClaimAccount.h"
@ -388,6 +389,11 @@ QStringList MinecraftInstance::javaArguments()
{ {
QStringList args; 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. // custom args go first. we want to override them if we have our own here.
args.append(extraArguments()); args.append(extraArguments());
@ -990,6 +996,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(step); 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 we aren't in offline mode,.
if(session->status != AuthSession::PlayableOffline) if(session->status != AuthSession::PlayableOffline)
{ {

View File

@ -173,6 +173,7 @@ protected: // data
mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list; mutable std::shared_ptr<TexturePackFolderModel> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list; mutable std::shared_ptr<WorldList> m_world_list;
mutable std::shared_ptr<GameOptions> m_game_options; mutable std::shared_ptr<GameOptions> m_game_options;
mutable std::shared_ptr<QString> m_authlibinjector_javaagent = std::make_shared<QString>();
}; };
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr; typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;

View File

@ -350,6 +350,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
type = AccountType::MSA; type = AccountType::MSA;
} else if (typeS == "Mojang") { } else if (typeS == "Mojang") {
type = AccountType::Mojang; type = AccountType::Mojang;
} else if (typeS == "Authlib-Injector") {
type = AccountType::AuthlibInjector;
} else if (typeS == "Offline") { } else if (typeS == "Offline") {
type = AccountType::Offline; type = AccountType::Offline;
} else { } else {
@ -362,6 +364,10 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false); canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
} }
if(type == AccountType::AuthlibInjector) {
authlibInjectorBaseUrl = data.value("authlibInjectorUrl").toString();
}
if(type == AccountType::MSA) { if(type == AccountType::MSA) {
auto clientIDV = data.value("msa-client-id"); auto clientIDV = data.value("msa-client-id");
if (clientIDV.isString()) { if (clientIDV.isString()) {
@ -405,8 +411,10 @@ QJsonObject AccountData::saveState() const {
tokenToJSONV3(output, userToken, "utoken"); tokenToJSONV3(output, userToken, "utoken");
tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, xboxApiToken, "xrp-main");
tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
} } else if (type == AccountType::AuthlibInjector) {
else if (type == AccountType::Offline) { output["type"] = "Authlib-Injector";
output["authlibInjectorUrl"] = authlibInjectorBaseUrl;
} else if (type == AccountType::Offline) {
output["type"] = "Offline"; output["type"] = "Offline";
} }
@ -428,14 +436,14 @@ QString AccountData::accessToken() const {
} }
QString AccountData::clientToken() const { QString AccountData::clientToken() const {
if(type != AccountType::Mojang) { if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) {
return QString(); return QString();
} }
return yggdrasilToken.extra["clientToken"].toString(); return yggdrasilToken.extra["clientToken"].toString();
} }
void AccountData::setClientToken(QString clientToken) { void AccountData::setClientToken(QString clientToken) {
if(type != AccountType::Mojang) { if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) {
return; return;
} }
yggdrasilToken.extra["clientToken"] = clientToken; yggdrasilToken.extra["clientToken"] = clientToken;
@ -449,7 +457,7 @@ void AccountData::generateClientTokenIfMissing() {
} }
void AccountData::invalidateClientToken() { void AccountData::invalidateClientToken() {
if(type != AccountType::Mojang) { if(type != AccountType::Mojang && type != AccountType::AuthlibInjector) {
return; return;
} }
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]")); yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
@ -470,6 +478,7 @@ QString AccountData::profileName() const {
QString AccountData::accountDisplayString() const { QString AccountData::accountDisplayString() const {
switch(type) { switch(type) {
case AccountType::AuthlibInjector:
case AccountType::Mojang: { case AccountType::Mojang: {
return userName(); return userName();
} }

View File

@ -74,6 +74,7 @@ struct MinecraftProfile {
enum class AccountType { enum class AccountType {
MSA, MSA,
Mojang, Mojang,
AuthlibInjector,
Offline Offline
}; };
@ -115,6 +116,9 @@ struct AccountData {
QString lastError() const; QString lastError() const;
AccountType type = AccountType::MSA; AccountType type = AccountType::MSA;
QString authlibInjectorBaseUrl;
QString authlibInjectorApiLocation;
bool legacy = false; bool legacy = false;
bool canMigrateToMSA = false; bool canMigrateToMSA = false;

View File

@ -38,8 +38,12 @@ struct AuthSession
QString player_name; QString player_name;
// profile ID // profile ID
QString uuid; QString uuid;
// 'legacy' or 'mojang', depending on account type // 'legacy' or 'mojang' or 'authlib-injector', depending on account type
QString user_type; QString user_type;
// If not using authlib injector, this is blank.
QString authlib_injector_base_url;
// Did the auth server reply? // Did the auth server reply?
bool auth_server_online = false; bool auth_server_online = false;
// Did the user request online mode? // Did the user request online mode?

View File

@ -50,7 +50,9 @@
#include "flows/MSA.h" #include "flows/MSA.h"
#include "flows/Mojang.h" #include "flows/Mojang.h"
#include "flows/AuthlibInjector.h"
#include "flows/Offline.h" #include "flows/Offline.h"
#include "minecraft/auth/AccountData.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]")); data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
@ -82,6 +84,16 @@ MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username
return account; 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 MinecraftAccount::createBlankMSA()
{ {
MinecraftAccountPtr account(new MinecraftAccount()); MinecraftAccountPtr account(new MinecraftAccount());
@ -132,7 +144,14 @@ QPixmap MinecraftAccount::getFace() const {
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) { shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
Q_ASSERT(m_currentTask.get() == nullptr); 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(succeeded()), SLOT(authSucceeded()));
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); }); connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
@ -173,6 +192,9 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
else if(data.type == AccountType::Offline) { else if(data.type == AccountType::Offline) {
m_currentTask.reset(new OfflineRefresh(&data)); m_currentTask.reset(new OfflineRefresh(&data));
} }
else if(data.type == AccountType::AuthlibInjector) {
m_currentTask.reset(new AuthlibInjectorRefresh(&data));
}
else { else {
m_currentTask.reset(new MojangRefresh(&data)); m_currentTask.reset(new MojangRefresh(&data));
} }
@ -300,8 +322,9 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
session->player_name = data.profileName(); session->player_name = data.profileName();
// profile ID // profile ID
session->uuid = data.profileId(); 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->user_type = typeString();
session->authlib_injector_base_url = data.authlibInjectorBaseUrl;
if (!session->access_token.isEmpty()) if (!session->access_token.isEmpty())
{ {
session->session = "token:" + data.accessToken() + ":" + data.profileId(); session->session = "token:" + data.accessToken() + ":" + data.profileId();

View File

@ -91,6 +91,8 @@ public: /* construction */
static MinecraftAccountPtr createFromUsername(const QString &username); static MinecraftAccountPtr createFromUsername(const QString &username);
static MinecraftAccountPtr createAuthlibInjectorFromUsername(const QString &username, QString baseUrl);
static MinecraftAccountPtr createBlankMSA(); static MinecraftAccountPtr createBlankMSA();
static MinecraftAccountPtr createOffline(const QString &username); static MinecraftAccountPtr createOffline(const QString &username);
@ -177,6 +179,10 @@ public: /* queries */
return "msa"; return "msa";
} }
break; break;
case AccountType::AuthlibInjector: {
return "authlib-injector";
}
break;
case AccountType::Offline: { case AccountType::Offline: {
return "offline"; return "offline";
} }

View File

@ -27,6 +27,25 @@
#include "Application.h" #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) Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
: AccountTask(data, parent) : AccountTask(data, parent)
{ {
@ -84,7 +103,7 @@ void Yggdrasil::refresh() {
req.insert("requestUser", false); req.insert("requestUser", false);
QJsonDocument doc(req); QJsonDocument doc(req);
QUrl reqUrl("https://authserver.mojang.com/refresh"); QUrl reqUrl = getBaseUrl() + "/refresh";
QByteArray requestData = doc.toJson(); QByteArray requestData = doc.toJson();
sendRequest(reqUrl, requestData); sendRequest(reqUrl, requestData);
@ -129,7 +148,8 @@ void Yggdrasil::login(QString password) {
QJsonDocument doc(req); QJsonDocument doc(req);
QUrl reqUrl("https://authserver.mojang.com/authenticate"); QUrl reqUrl = getBaseUrl() + "/authenticate";
qDebug() << "baseurl = " << getBaseUrl() << "requrl = " << reqUrl;
QNetworkRequest netRequest(reqUrl); QNetworkRequest netRequest(reqUrl);
QByteArray requestData = doc.toJson(); QByteArray requestData = doc.toJson();

View File

@ -90,6 +90,7 @@ public slots:
private: private:
void sendRequest(QUrl endpoint, QByteArray content); void sendRequest(QUrl endpoint, QByteArray content);
QString getBaseUrl();
protected: protected:
QNetworkReply *m_netReply = nullptr; QNetworkReply *m_netReply = nullptr;

View File

@ -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));
}

View File

@ -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;
};

View File

@ -0,0 +1,58 @@
#include "AuthlibInjectorStep.h"
#include "Application.h"
#include <iostream>
#include <QNetworkRequest>
#include <QUuid>
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"));
}
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <QObject>
#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<QNetworkReply> m_reply;
};

View File

@ -7,7 +7,21 @@
#include "net/NetUtils.h" #include "net/NetUtils.h"
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) { 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; MinecraftProfileStep::~MinecraftProfileStep() noexcept = default;
@ -18,7 +32,7 @@ QString MinecraftProfileStep::describe() {
void MinecraftProfileStep::perform() { void MinecraftProfileStep::perform() {
auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); QUrl url = baseUrl + "/minecraft/profile";
QNetworkRequest request = QNetworkRequest(url); QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());

View File

@ -19,4 +19,7 @@ public:
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
private:
QString baseUrl;
}; };

View File

@ -6,8 +6,24 @@
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "net/NetUtils.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; 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 // 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); QNetworkRequest req = QNetworkRequest(url);
AuthRequest *request = new AuthRequest(this); AuthRequest *request = new AuthRequest(this);
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone); connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);

View File

@ -19,4 +19,7 @@ public:
private slots: private slots:
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
private:
QString getBaseUrl();
}; };

View File

@ -1,5 +1,6 @@
#include "YggdrasilStep.h" #include "YggdrasilStep.h"
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h" #include "minecraft/auth/Parsers.h"
#include "minecraft/auth/Yggdrasil.h" #include "minecraft/auth/Yggdrasil.h"
@ -15,7 +16,14 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat
YggdrasilStep::~YggdrasilStep() noexcept = default; YggdrasilStep::~YggdrasilStep() noexcept = default;
QString YggdrasilStep::describe() { 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() { void YggdrasilStep::rehydrate() {
@ -32,7 +40,9 @@ void YggdrasilStep::perform() {
} }
void YggdrasilStep::onAuthSucceeded() { 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() { void YggdrasilStep::onAuthFailed() {
@ -41,12 +51,32 @@ void YggdrasilStep::onAuthFailed() {
// m_aborted = m_yggdrasil->m_aborted; // m_aborted = m_yggdrasil->m_aborted;
auto state = m_yggdrasil->taskState(); 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' // NOTE: soft error in the first step means 'offline'
if(state == AccountTaskState::STATE_FAILED_SOFT) { if(state == AccountTaskState::STATE_FAILED_SOFT) {
state = AccountTaskState::STATE_OFFLINE; 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); emit finished(state, errorMessage);
} }

View File

@ -0,0 +1,80 @@
#include "ConfigureAuthlibInjector.h"
#include <launch/LaunchTask.h>
#include <QDir>
#include <QFileInfo>
#include <QJsonDocument>
#include <Qt>
#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<QString> 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<NetJob>("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<NetJob>("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() {}

View File

@ -0,0 +1,24 @@
#pragma once
#include <launch/LaunchStep.h>
#include <minecraft/auth/MinecraftAccount.h>
#include "net/NetJob.h"
class ConfigureAuthlibInjector: public LaunchStep
{
Q_OBJECT
public:
explicit ConfigureAuthlibInjector(LaunchTask *parent, QString authlibinjector_base_url, std::shared_ptr<QString> javaagent_arg);
virtual ~ConfigureAuthlibInjector() {};
void executeTask() override;
void finalize() override;
bool canAbort() const override
{
return false;
}
private:
std::unique_ptr<NetJob> m_job;
std::shared_ptr<QString> m_javaagent_arg;
QString m_authlibinjector_base_url;
};

View File

@ -20,9 +20,10 @@
#include <QtWidgets/QPushButton> #include <QtWidgets/QPushButton>
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->setupUi(this);
ui->authlibInjectorBaseTextBox->setVisible(m_accountType == AccountType::AuthlibInjector);
ui->progressBar->setVisible(false); ui->progressBar->setVisible(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
@ -42,7 +43,14 @@ void LoginDialog::accept()
ui->progressBar->setVisible(true); ui->progressBar->setVisible(true);
// Setup the login task and start it // 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()); m_loginTask = m_account->login(ui->passTextBox->text());
connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed); 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);
@ -106,10 +114,9 @@ void LoginDialog::onTaskProgress(qint64 current, qint64 total)
ui->progressBar->setValue(current); ui->progressBar->setValue(current);
} }
// Public interface MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg, AccountType type)
MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
{ {
LoginDialog dlg(parent); LoginDialog dlg(parent, type);
dlg.ui->label->setText(msg); dlg.ui->label->setText(msg);
if (dlg.exec() == QDialog::Accepted) if (dlg.exec() == QDialog::Accepted)
{ {

View File

@ -18,6 +18,7 @@
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include <QtCore/QEventLoop> #include <QtCore/QEventLoop>
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/MinecraftAccount.h" #include "minecraft/auth/MinecraftAccount.h"
#include "tasks/Task.h" #include "tasks/Task.h"
@ -33,10 +34,13 @@ class LoginDialog : public QDialog
public: public:
~LoginDialog(); ~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: private:
explicit LoginDialog(QWidget *parent = 0); explicit LoginDialog(QWidget *parent = 0, AccountType type = AccountType::Mojang);
void setUserInputsEnabled(bool enable); void setUserInputsEnabled(bool enable);
@ -56,4 +60,5 @@ private:
Ui::LoginDialog *ui; Ui::LoginDialog *ui;
MinecraftAccountPtr m_account; MinecraftAccountPtr m_account;
Task::Ptr m_loginTask; Task::Ptr m_loginTask;
AccountType m_accountType;
}; };

View File

@ -33,6 +33,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLineEdit" name="authlibInjectorBaseTextBox">
<property name="placeholderText">
<string>AuthlibInjector base URL (e.g. ely.by)</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QLineEdit" name="userTextBox"> <widget class="QLineEdit" name="userTextBox">
<property name="placeholderText"> <property name="placeholderText">

View File

@ -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."
"<br><br>"
"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() void AccountListPage::on_actionAddMicrosoft_triggered()
{ {
if(BuildConfig.BUILD_PLATFORM == "osx64") { if(BuildConfig.BUILD_PLATFORM == "osx64") {

View File

@ -83,6 +83,7 @@ public:
public slots: public slots:
void on_actionAddMojang_triggered(); void on_actionAddMojang_triggered();
void on_actionAddAuthlibInjector_triggered();
void on_actionAddMicrosoft_triggered(); void on_actionAddMicrosoft_triggered();
void on_actionAddOffline_triggered(); void on_actionAddOffline_triggered();
void on_actionRemove_triggered(); void on_actionRemove_triggered();

View File

@ -54,6 +54,7 @@
</attribute> </attribute>
<addaction name="actionAddMicrosoft"/> <addaction name="actionAddMicrosoft"/>
<addaction name="actionAddMojang"/> <addaction name="actionAddMojang"/>
<addaction name="actionAddAuthlibInjector"/>
<addaction name="actionAddOffline"/> <addaction name="actionAddOffline"/>
<addaction name="actionRefresh"/> <addaction name="actionRefresh"/>
<addaction name="actionRemove"/> <addaction name="actionRemove"/>
@ -68,6 +69,11 @@
<string>Add &amp;Mojang</string> <string>Add &amp;Mojang</string>
</property> </property>
</action> </action>
<action name="actionAddAuthlibInjector">
<property name="text">
<string>Add Authlib-&amp;Injector</string>
</property>
</action>
<action name="actionRemove"> <action name="actionRemove">
<property name="text"> <property name="text">
<string>Remo&amp;ve</string> <string>Remo&amp;ve</string>