diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
| commit | 31b9a8949ed0a288143e23bf739f2eb64fdc63be (patch) | |
| tree | 8a984fa143c38fccad461a77792d6864f3e82cd3 /meshmc/launcher/minecraft/auth/steps | |
| parent | 934382c8a1ce738589dee9ee0f14e1cec812770e (diff) | |
| parent | fad6a1066616b69d7f5fef01178efdf014c59537 (diff) | |
| download | Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.tar.gz Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.zip | |
Add 'meshmc/' from commit 'fad6a1066616b69d7f5fef01178efdf014c59537'
git-subtree-dir: meshmc
git-subtree-mainline: 934382c8a1ce738589dee9ee0f14e1cec812770e
git-subtree-split: fad6a1066616b69d7f5fef01178efdf014c59537
Diffstat (limited to 'meshmc/launcher/minecraft/auth/steps')
16 files changed, 1261 insertions, 0 deletions
diff --git a/meshmc/launcher/minecraft/auth/steps/EntitlementsStep.cpp b/meshmc/launcher/minecraft/auth/steps/EntitlementsStep.cpp new file mode 100644 index 0000000000..8d0418042a --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/EntitlementsStep.cpp @@ -0,0 +1,80 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "EntitlementsStep.h" + +#include <QNetworkRequest> +#include <QUuid> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {} + +EntitlementsStep::~EntitlementsStep() noexcept = default; + +QString EntitlementsStep::describe() +{ + return tr("Determining game ownership."); +} + +void EntitlementsStep::perform() +{ + auto uuid = QUuid::createUuid(); + m_entitlementsRequestId = uuid.toString().remove('{').remove('}'); + auto url = + "https://api.minecraftservices.com/entitlements/license?requestId=" + + m_entitlementsRequestId; + QNetworkRequest request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Accept", "application/json"); + request.setRawHeader( + "Authorization", + QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); + AuthRequest* requestor = new AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &EntitlementsStep::onRequestDone); + requestor->get(request); + qDebug() << "Getting entitlements..."; +} + +void EntitlementsStep::rehydrate() +{ + // NOOP, for now. We only save bools and there's nothing to check. +} + +void EntitlementsStep::onRequestDone( + QNetworkReply::NetworkError error, QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + +#ifndef NDEBUG + qDebug() << data; +#endif + + // TODO: check presence of same entitlementsRequestId? + // TODO: validate JWTs? + Parsers::parseMinecraftEntitlements(data, m_data->minecraftEntitlement); + + emit finished(AccountTaskState::STATE_WORKING, tr("Got entitlements")); +} diff --git a/meshmc/launcher/minecraft/auth/steps/EntitlementsStep.h b/meshmc/launcher/minecraft/auth/steps/EntitlementsStep.h new file mode 100644 index 0000000000..bd97a1c59e --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/EntitlementsStep.h @@ -0,0 +1,47 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class EntitlementsStep : public AuthStep +{ + Q_OBJECT + + public: + explicit EntitlementsStep(AccountData* data); + virtual ~EntitlementsStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); + + private: + QString m_entitlementsRequestId; +}; diff --git a/meshmc/launcher/minecraft/auth/steps/GetSkinStep.cpp b/meshmc/launcher/minecraft/auth/steps/GetSkinStep.cpp new file mode 100644 index 0000000000..abf5db950f --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/GetSkinStep.cpp @@ -0,0 +1,64 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "GetSkinStep.h" + +#include <QNetworkRequest> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {} + +GetSkinStep::~GetSkinStep() noexcept = default; + +QString GetSkinStep::describe() +{ + return tr("Getting skin."); +} + +void GetSkinStep::perform() +{ + auto url = QUrl(m_data->minecraftProfile.skin.url); + QNetworkRequest request = QNetworkRequest(url); + AuthRequest* requestor = new AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &GetSkinStep::onRequestDone); + requestor->get(request); +} + +void GetSkinStep::rehydrate() +{ + // NOOP, for now. +} + +void GetSkinStep::onRequestDone(QNetworkReply::NetworkError error, + QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + + if (error == QNetworkReply::NoError) { + m_data->minecraftProfile.skin.data = data; + } + emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Got skin")); +} diff --git a/meshmc/launcher/minecraft/auth/steps/GetSkinStep.h b/meshmc/launcher/minecraft/auth/steps/GetSkinStep.h new file mode 100644 index 0000000000..ed6a288cdb --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/GetSkinStep.h @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class GetSkinStep : public AuthStep +{ + Q_OBJECT + + public: + explicit GetSkinStep(AccountData* data); + virtual ~GetSkinStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); +}; diff --git a/meshmc/launcher/minecraft/auth/steps/MSAStep.cpp b/meshmc/launcher/minecraft/auth/steps/MSAStep.cpp new file mode 100644 index 0000000000..9be4761549 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/MSAStep.cpp @@ -0,0 +1,161 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "MSAStep.h" + +#include <QNetworkRequest> +#include <QDesktopServices> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +#include "Application.h" + +MSAStep::MSAStep(AccountData* data, Action action) + : AuthStep(data), m_action(action) +{ + m_replyHandler = new QOAuthHttpServerReplyHandler(this); + m_replyHandler->setCallbackText( + tr("Login successful! You can close this page and return to MeshMC.")); + + m_oauth2 = new QOAuth2AuthorizationCodeFlow(this); + m_oauth2->setClientIdentifier(APPLICATION->msaClientId()); + m_oauth2->setAuthorizationUrl(QUrl( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize")); + m_oauth2->setTokenUrl( + QUrl("https://login.microsoftonline.com/consumers/oauth2/v2.0/token")); + m_oauth2->setScope("XboxLive.signin offline_access"); + m_oauth2->setReplyHandler(m_replyHandler); + m_oauth2->setNetworkAccessManager(APPLICATION->network().get()); + + connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::granted, this, + &MSAStep::onGranted); + connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::requestFailed, this, + &MSAStep::onRequestFailed); + connect(m_oauth2, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, + &MSAStep::onOpenBrowser); +} + +MSAStep::~MSAStep() noexcept = default; + +QString MSAStep::describe() +{ + return tr("Logging in with Microsoft account."); +} + +void MSAStep::rehydrate() +{ + switch (m_action) { + case Refresh: { + // TODO: check the tokens and see if they are old (older than a day) + return; + } + case Login: { + // NOOP + return; + } + } +} + +void MSAStep::perform() +{ + switch (m_action) { + case Refresh: { + // Load the refresh token from stored account data + m_oauth2->setRefreshToken(m_data->msaToken.refresh_token); + m_oauth2->refreshTokens(); + return; + } + case Login: { + *m_data = AccountData(); + if (!m_replyHandler->isListening()) { + if (!m_replyHandler->listen(QHostAddress::LocalHost)) { + emit finished(AccountTaskState::STATE_FAILED_HARD, + tr("Failed to start local HTTP server for " + "OAuth2 callback.")); + return; + } + } + m_oauth2->setModifyParametersFunction( + [](QAbstractOAuth::Stage stage, + QMultiMap<QString, QVariant>* parameters) { + if (stage == + QAbstractOAuth::Stage::RequestingAuthorization) { + parameters->insert("prompt", "select_account"); + } + }); + m_oauth2->grant(); + return; + } + } +} + +void MSAStep::onOpenBrowser(const QUrl& url) +{ + emit authorizeWithBrowser(url); + QDesktopServices::openUrl(url); +} + +void MSAStep::onGranted() +{ + m_replyHandler->close(); + + // Store the tokens in account data + m_data->msaToken.token = m_oauth2->token(); + m_data->msaToken.refresh_token = m_oauth2->refreshToken(); + m_data->msaToken.issueInstant = QDateTime::currentDateTimeUtc(); + m_data->msaToken.notAfter = m_oauth2->expirationAt(); + if (!m_data->msaToken.notAfter.isValid()) { + m_data->msaToken.notAfter = m_data->msaToken.issueInstant.addSecs(3600); + } + m_data->msaToken.validity = Katabasis::Validity::Certain; + + emit finished(AccountTaskState::STATE_WORKING, tr("Got MSA token.")); +} + +void MSAStep::onRequestFailed(QAbstractOAuth::Error error) +{ + m_replyHandler->close(); + + switch (error) { + case QAbstractOAuth::Error::NetworkError: + emit finished( + AccountTaskState::STATE_OFFLINE, + tr("Microsoft authentication failed due to a network error.")); + return; + case QAbstractOAuth::Error::ServerError: + case QAbstractOAuth::Error::OAuthTokenNotFoundError: + case QAbstractOAuth::Error::OAuthTokenSecretNotFoundError: + case QAbstractOAuth::Error::OAuthCallbackNotVerified: + emit finished(AccountTaskState::STATE_FAILED_HARD, + tr("Microsoft authentication failed.")); + return; + case QAbstractOAuth::Error::ExpiredError: + emit finished(AccountTaskState::STATE_FAILED_GONE, + tr("Microsoft authentication token expired.")); + return; + default: + emit finished(AccountTaskState::STATE_FAILED_HARD, + tr("Microsoft authentication failed with an " + "unrecognized error.")); + return; + } +} diff --git a/meshmc/launcher/minecraft/auth/steps/MSAStep.h b/meshmc/launcher/minecraft/auth/steps/MSAStep.h new file mode 100644 index 0000000000..2e223024e3 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/MSAStep.h @@ -0,0 +1,55 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +#include <QOAuth2AuthorizationCodeFlow> +#include <QOAuthHttpServerReplyHandler> + +class MSAStep : public AuthStep +{ + Q_OBJECT + public: + enum Action { Refresh, Login }; + + public: + explicit MSAStep(AccountData* data, Action action); + virtual ~MSAStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onGranted(); + void onRequestFailed(QAbstractOAuth::Error error); + void onOpenBrowser(const QUrl& url); + + private: + QOAuth2AuthorizationCodeFlow* m_oauth2 = nullptr; + QOAuthHttpServerReplyHandler* m_replyHandler = nullptr; + Action m_action; +}; diff --git a/meshmc/launcher/minecraft/auth/steps/MeshMCLoginStep.cpp b/meshmc/launcher/minecraft/auth/steps/MeshMCLoginStep.cpp new file mode 100644 index 0000000000..19afcda3fc --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/MeshMCLoginStep.cpp @@ -0,0 +1,98 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "MeshMCLoginStep.h" + +#include <QNetworkRequest> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" +#include "minecraft/auth/AccountTask.h" + +MeshMCLoginStep::MeshMCLoginStep(AccountData* data) : AuthStep(data) {} + +MeshMCLoginStep::~MeshMCLoginStep() noexcept = default; + +QString MeshMCLoginStep::describe() +{ + return tr("Accessing Mojang services."); +} + +void MeshMCLoginStep::perform() +{ + auto requestURL = "https://api.minecraftservices.com/launcher/login"; + auto uhs = m_data->mojangservicesToken.extra["uhs"].toString(); + auto xToken = m_data->mojangservicesToken.token; + + QString mc_auth_template = R"XXX( +{ + "xtoken": "XBL3.0 x=%1;%2", + "platform": "PC_LAUNCHER" +} +)XXX"; + auto requestBody = mc_auth_template.arg(uhs, xToken); + + QNetworkRequest request = QNetworkRequest(QUrl(requestURL)); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Accept", "application/json"); + AuthRequest* requestor = new AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &MeshMCLoginStep::onRequestDone); + requestor->post(request, requestBody.toUtf8()); + qDebug() << "Getting Minecraft access token..."; +} + +void MeshMCLoginStep::rehydrate() +{ + // TODO: check the token validity +} + +void MeshMCLoginStep::onRequestDone(QNetworkReply::NetworkError error, + QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + + qDebug() << data; + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; +#ifndef NDEBUG + qDebug() << data; +#endif + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to get Minecraft access token: %1") + .arg(requestor->errorString_)); + return; + } + + if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) { + qWarning() << "Could not parse login_with_xbox response..."; +#ifndef NDEBUG + qDebug() << data; +#endif + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to parse the Minecraft access token response.")); + return; + } + emit finished(AccountTaskState::STATE_WORKING, tr("")); +} diff --git a/meshmc/launcher/minecraft/auth/steps/MeshMCLoginStep.h b/meshmc/launcher/minecraft/auth/steps/MeshMCLoginStep.h new file mode 100644 index 0000000000..859ae867f3 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/MeshMCLoginStep.h @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class MeshMCLoginStep : public AuthStep +{ + Q_OBJECT + + public: + explicit MeshMCLoginStep(AccountData* data); + virtual ~MeshMCLoginStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); +}; diff --git a/meshmc/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/meshmc/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp new file mode 100644 index 0000000000..9955ff9738 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -0,0 +1,101 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "MinecraftProfileStep.h" + +#include <QNetworkRequest> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) +{ +} + +MinecraftProfileStep::~MinecraftProfileStep() noexcept = default; + +QString MinecraftProfileStep::describe() +{ + return tr("Fetching the Minecraft profile."); +} + +void MinecraftProfileStep::perform() +{ + auto url = QUrl("https://api.minecraftservices.com/minecraft/profile"); + QNetworkRequest request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader( + "Authorization", + QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8()); + + AuthRequest* requestor = new AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &MinecraftProfileStep::onRequestDone); + requestor->get(request); +} + +void MinecraftProfileStep::rehydrate() +{ + // NOOP, for now. We only save bools and there's nothing to check. +} + +void MinecraftProfileStep::onRequestDone( + QNetworkReply::NetworkError error, QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + +#ifndef NDEBUG + qDebug() << data; +#endif + if (error == QNetworkReply::ContentNotFoundError) { + // NOTE: Succeed even if we do not have a profile. This is a valid + // account state. + m_data->minecraftProfile = MinecraftProfile(); + emit finished(AccountTaskState::STATE_SUCCEEDED, + tr("Account has no Minecraft profile.")); + return; + } + if (error != QNetworkReply::NoError) { + qWarning() << "Error getting profile:"; + qWarning() << " HTTP Status: " << requestor->httpStatus_; + qWarning() << " Internal error no.: " << error; + qWarning() << " Error string: " << requestor->errorString_; + + qWarning() << " Response:"; + qWarning() << QString::fromUtf8(data); + + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile acquisition failed.")); + return; + } + if (!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) { + m_data->minecraftProfile = MinecraftProfile(); + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile response could not be parsed")); + return; + } + + emit finished(AccountTaskState::STATE_WORKING, + tr("Minecraft Java profile acquisition succeeded.")); +} diff --git a/meshmc/launcher/minecraft/auth/steps/MinecraftProfileStep.h b/meshmc/launcher/minecraft/auth/steps/MinecraftProfileStep.h new file mode 100644 index 0000000000..eb0594bdf8 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/MinecraftProfileStep.h @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class MinecraftProfileStep : public AuthStep +{ + Q_OBJECT + + public: + explicit MinecraftProfileStep(AccountData* data); + virtual ~MinecraftProfileStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); +}; diff --git a/meshmc/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/meshmc/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp new file mode 100644 index 0000000000..b54ad2a32b --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -0,0 +1,191 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "XboxAuthorizationStep.h" + +#include <QNetworkRequest> +#include <QJsonParseError> +#include <QJsonDocument> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, + Katabasis::Token* token, + QString relyingParty, + QString authorizationKind) + : AuthStep(data), m_token(token), m_relyingParty(relyingParty), + m_authorizationKind(authorizationKind) +{ +} + +XboxAuthorizationStep::~XboxAuthorizationStep() noexcept = default; + +QString XboxAuthorizationStep::describe() +{ + return tr("Getting authorization to access %1 services.") + .arg(m_authorizationKind); +} + +void XboxAuthorizationStep::rehydrate() +{ + // FIXME: check if the tokens are good? +} + +void XboxAuthorizationStep::perform() +{ + QString xbox_auth_template = R"XXX( +{ + "Properties": { + "SandboxId": "RETAIL", + "UserTokens": [ + "%1" + ] + }, + "RelyingParty": "%2", + "TokenType": "JWT" +} +)XXX"; + auto xbox_auth_data = + xbox_auth_template.arg(m_data->userToken.token, m_relyingParty); + // http://xboxlive.com + QNetworkRequest request = + QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize")); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Accept", "application/json"); + AuthRequest* requestor = new AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &XboxAuthorizationStep::onRequestDone); + requestor->post(request, xbox_auth_data.toUtf8()); + qDebug() << "Getting authorization token for " << m_relyingParty; +} + +void XboxAuthorizationStep::onRequestDone( + QNetworkReply::NetworkError error, QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + +#ifndef NDEBUG + qDebug() << data; +#endif + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; + if (!processSTSError(error, data, headers)) { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to get authorization for %1 services. Error %1.") + .arg(m_authorizationKind, error)); + } + return; + } + + Katabasis::Token temp; + if (!Parsers::parseXTokenResponse(data, temp, m_authorizationKind)) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Could not parse authorization response for access to " + "%1 services.") + .arg(m_authorizationKind)); + return; + } + + if (temp.extra["uhs"] != m_data->userToken.extra["uhs"]) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Server has changed %1 authorization user hash in the " + "reply. Something is wrong.") + .arg(m_authorizationKind)); + return; + } + auto& token = *m_token; + token = temp; + + emit finished(AccountTaskState::STATE_WORKING, + tr("Got authorization to access %1").arg(m_relyingParty)); +} + +bool XboxAuthorizationStep::processSTSError( + QNetworkReply::NetworkError error, QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + if (error == QNetworkReply::AuthenticationRequiredError) { + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error) { + qWarning() << "Cannot parse error XSTS response as JSON: " + << jsonError.errorString(); + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Cannot parse %1 authorization error response as JSON: %2") + .arg(m_authorizationKind, jsonError.errorString())); + return true; + } + + int64_t errorCode = -1; + auto obj = doc.object(); + if (!Parsers::getNumber(obj.value("XErr"), errorCode)) { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("XErr element is missing from %1 authorization " + "error response.") + .arg(m_authorizationKind)); + return true; + } + switch (errorCode) { + case 2148916233: { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("This Microsoft account does not have an XBox Live " + "profile. Buy the game on %1 first.") + .arg("<a " + "href=\"https://www.minecraft.net/en-us/store/" + "minecraft-java-edition\">minecraft.net</a>")); + return true; + } + case 2148916235: { + // NOTE: this is the Grulovia error + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("XBox Live is not available in your country. " + "You've been blocked.")); + return true; + } + case 2148916238: { + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("This Microsoft account is underaged and is not linked " + "to a family.\n\nPlease set up your account according " + "to %1.") + .arg( + "<a " + "href=\"https://help.minecraft.net/hc/en-us/" + "articles/4403181904525\">help.minecraft.net</a>")); + return true; + } + default: { + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("XSTS authentication ended with unrecognized " + "error(s):\n\n%1") + .arg(errorCode)); + return true; + } + } + } + return false; +} diff --git a/meshmc/launcher/minecraft/auth/steps/XboxAuthorizationStep.h b/meshmc/launcher/minecraft/auth/steps/XboxAuthorizationStep.h new file mode 100644 index 0000000000..a8413c939f --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/XboxAuthorizationStep.h @@ -0,0 +1,55 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class XboxAuthorizationStep : public AuthStep +{ + Q_OBJECT + + public: + explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, + QString relyingParty, + QString authorizationKind); + virtual ~XboxAuthorizationStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private: + bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers); + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); + + private: + Katabasis::Token* m_token; + QString m_relyingParty; + QString m_authorizationKind; +}; diff --git a/meshmc/launcher/minecraft/auth/steps/XboxProfileStep.cpp b/meshmc/launcher/minecraft/auth/steps/XboxProfileStep.cpp new file mode 100644 index 0000000000..aae94b0403 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/XboxProfileStep.cpp @@ -0,0 +1,96 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "XboxProfileStep.h" + +#include <QNetworkRequest> +#include <QUrlQuery> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {} + +XboxProfileStep::~XboxProfileStep() noexcept = default; + +QString XboxProfileStep::describe() +{ + return tr("Fetching Xbox profile."); +} + +void XboxProfileStep::rehydrate() +{ + // NOOP, for now. We only save bools and there's nothing to check. +} + +void XboxProfileStep::perform() +{ + auto url = QUrl("https://profile.xboxlive.com/users/me/profile/settings"); + QUrlQuery q; + q.addQueryItem( + "settings", + "GameDisplayName,AppDisplayName,AppDisplayPicRaw,GameDisplayPicRaw," + "PublicGamerpic,ShowUserAsAvatar,Gamerscore,Gamertag,ModernGamertag," + "ModernGamertagSuffix," + "UniqueModernGamertag,AccountTier,TenureLevel,XboxOneRep," + "PreferredColor,Location,Bio,Watermarks," + "RealName,RealNameOverride,IsQuarantined"); + url.setQuery(q); + + QNetworkRequest request = QNetworkRequest(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setRawHeader("Accept", "application/json"); + request.setRawHeader("x-xbl-contract-version", "3"); + request.setRawHeader("Authorization", + QString("XBL3.0 x=%1;%2") + .arg(m_data->userToken.extra["uhs"].toString(), + m_data->xboxApiToken.token) + .toUtf8()); + AuthRequest* requestor = new AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &XboxProfileStep::onRequestDone); + requestor->get(request); + qDebug() << "Getting Xbox profile..."; +} + +void XboxProfileStep::onRequestDone(QNetworkReply::NetworkError error, + QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; +#ifndef NDEBUG + qDebug() << data; +#endif + finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Failed to retrieve the Xbox profile.")); + return; + } + +#ifndef NDEBUG + qDebug() << "XBox profile: " << data; +#endif + + emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); +} diff --git a/meshmc/launcher/minecraft/auth/steps/XboxProfileStep.h b/meshmc/launcher/minecraft/auth/steps/XboxProfileStep.h new file mode 100644 index 0000000000..cf2c0c3c9b --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/XboxProfileStep.h @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class XboxProfileStep : public AuthStep +{ + Q_OBJECT + + public: + explicit XboxProfileStep(AccountData* data); + virtual ~XboxProfileStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); +}; diff --git a/meshmc/launcher/minecraft/auth/steps/XboxUserStep.cpp b/meshmc/launcher/minecraft/auth/steps/XboxUserStep.cpp new file mode 100644 index 0000000000..77afa17fb9 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -0,0 +1,93 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "XboxUserStep.h" + +#include <QNetworkRequest> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {} + +XboxUserStep::~XboxUserStep() noexcept = default; + +QString XboxUserStep::describe() +{ + return tr("Logging in as an Xbox user."); +} + +void XboxUserStep::rehydrate() +{ + // NOOP, for now. We only save bools and there's nothing to check. +} + +void XboxUserStep::perform() +{ + 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_data->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 AuthRequest(this); + connect(requestor, &AuthRequest::finished, this, + &XboxUserStep::onRequestDone); + requestor->post(request, xbox_auth_data.toUtf8()); + qDebug() << "First layer of XBox auth ... commencing."; +} + +void XboxUserStep::onRequestDone(QNetworkReply::NetworkError error, + QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers) +{ + auto requestor = qobject_cast<AuthRequest*>(QObject::sender()); + requestor->deleteLater(); + + if (error != QNetworkReply::NoError) { + qWarning() << "Reply error:" << error; + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("XBox user authentication failed.")); + return; + } + + Katabasis::Token temp; + if (!Parsers::parseXTokenResponse(data, temp, "UToken")) { + qWarning() << "Could not parse user authentication response..."; + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("XBox user authentication response could not be understood.")); + return; + } + m_data->userToken = temp; + emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox user token")); +} diff --git a/meshmc/launcher/minecraft/auth/steps/XboxUserStep.h b/meshmc/launcher/minecraft/auth/steps/XboxUserStep.h new file mode 100644 index 0000000000..d783b534c9 --- /dev/null +++ b/meshmc/launcher/minecraft/auth/steps/XboxUserStep.h @@ -0,0 +1,44 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +class XboxUserStep : public AuthStep +{ + Q_OBJECT + + public: + explicit XboxUserStep(AccountData* data); + virtual ~XboxUserStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + + private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, + QList<QNetworkReply::RawHeaderPair>); +}; |
