aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/auth-msa
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/auth-msa')
-rw-r--r--launcher/minecraft/auth-msa/BuildConfig.cpp.in9
-rw-r--r--launcher/minecraft/auth-msa/BuildConfig.h11
-rw-r--r--launcher/minecraft/auth-msa/CMakeLists.txt28
-rw-r--r--launcher/minecraft/auth-msa/context.cpp938
-rw-r--r--launcher/minecraft/auth-msa/context.h128
-rw-r--r--launcher/minecraft/auth-msa/main.cpp100
-rw-r--r--launcher/minecraft/auth-msa/mainwindow.cpp97
-rw-r--r--launcher/minecraft/auth-msa/mainwindow.h34
-rw-r--r--launcher/minecraft/auth-msa/mainwindow.ui72
9 files changed, 0 insertions, 1417 deletions
diff --git a/launcher/minecraft/auth-msa/BuildConfig.cpp.in b/launcher/minecraft/auth-msa/BuildConfig.cpp.in
deleted file mode 100644
index 8f470e25..00000000
--- a/launcher/minecraft/auth-msa/BuildConfig.cpp.in
+++ /dev/null
@@ -1,9 +0,0 @@
-#include "BuildConfig.h"
-#include <QObject>
-
-const Config BuildConfig;
-
-Config::Config()
-{
- CLIENT_ID = "@MOJANGDEMO_CLIENT_ID@";
-}
diff --git a/launcher/minecraft/auth-msa/BuildConfig.h b/launcher/minecraft/auth-msa/BuildConfig.h
deleted file mode 100644
index 7a01d704..00000000
--- a/launcher/minecraft/auth-msa/BuildConfig.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-#include <QString>
-
-class Config
-{
-public:
- Config();
- QString CLIENT_ID;
-};
-
-extern const Config BuildConfig;
diff --git a/launcher/minecraft/auth-msa/CMakeLists.txt b/launcher/minecraft/auth-msa/CMakeLists.txt
deleted file mode 100644
index 22777d1b..00000000
--- a/launcher/minecraft/auth-msa/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-find_package(Qt5 COMPONENTS Core Gui Network Widgets REQUIRED)
-
-set(CMAKE_AUTOMOC ON)
-set(CMAKE_AUTOUIC ON)
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
-
-
-set(MOJANGDEMO_CLIENT_ID "" CACHE STRING "Client ID used for OAuth2 in mojangdemo")
-
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/BuildConfig.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp")
-
-set(mojang_SRCS
- main.cpp
- context.cpp
- context.h
-
- mainwindow.cpp
- mainwindow.h
- mainwindow.ui
-
- ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp
- BuildConfig.h
-)
-
-add_executable( mojangdemo ${mojang_SRCS} )
-target_link_libraries( mojangdemo Katabasis Qt5::Gui Qt5::Widgets )
-target_include_directories(mojangdemo PRIVATE logic)
diff --git a/launcher/minecraft/auth-msa/context.cpp b/launcher/minecraft/auth-msa/context.cpp
deleted file mode 100644
index d7ecda30..00000000
--- a/launcher/minecraft/auth-msa/context.cpp
+++ /dev/null
@@ -1,938 +0,0 @@
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-#include <QDesktopServices>
-#include <QMetaEnum>
-#include <QDebug>
-
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
-
-#include <QUrlQuery>
-
-#include <QPixmap>
-#include <QPainter>
-
-#include "context.h"
-#include "katabasis/Globals.h"
-#include "katabasis/StoreQSettings.h"
-#include "katabasis/Requestor.h"
-#include "BuildConfig.h"
-
-using OAuth2 = Katabasis::OAuth2;
-using Requestor = Katabasis::Requestor;
-using Activity = Katabasis::Activity;
-
-Context::Context(QObject *parent) :
- QObject(parent)
-{
- mgr = new QNetworkAccessManager(this);
-
- Katabasis::OAuth2::Options opts;
- opts.scope = "XboxLive.signin offline_access";
- opts.clientIdentifier = BuildConfig.CLIENT_ID;
- opts.authorizationUrl = "https://login.live.com/oauth20_authorize.srf";
- opts.accessTokenUrl = "https://login.live.com/oauth20_token.srf";
- opts.listenerPorts = {28562, 28563, 28564, 28565, 28566};
-
- oauth2 = new OAuth2(opts, m_account.msaToken, this, mgr);
-
- connect(oauth2, &OAuth2::linkingFailed, this, &Context::onLinkingFailed);
- connect(oauth2, &OAuth2::linkingSucceeded, this, &Context::onLinkingSucceeded);
- connect(oauth2, &OAuth2::openBrowser, this, &Context::onOpenBrowser);
- connect(oauth2, &OAuth2::closeBrowser, this, &Context::onCloseBrowser);
- connect(oauth2, &OAuth2::activityChanged, this, &Context::onOAuthActivityChanged);
-}
-
-void Context::beginActivity(Activity activity) {
- if(isBusy()) {
- throw 0;
- }
- activity_ = activity;
- emit activityChanged(activity_);
-}
-
-void Context::finishActivity() {
- if(!isBusy()) {
- throw 0;
- }
- activity_ = Katabasis::Activity::Idle;
- m_account.validity_ = m_account.minecraftProfile.validity;
- emit activityChanged(activity_);
-}
-
-QString Context::gameToken() {
- return m_account.minecraftToken.token;
-}
-
-QString Context::userId() {
- return m_account.minecraftProfile.id;
-}
-
-QString Context::userName() {
- return m_account.minecraftProfile.name;
-}
-
-bool Context::silentSignIn() {
- if(isBusy()) {
- return false;
- }
- beginActivity(Activity::Refreshing);
- if(!oauth2->refresh()) {
- finishActivity();
- return false;
- }
-
- requestsDone = 0;
- xboxProfileSucceeded = false;
- mcAuthSucceeded = false;
-
- return true;
-}
-
-bool Context::signIn() {
- if(isBusy()) {
- return false;
- }
-
- requestsDone = 0;
- xboxProfileSucceeded = false;
- mcAuthSucceeded = false;
-
- beginActivity(Activity::LoggingIn);
- oauth2->unlink();
- m_account = AccountData();
- oauth2->link();
- return true;
-}
-
-bool Context::signOut() {
- if(isBusy()) {
- return false;
- }
- beginActivity(Activity::LoggingOut);
- oauth2->unlink();
- m_account = AccountData();
- finishActivity();
- return true;
-}
-
-
-void Context::onOpenBrowser(const QUrl &url) {
- QDesktopServices::openUrl(url);
-}
-
-void Context::onCloseBrowser() {
-
-}
-
-void Context::onLinkingFailed() {
- finishActivity();
-}
-
-void Context::onLinkingSucceeded() {
- auto *o2t = qobject_cast<OAuth2 *>(sender());
- if (!o2t->linked()) {
- finishActivity();
- return;
- }
- QVariantMap extraTokens = o2t->extraTokens();
- if (!extraTokens.isEmpty()) {
- qDebug() << "Extra tokens in response:";
- foreach (QString key, extraTokens.keys()) {
- qDebug() << "\t" << key << ":" << extraTokens.value(key);
- }
- }
- doUserAuth();
-}
-
-void Context::onOAuthActivityChanged(Katabasis::Activity activity) {
- // respond to activity change here
-}
-
-void Context::doUserAuth() {
- QString xbox_auth_template = R"XXX(
-{
- "Properties": {
- "AuthMethod": "RPS",
- "SiteName": "user.auth.xboxlive.com",
- "RpsTicket": "d=%1"
- },
- "RelyingParty": "http://auth.xboxlive.com",
- "TokenType": "JWT"
-}
-)XXX";
- auto xbox_auth_data = xbox_auth_template.arg(m_account.msaToken.token);
-
- QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Accept", "application/json");
- auto *requestor = new Katabasis::Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
-
- connect(requestor, &Requestor::finished, this, &Context::onUserAuthDone);
- requestor->post(request, xbox_auth_data.toUtf8());
- qDebug() << "First layer of XBox auth ... commencing.";
-}
-
-namespace {
-bool getDateTime(QJsonValue value, QDateTime & out) {
- if(!value.isString()) {
- return false;
- }
- out = QDateTime::fromString(value.toString(), Qt::ISODateWithMs);
- return out.isValid();
-}
-
-bool getString(QJsonValue value, QString & out) {
- if(!value.isString()) {
- return false;
- }
- out = value.toString();
- return true;
-}
-
-bool getNumber(QJsonValue value, double & out) {
- if(!value.isDouble()) {
- return false;
- }
- out = value.toDouble();
- return true;
-}
-
-/*
-{
- "IssueInstant":"2020-12-07T19:52:08.4463796Z",
- "NotAfter":"2020-12-21T19:52:08.4463796Z",
- "Token":"token",
- "DisplayClaims":{
- "xui":[
- {
- "uhs":"userhash"
- }
- ]
- }
- }
-*/
-// TODO: handle error responses ...
-/*
-{
- "Identity":"0",
- "XErr":2148916238,
- "Message":"",
- "Redirect":"https://start.ui.xboxlive.com/AddChildToFamily"
-}
-// 2148916233 = missing XBox account
-// 2148916238 = child account not linked to a family
-*/
-
-bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output) {
- QJsonParseError jsonError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
- qDebug() << data;
- return false;
- }
-
- auto obj = doc.object();
- if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
- qWarning() << "User IssueInstant is not a timestamp";
- qDebug() << data;
- return false;
- }
- if(!getDateTime(obj.value("NotAfter"), output.notAfter)) {
- qWarning() << "User NotAfter is not a timestamp";
- qDebug() << data;
- return false;
- }
- if(!getString(obj.value("Token"), output.token)) {
- qWarning() << "User Token is not a timestamp";
- qDebug() << data;
- return false;
- }
- auto arrayVal = obj.value("DisplayClaims").toObject().value("xui");
- if(!arrayVal.isArray()) {
- qWarning() << "Missing xui claims array";
- qDebug() << data;
- return false;
- }
- bool foundUHS = false;
- for(auto item: arrayVal.toArray()) {
- if(!item.isObject()) {
- continue;
- }
- auto obj = item.toObject();
- if(obj.contains("uhs")) {
- foundUHS = true;
- } else {
- continue;
- }
- // consume all 'display claims' ... whatever that means
- for(auto iter = obj.begin(); iter != obj.end(); iter++) {
- QString claim;
- if(!getString(obj.value(iter.key()), claim)) {
- qWarning() << "display claim " << iter.key() << " is not a string...";
- qDebug() << data;
- return false;
- }
- output.extra[iter.key()] = claim;
- }
-
- break;
- }
- if(!foundUHS) {
- qWarning() << "Missing uhs";
- qDebug() << data;
- return false;
- }
- output.validity = Katabasis::Validity::Certain;
- qDebug() << data;
- return true;
-}
-
-}
-
-void Context::onUserAuthDone(
- int requestId,
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- finishActivity();
- return;
- }
-
- Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp)) {
- qWarning() << "Could not parse user authentication response...";
- finishActivity();
- return;
- }
- m_account.userToken = temp;
-
- doSTSAuthMinecraft();
- doSTSAuthGeneric();
-}
-/*
- url = "https://xsts.auth.xboxlive.com/xsts/authorize"
- headers = {"x-xbl-contract-version": "1"}
- data = {
- "RelyingParty": relying_party,
- "TokenType": "JWT",
- "Properties": {
- "UserTokens": [self.user_token.token],
- "SandboxId": "RETAIL",
- },
- }
-*/
-void Context::doSTSAuthMinecraft() {
- QString xbox_auth_template = R"XXX(
-{
- "Properties": {
- "SandboxId": "RETAIL",
- "UserTokens": [
- "%1"
- ]
- },
- "RelyingParty": "rp://api.minecraftservices.com/",
- "TokenType": "JWT"
-}
-)XXX";
- auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token);
-
- QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Accept", "application/json");
- Requestor *requestor = new Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
-
- connect(requestor, &Requestor::finished, this, &Context::onSTSAuthMinecraftDone);
- requestor->post(request, xbox_auth_data.toUtf8());
- qDebug() << "Second layer of XBox auth ... commencing.";
-}
-
-void Context::onSTSAuthMinecraftDone(
- int requestId,
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- finishActivity();
- return;
- }
-
- Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp)) {
- qWarning() << "Could not parse authorization response for access to mojang services...";
- finishActivity();
- return;
- }
-
- if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) {
- qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
- qDebug() << replyData;
- finishActivity();
- return;
- }
- m_account.mojangservicesToken = temp;
-
- doMinecraftAuth();
-}
-
-void Context::doSTSAuthGeneric() {
- QString xbox_auth_template = R"XXX(
-{
- "Properties": {
- "SandboxId": "RETAIL",
- "UserTokens": [
- "%1"
- ]
- },
- "RelyingParty": "http://xboxlive.com",
- "TokenType": "JWT"
-}
-)XXX";
- auto xbox_auth_data = xbox_auth_template.arg(m_account.userToken.token);
-
- QNetworkRequest request = QNetworkRequest(QUrl("https://xsts.auth.xboxlive.com/xsts/authorize"));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Accept", "application/json");
- Requestor *requestor = new Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
-
- connect(requestor, &Requestor::finished, this, &Context::onSTSAuthGenericDone);
- requestor->post(request, xbox_auth_data.toUtf8());
- qDebug() << "Second layer of XBox auth ... commencing.";
-}
-
-void Context::onSTSAuthGenericDone(
- int requestId,
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- finishActivity();
- return;
- }
-
- Katabasis::Token temp;
- if(!parseXTokenResponse(replyData, temp)) {
- qWarning() << "Could not parse authorization response for access to xbox API...";
- finishActivity();
- return;
- }
-
- if(temp.extra["uhs"] != m_account.userToken.extra["uhs"]) {
- qWarning() << "Server has changed user hash in the reply... something is wrong. ABORTING";
- qDebug() << replyData;
- finishActivity();
- return;
- }
- m_account.xboxApiToken = temp;
-
- doXBoxProfile();
-}
-
-
-void Context::doMinecraftAuth() {
- QString mc_auth_template = R"XXX(
-{
- "identityToken": "XBL3.0 x=%1;%2"
-}
-)XXX";
- auto data = mc_auth_template.arg(m_account.mojangservicesToken.extra["uhs"].toString(), m_account.mojangservicesToken.token);
-
- QNetworkRequest request = QNetworkRequest(QUrl("https://api.minecraftservices.com/authentication/login_with_xbox"));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Accept", "application/json");
- Requestor *requestor = new Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
-
- connect(requestor, &Requestor::finished, this, &Context::onMinecraftAuthDone);
- requestor->post(request, data.toUtf8());
- qDebug() << "Getting Minecraft access token...";
-}
-
-namespace {
-bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
- QJsonParseError jsonError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
- qDebug() << data;
- return false;
- }
-
- auto obj = doc.object();
- double expires_in = 0;
- if(!getNumber(obj.value("expires_in"), expires_in)) {
- qWarning() << "expires_in is not a valid number";
- qDebug() << data;
- return false;
- }
- auto currentTime = QDateTime::currentDateTimeUtc();
- output.issueInstant = currentTime;
- output.notAfter = currentTime.addSecs(expires_in);
-
- QString username;
- if(!getString(obj.value("username"), username)) {
- qWarning() << "username is not valid";
- qDebug() << data;
- return false;
- }
-
- // TODO: it's a JWT... validate it?
- if(!getString(obj.value("access_token"), output.token)) {
- qWarning() << "access_token is not valid";
- qDebug() << data;
- return false;
- }
- output.validity = Katabasis::Validity::Certain;
- qDebug() << data;
- return true;
-}
-}
-
-void Context::onMinecraftAuthDone(
- int requestId,
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- requestsDone++;
-
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- qDebug() << replyData;
- finishActivity();
- return;
- }
-
- if(!parseMojangResponse(replyData, m_account.minecraftToken)) {
- qWarning() << "Could not parse login_with_xbox response...";
- qDebug() << replyData;
- finishActivity();
- return;
- }
- mcAuthSucceeded = true;
-
- checkResult();
-}
-
-void Context::doXBoxProfile() {
- 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_account.userToken.extra["uhs"].toString(), m_account.xboxApiToken.token).toUtf8());
- Requestor *requestor = new Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
-
- connect(requestor, &Requestor::finished, this, &Context::onXBoxProfileDone);
- requestor->get(request);
- qDebug() << "Getting Xbox profile...";
-}
-
-void Context::onXBoxProfileDone(
- int requestId,
- QNetworkReply::NetworkError error,
- QByteArray replyData,
- QList<QNetworkReply::RawHeaderPair> headers
-) {
- requestsDone ++;
-
- if (error != QNetworkReply::NoError) {
- qWarning() << "Reply error:" << error;
- qDebug() << replyData;
- finishActivity();
- return;
- }
-
- qDebug() << "XBox profile: " << replyData;
-
- xboxProfileSucceeded = true;
- checkResult();
-}
-
-void Context::checkResult() {
- if(requestsDone != 2) {
- return;
- }
- if(mcAuthSucceeded && xboxProfileSucceeded) {
- doMinecraftProfile();
- }
- else {
- finishActivity();
- }
-}
-
-namespace {
-bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
- QJsonParseError jsonError;
- QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
- if(jsonError.error) {
- qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
- qDebug() << data;
- return false;
- }
-
- auto obj = doc.object();
- if(!getString(obj.value("id"), output.id)) {
- qWarning() << "minecraft profile id is not a string";
- qDebug() << data;
- return false;
- }
-
- if(!getString(obj.value("name"), output.name)) {
- qWarning() << "minecraft profile name is not a string";
- qDebug() << data;
- return false;
- }
-
- auto skinsArray = obj.value("skins").toArray();
- for(auto skin: skinsArray) {
- auto skinObj = skin.toObject();
- Skin skinOut;
- if(!getString(skinObj.value("id"), skinOut.id)) {
- continue;
- }
- QString state;
- if(!getString(skinObj.value("state"), state)) {
- continue;
- }
- if(state != "ACTIVE") {
- continue;
- }
- if(!getString(skinObj.value("url"), skinOut.url)) {
- continue;
- }
- if(!getString(skinObj.value("variant"), skinOut.variant)) {
- continue;
- }
- // we deal with only the active skin
- output.skin = skinOut;
- break;
- }
- auto capesArray = obj.value("capes").toArray();
- int i = -1;
- int currentCape = -1;
- for(auto cape: capesArray) {
- i++;
- auto capeObj = cape.toObject();
- Cape capeOut;
- if(!getString(capeObj.value("id"), capeOut.id)) {
- continue;
- }
- QString state;
- if(!getString(capeObj.value("state"), state)) {
- continue;
- }
- if(state == "ACTIVE") {
- currentCape = i;
- }
- if(!getString(capeObj.value("url"), capeOut.url)) {
- continue;
- }
- if(!getString(capeObj.value("alias"), capeOut.alias)) {
- continue;
- }
-
- // we deal with only the active skin
- output.capes.push_back(capeOut);
- }
- output.currentCape = currentCape;
- output.validity = Katabasis::Validity::Certain;
- return true;
-}
-}
-
-void Context::doMinecraftProfile() {
- auto url = QUrl("https://api.minecraftservices.com/minecraft/profile");
- QNetworkRequest request = QNetworkRequest(url);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- // request.setRawHeader("Accept", "application/json");
- request.setRawHeader("Authorization", QString("Bearer %1").arg(m_account.minecraftToken.token).toUtf8());
-
- Requestor *requestor = new Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
-
- connect(requestor, &Requestor::finished, this, &Context::onMinecraftProfileDone);
- requestor->get(request);
-}
-
-void Context::onMinecraftProfileDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers) {
- qDebug() << data;
- if (error == QNetworkReply::ContentNotFoundError) {
- m_account.minecraftProfile = MinecraftProfile();
- finishActivity();
- return;
- }
- if (error != QNetworkReply::NoError) {
- finishActivity();
- return;
- }
- if(!parseMinecraftProfile(data, m_account.minecraftProfile)) {
- m_account.minecraftProfile = MinecraftProfile();
- finishActivity();
- return;
- }
- doGetSkin();
-}
-
-void Context::doGetSkin() {
- auto url = QUrl(m_account.minecraftProfile.skin.url);
- QNetworkRequest request = QNetworkRequest(url);
- Requestor *requestor = new Requestor(mgr, oauth2, this);
- requestor->setAddAccessTokenInQuery(false);
- connect(requestor, &Requestor::finished, this, &Context::onSkinDone);
- requestor->get(request);
-}
-
-void Context::onSkinDone(int, QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair>) {
- if (error == QNetworkReply::NoError) {
- m_account.minecraftProfile.skin.data = data;
- }
- finishActivity();
-}
-
-namespace {
-void tokenToJSON(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
- if(t.validity == Katabasis::Validity::None || !t.persistent) {
- return;
- }
- QJsonObject out;
- if(t.issueInstant.isValid()) {
- out["iat"] = QJsonValue(t.issueInstant.toSecsSinceEpoch());
- }
-
- if(t.notAfter.isValid()) {
- out["exp"] = QJsonValue(t.notAfter.toSecsSinceEpoch());
- }
-
- if(!t.token.isEmpty()) {
- out["token"] = QJsonValue(t.token);
- }
- if(!t.refresh_token.isEmpty()) {
- out["refresh_token"] = QJsonValue(t.refresh_token);
- }
- if(t.extra.size()) {
- out["extra"] = QJsonObject::fromVariantMap(t.extra);
- }
- if(out.size()) {
- parent[tokenName] = out;
- }
-}
-
-Katabasis::Token tokenFromJSON(const QJsonObject &parent, const char * tokenName) {
- Katabasis::Token out;
- auto tokenObject = parent.value(tokenName).toObject();
- if(tokenObject.isEmpty()) {
- return out;
- }
- auto issueInstant = tokenObject.value("iat");
- if(issueInstant.isDouble()) {
- out.issueInstant = QDateTime::fromSecsSinceEpoch((int64_t) issueInstant.toDouble());
- }
-
- auto notAfter = tokenObject.value("exp");
- if(notAfter.isDouble()) {
- out.notAfter = QDateTime::fromSecsSinceEpoch((int64_t) notAfter.toDouble());
- }
-
- auto token = tokenObject.value("token");
- if(token.isString()) {
- out.token = token.toString();
- out.validity = Katabasis::Validity::Assumed;
- }
-
- auto refresh_token = tokenObject.value("refresh_token");
- if(refresh_token.isString()) {
- out.refresh_token = refresh_token.toString();
- }
-
- auto extra = tokenObject.value("extra");
- if(extra.isObject()) {
- out.extra = extra.toObject().toVariantMap();
- }
- return out;
-}
-
-void profileToJSON(QJsonObject &parent, MinecraftProfile p, const char * tokenName) {
- if(p.id.isEmpty()) {
- return;
- }
- QJsonObject out;
- out["id"] = QJsonValue(p.id);
- out["name"] = QJsonValue(p.name);
- if(p.currentCape != -1) {
- out["cape"] = p.capes[p.currentCape].id;
- }
-
- {
- QJsonObject skinObj;
- skinObj["id"] = p.skin.id;
- skinObj["url"] = p.skin.url;
- skinObj["variant"] = p.skin.variant;
- if(p.skin.data.size()) {
- skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64());
- }
- out["skin"] = skinObj;
- }
-
- QJsonArray capesArray;
- for(auto & cape: p.capes) {
- QJsonObject capeObj;
- capeObj["id"] = cape.id;
- capeObj["url"] = cape.url;
- capeObj["alias"] = cape.alias;
- if(cape.data.size()) {
- capeObj["data"] = QString::fromLatin1(cape.data.toBase64());
- }
- capesArray.push_back(capeObj);
- }
- out["capes"] = capesArray;
- parent[tokenName] = out;
-}
-
-MinecraftProfile profileFromJSON(const QJsonObject &parent, const char * tokenName) {
- MinecraftProfile out;
- auto tokenObject = parent.value(tokenName).toObject();
- if(tokenObject.isEmpty()) {
- return out;
- }
- {
- auto idV = tokenObject.value("id");
- auto nameV = tokenObject.value("name");
- if(!idV.isString() || !nameV.isString()) {
- qWarning() << "mandatory profile attributes are missing or of unexpected type";
- return MinecraftProfile();
- }
- out.name = nameV.toString();
- out.id = idV.toString();
- }
-
- {
- auto skinV = tokenObject.value("skin");
- if(!skinV.isObject()) {
- qWarning() << "skin is missing";
- return MinecraftProfile();
- }
- auto skinObj = skinV.toObject();
- auto idV = skinObj.value("id");
- auto urlV = skinObj.value("url");
- auto variantV = skinObj.value("variant");
- if(!idV.isString() || !urlV.isString() || !variantV.isString()) {
- qWarning() << "mandatory skin attributes are missing or of unexpected type";
- return MinecraftProfile();
- }
- out.skin.id = idV.toString();
- out.skin.url = urlV.toString();
- out.skin.variant = variantV.toString();
-
- // data for skin is optional
- auto dataV = skinObj.value("data");
- if(dataV.isString()) {
- // TODO: validate base64
- out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1());
- }
- else if (!dataV.isUndefined()) {
- qWarning() << "skin data is something unexpected";
- return MinecraftProfile();
- }
- }
-
- auto capesV = tokenObject.value("capes");
- if(!capesV.isArray()) {
- qWarning() << "capes is not an array!";
- return MinecraftProfile();
- }
- auto capesArray = capesV.toArray();
- for(auto capeV: capesArray) {
- if(!capeV.isObject()) {
- qWarning() << "cape is not an object!";
- return MinecraftProfile();
- }
- auto capeObj = capeV.toObject();
- auto idV = capeObj.value("id");
- auto urlV = capeObj.value("url");
- auto aliasV = capeObj.value("alias");
- if(!idV.isString() || !urlV.isString() || !aliasV.isString()) {
- qWarning() << "mandatory skin attributes are missing or of unexpected type";
- return MinecraftProfile();
- }
- Cape cape;
- cape.id = idV.toString();
- cape.url = urlV.toString();
- cape.alias = aliasV.toString();
-
- // data for cape is optional.
- auto dataV = capeObj.value("data");
- if(dataV.isString()) {
- // TODO: validate base64
- cape.data = QByteArray::fromBase64(dataV.toString().toLatin1());
- }
- else if (!dataV.isUndefined()) {
- qWarning() << "cape data is something unexpected";
- return MinecraftProfile();
- }
- out.capes.push_back(cape);
- }
- out.validity = Katabasis::Validity::Assumed;
- return out;
-}
-
-}
-
-bool Context::resumeFromState(QByteArray data) {
- QJsonParseError error;
- auto doc = QJsonDocument::fromJson(data, &error);
- if(error.error != QJsonParseError::NoError) {
- qWarning() << "Failed to parse account data as JSON.";
- return false;
- }
- auto docObject = doc.object();
- m_account.msaToken = tokenFromJSON(docObject, "msa");
- m_account.userToken = tokenFromJSON(docObject, "utoken");
- m_account.xboxApiToken = tokenFromJSON(docObject, "xrp-main");
- m_account.mojangservicesToken = tokenFromJSON(docObject, "xrp-mc");
- m_account.minecraftToken = tokenFromJSON(docObject, "ygg");
-
- m_account.minecraftProfile = profileFromJSON(docObject, "profile");
-
- m_account.validity_ = m_account.minecraftProfile.validity;
-
- return true;
-}
-
-QByteArray Context::saveState() {
- QJsonDocument doc;
- QJsonObject output;
- tokenToJSON(output, m_account.msaToken, "msa");
- tokenToJSON(output, m_account.userToken, "utoken");
- tokenToJSON(output, m_account.xboxApiToken, "xrp-main");
- tokenToJSON(output, m_account.mojangservicesToken, "xrp-mc");
- tokenToJSON(output, m_account.minecraftToken, "ygg");
- profileToJSON(output, m_account.minecraftProfile, "profile");
- doc.setObject(output);
- return doc.toJson(QJsonDocument::Indented);
-}
diff --git a/launcher/minecraft/auth-msa/context.h b/launcher/minecraft/auth-msa/context.h
deleted file mode 100644
index f1ac99b8..00000000
--- a/launcher/minecraft/auth-msa/context.h
+++ /dev/null
@@ -1,128 +0,0 @@
-#pragma once
-
-#include <QObject>
-#include <QList>
-#include <QVector>
-#include <QNetworkReply>
-#include <QImage>
-
-#include <katabasis/OAuth2.h>
-
-struct Skin {
- QString id;
- QString url;
- QString variant;
-
- QByteArray data;
-};
-
-struct Cape {
- QString id;
- QString url;
- QString alias;
-
- QByteArray data;
-};
-
-struct MinecraftProfile {
- QString id;
- QString name;
- Skin skin;
- int currentCape = -1;
- QVector<Cape> capes;
- Katabasis::Validity validity = Katabasis::Validity::None;
-};
-
-enum class AccountType {
- MSA,
- Mojang
-};
-
-struct AccountData {
- AccountType type = AccountType::MSA;
-
- Katabasis::Token msaToken;
- Katabasis::Token userToken;
- Katabasis::Token xboxApiToken;
- Katabasis::Token mojangservicesToken;
- Katabasis::Token minecraftToken;
-
- MinecraftProfile minecraftProfile;
- Katabasis::Validity validity_ = Katabasis::Validity::None;
-};
-
-class Context : public QObject
-{
- Q_OBJECT
-
-public:
- explicit Context(QObject *parent = 0);
-
- QByteArray saveState();
- bool resumeFromState(QByteArray data);
-
- bool isBusy() {
- return activity_ != Katabasis::Activity::Idle;
- };
- Katabasis::Validity validity() {
- return m_account.validity_;
- };
-
- bool signIn();
- bool silentSignIn();
- bool signOut();
-
- QString userName();
- QString userId();
- QString gameToken();
-signals:
- void succeeded();
- void failed();
- void activityChanged(Katabasis::Activity activity);
-
-private slots:
- void onLinkingSucceeded();
- void onLinkingFailed();
- void onOpenBrowser(const QUrl &url);
- void onCloseBrowser();
- void onOAuthActivityChanged(Katabasis::Activity activity);
-
-private:
- void doUserAuth();
- Q_SLOT void onUserAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doSTSAuthMinecraft();
- Q_SLOT void onSTSAuthMinecraftDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
- void doMinecraftAuth();
- Q_SLOT void onMinecraftAuthDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doSTSAuthGeneric();
- Q_SLOT void onSTSAuthGenericDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
- void doXBoxProfile();
- Q_SLOT void onXBoxProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doMinecraftProfile();
- Q_SLOT void onMinecraftProfileDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void doGetSkin();
- Q_SLOT void onSkinDone(int, QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-
- void checkResult();
-
-private:
- void beginActivity(Katabasis::Activity activity);
- void finishActivity();
- void clearTokens();
-
-private:
- Katabasis::OAuth2 *oauth2 = nullptr;
-
- int requestsDone = 0;
- bool xboxProfileSucceeded = false;
- bool mcAuthSucceeded = false;
- Katabasis::Activity activity_ = Katabasis::Activity::Idle;
-
- AccountData m_account;
-
- QNetworkAccessManager *mgr = nullptr;
-};
diff --git a/launcher/minecraft/auth-msa/main.cpp b/launcher/minecraft/auth-msa/main.cpp
deleted file mode 100644
index 481e0126..00000000
--- a/launcher/minecraft/auth-msa/main.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-#include <QApplication>
-#include <QStringList>
-#include <QTimer>
-#include <QDebug>
-#include <QFile>
-#include <QSaveFile>
-
-#include "context.h"
-#include "mainwindow.h"
-
-void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
-{
- QByteArray localMsg = msg.toLocal8Bit();
- const char *file = context.file ? context.file : "";
- const char *function = context.function ? context.function : "";
- switch (type) {
- case QtDebugMsg:
- fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
- break;
- case QtInfoMsg:
- fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
- break;
- case QtWarningMsg:
- fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
- break;
- case QtCriticalMsg:
- fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
- break;
- case QtFatalMsg:
- fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
- break;
- }
-}
-
-class Helper : public QObject {
- Q_OBJECT
-
-public:
- Helper(Context * context) : QObject(), context_(context), msg_(QString()) {
- QFile tokenCache("usercache.dat");
- if(tokenCache.open(QIODevice::ReadOnly)) {
- context_->resumeFromState(tokenCache.readAll());
- }
- }
-
-public slots:
- void run() {
- connect(context_, &Context::activityChanged, this, &Helper::onActivityChanged);
- context_->silentSignIn();
- }
-
- void onFailed() {
- qDebug() << "Login failed";
- }
-
- void onActivityChanged(Katabasis::Activity activity) {
- if(activity == Katabasis::Activity::Idle) {
- switch(context_->validity()) {
- case Katabasis::Validity::None: {
- // account is gone, remove it.
- QFile::remove("usercache.dat");
- }
- break;
- case Katabasis::Validity::Assumed: {
- // this is basically a soft-failed refresh. do nothing.
- }
- break;
- case Katabasis::Validity::Certain: {
- // stuff got refreshed / signed in. Save.
- auto data = context_->saveState();
- QSaveFile tokenCache("usercache.dat");
- if(tokenCache.open(QIODevice::WriteOnly)) {
- tokenCache.write(context_->saveState());
- tokenCache.commit();
- }
- }
- break;
- }
- }
- }
-
-private:
- Context *context_;
- QString msg_;
-};
-
-int main(int argc, char *argv[]) {
- qInstallMessageHandler(myMessageOutput);
- QApplication a(argc, argv);
- QCoreApplication::setOrganizationName("MultiMC");
- QCoreApplication::setApplicationName("MultiMC");
- Context c;
- Helper helper(&c);
- MainWindow window(&c);
- window.show();
- QTimer::singleShot(0, &helper, &Helper::run);
- return a.exec();
-}
-
-#include "main.moc"
diff --git a/launcher/minecraft/auth-msa/mainwindow.cpp b/launcher/minecraft/auth-msa/mainwindow.cpp
deleted file mode 100644
index d4e18dc0..00000000
--- a/launcher/minecraft/auth-msa/mainwindow.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#include "mainwindow.h"
-#include "ui_mainwindow.h"
-#include <QDebug>
-
-#include <QDesktopServices>
-
-#include "BuildConfig.h"
-
-MainWindow::MainWindow(Context * context, QWidget *parent) :
- QMainWindow(parent),
- m_context(context),
- m_ui(new Ui::MainWindow)
-{
- m_ui->setupUi(this);
- connect(m_ui->signInButton_MSA, &QPushButton::clicked, this, &MainWindow::SignInMSAClicked);
- connect(m_ui->signInButton_Mojang, &QPushButton::clicked, this, &MainWindow::SignInMojangClicked);
- connect(m_ui->signOutButton, &QPushButton::clicked, this, &MainWindow::SignOutClicked);
- connect(m_ui->refreshButton, &QPushButton::clicked, this, &MainWindow::RefreshClicked);
-
- // connect(m_context, &Context::linkingSucceeded, this, &MainWindow::SignInSucceeded);
- // connect(m_context, &Context::linkingFailed, this, &MainWindow::SignInFailed);
- connect(m_context, &Context::activityChanged, this, &MainWindow::ActivityChanged);
- ActivityChanged(Katabasis::Activity::Idle);
-}
-
-MainWindow::~MainWindow() = default;
-
-void MainWindow::ActivityChanged(Katabasis::Activity activity) {
- switch(activity) {
- case Katabasis::Activity::Idle: {
- if(m_context->validity() != Katabasis::Validity::None) {
- m_ui->signInButton_Mojang->setEnabled(false);
- m_ui->signInButton_MSA->setEnabled(false);
- m_ui->signOutButton->setEnabled(true);
- m_ui->refreshButton->setEnabled(true);
- m_ui->statusBar->showMessage(QString("Hello %1!").arg(m_context->userName()));
- }
- else {
- m_ui->signInButton_Mojang->setEnabled(true);
- m_ui->signInButton_MSA->setEnabled(true);
- m_ui->signOutButton->setEnabled(false);
- m_ui->refreshButton->setEnabled(false);
- m_ui->statusBar->showMessage("Press the login button to start.");
- }
- }
- break;
- case Katabasis::Activity::LoggingIn: {
- m_ui->signInButton_Mojang->setEnabled(false);
- m_ui->signInButton_MSA->setEnabled(false);
- m_ui->signOutButton->setEnabled(false);
- m_ui->refreshButton->setEnabled(false);
- m_ui->statusBar->showMessage("Logging in...");
- }
- break;
- case Katabasis::Activity::LoggingOut: {
- m_ui->signInButton_Mojang->setEnabled(false);
- m_ui->signInButton_MSA->setEnabled(false);
- m_ui->signOutButton->setEnabled(false);
- m_ui->refreshButton->setEnabled(false);
- m_ui->statusBar->showMessage("Logging out...");
- }
- break;
- case Katabasis::Activity::Refreshing: {
- m_ui->signInButton_Mojang->setEnabled(false);
- m_ui->signInButton_MSA->setEnabled(false);
- m_ui->signOutButton->setEnabled(false);
- m_ui->refreshButton->setEnabled(false);
- m_ui->statusBar->showMessage("Refreshing login...");
- }
- break;
- }
-}
-
-void MainWindow::SignInMSAClicked() {
- qDebug() << "Sign In MSA";
- // signIn({{"prompt", "select_account"}})
- // FIXME: wrong. very wrong. this should not be operating on the current context
- m_context->signIn();
-}
-
-void MainWindow::SignInMojangClicked() {
- qDebug() << "Sign In Mojang";
- // signIn({{"prompt", "select_account"}})
- // FIXME: wrong. very wrong. this should not be operating on the current context
- m_context->signIn();
-}
-
-
-void MainWindow::SignOutClicked() {
- qDebug() << "Sign Out";
- m_context->signOut();
-}
-
-void MainWindow::RefreshClicked() {
- qDebug() << "Refresh";
- m_context->silentSignIn();
-}
diff --git a/launcher/minecraft/auth-msa/mainwindow.h b/launcher/minecraft/auth-msa/mainwindow.h
deleted file mode 100644
index abde52d8..00000000
--- a/launcher/minecraft/auth-msa/mainwindow.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#pragma once
-
-#include <QMainWindow>
-#include <QScopedPointer>
-#include <QtNetwork>
-#include <katabasis/Bits.h>
-
-#include "context.h"
-
-namespace Ui {
-class MainWindow;
-}
-
-class MainWindow : public QMainWindow {
- Q_OBJECT
-
-public:
- explicit MainWindow(Context * context, QWidget *parent = nullptr);
- ~MainWindow() override;
-
-private slots:
- void SignInMojangClicked();
- void SignInMSAClicked();
-
- void SignOutClicked();
- void RefreshClicked();
-
- void ActivityChanged(Katabasis::Activity activity);
-
-private:
- Context* m_context;
- QScopedPointer<Ui::MainWindow> m_ui;
-};
-
diff --git a/launcher/minecraft/auth-msa/mainwindow.ui b/launcher/minecraft/auth-msa/mainwindow.ui
deleted file mode 100644
index 32b34128..00000000
--- a/launcher/minecraft/auth-msa/mainwindow.ui
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>MainWindow</class>
- <widget class="QMainWindow" name="MainWindow">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>1037</width>
- <height>511</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>SmartMapsClient</string>
- </property>
- <property name="dockNestingEnabled">
- <bool>true</bool>
- </property>
- <widget class="QWidget" name="centralWidget">
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="3">
- <widget class="QPushButton" name="signInButton_Mojang">
- <property name="text">
- <string>SignIn Mojang</string>
- </property>
- </widget>
- </item>
- <item row="3" column="3">
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="0" rowspan="7" colspan="3">
- <widget class="QTreeView" name="accountView"/>
- </item>
- <item row="5" column="3">
- <widget class="QPushButton" name="refreshButton">
- <property name="text">
- <string>Refresh</string>
- </property>
- </widget>
- </item>
- <item row="2" column="3">
- <widget class="QPushButton" name="signInButton_MSA">
- <property name="text">
- <string>SignIn MSA</string>
- </property>
- </widget>
- </item>
- <item row="6" column="3">
- <widget class="QPushButton" name="signOutButton">
- <property name="text">
- <string>SignOut</string>
- </property>
- </widget>
- </item>
- <item row="4" column="3">
- <widget class="QPushButton" name="makeActiveButton">
- <property name="text">
- <string>Make Active</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QStatusBar" name="statusBar"/>
- </widget>
- <resources/>
- <connections/>
-</ui>