aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2021-07-26 21:44:11 +0200
committerPetr Mrázek <peterix@gmail.com>2021-08-15 23:18:50 +0200
commit3a53349e332599221bc325f7fac9dc7927194bc2 (patch)
tree2ee40fa6044c241b3b7db27fe0b83931b453c2b2 /launcher/minecraft
parentfca2e9e44cb44004eec7f47c03b186bd5e44dc32 (diff)
downloadPrismLauncher-3a53349e332599221bc325f7fac9dc7927194bc2.tar.gz
PrismLauncher-3a53349e332599221bc325f7fac9dc7927194bc2.tar.bz2
PrismLauncher-3a53349e332599221bc325f7fac9dc7927194bc2.zip
GH-3392 dirty initial MSA support that shares logic with Mojang flows
Both act as the first step of AuthContext.
Diffstat (limited to 'launcher/minecraft')
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp16
-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/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
-rw-r--r--launcher/minecraft/auth/AccountData.cpp387
-rw-r--r--launcher/minecraft/auth/AccountData.h73
-rw-r--r--launcher/minecraft/auth/AccountList.cpp (renamed from launcher/minecraft/auth/MojangAccountList.cpp)321
-rw-r--r--launcher/minecraft/auth/AccountList.h118
-rw-r--r--launcher/minecraft/auth/AccountTask.cpp69
-rw-r--r--launcher/minecraft/auth/AccountTask.h (renamed from launcher/minecraft/auth/YggdrasilTask.h)78
-rw-r--r--launcher/minecraft/auth/AuthSession.cpp2
-rw-r--r--launcher/minecraft/auth/AuthSession.h13
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp303
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.h (renamed from launcher/minecraft/auth/MojangAccount.h)120
-rw-r--r--launcher/minecraft/auth/MojangAccount.cpp315
-rw-r--r--launcher/minecraft/auth/MojangAccountList.h199
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.cpp (renamed from launcher/minecraft/auth-msa/context.cpp)508
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.h (renamed from launcher/minecraft/auth-msa/context.h)108
-rw-r--r--launcher/minecraft/auth/flows/AuthenticateTask.cpp202
-rw-r--r--launcher/minecraft/auth/flows/AuthenticateTask.h46
-rw-r--r--launcher/minecraft/auth/flows/MSAHelper.txt51
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.cpp20
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.h10
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.cpp16
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.h10
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.cpp14
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.h13
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.cpp14
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.h10
-rw-r--r--launcher/minecraft/auth/flows/RefreshTask.cpp144
-rw-r--r--launcher/minecraft/auth/flows/RefreshTask.h44
-rw-r--r--launcher/minecraft/auth/flows/ValidateTask.cpp61
-rw-r--r--launcher/minecraft/auth/flows/ValidateTask.h47
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.cpp (renamed from launcher/minecraft/auth/YggdrasilTask.cpp)224
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.h82
-rw-r--r--launcher/minecraft/launch/ClaimAccount.h4
40 files changed, 1810 insertions, 2183 deletions
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index dbf9f816..5f3c7244 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -423,7 +423,7 @@ QStringList MinecraftInstance::processMinecraftArgs(
// yggdrasil!
if(session)
{
- token_mapping["auth_username"] = session->username;
+ // token_mapping["auth_username"] = session->username;
token_mapping["auth_session"] = session->session;
token_mapping["auth_access_token"] = session->access_token;
token_mapping["auth_player_name"] = session->player_name;
@@ -691,19 +691,11 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
addToFilter(sessionRef.session, tr("<SESSION ID>"));
}
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
- addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
+ if(sessionRef.client_token.size()) {
+ addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
+ }
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
- auto i = sessionRef.u.properties.begin();
- while (i != sessionRef.u.properties.end())
- {
- if(i.value().length() <= 3) {
- ++i;
- continue;
- }
- addToFilter(i.value(), "<" + i.key().toUpper() + ">");
- ++i;
- }
return filter;
}
diff --git a/launcher/minecraft/auth-msa/BuildConfig.cpp.in b/launcher/minecraft/auth-msa/BuildConfig.cpp.in
deleted file mode 100644
index 8f470e25..00000000
--- a/launcher/minecraft/auth-msa/BuildConfig.cpp.in
+++ /dev/null
@@ -1,9 +0,0 @@
-#include "BuildConfig.h"
-#include <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/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>
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
new file mode 100644
index 00000000..77c73c1b
--- /dev/null
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -0,0 +1,387 @@
+#include "AccountData.h"
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QDebug>
+#include <QUuid>
+
+namespace {
+void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
+ if(!t.persistent) {
+ return;
+ }
+ QJsonObject out;
+ if(t.issueInstant.isValid()) {
+ out["iat"] = QJsonValue(t.issueInstant.toMSecsSinceEpoch() / 1000);
+ }
+
+ if(t.notAfter.isValid()) {
+ out["exp"] = QJsonValue(t.notAfter.toMSecsSinceEpoch() / 1000);
+ }
+
+ bool save = false;
+ if(!t.token.isEmpty()) {
+ out["token"] = QJsonValue(t.token);
+ save = true;
+ }
+ if(!t.refresh_token.isEmpty()) {
+ out["refresh_token"] = QJsonValue(t.refresh_token);
+ save = true;
+ }
+ if(t.extra.size()) {
+ out["extra"] = QJsonObject::fromVariantMap(t.extra);
+ save = true;
+ }
+ if(save) {
+ parent[tokenName] = out;
+ }
+}
+
+Katabasis::Token tokenFromJSONV3(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::fromMSecsSinceEpoch(((int64_t) issueInstant.toDouble()) * 1000);
+ }
+
+ auto notAfter = tokenObject.value("exp");
+ if(notAfter.isDouble()) {
+ out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t) notAfter.toDouble()) * 1000);
+ }
+
+ 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 profileToJSONV3(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 profileFromJSONV3(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 AccountData::resumeStateFromV2(QJsonObject data) {
+ // The JSON object must at least have a username for it to be valid.
+ if (!data.value("username").isString())
+ {
+ qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
+ return false;
+ }
+
+ QString userName = data.value("username").toString("");
+ QString clientToken = data.value("clientToken").toString("");
+ QString accessToken = data.value("accessToken").toString("");
+
+ QJsonArray profileArray = data.value("profiles").toArray();
+ if (profileArray.size() < 1)
+ {
+ qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
+ return false;
+ }
+
+ struct AccountProfile
+ {
+ QString id;
+ QString name;
+ bool legacy;
+ };
+
+ QList<AccountProfile> profiles;
+ int currentProfileIndex = 0;
+ int index = -1;
+ QString currentProfile = data.value("activeProfile").toString("");
+ for (QJsonValue profileVal : profileArray)
+ {
+ index++;
+ QJsonObject profileObject = profileVal.toObject();
+ QString id = profileObject.value("id").toString("");
+ QString name = profileObject.value("name").toString("");
+ bool legacy = profileObject.value("legacy").toBool(false);
+ if (id.isEmpty() || name.isEmpty())
+ {
+ qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
+ continue;
+ }
+ if(id == currentProfile) {
+ currentProfileIndex = index;
+ }
+ profiles.append({id, name, legacy});
+ }
+ auto & profile = profiles[currentProfileIndex];
+
+ type = AccountType::Mojang;
+ legacy = profile.legacy;
+
+ minecraftProfile.id = profile.id;
+ minecraftProfile.name = profile.name;
+ minecraftProfile.validity = Katabasis::Validity::Assumed;
+
+ yggdrasilToken.token = accessToken;
+ yggdrasilToken.extra["clientToken"] = clientToken;
+ yggdrasilToken.extra["userName"] = userName;
+ yggdrasilToken.validity = Katabasis::Validity::Assumed;
+
+ validity_ = minecraftProfile.validity;
+ return true;
+}
+
+bool AccountData::resumeStateFromV3(QJsonObject data) {
+ auto typeV = data.value("type")