Merge branch 'develop' into feat/acknowledge_release_type
Signed-off-by: Alexandru Ionut Tripon <alexandru.tripon97@gmail.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -34,87 +34,90 @@
|
||||
*/
|
||||
|
||||
#include "AccountData.h"
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
#include <QUuid>
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
|
||||
namespace {
|
||||
void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
|
||||
if(!t.persistent) {
|
||||
void tokenToJSONV3(QJsonObject& parent, Katabasis::Token t, const char* tokenName)
|
||||
{
|
||||
if (!t.persistent) {
|
||||
return;
|
||||
}
|
||||
QJsonObject out;
|
||||
if(t.issueInstant.isValid()) {
|
||||
if (t.issueInstant.isValid()) {
|
||||
out["iat"] = QJsonValue(t.issueInstant.toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
if(t.notAfter.isValid()) {
|
||||
if (t.notAfter.isValid()) {
|
||||
out["exp"] = QJsonValue(t.notAfter.toMSecsSinceEpoch() / 1000);
|
||||
}
|
||||
|
||||
bool save = false;
|
||||
if(!t.token.isEmpty()) {
|
||||
if (!t.token.isEmpty()) {
|
||||
out["token"] = QJsonValue(t.token);
|
||||
save = true;
|
||||
}
|
||||
if(!t.refresh_token.isEmpty()) {
|
||||
if (!t.refresh_token.isEmpty()) {
|
||||
out["refresh_token"] = QJsonValue(t.refresh_token);
|
||||
save = true;
|
||||
}
|
||||
if(t.extra.size()) {
|
||||
if (t.extra.size()) {
|
||||
out["extra"] = QJsonObject::fromVariantMap(t.extra);
|
||||
save = true;
|
||||
}
|
||||
if(save) {
|
||||
if (save) {
|
||||
parent[tokenName] = out;
|
||||
}
|
||||
}
|
||||
|
||||
Katabasis::Token tokenFromJSONV3(const QJsonObject &parent, const char * tokenName) {
|
||||
Katabasis::Token tokenFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
||||
{
|
||||
Katabasis::Token out;
|
||||
auto tokenObject = parent.value(tokenName).toObject();
|
||||
if(tokenObject.isEmpty()) {
|
||||
if (tokenObject.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
auto issueInstant = tokenObject.value("iat");
|
||||
if(issueInstant.isDouble()) {
|
||||
out.issueInstant = QDateTime::fromMSecsSinceEpoch(((int64_t) issueInstant.toDouble()) * 1000);
|
||||
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);
|
||||
if (notAfter.isDouble()) {
|
||||
out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t)notAfter.toDouble()) * 1000);
|
||||
}
|
||||
|
||||
auto token = tokenObject.value("token");
|
||||
if(token.isString()) {
|
||||
if (token.isString()) {
|
||||
out.token = token.toString();
|
||||
out.validity = Katabasis::Validity::Assumed;
|
||||
}
|
||||
|
||||
auto refresh_token = tokenObject.value("refresh_token");
|
||||
if(refresh_token.isString()) {
|
||||
if (refresh_token.isString()) {
|
||||
out.refresh_token = refresh_token.toString();
|
||||
}
|
||||
|
||||
auto extra = tokenObject.value("extra");
|
||||
if(extra.isObject()) {
|
||||
if (extra.isObject()) {
|
||||
out.extra = extra.toObject().toVariantMap();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * tokenName) {
|
||||
if(p.id.isEmpty()) {
|
||||
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.isEmpty()) {
|
||||
if (!p.currentCape.isEmpty()) {
|
||||
out["cape"] = p.currentCape;
|
||||
}
|
||||
|
||||
@ -123,19 +126,19 @@ void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * token
|
||||
skinObj["id"] = p.skin.id;
|
||||
skinObj["url"] = p.skin.url;
|
||||
skinObj["variant"] = p.skin.variant;
|
||||
if(p.skin.data.size()) {
|
||||
if (p.skin.data.size()) {
|
||||
skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64());
|
||||
}
|
||||
out["skin"] = skinObj;
|
||||
}
|
||||
|
||||
QJsonArray capesArray;
|
||||
for(auto & cape: p.capes) {
|
||||
for (auto& cape : p.capes) {
|
||||
QJsonObject capeObj;
|
||||
capeObj["id"] = cape.id;
|
||||
capeObj["url"] = cape.url;
|
||||
capeObj["alias"] = cape.alias;
|
||||
if(cape.data.size()) {
|
||||
if (cape.data.size()) {
|
||||
capeObj["data"] = QString::fromLatin1(cape.data.toBase64());
|
||||
}
|
||||
capesArray.push_back(capeObj);
|
||||
@ -144,16 +147,17 @@ void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * token
|
||||
parent[tokenName] = out;
|
||||
}
|
||||
|
||||
MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * tokenName) {
|
||||
MinecraftProfile profileFromJSONV3(const QJsonObject& parent, const char* tokenName)
|
||||
{
|
||||
MinecraftProfile out;
|
||||
auto tokenObject = parent.value(tokenName).toObject();
|
||||
if(tokenObject.isEmpty()) {
|
||||
if (tokenObject.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
{
|
||||
auto idV = tokenObject.value("id");
|
||||
auto nameV = tokenObject.value("name");
|
||||
if(!idV.isString() || !nameV.isString()) {
|
||||
if (!idV.isString() || !nameV.isString()) {
|
||||
qWarning() << "mandatory profile attributes are missing or of unexpected type";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -163,7 +167,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
{
|
||||
auto skinV = tokenObject.value("skin");
|
||||
if(!skinV.isObject()) {
|
||||
if (!skinV.isObject()) {
|
||||
qWarning() << "skin is missing";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -171,7 +175,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
auto idV = skinObj.value("id");
|
||||
auto urlV = skinObj.value("url");
|
||||
auto variantV = skinObj.value("variant");
|
||||
if(!idV.isString() || !urlV.isString() || !variantV.isString()) {
|
||||
if (!idV.isString() || !urlV.isString() || !variantV.isString()) {
|
||||
qWarning() << "mandatory skin attributes are missing or of unexpected type";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -181,11 +185,10 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
// data for skin is optional
|
||||
auto dataV = skinObj.value("data");
|
||||
if(dataV.isString()) {
|
||||
if (dataV.isString()) {
|
||||
// TODO: validate base64
|
||||
out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1());
|
||||
}
|
||||
else if (!dataV.isUndefined()) {
|
||||
} else if (!dataV.isUndefined()) {
|
||||
qWarning() << "skin data is something unexpected";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -193,13 +196,13 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
{
|
||||
auto capesV = tokenObject.value("capes");
|
||||
if(!capesV.isArray()) {
|
||||
if (!capesV.isArray()) {
|
||||
qWarning() << "capes is not an array!";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
auto capesArray = capesV.toArray();
|
||||
for(auto capeV: capesArray) {
|
||||
if(!capeV.isObject()) {
|
||||
for (auto capeV : capesArray) {
|
||||
if (!capeV.isObject()) {
|
||||
qWarning() << "cape is not an object!";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -207,7 +210,7 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
auto idV = capeObj.value("id");
|
||||
auto urlV = capeObj.value("url");
|
||||
auto aliasV = capeObj.value("alias");
|
||||
if(!idV.isString() || !urlV.isString() || !aliasV.isString()) {
|
||||
if (!idV.isString() || !urlV.isString() || !aliasV.isString()) {
|
||||
qWarning() << "mandatory skin attributes are missing or of unexpected type";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -218,11 +221,10 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
|
||||
// data for cape is optional.
|
||||
auto dataV = capeObj.value("data");
|
||||
if(dataV.isString()) {
|
||||
if (dataV.isString()) {
|
||||
// TODO: validate base64
|
||||
cape.data = QByteArray::fromBase64(dataV.toString().toLatin1());
|
||||
}
|
||||
else if (!dataV.isUndefined()) {
|
||||
} else if (!dataV.isUndefined()) {
|
||||
qWarning() << "cape data is something unexpected";
|
||||
return MinecraftProfile();
|
||||
}
|
||||
@ -232,9 +234,9 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
// current cape
|
||||
{
|
||||
auto capeV = tokenObject.value("cape");
|
||||
if(capeV.isString()) {
|
||||
if (capeV.isString()) {
|
||||
auto currentCape = capeV.toString();
|
||||
if(out.capes.contains(currentCape)) {
|
||||
if (out.capes.contains(currentCape)) {
|
||||
out.currentCape = currentCape;
|
||||
}
|
||||
}
|
||||
@ -243,8 +245,9 @@ MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * token
|
||||
return out;
|
||||
}
|
||||
|
||||
void entitlementToJSONV3(QJsonObject &parent, MinecraftEntitlement p) {
|
||||
if(p.validity == Katabasis::Validity::None) {
|
||||
void entitlementToJSONV3(QJsonObject& parent, MinecraftEntitlement p)
|
||||
{
|
||||
if (p.validity == Katabasis::Validity::None) {
|
||||
return;
|
||||
}
|
||||
QJsonObject out;
|
||||
@ -253,15 +256,16 @@ void entitlementToJSONV3(QJsonObject &parent, MinecraftEntitlement p) {
|
||||
parent["entitlement"] = out;
|
||||
}
|
||||
|
||||
bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out) {
|
||||
bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
|
||||
{
|
||||
auto entitlementObject = parent.value("entitlement").toObject();
|
||||
if(entitlementObject.isEmpty()) {
|
||||
if (entitlementObject.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
auto ownsMinecraftV = entitlementObject.value("ownsMinecraft");
|
||||
auto canPlayMinecraftV = entitlementObject.value("canPlayMinecraft");
|
||||
if(!ownsMinecraftV.isBool() || !canPlayMinecraftV.isBool()) {
|
||||
if (!ownsMinecraftV.isBool() || !canPlayMinecraftV.isBool()) {
|
||||
qWarning() << "mandatory attributes are missing or of unexpected type";
|
||||
return false;
|
||||
}
|
||||
@ -272,12 +276,12 @@ bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & out
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
bool AccountData::resumeStateFromV2(QJsonObject data)
|
||||
{
|
||||
// The JSON object must at least have a username for it to be valid.
|
||||
if (!data.value("username").isString())
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -287,14 +291,12 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
QString accessToken = data.value("accessToken").toString("");
|
||||
|
||||
QJsonArray profileArray = data.value("profiles").toArray();
|
||||
if (profileArray.size() < 1)
|
||||
{
|
||||
if (profileArray.size() < 1) {
|
||||
qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
struct AccountProfile
|
||||
{
|
||||
struct AccountProfile {
|
||||
QString id;
|
||||
QString name;
|
||||
bool legacy;
|
||||
@ -304,24 +306,22 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
int currentProfileIndex = 0;
|
||||
int index = -1;
|
||||
QString currentProfile = data.value("activeProfile").toString("");
|
||||
for (QJsonValue profileVal : profileArray)
|
||||
{
|
||||
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())
|
||||
{
|
||||
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) {
|
||||
if (id == currentProfile) {
|
||||
currentProfileIndex = index;
|
||||
}
|
||||
profiles.append({id, name, legacy});
|
||||
profiles.append({ id, name, legacy });
|
||||
}
|
||||
auto & profile = profiles[currentProfileIndex];
|
||||
auto& profile = profiles[currentProfileIndex];
|
||||
|
||||
type = AccountType::Mojang;
|
||||
legacy = profile.legacy;
|
||||
@ -339,14 +339,15 @@ bool AccountData::resumeStateFromV2(QJsonObject data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
bool AccountData::resumeStateFromV3(QJsonObject data)
|
||||
{
|
||||
auto typeV = data.value("type");
|
||||
if(!typeV.isString()) {
|
||||
if (!typeV.isString()) {
|
||||
qWarning() << "Failed to parse account data: type is missing.";
|
||||
return false;
|
||||
}
|
||||
auto typeS = typeV.toString();
|
||||
if(typeS == "MSA") {
|
||||
if (typeS == "MSA") {
|
||||
type = AccountType::MSA;
|
||||
} else if (typeS == "Mojang") {
|
||||
type = AccountType::Mojang;
|
||||
@ -357,16 +358,16 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(type == AccountType::Mojang) {
|
||||
if (type == AccountType::Mojang) {
|
||||
legacy = data.value("legacy").toBool(false);
|
||||
canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
|
||||
}
|
||||
|
||||
if(type == AccountType::MSA) {
|
||||
if (type == AccountType::MSA) {
|
||||
auto clientIDV = data.value("msa-client-id");
|
||||
if (clientIDV.isString()) {
|
||||
msaClientID = clientIDV.toString();
|
||||
} // leave msaClientID empty if it doesn't exist or isn't a string
|
||||
} // leave msaClientID empty if it doesn't exist or isn't a string
|
||||
msaToken = tokenFromJSONV3(data, "msa");
|
||||
userToken = tokenFromJSONV3(data, "utoken");
|
||||
xboxApiToken = tokenFromJSONV3(data, "xrp-main");
|
||||
@ -374,9 +375,13 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
}
|
||||
|
||||
yggdrasilToken = tokenFromJSONV3(data, "ygg");
|
||||
// versions before 7.2 used "offline" as the offline token
|
||||
if (yggdrasilToken.token == "offline")
|
||||
yggdrasilToken.token = "0";
|
||||
|
||||
minecraftProfile = profileFromJSONV3(data, "profile");
|
||||
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||
if(minecraftProfile.validity != Katabasis::Validity::None) {
|
||||
if (!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||
if (minecraftProfile.validity != Katabasis::Validity::None) {
|
||||
minecraftEntitlement.canPlayMinecraft = true;
|
||||
minecraftEntitlement.ownsMinecraft = true;
|
||||
minecraftEntitlement.validity = Katabasis::Validity::Assumed;
|
||||
@ -387,26 +392,25 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject AccountData::saveState() const {
|
||||
QJsonObject AccountData::saveState() const
|
||||
{
|
||||
QJsonObject output;
|
||||
if(type == AccountType::Mojang) {
|
||||
if (type == AccountType::Mojang) {
|
||||
output["type"] = "Mojang";
|
||||
if(legacy) {
|
||||
if (legacy) {
|
||||
output["legacy"] = true;
|
||||
}
|
||||
if(canMigrateToMSA) {
|
||||
if (canMigrateToMSA) {
|
||||
output["canMigrateToMSA"] = true;
|
||||
}
|
||||
}
|
||||
else if (type == AccountType::MSA) {
|
||||
} else if (type == AccountType::MSA) {
|
||||
output["type"] = "MSA";
|
||||
output["msa-client-id"] = msaClientID;
|
||||
tokenToJSONV3(output, msaToken, "msa");
|
||||
tokenToJSONV3(output, userToken, "utoken");
|
||||
tokenToJSONV3(output, xboxApiToken, "xrp-main");
|
||||
tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
|
||||
}
|
||||
else if (type == AccountType::Offline) {
|
||||
} else if (type == AccountType::Offline) {
|
||||
output["type"] = "Offline";
|
||||
}
|
||||
|
||||
@ -416,60 +420,68 @@ QJsonObject AccountData::saveState() const {
|
||||
return output;
|
||||
}
|
||||
|
||||
QString AccountData::userName() const {
|
||||
if(type == AccountType::MSA) {
|
||||
QString AccountData::userName() const
|
||||
{
|
||||
if (type == AccountType::MSA) {
|
||||
return QString();
|
||||
}
|
||||
return yggdrasilToken.extra["userName"].toString();
|
||||
}
|
||||
|
||||
QString AccountData::accessToken() const {
|
||||
QString AccountData::accessToken() const
|
||||
{
|
||||
return yggdrasilToken.token;
|
||||
}
|
||||
|
||||
QString AccountData::clientToken() const {
|
||||
if(type != AccountType::Mojang) {
|
||||
QString AccountData::clientToken() const
|
||||
{
|
||||
if (type != AccountType::Mojang) {
|
||||
return QString();
|
||||
}
|
||||
return yggdrasilToken.extra["clientToken"].toString();
|
||||
}
|
||||
|
||||
void AccountData::setClientToken(QString clientToken) {
|
||||
if(type != AccountType::Mojang) {
|
||||
void AccountData::setClientToken(QString clientToken)
|
||||
{
|
||||
if (type != AccountType::Mojang) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = clientToken;
|
||||
}
|
||||
|
||||
void AccountData::generateClientTokenIfMissing() {
|
||||
if(yggdrasilToken.extra.contains("clientToken")) {
|
||||
void AccountData::generateClientTokenIfMissing()
|
||||
{
|
||||
if (yggdrasilToken.extra.contains("clientToken")) {
|
||||
return;
|
||||
}
|
||||
invalidateClientToken();
|
||||
}
|
||||
|
||||
void AccountData::invalidateClientToken() {
|
||||
if(type != AccountType::Mojang) {
|
||||
void AccountData::invalidateClientToken()
|
||||
{
|
||||
if (type != AccountType::Mojang) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
||||
}
|
||||
|
||||
QString AccountData::profileId() const {
|
||||
QString AccountData::profileId() const
|
||||
{
|
||||
return minecraftProfile.id;
|
||||
}
|
||||
|
||||
QString AccountData::profileName() const {
|
||||
if(minecraftProfile.name.size() == 0) {
|
||||
QString AccountData::profileName() const
|
||||
{
|
||||
if (minecraftProfile.name.size() == 0) {
|
||||
return QObject::tr("No profile (%1)").arg(accountDisplayString());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return minecraftProfile.name;
|
||||
}
|
||||
}
|
||||
|
||||
QString AccountData::accountDisplayString() const {
|
||||
switch(type) {
|
||||
QString AccountData::accountDisplayString() const
|
||||
{
|
||||
switch (type) {
|
||||
case AccountType::Mojang: {
|
||||
return userName();
|
||||
}
|
||||
@ -477,7 +489,7 @@ QString AccountData::accountDisplayString() const {
|
||||
return QObject::tr("<Offline>");
|
||||
}
|
||||
case AccountType::MSA: {
|
||||
if(xboxApiToken.extra.contains("gtg")) {
|
||||
if (xboxApiToken.extra.contains("gtg")) {
|
||||
return xboxApiToken.extra["gtg"].toString();
|
||||
}
|
||||
return "Xbox profile missing";
|
||||
@ -488,6 +500,7 @@ QString AccountData::accountDisplayString() const {
|
||||
}
|
||||
}
|
||||
|
||||
QString AccountData::lastError() const {
|
||||
QString AccountData::lastError() const
|
||||
{
|
||||
return errorString;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -34,11 +34,11 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QVector>
|
||||
#include <katabasis/Bits.h>
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
struct Skin {
|
||||
QString id;
|
||||
@ -71,22 +71,9 @@ struct MinecraftProfile {
|
||||
Katabasis::Validity validity = Katabasis::Validity::None;
|
||||
};
|
||||
|
||||
enum class AccountType {
|
||||
MSA,
|
||||
Mojang,
|
||||
Offline
|
||||
};
|
||||
enum class AccountType { MSA, Mojang, Offline };
|
||||
|
||||
enum class AccountState {
|
||||
Unchecked,
|
||||
Offline,
|
||||
Working,
|
||||
Online,
|
||||
Disabled,
|
||||
Errored,
|
||||
Expired,
|
||||
Gone
|
||||
};
|
||||
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
|
||||
|
||||
struct AccountData {
|
||||
QJsonObject saveState() const;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -37,14 +37,14 @@
|
||||
#include "AccountData.h"
|
||||
#include "AccountTask.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QIODevice>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QDebug>
|
||||
@ -54,12 +54,10 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
enum AccountListVersion {
|
||||
MojangOnly = 2,
|
||||
MojangMSA = 3
|
||||
};
|
||||
enum AccountListVersion { MojangOnly = 2, MojangMSA = 3 };
|
||||
|
||||
AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) {
|
||||
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
|
||||
{
|
||||
m_refreshTimer = new QTimer(this);
|
||||
m_refreshTimer->setSingleShot(true);
|
||||
connect(m_refreshTimer, &QTimer::timeout, this, &AccountList::fillQueue);
|
||||
@ -70,7 +68,8 @@ AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) {
|
||||
|
||||
AccountList::~AccountList() noexcept {}
|
||||
|
||||
int AccountList::findAccountByProfileId(const QString& profileId) const {
|
||||
int AccountList::findAccountByProfileId(const QString& profileId) const
|
||||
{
|
||||
for (int i = 0; i < count(); i++) {
|
||||
MinecraftAccountPtr account = at(i);
|
||||
if (account->profileId() == profileId) {
|
||||
@ -80,7 +79,8 @@ int AccountList::findAccountByProfileId(const QString& profileId) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr AccountList::getAccountByProfileName(const QString& profileName) const {
|
||||
MinecraftAccountPtr AccountList::getAccountByProfileName(const QString& profileName) const
|
||||
{
|
||||
for (int i = 0; i < count(); i++) {
|
||||
MinecraftAccountPtr account = at(i);
|
||||
if (account->profileName() == profileName) {
|
||||
@ -95,11 +95,12 @@ const MinecraftAccountPtr AccountList::at(int i) const
|
||||
return MinecraftAccountPtr(m_accounts.at(i));
|
||||
}
|
||||
|
||||
QStringList AccountList::profileNames() const {
|
||||
QStringList AccountList::profileNames() const
|
||||
{
|
||||
QStringList out;
|
||||
for(auto & account: m_accounts) {
|
||||
auto profileName = account->profileName();
|
||||
if(profileName.isEmpty()) {
|
||||
for (auto& account : m_accounts) {
|
||||
auto profileName = account->profileName();
|
||||
if (profileName.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
out.append(profileName);
|
||||
@ -122,14 +123,14 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
|
||||
|
||||
// override/replace existing account with the same profileId
|
||||
auto profileId = account->profileId();
|
||||
if(profileId.size()) {
|
||||
if (profileId.size()) {
|
||||
auto existingAccount = findAccountByProfileId(profileId);
|
||||
if(existingAccount != -1) {
|
||||
if (existingAccount != -1) {
|
||||
qDebug() << "Replacing old account with a new one with the same profile ID!";
|
||||
|
||||
MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount];
|
||||
m_accounts[existingAccount] = account;
|
||||
if(m_defaultAccount == existingAccountPtr) {
|
||||
if (m_defaultAccount == existingAccountPtr) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
// disconnect notifications for changes in the account being replaced
|
||||
@ -154,11 +155,9 @@ void AccountList::addAccount(const MinecraftAccountPtr account)
|
||||
void AccountList::removeAccount(QModelIndex index)
|
||||
{
|
||||
int row = index.row();
|
||||
if(index.isValid() && row >= 0 && row < m_accounts.size())
|
||||
{
|
||||
auto & account = m_accounts[row];
|
||||
if(account == m_defaultAccount)
|
||||
{
|
||||
if (index.isValid() && row >= 0 && row < m_accounts.size()) {
|
||||
auto& account = m_accounts[row];
|
||||
if (account == m_defaultAccount) {
|
||||
m_defaultAccount = nullptr;
|
||||
onDefaultAccountChanged();
|
||||
}
|
||||
@ -178,43 +177,34 @@ MinecraftAccountPtr AccountList::defaultAccount() const
|
||||
|
||||
void AccountList::setDefaultAccount(MinecraftAccountPtr newAccount)
|
||||
{
|
||||
if (!newAccount && m_defaultAccount)
|
||||
{
|
||||
if (!newAccount && m_defaultAccount) {
|
||||
int idx = 0;
|
||||
auto previousDefaultAccount = m_defaultAccount;
|
||||
m_defaultAccount = nullptr;
|
||||
for (MinecraftAccountPtr account : m_accounts)
|
||||
{
|
||||
if (account == previousDefaultAccount)
|
||||
{
|
||||
for (MinecraftAccountPtr account : m_accounts) {
|
||||
if (account == previousDefaultAccount) {
|
||||
emit dataChanged(index(idx), index(idx, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
idx ++;
|
||||
idx++;
|
||||
}
|
||||
onDefaultAccountChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
auto currentDefaultAccount = m_defaultAccount;
|
||||
int currentDefaultAccountIdx = -1;
|
||||
auto newDefaultAccount = m_defaultAccount;
|
||||
int newDefaultAccountIdx = -1;
|
||||
int idx = 0;
|
||||
for (MinecraftAccountPtr account : m_accounts)
|
||||
{
|
||||
if (account == newAccount)
|
||||
{
|
||||
for (MinecraftAccountPtr account : m_accounts) {
|
||||
if (account == newAccount) {
|
||||
newDefaultAccount = account;
|
||||
newDefaultAccountIdx = idx;
|
||||
}
|
||||
if(currentDefaultAccount == account)
|
||||
{
|
||||
if (currentDefaultAccount == account) {
|
||||
currentDefaultAccountIdx = idx;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
if(currentDefaultAccount != newDefaultAccount)
|
||||
{
|
||||
if (currentDefaultAccount != newDefaultAccount) {
|
||||
emit dataChanged(index(currentDefaultAccountIdx), index(currentDefaultAccountIdx, columnCount(QModelIndex()) - 1));
|
||||
emit dataChanged(index(newDefaultAccountIdx), index(newDefaultAccountIdx, columnCount(QModelIndex()) - 1));
|
||||
m_defaultAccount = newDefaultAccount;
|
||||
@ -231,27 +221,25 @@ void AccountList::accountChanged()
|
||||
|
||||
void AccountList::accountActivityChanged(bool active)
|
||||
{
|
||||
MinecraftAccount *account = qobject_cast<MinecraftAccount *>(sender());
|
||||
MinecraftAccount* account = qobject_cast<MinecraftAccount*>(sender());
|
||||
bool found = false;
|
||||
for (int i = 0; i < count(); i++) {
|
||||
if (at(i).get() == account) {
|
||||
emit dataChanged(index(i), index(i, columnCount(QModelIndex()) - 1));
|
||||
emit dataChanged(index(i), index(i, columnCount(QModelIndex()) - 1));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(found) {
|
||||
if (found) {
|
||||
emit listActivityChanged();
|
||||
if(active) {
|
||||
if (active) {
|
||||
beginActivity();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
endActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AccountList::onListChanged()
|
||||
{
|
||||
if (m_autosave)
|
||||
@ -274,7 +262,7 @@ int AccountList::count() const
|
||||
return m_accounts.count();
|
||||
}
|
||||
|
||||
QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@ -284,67 +272,67 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
|
||||
MinecraftAccountPtr account = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ProfileNameColumn: {
|
||||
return account->profileName();
|
||||
}
|
||||
switch (index.column()) {
|
||||
case ProfileNameColumn: {
|
||||
return account->profileName();
|
||||
}
|
||||
|
||||
case NameColumn:
|
||||
return account->accountDisplayString();
|
||||
case NameColumn:
|
||||
return account->accountDisplayString();
|
||||
|
||||
case TypeColumn: {
|
||||
auto typeStr = account->typeString();
|
||||
typeStr[0] = typeStr[0].toUpper();
|
||||
return typeStr;
|
||||
}
|
||||
case TypeColumn: {
|
||||
auto typeStr = account->typeString();
|
||||
typeStr[0] = typeStr[0].toUpper();
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
case StatusColumn: {
|
||||
switch(account->accountState()) {
|
||||
case AccountState::Unchecked: {
|
||||
return tr("Unchecked", "Account status");
|
||||
}
|
||||
case AccountState::Offline: {
|
||||
return tr("Offline", "Account status");
|
||||
}
|
||||
case AccountState::Online: {
|
||||
return tr("Ready", "Account status");
|
||||
}
|
||||
case AccountState::Working: {
|
||||
return tr("Working", "Account status");
|
||||
}
|
||||
case AccountState::Errored: {
|
||||
return tr("Errored", "Account status");
|
||||
}
|
||||
case AccountState::Expired: {
|
||||
return tr("Expired", "Account status");
|
||||
}
|
||||
case AccountState::Disabled: {
|
||||
return tr("Disabled", "Account status");
|
||||
}
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
case StatusColumn: {
|
||||
switch (account->accountState()) {
|
||||
case AccountState::Unchecked: {
|
||||
return tr("Unchecked", "Account status");
|
||||
}
|
||||
case AccountState::Offline: {
|
||||
return tr("Offline", "Account status");
|
||||
}
|
||||
case AccountState::Online: {
|
||||
return tr("Ready", "Account status");
|
||||
}
|
||||
case AccountState::Working: {
|
||||
return tr("Working", "Account status");
|
||||
}
|
||||
case AccountState::Errored: {
|
||||
return tr("Errored", "Account status");
|
||||
}
|
||||
case AccountState::Expired: {
|
||||
return tr("Expired", "Account status");
|
||||
}
|
||||
case AccountState::Disabled: {
|
||||
return tr("Disabled", "Account status");
|
||||
}
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case MigrationColumn: {
|
||||
if(account->isMSA() || account->isOffline()) {
|
||||
return tr("N/A", "Can Migrate");
|
||||
case MigrationColumn: {
|
||||
if (account->isMSA() || account->isOffline()) {
|
||||
return tr("N/A", "Can Migrate");
|
||||
}
|
||||
if (account->canMigrate()) {
|
||||
return tr("Yes", "Can Migrate");
|
||||
} else {
|
||||
return tr("No", "Can Migrate");
|
||||
}
|
||||
}
|
||||
if (account->canMigrate()) {
|
||||
return tr("Yes", "Can Migrate");
|
||||
}
|
||||
else {
|
||||
return tr("No", "Can Migrate");
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
@ -354,10 +342,10 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(account);
|
||||
|
||||
case Qt::CheckStateRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
if (index.column() == ProfileNameColumn) {
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
@ -367,79 +355,72 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
|
||||
QVariant AccountList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return tr("Username");
|
||||
case NameColumn:
|
||||
return tr("Account");
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
case StatusColumn:
|
||||
return tr("Status");
|
||||
case MigrationColumn:
|
||||
return tr("Can Migrate?");
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case ProfileNameColumn:
|
||||
return tr("Username");
|
||||
case NameColumn:
|
||||
return tr("Account");
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
case StatusColumn:
|
||||
return tr("Status");
|
||||
case MigrationColumn:
|
||||
return tr("Can Migrate?");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section) {
|
||||
case ProfileNameColumn:
|
||||
return tr("Minecraft username associated with the account.");
|
||||
case NameColumn:
|
||||
return tr("User name of the account.");
|
||||
case TypeColumn:
|
||||
return tr("Type of the account - Mojang or MSA.");
|
||||
case StatusColumn:
|
||||
return tr("Current status of the account.");
|
||||
case MigrationColumn:
|
||||
return tr("Can this account migrate to a Microsoft account?");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return tr("Minecraft username associated with the account.");
|
||||
case NameColumn:
|
||||
return tr("User name of the account.");
|
||||
case TypeColumn:
|
||||
return tr("Type of the account - Mojang or MSA.");
|
||||
case StatusColumn:
|
||||
return tr("Current status of the account.");
|
||||
case MigrationColumn:
|
||||
return tr("Can this account migrate to a Microsoft account?");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int AccountList::rowCount(const QModelIndex &parent) const
|
||||
int AccountList::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
// Return count
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int AccountList::columnCount(const QModelIndex &parent) const
|
||||
int AccountList::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
bool AccountList::setData(const QModelIndex &idx, const QVariant &value, int role)
|
||||
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
|
||||
{
|
||||
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid())
|
||||
{
|
||||
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(role == Qt::CheckStateRole)
|
||||
{
|
||||
if(value == Qt::Checked)
|
||||
{
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if (value == Qt::Checked) {
|
||||
MinecraftAccountPtr account = at(idx.row());
|
||||
setDefaultAccount(account);
|
||||
}
|
||||
@ -451,8 +432,7 @@ bool AccountList::setData(const QModelIndex &idx, const QVariant &value, int rol
|
||||
|
||||
bool AccountList::loadList()
|
||||
{
|
||||
if (m_listFilePath.isEmpty())
|
||||
{
|
||||
if (m_listFilePath.isEmpty()) {
|
||||
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
|
||||
return false;
|
||||
}
|
||||
@ -461,8 +441,7 @@ bool AccountList::loadList()
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
|
||||
return false;
|
||||
}
|
||||
@ -475,17 +454,15 @@ bool AccountList::loadList()
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
|
||||
|
||||
// Fail if the JSON is invalid.
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qCritical() << QString("Failed to parse account list file: %1 at offset %2")
|
||||
.arg(parseError.errorString(), QString::number(parseError.offset))
|
||||
.toUtf8();
|
||||
.arg(parseError.errorString(), QString::number(parseError.offset))
|
||||
.toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the root is an object.
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
if (!jsonDoc.isObject()) {
|
||||
qCritical() << "Invalid account list JSON: Root should be an array.";
|
||||
return false;
|
||||
}
|
||||
@ -494,15 +471,13 @@ bool AccountList::loadList()
|
||||
|
||||
// Make sure the format version matches.
|
||||
auto listVersion = root.value("formatVersion").toVariant().toInt();
|
||||
switch(listVersion) {
|
||||
switch (listVersion) {
|
||||
case AccountListVersion::MojangOnly: {
|
||||
return loadV2(root);
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountListVersion::MojangMSA: {
|
||||
return loadV3(root);
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
default: {
|
||||
QString newName = "accounts-old.json";
|
||||
qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
|
||||
@ -513,21 +488,20 @@ bool AccountList::loadList()
|
||||
}
|
||||
}
|
||||
|
||||
bool AccountList::loadV2(QJsonObject& root) {
|
||||
bool AccountList::loadV2(QJsonObject& root)
|
||||
{
|
||||
beginResetModel();
|
||||
auto defaultUserName = root.value("activeAccount").toString("");
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
for (QJsonValue accountVal : accounts)
|
||||
{
|
||||
for (QJsonValue accountVal : accounts) {
|
||||
QJsonObject accountObj = accountVal.toObject();
|
||||
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
|
||||
if (account.get() != nullptr)
|
||||
{
|
||||
if (account.get() != nullptr) {
|
||||
auto profileId = account->profileId();
|
||||
if(!profileId.size()) {
|
||||
if (!profileId.size()) {
|
||||
continue;
|
||||
}
|
||||
if(findAccountByProfileId(profileId) != -1) {
|
||||
if (findAccountByProfileId(profileId) != -1) {
|
||||
continue;
|
||||
}
|
||||
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
|
||||
@ -536,9 +510,7 @@ bool AccountList::loadV2(QJsonObject& root) {
|
||||
if (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to load an account.";
|
||||
}
|
||||
}
|
||||
@ -546,30 +518,27 @@ bool AccountList::loadV2(QJsonObject& root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountList::loadV3(QJsonObject& root) {
|
||||
bool AccountList::loadV3(QJsonObject& root)
|
||||
{
|
||||
beginResetModel();
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
for (QJsonValue accountVal : accounts)
|
||||
{
|
||||
for (QJsonValue accountVal : accounts) {
|
||||
QJsonObject accountObj = accountVal.toObject();
|
||||
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV3(accountObj);
|
||||
if (account.get() != nullptr)
|
||||
{
|
||||
if (account.get() != nullptr) {
|
||||
auto profileId = account->profileId();
|
||||
if(profileId.size()) {
|
||||
if(findAccountByProfileId(profileId) != -1) {
|
||||
if (profileId.size()) {
|
||||
if (findAccountByProfileId(profileId) != -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
|
||||
connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
|
||||
m_accounts.append(account);
|
||||
if(accountObj.value("active").toBool(false)) {
|
||||
if (accountObj.value("active").toBool(false)) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qWarning() << "Failed to load an account.";
|
||||
}
|
||||
}
|
||||
@ -577,23 +546,20 @@ bool AccountList::loadV3(QJsonObject& root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool AccountList::saveList()
|
||||
{
|
||||
if (m_listFilePath.isEmpty())
|
||||
{
|
||||
if (m_listFilePath.isEmpty()) {
|
||||
qCritical() << "Can't save Mojang account list. No file path given and no default set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the parent folder exists
|
||||
if(!FS::ensureFilePathExists(m_listFilePath))
|
||||
if (!FS::ensureFilePathExists(m_listFilePath))
|
||||
return false;
|
||||
|
||||
// make sure the file wasn't overwritten with a folder before (fixes a bug)
|
||||
QFileInfo finfo(m_listFilePath);
|
||||
if(finfo.isDir())
|
||||
{
|
||||
if (finfo.isDir()) {
|
||||
QDir badDir(m_listFilePath);
|
||||
badDir.removeRecursively();
|
||||
}
|
||||
@ -609,10 +575,9 @@ bool AccountList::saveList()
|
||||
// Build a list of accounts.
|
||||
qDebug() << "Building account array.";
|
||||
QJsonArray accounts;
|
||||
for (MinecraftAccountPtr account : m_accounts)
|
||||
{
|
||||
for (MinecraftAccountPtr account : m_accounts) {
|
||||
QJsonObject accountObj = account->saveToJson();
|
||||
if(m_defaultAccount == account) {
|
||||
if (m_defaultAccount == account) {
|
||||
accountObj["active"] = true;
|
||||
}
|
||||
accounts.append(accountObj);
|
||||
@ -630,20 +595,18 @@ bool AccountList::saveList()
|
||||
|
||||
// Try to open the file and fail if we can't.
|
||||
// TODO: We should probably report this error to the user.
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the JSON to the file.
|
||||
file.write(doc.toJson());
|
||||
file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser);
|
||||
if(file.commit()) {
|
||||
file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
||||
if (file.commit()) {
|
||||
qDebug() << "Saved account list to" << m_listFilePath;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
qDebug() << "Failed to save accounts to" << m_listFilePath;
|
||||
return false;
|
||||
}
|
||||
@ -657,30 +620,29 @@ void AccountList::setListFilePath(QString path, bool autosave)
|
||||
|
||||
bool AccountList::anyAccountIsValid()
|
||||
{
|
||||
for(auto account: m_accounts)
|
||||
{
|
||||
if(account->ownsMinecraft()) {
|
||||
for (auto account : m_accounts) {
|
||||
if (account->ownsMinecraft()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AccountList::fillQueue() {
|
||||
|
||||
if(m_defaultAccount && m_defaultAccount->shouldRefresh()) {
|
||||
void AccountList::fillQueue()
|
||||
{
|
||||
if (m_defaultAccount && m_defaultAccount->shouldRefresh()) {
|
||||
auto idToRefresh = m_defaultAccount->internalId();
|
||||
m_refreshQueue.push_back(idToRefresh);
|
||||
qDebug() << "AccountList: Queued default account with internal ID " << idToRefresh << " to refresh first";
|
||||
}
|
||||
|
||||
for(int i = 0; i < count(); i++) {
|
||||
for (int i = 0; i < count(); i++) {
|
||||
auto account = at(i);
|
||||
if(account == m_defaultAccount) {
|
||||
if (account == m_defaultAccount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(account->shouldRefresh()) {
|
||||
if (account->shouldRefresh()) {
|
||||
auto idToRefresh = account->internalId();
|
||||
queueRefresh(idToRefresh);
|
||||
}
|
||||
@ -688,40 +650,43 @@ void AccountList::fillQueue() {
|
||||
tryNext();
|
||||
}
|
||||
|
||||
void AccountList::requestRefresh(QString accountId) {
|
||||
void AccountList::requestRefresh(QString accountId)
|
||||
{
|
||||
auto index = m_refreshQueue.indexOf(accountId);
|
||||
if(index != -1) {
|
||||
if (index != -1) {
|
||||
m_refreshQueue.removeAt(index);
|
||||
}
|
||||
m_refreshQueue.push_front(accountId);
|
||||
qDebug() << "AccountList: Pushed account with internal ID " << accountId << " to the front of the queue";
|
||||
if(!isActive()) {
|
||||
if (!isActive()) {
|
||||
tryNext();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountList::queueRefresh(QString accountId) {
|
||||
if(m_refreshQueue.indexOf(accountId) != -1) {
|
||||
void AccountList::queueRefresh(QString accountId)
|
||||
{
|
||||
if (m_refreshQueue.indexOf(accountId) != -1) {
|
||||
return;
|
||||
}
|
||||
m_refreshQueue.push_back(accountId);
|
||||
qDebug() << "AccountList: Queued account with internal ID " << accountId << " to refresh";
|
||||
}
|
||||
|
||||
|
||||
void AccountList::tryNext() {
|
||||
void AccountList::tryNext()
|
||||
{
|
||||
while (m_refreshQueue.length()) {
|
||||
auto accountId = m_refreshQueue.front();
|
||||
m_refreshQueue.pop_front();
|
||||
for(int i = 0; i < count(); i++) {
|
||||
for (int i = 0; i < count(); i++) {
|
||||
auto account = at(i);
|
||||
if(account->internalId() == accountId) {
|
||||
if (account->internalId() == accountId) {
|
||||
m_currentTask = account->refresh();
|
||||
if(m_currentTask) {
|
||||
if (m_currentTask) {
|
||||
connect(m_currentTask.get(), &AccountTask::succeeded, this, &AccountList::authSucceeded);
|
||||
connect(m_currentTask.get(), &AccountTask::failed, this, &AccountList::authFailed);
|
||||
m_currentTask->start();
|
||||
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID " << accountId;
|
||||
qDebug() << "RefreshSchedule: Processing account " << account->accountDisplayString() << " with internal ID "
|
||||
<< accountId;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -732,38 +697,43 @@ void AccountList::tryNext() {
|
||||
m_refreshTimer->start(1000 * 3600);
|
||||
}
|
||||
|
||||
void AccountList::authSucceeded() {
|
||||
void AccountList::authSucceeded()
|
||||
{
|
||||
qDebug() << "RefreshSchedule: Background account refresh succeeded";
|
||||
m_currentTask.reset();
|
||||
m_nextTimer->start(1000 * 20);
|
||||
}
|
||||
|
||||
void AccountList::authFailed(QString reason) {
|
||||
void AccountList::authFailed(QString reason)
|
||||
{
|
||||
qDebug() << "RefreshSchedule: Background account refresh failed: " << reason;
|
||||
m_currentTask.reset();
|
||||
m_nextTimer->start(1000 * 20);
|
||||
}
|
||||
|
||||
bool AccountList::isActive() const {
|
||||
bool AccountList::isActive() const
|
||||
{
|
||||
return m_activityCount != 0;
|
||||
}
|
||||
|
||||
void AccountList::beginActivity() {
|
||||
void AccountList::beginActivity()
|
||||
{
|
||||
bool activating = m_activityCount == 0;
|
||||
m_activityCount++;
|
||||
if(activating) {
|
||||
if (activating) {
|
||||
emit activityChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountList::endActivity() {
|
||||
if(m_activityCount == 0) {
|
||||
void AccountList::endActivity()
|
||||
{
|
||||
if (m_activityCount == 0) {
|
||||
qWarning() << m_name << " - Activity count would become below zero";
|
||||
return;
|
||||
}
|
||||
bool deactivating = m_activityCount == 1;
|
||||
m_activityCount--;
|
||||
if(deactivating) {
|
||||
if (deactivating) {
|
||||
emit activityChanged(false);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -37,26 +37,21 @@
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QVariant>
|
||||
|
||||
/*!
|
||||
* List of available Mojang accounts.
|
||||
* This should be loaded in the background by Prism Launcher on startup.
|
||||
*/
|
||||
class AccountList : public QAbstractListModel
|
||||
{
|
||||
class AccountList : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
PointerRole = 0x34B1CB48
|
||||
};
|
||||
public:
|
||||
enum ModelRoles { PointerRole = 0x34B1CB48 };
|
||||
|
||||
enum VListColumns
|
||||
{
|
||||
enum VListColumns {
|
||||
// TODO: Add icon column.
|
||||
ProfileNameColumn = 0,
|
||||
NameColumn,
|
||||
@ -67,24 +62,24 @@ public:
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
explicit AccountList(QObject *parent = 0);
|
||||
explicit AccountList(QObject* parent = 0);
|
||||
virtual ~AccountList() noexcept;
|
||||
|
||||
const MinecraftAccountPtr at(int i) const;
|
||||
int count() const;
|
||||
|
||||
//////// List Model Functions ////////
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
virtual int rowCount(const QModelIndex &parent) const override;
|
||||
virtual int columnCount(const QModelIndex &parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
virtual int rowCount(const QModelIndex& parent) const override;
|
||||
virtual int columnCount(const QModelIndex& parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
||||
|
||||
void addAccount(const MinecraftAccountPtr account);
|
||||
void removeAccount(QModelIndex index);
|
||||
int findAccountByProfileId(const QString &profileId) const;
|
||||
MinecraftAccountPtr getAccountByProfileName(const QString &profileName) const;
|
||||
int findAccountByProfileId(const QString& profileId) const;
|
||||
MinecraftAccountPtr getAccountByProfileName(const QString& profileName) const;
|
||||
QStringList profileNames() const;
|
||||
|
||||
// requesting a refresh pushes it to the front of the queue
|
||||
@ -102,8 +97,8 @@ public:
|
||||
void setListFilePath(QString path, bool autosave = false);
|
||||
|
||||
bool loadList();
|
||||
bool loadV2(QJsonObject &root);
|
||||
bool loadV3(QJsonObject &root);
|
||||
bool loadV2(QJsonObject& root);
|
||||
bool loadV3(QJsonObject& root);
|
||||
bool saveList();
|
||||
|
||||
MinecraftAccountPtr defaultAccount() const;
|
||||
@ -112,20 +107,20 @@ public:
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void beginActivity();
|
||||
void endActivity();
|
||||
|
||||
private:
|
||||
private:
|
||||
const char* m_name;
|
||||
uint32_t m_activityCount = 0;
|
||||
signals:
|
||||
signals:
|
||||
void listChanged();
|
||||
void listActivityChanged();
|
||||
void defaultAccountChanged();
|
||||
void activityChanged(bool active);
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
/**
|
||||
* This is called when one of the accounts changes and the list needs to be updated
|
||||
*/
|
||||
@ -141,16 +136,16 @@ public slots:
|
||||
*/
|
||||
void fillQueue();
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void tryNext();
|
||||
|
||||
void authSucceeded();
|
||||
void authFailed(QString reason);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
QList<QString> m_refreshQueue;
|
||||
QTimer *m_refreshTimer;
|
||||
QTimer *m_nextTimer;
|
||||
QTimer* m_refreshTimer;
|
||||
QTimer* m_nextTimer;
|
||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
||||
|
||||
/*!
|
||||
@ -178,4 +173,3 @@ protected:
|
||||
*/
|
||||
bool m_autosave = false;
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -36,43 +36,41 @@
|
||||
#include "AccountTask.h"
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
AccountTask::AccountTask(AccountData *data, QObject *parent)
|
||||
: Task(parent), m_data(data)
|
||||
AccountTask::AccountTask(AccountData* data, QObject* parent) : Task(parent), m_data(data)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_CREATED);
|
||||
}
|
||||
|
||||
QString AccountTask::getStateMessage() const
|
||||
{
|
||||
switch (m_taskState)
|
||||
{
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
return "Waiting...";
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
return tr("Sending request to auth servers...");
|
||||
case AccountTaskState::STATE_SUCCEEDED:
|
||||
return tr("Authentication task succeeded.");
|
||||
case AccountTaskState::STATE_OFFLINE:
|
||||
return tr("Failed to contact the authentication server.");
|
||||
case AccountTaskState::STATE_DISABLED:
|
||||
return tr("Client ID has changed. New session needs to be created.");
|
||||
case AccountTaskState::STATE_FAILED_SOFT:
|
||||
return tr("Encountered an error during authentication.");
|
||||
case AccountTaskState::STATE_FAILED_HARD:
|
||||
return tr("Failed to authenticate. The session has expired.");
|
||||
case AccountTaskState::STATE_FAILED_GONE:
|
||||
return tr("Failed to authenticate. The account no longer exists.");
|
||||
default:
|
||||
return tr("...");
|
||||
switch (m_taskState) {
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
return "Waiting...";
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
return tr("Sending request to auth servers...");
|
||||
case AccountTaskState::STATE_SUCCEEDED:
|
||||
return tr("Authentication task succeeded.");
|
||||
case AccountTaskState::STATE_OFFLINE:
|
||||
return tr("Failed to contact the authentication server.");
|
||||
case AccountTaskState::STATE_DISABLED:
|
||||
return tr("Client ID has changed. New session needs to be created.");
|
||||
case AccountTaskState::STATE_FAILED_SOFT:
|
||||
return tr("Encountered an error during authentication.");
|
||||
case AccountTaskState::STATE_FAILED_HARD:
|
||||
return tr("Failed to authenticate. The session has expired.");
|
||||
case AccountTaskState::STATE_FAILED_GONE:
|
||||
return tr("Failed to authenticate. The account no longer exists.");
|
||||
default:
|
||||
return tr("...");
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +80,7 @@ bool AccountTask::changeState(AccountTaskState newState, QString reason)
|
||||
// FIXME: virtual method invoked in constructor.
|
||||
// We want that behavior, but maybe make it less weird?
|
||||
setStatus(getStateMessage());
|
||||
switch(newState) {
|
||||
switch (newState) {
|
||||
case AccountTaskState::STATE_CREATED: {
|
||||
m_data->errorString.clear();
|
||||
return true;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -37,10 +37,10 @@
|
||||
|
||||
#include <tasks/Task.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <qsslerror.h>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
@ -50,37 +50,32 @@ class QNetworkReply;
|
||||
* Enum for describing the state of the current task.
|
||||
* Used by the getStateMessage function to determine what the status message should be.
|
||||
*/
|
||||
enum class AccountTaskState
|
||||
{
|
||||
enum class AccountTaskState {
|
||||
STATE_CREATED,
|
||||
STATE_WORKING,
|
||||
STATE_SUCCEEDED,
|
||||
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
||||
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
||||
};
|
||||
|
||||
class AccountTask : public Task
|
||||
{
|
||||
class AccountTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AccountTask(AccountData * data, QObject *parent = 0);
|
||||
virtual ~AccountTask() {};
|
||||
public:
|
||||
explicit AccountTask(AccountData* data, QObject* parent = 0);
|
||||
virtual ~AccountTask(){};
|
||||
|
||||
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
||||
|
||||
AccountTaskState taskState() {
|
||||
return m_taskState;
|
||||
}
|
||||
AccountTaskState taskState() { return m_taskState; }
|
||||
|
||||
signals:
|
||||
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
|
||||
signals:
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
protected:
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns the state message for the given state.
|
||||
* Used to set the status message for the task.
|
||||
@ -88,10 +83,10 @@ protected:
|
||||
*/
|
||||
virtual QString getStateMessage() const;
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
// NOTE: true -> non-terminal state, false -> terminal state
|
||||
bool changeState(AccountTaskState newState, QString reason = QString());
|
||||
|
||||
protected:
|
||||
AccountData *m_data = nullptr;
|
||||
protected:
|
||||
AccountData* m_data = nullptr;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -35,44 +35,44 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QBuffer>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AuthRequest.h"
|
||||
#include "katabasis/Globals.h"
|
||||
|
||||
AuthRequest::AuthRequest(QObject *parent): QObject(parent) {
|
||||
}
|
||||
AuthRequest::AuthRequest(QObject* parent) : QObject(parent) {}
|
||||
|
||||
AuthRequest::~AuthRequest() {
|
||||
}
|
||||
AuthRequest::~AuthRequest() {}
|
||||
|
||||
void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
|
||||
void AuthRequest::get(const QNetworkRequest& req, int timeout /* = 60*1000*/)
|
||||
{
|
||||
setup(req, QNetworkAccessManager::GetOperation);
|
||||
reply_ = APPLICATION->network()->get(request_);
|
||||
status_ = Requesting;
|
||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||
#endif
|
||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
||||
}
|
||||
|
||||
void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int timeout/* = 60*1000*/) {
|
||||
void AuthRequest::post(const QNetworkRequest& req, const QByteArray& data, int timeout /* = 60*1000*/)
|
||||
{
|
||||
setup(req, QNetworkAccessManager::PostOperation);
|
||||
data_ = data;
|
||||
status_ = Requesting;
|
||||
reply_ = APPLICATION->network()->post(request_, data_);
|
||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||
#endif
|
||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||
@ -80,35 +80,39 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
|
||||
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
|
||||
}
|
||||
|
||||
void AuthRequest::onRequestFinished() {
|
||||
void AuthRequest::onRequestFinished()
|
||||
{
|
||||
if (status_ == Idle) {
|
||||
return;
|
||||
}
|
||||
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
|
||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
||||
return;
|
||||
}
|
||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
finish();
|
||||
}
|
||||
|
||||
void AuthRequest::onRequestError(QNetworkReply::NetworkError error) {
|
||||
void AuthRequest::onRequestError(QNetworkReply::NetworkError error)
|
||||
{
|
||||
qWarning() << "AuthRequest::onRequestError: Error" << (int)error;
|
||||
if (status_ == Idle) {
|
||||
return;
|
||||
}
|
||||
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
|
||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
||||
return;
|
||||
}
|
||||
errorString_ = reply_->errorString();
|
||||
httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
error_ = error;
|
||||
qWarning() << "AuthRequest::onRequestError: Error string: " << errorString_;
|
||||
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_ << reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
qWarning() << "AuthRequest::onRequestError: HTTP status" << httpStatus_
|
||||
<< reply_->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||
|
||||
// QTimer::singleShot(10, this, SLOT(finish()));
|
||||
}
|
||||
|
||||
void AuthRequest::onSslErrors(QList<QSslError> errors) {
|
||||
void AuthRequest::onSslErrors(QList<QSslError> errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
||||
@ -118,23 +122,25 @@ void AuthRequest::onSslErrors(QList<QSslError> errors) {
|
||||
}
|
||||
}
|
||||
|
||||
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total) {
|
||||
void AuthRequest::onUploadProgress(qint64 uploaded, qint64 total)
|
||||
{
|
||||
if (status_ == Idle) {
|
||||
qWarning() << "AuthRequest::onUploadProgress: No pending request";
|
||||
return;
|
||||
}
|
||||
if (reply_ != qobject_cast<QNetworkReply *>(sender())) {
|
||||
if (reply_ != qobject_cast<QNetworkReply*>(sender())) {
|
||||
return;
|
||||
}
|
||||
// Restart timeout because request in progress
|
||||
Katabasis::Reply *o2Reply = timedReplies_.find(reply_);
|
||||
if(o2Reply) {
|
||||
Katabasis::Reply* o2Reply = timedReplies_.find(reply_);
|
||||
if (o2Reply) {
|
||||
o2Reply->start();
|
||||
}
|
||||
emit uploadProgress(uploaded, total);
|
||||
}
|
||||
|
||||
void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Operation operation, const QByteArray &verb) {
|
||||
void AuthRequest::setup(const QNetworkRequest& req, QNetworkAccessManager::Operation operation, const QByteArray& verb)
|
||||
{
|
||||
request_ = req;
|
||||
operation_ = operation;
|
||||
url_ = req.url();
|
||||
@ -152,7 +158,8 @@ void AuthRequest::setup(const QNetworkRequest &req, QNetworkAccessManager::Opera
|
||||
httpStatus_ = 0;
|
||||
}
|
||||
|
||||
void AuthRequest::finish() {
|
||||
void AuthRequest::finish()
|
||||
{
|
||||
QByteArray data;
|
||||
if (status_ == Idle) {
|
||||
qWarning() << "AuthRequest::finish: No pending request";
|
||||
|
@ -1,27 +1,26 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QUrl>
|
||||
#include <QByteArray>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include "katabasis/Reply.h"
|
||||
|
||||
/// Makes authentication requests.
|
||||
class AuthRequest: public QObject {
|
||||
class AuthRequest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuthRequest(QObject *parent = 0);
|
||||
public:
|
||||
explicit AuthRequest(QObject* parent = 0);
|
||||
~AuthRequest();
|
||||
|
||||
public slots:
|
||||
void get(const QNetworkRequest &req, int timeout = 60*1000);
|
||||
void post(const QNetworkRequest &req, const QByteArray &data, int timeout = 60*1000);
|
||||
public slots:
|
||||
void get(const QNetworkRequest& req, int timeout = 60 * 1000);
|
||||
void post(const QNetworkRequest& req, const QByteArray& data, int timeout = 60 * 1000);
|
||||
|
||||
|
||||
signals:
|
||||
signals:
|
||||
|
||||
/// Emitted when a request has been completed or failed.
|
||||
void finished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
||||
@ -29,7 +28,7 @@ signals:
|
||||
/// Emitted when an upload has progressed.
|
||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
|
||||
/// Handle request finished.
|
||||
void onRequestFinished();
|
||||
@ -46,25 +45,23 @@ protected slots:
|
||||
/// Handle upload progress.
|
||||
void onUploadProgress(qint64 uploaded, qint64 total);
|
||||
|
||||
public:
|
||||
public:
|
||||
QNetworkReply::NetworkError error_;
|
||||
int httpStatus_ = 0;
|
||||
QString errorString_;
|
||||
|
||||
protected:
|
||||
void setup(const QNetworkRequest &request, QNetworkAccessManager::Operation operation, const QByteArray &verb = QByteArray());
|
||||
protected:
|
||||
void setup(const QNetworkRequest& request, QNetworkAccessManager::Operation operation, const QByteArray& verb = QByteArray());
|
||||
|
||||
enum Status {
|
||||
Idle, Requesting, ReRequesting
|
||||
};
|
||||
enum Status { Idle, Requesting, ReRequesting };
|
||||
|
||||
QNetworkRequest request_;
|
||||
QByteArray data_;
|
||||
QNetworkReply *reply_;
|
||||
QNetworkReply* reply_;
|
||||
Status status_;
|
||||
QNetworkAccessManager::Operation operation_;
|
||||
QUrl url_;
|
||||
Katabasis::ReplyList timedReplies_;
|
||||
|
||||
QTimer *timer_;
|
||||
QTimer* timer_;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "AuthSession.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
|
||||
QString AuthSession::serializeUserProperties()
|
||||
@ -16,22 +16,22 @@ QString AuthSession::serializeUserProperties()
|
||||
*/
|
||||
QJsonDocument value(userAttrs);
|
||||
return value.toJson(QJsonDocument::Compact);
|
||||
|
||||
}
|
||||
|
||||
bool AuthSession::MakeOffline(QString offline_playername)
|
||||
{
|
||||
if (status != PlayableOffline && status != PlayableOnline)
|
||||
{
|
||||
if (status != PlayableOffline && status != PlayableOnline) {
|
||||
return false;
|
||||
}
|
||||
session = "-";
|
||||
access_token = "0";
|
||||
player_name = offline_playername;
|
||||
status = PlayableOffline;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AuthSession::MakeDemo() {
|
||||
void AuthSession::MakeDemo()
|
||||
{
|
||||
player_name = "Player";
|
||||
demo = true;
|
||||
}
|
||||
|
@ -1,22 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QMultiMap>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class MinecraftAccount;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
struct AuthSession
|
||||
{
|
||||
struct AuthSession {
|
||||
bool MakeOffline(QString offline_playername);
|
||||
void MakeDemo();
|
||||
|
||||
QString serializeUserProperties();
|
||||
|
||||
enum Status
|
||||
{
|
||||
enum Status {
|
||||
Undetermined,
|
||||
RequiresOAuth,
|
||||
RequiresPassword,
|
||||
@ -45,7 +43,7 @@ struct AuthSession
|
||||
// Did the user request online mode?
|
||||
bool wants_online = true;
|
||||
|
||||
//Is this a demo session?
|
||||
// Is this a demo session?
|
||||
bool demo = false;
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
#include "AuthStep.h"
|
||||
|
||||
AuthStep::AuthStep(AccountData *data) : QObject(nullptr), m_data(data) {
|
||||
}
|
||||
AuthStep::AuthStep(AccountData* data) : QObject(nullptr), m_data(data) {}
|
||||
|
||||
AuthStep::~AuthStep() noexcept = default;
|
||||
|
||||
|
@ -1,33 +1,33 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include "AccountTask.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "AccountTask.h"
|
||||
|
||||
class AuthStep : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<AuthStep>;
|
||||
|
||||
public:
|
||||
explicit AuthStep(AccountData *data);
|
||||
public:
|
||||
explicit AuthStep(AccountData* data);
|
||||
virtual ~AuthStep() noexcept;
|
||||
|
||||
virtual QString describe() = 0;
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
virtual void perform() = 0;
|
||||
virtual void rehydrate() = 0;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void finished(AccountTaskState resultingState, QString message);
|
||||
void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn);
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
protected:
|
||||
AccountData *m_data;
|
||||
protected:
|
||||
AccountData* m_data;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -37,12 +37,13 @@
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
#include <QUuid>
|
||||
#include <QJsonObject>
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QJsonDocument>
|
||||
#include <QUuid>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -52,28 +53,30 @@
|
||||
#include "flows/Mojang.h"
|
||||
#include "flows/Offline.h"
|
||||
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
|
||||
{
|
||||
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
}
|
||||
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json) {
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json)
|
||||
{
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
if(account->data.resumeStateFromV2(json)) {
|
||||
if (account->data.resumeStateFromV2(json)) {
|
||||
return account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
||||
{
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
if(account->data.resumeStateFromV3(json)) {
|
||||
if (account->data.resumeStateFromV3(json)) {
|
||||
return account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username)
|
||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
|
||||
{
|
||||
auto account = makeShared<MinecraftAccount>();
|
||||
account->data.type = AccountType::Mojang;
|
||||
@ -89,36 +92,37 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA()
|
||||
return account;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username)
|
||||
MinecraftAccountPtr MinecraftAccount::createOffline(const QString& username)
|
||||
{
|
||||
auto account = makeShared<MinecraftAccount>();
|
||||
account->data.type = AccountType::Offline;
|
||||
account->data.yggdrasilToken.token = "offline";
|
||||
account->data.yggdrasilToken.token = "0";
|
||||
account->data.yggdrasilToken.validity = Katabasis::Validity::Certain;
|
||||
account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||
account->data.yggdrasilToken.extra["userName"] = username;
|
||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftEntitlement.ownsMinecraft = true;
|
||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||
account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftProfile.id = uuidFromUsername(username).toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftProfile.name = username;
|
||||
account->data.minecraftProfile.validity = Katabasis::Validity::Certain;
|
||||
return account;
|
||||
}
|
||||
|
||||
|
||||
QJsonObject MinecraftAccount::saveToJson() const
|
||||
{
|
||||
return data.saveState();
|
||||
}
|
||||
|
||||
AccountState MinecraftAccount::accountState() const {
|
||||
AccountState MinecraftAccount::accountState() const
|
||||
{
|
||||
return data.accountState;
|
||||
}
|
||||
|
||||
QPixmap MinecraftAccount::getFace() const {
|
||||
QPixmap MinecraftAccount::getFace() const
|
||||
{
|
||||
QPixmap skinTexture;
|
||||
if(!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) {
|
||||
if (!skinTexture.loadFromData(data.minecraftProfile.skin.data, "PNG")) {
|
||||
return QPixmap();
|
||||
}
|
||||
QPixmap skin = QPixmap(8, 8);
|
||||
@ -128,67 +132,68 @@ QPixmap MinecraftAccount::getFace() const {
|
||||
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
||||
}
|
||||
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password)
|
||||
{
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MojangLogin(&data, password));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
|
||||
{
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MSAInteractive(&data));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline() {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline()
|
||||
{
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new OfflineLogin(&data));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
if(m_currentTask) {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
|
||||
{
|
||||
if (m_currentTask) {
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
if(data.type == AccountType::MSA) {
|
||||
if (data.type == AccountType::MSA) {
|
||||
m_currentTask.reset(new MSASilent(&data));
|
||||
}
|
||||
else if(data.type == AccountType::Offline) {
|
||||
} else if (data.type == AccountType::Offline) {
|
||||
m_currentTask.reset(new OfflineRefresh(&data));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
m_currentTask.reset(new MojangRefresh(&data));
|
||||
}
|
||||
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask() {
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask()
|
||||
{
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
|
||||
void MinecraftAccount::authSucceeded()
|
||||
{
|
||||
m_currentTask.reset();
|
||||
@ -205,28 +210,24 @@ void MinecraftAccount::authFailed(QString reason)
|
||||
}
|
||||
case AccountTaskState::STATE_FAILED_SOFT: {
|
||||
// NOTE: this doesn't do much. There was an error of some sort.
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountTaskState::STATE_FAILED_HARD: {
|
||||
if(isMSA()) {
|
||||
if (isMSA()) {
|
||||
data.msaToken.token = QString();
|
||||
data.msaToken.refresh_token = QString();
|
||||
data.msaToken.validity = Katabasis::Validity::None;
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
data.yggdrasilToken.token = QString();
|
||||
data.yggdrasilToken.validity = Katabasis::Validity::None;
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountTaskState::STATE_FAILED_GONE: {
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
emit changed();
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountTaskState::STATE_CREATED:
|
||||
case AccountTaskState::STATE_WORKING:
|
||||
case AccountTaskState::STATE_SUCCEEDED: {
|
||||
@ -237,21 +238,23 @@ void MinecraftAccount::authFailed(QString reason)
|
||||
emit activityChanged(false);
|
||||
}
|
||||
|
||||
bool MinecraftAccount::isActive() const {
|
||||
bool MinecraftAccount::isActive() const
|
||||
{
|
||||
return !m_currentTask.isNull();
|
||||
}
|
||||
|
||||
bool MinecraftAccount::shouldRefresh() const {
|
||||
bool MinecraftAccount::shouldRefresh() const
|
||||
{
|
||||
/*
|
||||
* Never refresh accounts that are being used by the game, it breaks the game session.
|
||||
* Always refresh accounts that have not been refreshed yet during this session.
|
||||
* Don't refresh broken accounts.
|
||||
* Refresh accounts that would expire in the next 12 hours (fresh token validity is 24 hours).
|
||||
*/
|
||||
if(isInUse()) {
|
||||
if (isInUse()) {
|
||||
return false;
|
||||
}
|
||||
switch(data.validity_) {
|
||||
switch (data.validity_) {
|
||||
case Katabasis::Validity::Certain: {
|
||||
break;
|
||||
}
|
||||
@ -266,7 +269,7 @@ bool MinecraftAccount::shouldRefresh() const {
|
||||
auto issuedTimestamp = data.yggdrasilToken.issueInstant;
|
||||
auto expiresTimestamp = data.yggdrasilToken.notAfter;
|
||||
|
||||
if(!expiresTimestamp.isValid()) {
|
||||
if (!expiresTimestamp.isValid()) {
|
||||
expiresTimestamp = issuedTimestamp.addSecs(24 * 3600);
|
||||
}
|
||||
if (now.secsTo(expiresTimestamp) < (12 * 3600)) {
|
||||
@ -277,14 +280,12 @@ bool MinecraftAccount::shouldRefresh() const {
|
||||
|
||||
void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
{
|
||||
if(ownsMinecraft() && !hasProfile()) {
|
||||
if (ownsMinecraft() && !hasProfile()) {
|
||||
session->status = AuthSession::RequiresProfileSetup;
|
||||
}
|
||||
else {
|
||||
if(session->wants_online) {
|
||||
} else {
|
||||
if (session->wants_online) {
|
||||
session->status = AuthSession::PlayableOnline;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
session->status = AuthSession::PlayableOffline;
|
||||
}
|
||||
}
|
||||
@ -302,12 +303,9 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
session->uuid = data.profileId();
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
session->user_type = typeString();
|
||||
if (!session->access_token.isEmpty())
|
||||
{
|
||||
if (!session->access_token.isEmpty()) {
|
||||
session->session = "token:" + data.accessToken() + ":" + data.profileId();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
session->session = "-";
|
||||
}
|
||||
}
|
||||
@ -315,8 +313,7 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
void MinecraftAccount::decrementUses()
|
||||
{
|
||||
Usable::decrementUses();
|
||||
if(!isInUse())
|
||||
{
|
||||
if (!isInUse()) {
|
||||
emit changed();
|
||||
// FIXME: we now need a better way to identify accounts...
|
||||
qWarning() << "Profile" << data.profileId() << "is no longer in use.";
|
||||
@ -327,10 +324,31 @@ void MinecraftAccount::incrementUses()
|
||||
{
|
||||
bool wasInUse = isInUse();
|
||||
Usable::incrementUses();
|
||||
if(!wasInUse)
|
||||
{
|
||||
if (!wasInUse) {
|
||||
emit changed();
|
||||
// FIXME: we now need a better way to identify accounts...
|
||||
qWarning() << "Profile" << data.profileId() << "is now in use.";
|
||||
}
|
||||
}
|
||||
|
||||
QUuid MinecraftAccount::uuidFromUsername(QString username)
|
||||
{
|
||||
auto input = QString("OfflinePlayer:%1").arg(username).toUtf8();
|
||||
|
||||
// basically a reimplementation of Java's UUID#nameUUIDFromBytes
|
||||
QByteArray digest = QCryptographicHash::hash(input, QCryptographicHash::Md5);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
auto bOr = [](QByteArray& array, int index, char value) { array[index] = array.at(index) | value; };
|
||||
auto bAnd = [](QByteArray& array, int index, char value) { array[index] = array.at(index) & value; };
|
||||
#else
|
||||
auto bOr = [](QByteArray& array, qsizetype index, char value) { array[index] |= value; };
|
||||
auto bAnd = [](QByteArray& array, qsizetype index, char value) { array[index] &= value; };
|
||||
#endif
|
||||
bAnd(digest, 6, (char)0x0f); // clear version
|
||||
bOr(digest, 6, (char)0x30); // set to version 3
|
||||
bAnd(digest, 8, (char)0x3f); // clear variant
|
||||
bOr(digest, 8, (char)0x80); // set to IETF variant
|
||||
|
||||
return QUuid::fromRfc4122(digest);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -35,20 +35,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QJsonObject>
|
||||
#include <QPair>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "AuthSession.h"
|
||||
#include "Usable.h"
|
||||
#include "AccountData.h"
|
||||
#include "AuthSession.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "Usable.h"
|
||||
|
||||
class Task;
|
||||
class AccountTask;
|
||||
@ -64,8 +64,7 @@ Q_DECLARE_METATYPE(MinecraftAccountPtr)
|
||||
* but we might as well add some things for it in Prism Launcher right now so
|
||||
* we don't have to rip the code to pieces to add it later.
|
||||
*/
|
||||
struct AccountProfile
|
||||
{
|
||||
struct AccountProfile {
|
||||
QString id;
|
||||
QString name;
|
||||
bool legacy;
|
||||
@ -77,32 +76,30 @@ struct AccountProfile
|
||||
* Said information may include things such as that account's username, client token, and access
|
||||
* token if the user chose to stay logged in.
|
||||
*/
|
||||
class MinecraftAccount :
|
||||
public QObject,
|
||||
public Usable
|
||||
{
|
||||
class MinecraftAccount : public QObject, public Usable {
|
||||
Q_OBJECT
|
||||
public: /* construction */
|
||||
public: /* construction */
|
||||
//! Do not copy accounts. ever.
|
||||
explicit MinecraftAccount(const MinecraftAccount &other, QObject *parent) = delete;
|
||||
explicit MinecraftAccount(const MinecraftAccount& other, QObject* parent) = delete;
|
||||
|
||||
//! Default constructor
|
||||
explicit MinecraftAccount(QObject *parent = 0);
|
||||
explicit MinecraftAccount(QObject* parent = 0);
|
||||
|
||||
static MinecraftAccountPtr createFromUsername(const QString &username);
|
||||
static MinecraftAccountPtr createFromUsername(const QString& username);
|
||||
|
||||
static MinecraftAccountPtr createBlankMSA();
|
||||
|
||||
static MinecraftAccountPtr createOffline(const QString &username);
|
||||
static MinecraftAccountPtr createOffline(const QString& username);
|
||||
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject& json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
|
||||
|
||||
static QUuid uuidFromUsername(QString username);
|
||||
|
||||
//! Saves a MinecraftAccount to a JSON object and returns it.
|
||||
QJsonObject saveToJson() const;
|
||||
|
||||
public: /* manipulation */
|
||||
|
||||
public: /* manipulation */
|
||||
/**
|
||||
* Attempt to login. Empty password means we use the token.
|
||||
* If the attempt fails because we already are performing some task, it returns false.
|
||||
@ -117,70 +114,46 @@ public: /* manipulation */
|
||||
|
||||
shared_qobject_ptr<AccountTask> currentTask();
|
||||
|
||||
public: /* queries */
|
||||
QString internalId() const {
|
||||
return data.internalId;
|
||||
}
|
||||
public: /* queries */
|
||||
QString internalId() const { return data.internalId; }
|
||||
|
||||
QString accountDisplayString() const {
|
||||
return data.accountDisplayString();
|
||||
}
|
||||
QString accountDisplayString() const { return data.accountDisplayString(); }
|
||||
|
||||
QString mojangUserName() const {
|
||||
return data.userName();
|
||||
}
|
||||
QString mojangUserName() const { return data.userName(); }
|
||||
|
||||
QString accessToken() const {
|
||||
return data.accessToken();
|
||||
}
|
||||
QString accessToken() const { return data.accessToken(); }
|
||||
|
||||
QString profileId() const {
|
||||
return data.profileId();
|
||||
}
|
||||
QString profileId() const { return data.profileId(); }
|
||||
|
||||
QString profileName() const {
|
||||
return data.profileName();
|
||||
}
|
||||
QString profileName() const { return data.profileName(); }
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
bool canMigrate() const {
|
||||
return data.canMigrateToMSA;
|
||||
}
|
||||
bool canMigrate() const { return data.canMigrateToMSA; }
|
||||
|
||||
bool isMSA() const {
|
||||
return data.type == AccountType::MSA;
|
||||
}
|
||||
bool isMSA() const { return data.type == AccountType::MSA; }
|
||||
|
||||
bool isOffline() const {
|
||||
return data.type == AccountType::Offline;
|
||||
}
|
||||
bool isOffline() const { return data.type == AccountType::Offline; }
|
||||
|
||||
bool ownsMinecraft() const {
|
||||
return data.minecraftEntitlement.ownsMinecraft;
|
||||
}
|
||||
bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; }
|
||||
|
||||
bool hasProfile() const {
|
||||
return data.profileId().size() != 0;
|
||||
}
|
||||
bool hasProfile() const { return data.profileId().size() != 0; }
|
||||
|
||||
QString typeString() const {
|
||||
switch(data.type) {
|
||||
QString typeString() const
|
||||
{
|
||||
switch (data.type) {
|
||||
case AccountType::Mojang: {
|
||||
if(data.legacy) {
|
||||
if (data.legacy) {
|
||||
return "legacy";
|
||||
}
|
||||
return "mojang";
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountType::MSA: {
|
||||
return "msa";
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case AccountType::Offline: {
|
||||
return "offline";
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
default: {
|
||||
return "unknown";
|
||||
}
|
||||
@ -192,19 +165,15 @@ public: /* queries */
|
||||
//! Returns the current state of the account
|
||||
AccountState accountState() const;
|
||||
|
||||
AccountData * accountData() {
|
||||
return &data;
|
||||
}
|
||||
AccountData* accountData() { return &data; }
|
||||
|
||||
bool shouldRefresh() const;
|
||||
|
||||
void fillSession(AuthSessionPtr session);
|
||||
|
||||
QString lastError() const {
|
||||
return data.lastError();
|
||||
}
|
||||
QString lastError() const { return data.lastError(); }
|
||||
|
||||
signals:
|
||||
signals:
|
||||
/**
|
||||
* This signal is emitted when the account changes
|
||||
*/
|
||||
@ -214,20 +183,17 @@ signals:
|
||||
|
||||
// TODO: better signalling for the various possible state changes - especially errors
|
||||
|
||||
protected: /* variables */
|
||||
protected: /* variables */
|
||||
AccountData data;
|
||||
|
||||
// current task we are executing here
|
||||
shared_qobject_ptr<AccountTask> m_currentTask;
|
||||
|
||||
protected: /* methods */
|
||||
|
||||
protected: /* methods */
|
||||
void incrementUses() override;
|
||||
void decrementUses() override;
|
||||
|
||||
private
|
||||
slots:
|
||||
private slots:
|
||||
void authSucceeded();
|
||||
void authFailed(QString reason);
|
||||
};
|
||||
|
||||
|
@ -2,46 +2,51 @@
|
||||
#include "Json.h"
|
||||
#include "Logging.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace Parsers {
|
||||
|
||||
bool getDateTime(QJsonValue value, QDateTime & out) {
|
||||
if(!value.isString()) {
|
||||
bool getDateTime(QJsonValue value, QDateTime& out)
|
||||
{
|
||||
if (!value.isString()) {
|
||||
return false;
|
||||
}
|
||||
out = QDateTime::fromString(value.toString(), Qt::ISODate);
|
||||
return out.isValid();
|
||||
}
|
||||
|
||||
bool getString(QJsonValue value, QString & out) {
|
||||
if(!value.isString()) {
|
||||
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()) {
|
||||
bool getNumber(QJsonValue value, double& out)
|
||||
{
|
||||
if (!value.isDouble()) {
|
||||
return false;
|
||||
}
|
||||
out = value.toDouble();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getNumber(QJsonValue value, int64_t & out) {
|
||||
if(!value.isDouble()) {
|
||||
bool getNumber(QJsonValue value, int64_t& out)
|
||||
{
|
||||
if (!value.isDouble()) {
|
||||
return false;
|
||||
}
|
||||
out = (int64_t) value.toDouble();
|
||||
out = (int64_t)value.toDouble();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getBool(QJsonValue value, bool & out) {
|
||||
if(!value.isBool()) {
|
||||
bool getBool(QJsonValue value, bool& out)
|
||||
{
|
||||
if (!value.isBool()) {
|
||||
return false;
|
||||
}
|
||||
out = value.toBool();
|
||||
@ -74,49 +79,50 @@ bool getBool(QJsonValue value, bool & out) {
|
||||
// 2148916238 = child account not linked to a family
|
||||
*/
|
||||
|
||||
bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) {
|
||||
qDebug() << "Parsing" << name <<":";
|
||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name)
|
||||
{
|
||||
qDebug() << "Parsing" << name << ":";
|
||||
qCDebug(authCredentials()) << data;
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
if(!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
|
||||
if (!getDateTime(obj.value("IssueInstant"), output.issueInstant)) {
|
||||
qWarning() << "User IssueInstant is not a timestamp";
|
||||
return false;
|
||||
}
|
||||
if(!getDateTime(obj.value("NotAfter"), output.notAfter)) {
|
||||
if (!getDateTime(obj.value("NotAfter"), output.notAfter)) {
|
||||
qWarning() << "User NotAfter is not a timestamp";
|
||||
return false;
|
||||
}
|
||||
if(!getString(obj.value("Token"), output.token)) {
|
||||
if (!getString(obj.value("Token"), output.token)) {
|
||||
qWarning() << "User Token is not a string";
|
||||
return false;
|
||||
}
|
||||
auto arrayVal = obj.value("DisplayClaims").toObject().value("xui");
|
||||
if(!arrayVal.isArray()) {
|
||||
if (!arrayVal.isArray()) {
|
||||
qWarning() << "Missing xui claims array";
|
||||
return false;
|
||||
}
|
||||
bool foundUHS = false;
|
||||
for(auto item: arrayVal.toArray()) {
|
||||
if(!item.isObject()) {
|
||||
for (auto item : arrayVal.toArray()) {
|
||||
if (!item.isObject()) {
|
||||
continue;
|
||||
}
|
||||
auto obj = item.toObject();
|
||||
if(obj.contains("uhs")) {
|
||||
if (obj.contains("uhs")) {
|
||||
foundUHS = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
// consume all 'display claims' ... whatever that means
|
||||
for(auto iter = obj.begin(); iter != obj.end(); iter++) {
|
||||
for (auto iter = obj.begin(); iter != obj.end(); iter++) {
|
||||
QString claim;
|
||||
if(!getString(obj.value(iter.key()), claim)) {
|
||||
if (!getString(obj.value(iter.key()), claim)) {
|
||||
qWarning() << "display claim " << iter.key() << " is not a string...";
|
||||
return false;
|
||||
}
|
||||
@ -125,7 +131,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
|
||||
|
||||
break;
|
||||
}
|
||||
if(!foundUHS) {
|
||||
if (!foundUHS) {
|
||||
qWarning() << "Missing uhs";
|
||||
return false;
|
||||
}
|
||||
@ -134,46 +140,47 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
||||
{
|
||||
qDebug() << "Parsing Minecraft profile...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
if(!getString(obj.value("id"), output.id)) {
|
||||
if (!getString(obj.value("id"), output.id)) {
|
||||
qWarning() << "Minecraft profile id is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!getString(obj.value("name"), output.name)) {
|
||||
if (!getString(obj.value("name"), output.name)) {
|
||||
qWarning() << "Minecraft profile name is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto skinsArray = obj.value("skins").toArray();
|
||||
for(auto skin: skinsArray) {
|
||||
for (auto skin : skinsArray) {
|
||||
auto skinObj = skin.toObject();
|
||||
Skin skinOut;
|
||||
if(!getString(skinObj.value("id"), skinOut.id)) {
|
||||
if (!getString(skinObj.value("id"), skinOut.id)) {
|
||||
continue;
|
||||
}
|
||||
QString state;
|
||||
if(!getString(skinObj.value("state"), state)) {
|
||||
if (!getString(skinObj.value("state"), state)) {
|
||||
continue;
|
||||
}
|
||||
if(state != "ACTIVE") {
|
||||
if (state != "ACTIVE") {
|
||||
continue;
|
||||
}
|
||||
if(!getString(skinObj.value("url"), skinOut.url)) {
|
||||
if (!getString(skinObj.value("url"), skinOut.url)) {
|
||||
continue;
|
||||
}
|
||||
if(!getString(skinObj.value("variant"), skinOut.variant)) {
|
||||
if (!getString(skinObj.value("variant"), skinOut.variant)) {
|
||||
continue;
|
||||
}
|
||||
// we deal with only the active skin
|
||||
@ -183,23 +190,23 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
auto capesArray = obj.value("capes").toArray();
|
||||
|
||||
QString currentCape;
|
||||
for(auto cape: capesArray) {
|
||||
for (auto cape : capesArray) {
|
||||
auto capeObj = cape.toObject();
|
||||
Cape capeOut;
|
||||
if(!getString(capeObj.value("id"), capeOut.id)) {
|
||||
if (!getString(capeObj.value("id"), capeOut.id)) {
|
||||
continue;
|
||||
}
|
||||
QString state;
|
||||
if(!getString(capeObj.value("state"), state)) {
|
||||
if (!getString(capeObj.value("state"), state)) {
|
||||
continue;
|
||||
}
|
||||
if(state == "ACTIVE") {
|
||||
if (state == "ACTIVE") {
|
||||
currentCape = capeOut.id;
|
||||
}
|
||||
if(!getString(capeObj.value("url"), capeOut.url)) {
|
||||
if (!getString(capeObj.value("url"), capeOut.url)) {
|
||||
continue;
|
||||
}
|
||||
if(!getString(capeObj.value("alias"), capeOut.alias)) {
|
||||
if (!getString(capeObj.value("alias"), capeOut.alias)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -211,30 +218,33 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
|
||||
// they are needed because the session server doesn't return skin urls for default skins
|
||||
static const QString SKIN_URL_STEVE = "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
|
||||
static const QString SKIN_URL_ALEX = "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
|
||||
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
|
||||
// they are needed because the session server doesn't return skin urls for default skins
|
||||
static const QString SKIN_URL_STEVE =
|
||||
"http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
|
||||
static const QString SKIN_URL_ALEX =
|
||||
"http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
|
||||
|
||||
bool isDefaultModelSteve(QString uuid) {
|
||||
// need to calculate *Java* hashCode of UUID
|
||||
// if number is even, skin/model is steve, otherwise it is alex
|
||||
bool isDefaultModelSteve(QString uuid)
|
||||
{
|
||||
// need to calculate *Java* hashCode of UUID
|
||||
// if number is even, skin/model is steve, otherwise it is alex
|
||||
|
||||
// just in case dashes are in the id
|
||||
uuid.remove('-');
|
||||
// just in case dashes are in the id
|
||||
uuid.remove('-');
|
||||
|
||||
if (uuid.size() != 32) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// qulonglong is guaranteed to be 64 bits
|
||||
// we need to use unsigned numbers to guarantee truncation below
|
||||
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
|
||||
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
|
||||
qulonglong xored = most ^ least;
|
||||
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
|
||||
if (uuid.size() != 32) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// qulonglong is guaranteed to be 64 bits
|
||||
// we need to use unsigned numbers to guarantee truncation below
|
||||
qulonglong most = uuid.left(16).toULongLong(nullptr, 16);
|
||||
qulonglong least = uuid.right(16).toULongLong(nullptr, 16);
|
||||
qulonglong xored = most ^ least;
|
||||
return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
Uses session server for skin/cape lookup instead of profile,
|
||||
@ -270,31 +280,32 @@ decoded base64 "value":
|
||||
}
|
||||
*/
|
||||
|
||||
bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output)
|
||||
{
|
||||
qDebug() << "Parsing Minecraft profile...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = Json::requireObject(doc, "mojang minecraft profile");
|
||||
if(!getString(obj.value("id"), output.id)) {
|
||||
if (!getString(obj.value("id"), output.id)) {
|
||||
qWarning() << "Minecraft profile id is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!getString(obj.value("name"), output.name)) {
|
||||
if (!getString(obj.value("name"), output.name)) {
|
||||
qWarning() << "Minecraft profile name is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto propsArray = obj.value("properties").toArray();
|
||||
QByteArray texturePayload;
|
||||
for( auto p : propsArray) {
|
||||
for (auto p : propsArray) {
|
||||
auto pObj = p.toObject();
|
||||
auto name = pObj.value("name");
|
||||
if (!name.isString() || name.toString() != "textures") {
|
||||
@ -321,7 +332,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
}
|
||||
|
||||
doc = QJsonDocument::fromJson(texturePayload, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
@ -357,8 +368,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
// might not be present
|
||||
getString(meta.value("model"), skinOut.variant);
|
||||
}
|
||||
}
|
||||
else if (idx.key() == "CAPE") {
|
||||
} else if (idx.key() == "CAPE") {
|
||||
auto cape = idx->toObject();
|
||||
if (!getString(cape.value("url"), capeOut.url)) {
|
||||
qWarning() << "Cape url is not a string";
|
||||
@ -374,7 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
|
||||
output.skin = skinOut;
|
||||
if (capeOut.alias == "cape") {
|
||||
output.capes = QMap<QString, Cape>({{capeOut.alias, capeOut}});
|
||||
output.capes = QMap<QString, Cape>({ { capeOut.alias, capeOut } });
|
||||
output.currentCape = capeOut.alias;
|
||||
}
|
||||
|
||||
@ -382,13 +392,14 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) {
|
||||
bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output)
|
||||
{
|
||||
qDebug() << "Parsing Minecraft entitlements...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from user.auth.xboxlive.com as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
@ -398,16 +409,16 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
||||
output.ownsMinecraft = false;
|
||||
|
||||
auto itemsArray = obj.value("items").toArray();
|
||||
for(auto item: itemsArray) {
|
||||
for (auto item : itemsArray) {
|
||||
auto itemObj = item.toObject();
|
||||
QString name;
|
||||
if(!getString(itemObj.value("name"), name)) {
|
||||
if (!getString(itemObj.value("name"), name)) {
|
||||
continue;
|
||||
}
|
||||
if(name == "game_minecraft") {
|
||||
if (name == "game_minecraft") {
|
||||
output.canPlayMinecraft = true;
|
||||
}
|
||||
if(name == "product_minecraft") {
|
||||
if (name == "product_minecraft") {
|
||||
output.ownsMinecraft = true;
|
||||
}
|
||||
}
|
||||
@ -415,47 +426,50 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseRolloutResponse(QByteArray & data, bool& result) {
|
||||
bool parseRolloutResponse(QByteArray& data, bool& result)
|
||||
{
|
||||
qDebug() << "Parsing Rollout response...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: " << jsonError.errorString();
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigration as JSON: "
|
||||
<< jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
QString feature;
|
||||
if(!getString(obj.value("feature"), feature)) {
|
||||
if (!getString(obj.value("feature"), feature)) {
|
||||
qWarning() << "Rollout feature is not a string";
|
||||
return false;
|
||||
}
|
||||
if(feature != "msamigration") {
|
||||
if (feature != "msamigration") {
|
||||
qWarning() << "Rollout feature is not what we expected (msamigration), but is instead \"" << feature << "\"";
|
||||
return false;
|
||||
}
|
||||
if(!getBool(obj.value("rollout"), result)) {
|
||||
if (!getBool(obj.value("rollout"), result)) {
|
||||
qWarning() << "Rollout feature is not a string";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
qDebug() << "Parsing Mojang response...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
if (jsonError.error) {
|
||||
qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
double expires_in = 0;
|
||||
if(!getNumber(obj.value("expires_in"), expires_in)) {
|
||||
if (!getNumber(obj.value("expires_in"), expires_in)) {
|
||||
qWarning() << "expires_in is not a valid number";
|
||||
return false;
|
||||
}
|
||||
@ -464,13 +478,13 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
output.notAfter = currentTime.addSecs(expires_in);
|
||||
|
||||
QString username;
|
||||
if(!getString(obj.value("username"), username)) {
|
||||
if (!getString(obj.value("username"), username)) {
|
||||
qWarning() << "username is not valid";
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: it's a JWT... validate it?
|
||||
if(!getString(obj.value("access_token"), output.token)) {
|
||||
if (!getString(obj.value("access_token"), output.token)) {
|
||||
qWarning() << "access_token is not valid";
|
||||
return false;
|
||||
}
|
||||
@ -479,4 +493,4 @@ bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace Parsers
|
||||
|
@ -2,19 +2,18 @@
|
||||
|
||||
#include "AccountData.h"
|
||||
|
||||
namespace Parsers
|
||||
{
|
||||
bool getDateTime(QJsonValue value, QDateTime & out);
|
||||
bool getString(QJsonValue value, QString & out);
|
||||
bool getNumber(QJsonValue value, double & out);
|
||||
bool getNumber(QJsonValue value, int64_t & out);
|
||||
bool getBool(QJsonValue value, bool & out);
|
||||
namespace Parsers {
|
||||
bool getDateTime(QJsonValue value, QDateTime& out);
|
||||
bool getString(QJsonValue value, QString& out);
|
||||
bool getNumber(QJsonValue value, double& out);
|
||||
bool getNumber(QJsonValue value, int64_t& out);
|
||||
bool getBool(QJsonValue value, bool& out);
|
||||
|
||||
bool parseXTokenResponse(QByteArray &data, Katabasis::Token &output, QString name);
|
||||
bool parseMojangResponse(QByteArray &data, Katabasis::Token &output);
|
||||
bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString name);
|
||||
bool parseMojangResponse(QByteArray& data, Katabasis::Token& output);
|
||||
|
||||
bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output);
|
||||
bool parseMinecraftProfileMojang(QByteArray &data, MinecraftProfile &output);
|
||||
bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output);
|
||||
bool parseRolloutResponse(QByteArray &data, bool& result);
|
||||
}
|
||||
bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output);
|
||||
bool parseMinecraftProfileMojang(QByteArray& data, MinecraftProfile& output);
|
||||
bool parseMinecraftEntitlements(QByteArray& data, MinecraftEntitlement& output);
|
||||
bool parseRolloutResponse(QByteArray& data, bool& result);
|
||||
} // namespace Parsers
|
||||
|
@ -16,24 +16,24 @@
|
||||
#include "Yggdrasil.h"
|
||||
#include "AccountData.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
Yggdrasil::Yggdrasil(AccountData *data, QObject *parent)
|
||||
: AccountTask(data, parent)
|
||||
Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_CREATED);
|
||||
}
|
||||
|
||||
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
|
||||
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_WORKING);
|
||||
|
||||
QNetworkRequest netRequest(endpoint);
|
||||
@ -52,10 +52,10 @@ void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
|
||||
connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
|
||||
}
|
||||
|
||||
void Yggdrasil::executeTask() {
|
||||
}
|
||||
void Yggdrasil::executeTask() {}
|
||||
|
||||
void Yggdrasil::refresh() {
|
||||
void Yggdrasil::refresh()
|
||||
{
|
||||
start();
|
||||
/*
|
||||
* {
|
||||
@ -90,7 +90,8 @@ void Yggdrasil::refresh() {
|
||||
sendRequest(reqUrl, requestData);
|
||||
}
|
||||
|
||||
void Yggdrasil::login(QString password) {
|
||||
void Yggdrasil::login(QString password)
|
||||
{
|
||||
start();
|
||||
/*
|
||||
* {
|
||||
@ -136,20 +137,21 @@ void Yggdrasil::login(QString password) {
|
||||
sendRequest(reqUrl, requestData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Yggdrasil::refreshTimers(qint64, qint64) {
|
||||
void Yggdrasil::refreshTimers(qint64, qint64)
|
||||
{
|
||||
timeout_keeper.stop();
|
||||
timeout_keeper.start(timeout_max);
|
||||
progress(count = 0, timeout_max);
|
||||
}
|
||||
|
||||
void Yggdrasil::heartbeat() {
|
||||
void Yggdrasil::heartbeat()
|
||||
{
|
||||
count += time_step;
|
||||
progress(count, timeout_max);
|
||||
}
|
||||
|
||||
bool Yggdrasil::abort() {
|
||||
bool Yggdrasil::abort()
|
||||
{
|
||||
progress(timeout_max, timeout_max);
|
||||
// TODO: actually use this in a meaningful way
|
||||
m_aborted = Yggdrasil::BY_USER;
|
||||
@ -157,14 +159,16 @@ bool Yggdrasil::abort() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Yggdrasil::abortByTimeout() {
|
||||
void Yggdrasil::abortByTimeout()
|
||||
{
|
||||
progress(timeout_max, timeout_max);
|
||||
// TODO: actually use this in a meaningful way
|
||||
m_aborted = Yggdrasil::BY_TIMEOUT;
|
||||
m_netReply->abort();
|
||||
}
|
||||
|
||||
void Yggdrasil::sslErrors(QList<QSslError> errors) {
|
||||
void Yggdrasil::sslErrors(QList<QSslError> errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
||||
@ -174,7 +178,8 @@ void Yggdrasil::sslErrors(QList<QSslError> errors) {
|
||||
}
|
||||
}
|
||||
|
||||
void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
void Yggdrasil::processResponse(QJsonObject responseData)
|
||||
{
|
||||
// Read the response data. We need to get the client token, access token, and the selected
|
||||
// profile.
|
||||
qDebug() << "Processing authentication response.";
|
||||
@ -188,11 +193,11 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
|
||||
return;
|
||||
}
|
||||
if(m_data->clientToken().isEmpty()) {
|
||||
if (m_data->clientToken().isEmpty()) {
|
||||
m_data->setClientToken(clientToken);
|
||||
}
|
||||
else if(clientToken != m_data->clientToken()) {
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported."));
|
||||
} else if (clientToken != m_data->clientToken()) {
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD,
|
||||
tr("Authentication server attempted to change the client token. This isn't supported."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -220,8 +225,7 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
|
||||
if (i.key() == "name" && i.value().isString()) {
|
||||
m_data->minecraftProfile.name = i->toString();
|
||||
}
|
||||
else if (i.key() == "id" && i.value().isString()) {
|
||||
} else if (i.key() == "id" && i.value().isString()) {
|
||||
m_data->minecraftProfile.id = i->toString();
|
||||
}
|
||||
}
|
||||
@ -237,49 +241,43 @@ void Yggdrasil::processResponse(QJsonObject responseData) {
|
||||
changeState(AccountTaskState::STATE_SUCCEEDED);
|
||||
}
|
||||
|
||||
void Yggdrasil::processReply() {
|
||||
void Yggdrasil::processReply()
|
||||
{
|
||||
changeState(AccountTaskState::STATE_WORKING);
|
||||
|
||||
switch (m_netReply->error())
|
||||
{
|
||||
case QNetworkReply::NoError:
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
|
||||
return;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
|
||||
return;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr(
|
||||
"<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
|
||||
"<ul>"
|
||||
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
|
||||
"<li>Some device on your network is interfering with SSL traffic. In that case, "
|
||||
"you have bigger worries than Minecraft not starting.</li>"
|
||||
"<li>Possibly something else. Check the log file for details</li>"
|
||||
"</ul>"
|
||||
)
|
||||
);
|
||||
return;
|
||||
// used for invalid credentials and similar errors. Fall through.
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError: {
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
|
||||
);
|
||||
}
|
||||
default:
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Authentication operation failed due to a network error: %1 (%2)").arg(m_netReply->errorString()).arg(m_netReply->error())
|
||||
);
|
||||
return;
|
||||
switch (m_netReply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
|
||||
return;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
|
||||
return;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
|
||||
"<ul>"
|
||||
"<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
|
||||
"<li>Some device on your network is interfering with SSL traffic. In that case, "
|
||||
"you have bigger worries than Minecraft not starting.</li>"
|
||||
"<li>Possibly something else. Check the log file for details</li>"
|
||||
"</ul>"));
|
||||
return;
|
||||
// used for invalid credentials and similar errors. Fall through.
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
break;
|
||||
case QNetworkReply::ContentGoneError: {
|
||||
changeState(AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
|
||||
.arg(m_netReply->errorString())
|
||||
.arg(m_netReply->error()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to parse the response regardless of the response code.
|
||||
@ -298,12 +296,11 @@ void Yggdrasil::processReply() {
|
||||
if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
|
||||
processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
|
||||
return;
|
||||
}
|
||||
else {
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse authentication server response JSON response: %1 at offset %2.").arg(jsonError.errorString()).arg(jsonError.offset)
|
||||
);
|
||||
} else {
|
||||
changeState(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
|
||||
.arg(jsonError.errorString())
|
||||
.arg(jsonError.offset));
|
||||
qCritical() << replyData;
|
||||
}
|
||||
return;
|
||||
@ -319,34 +316,26 @@ void Yggdrasil::processReply() {
|
||||
// stuff there.
|
||||
qDebug() << "The request failed, but the server gave us an error message. Processing error.";
|
||||
processError(doc.object());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// The server didn't say anything regarding the error. Give the user an unknown
|
||||
// error.
|
||||
qDebug() << "The request failed and the server gave no error message. Unknown error.";
|
||||
changeState(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString())
|
||||
);
|
||||
tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
void Yggdrasil::processError(QJsonObject responseData) {
|
||||
void Yggdrasil::processError(QJsonObject responseData)
|
||||
{
|
||||
QJsonValue errorVal = responseData.value("error");
|
||||
QJsonValue errorMessageValue = responseData.value("errorMessage");
|
||||
QJsonValue causeVal = responseData.value("cause");
|
||||
|
||||
if (errorVal.isString() && errorMessageValue.isString()) {
|
||||
m_error = std::shared_ptr<Error>(
|
||||
new Error {
|
||||
errorVal.toString(""),
|
||||
errorMessageValue.toString(""),
|
||||
causeVal.toString("")
|
||||
}
|
||||
);
|
||||
m_error = std::shared_ptr<Error>(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Error is not in standard format. Don't set m_error and return unknown error.
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
|
||||
}
|
||||
|
@ -17,10 +17,10 @@
|
||||
|
||||
#include "AccountTask.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <qsslerror.h>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
@ -30,35 +30,25 @@ class QNetworkReply;
|
||||
/**
|
||||
* A Yggdrasil task is a task that performs an operation on a given mojang account.
|
||||
*/
|
||||
class Yggdrasil : public AccountTask
|
||||
{
|
||||
class Yggdrasil : public AccountTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Yggdrasil(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit Yggdrasil(AccountData* data, QObject* parent = 0);
|
||||
virtual ~Yggdrasil() = default;
|
||||
|
||||
void refresh();
|
||||
void login(QString password);
|
||||
|
||||
struct Error
|
||||
{
|
||||
struct Error {
|
||||
QString m_errorMessageShort;
|
||||
QString m_errorMessageVerbose;
|
||||
QString m_cause;
|
||||
};
|
||||
std::shared_ptr<Error> m_error;
|
||||
|
||||
enum AbortedBy
|
||||
{
|
||||
BY_NOTHING,
|
||||
BY_USER,
|
||||
BY_TIMEOUT
|
||||
} m_aborted = BY_NOTHING;
|
||||
enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
||||
/**
|
||||
@ -78,24 +68,24 @@ protected:
|
||||
*/
|
||||
virtual void processError(QJsonObject responseData);
|
||||
|
||||
protected slots:
|
||||
protected slots:
|
||||
void processReply();
|
||||
void refreshTimers(qint64, qint64);
|
||||
void heartbeat();
|
||||
void sslErrors(QList<QSslError>);
|
||||
void abortByTimeout();
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
virtual bool abort() override;
|
||||
|
||||
private:
|
||||
private:
|
||||
void sendRequest(QUrl endpoint, QByteArray content);
|
||||
|
||||
protected:
|
||||
QNetworkReply *m_netReply = nullptr;
|
||||
protected:
|
||||
QNetworkReply* m_netReply = nullptr;
|
||||
QTimer timeout_keeper;
|
||||
QTimer counter;
|
||||
int count = 0; // num msec since time reset
|
||||
int count = 0; // num msec since time reset
|
||||
|
||||
const int timeout_max = 30000;
|
||||
const int time_step = 50;
|
||||
|
@ -1,36 +1,33 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "AuthFlow.h"
|
||||
#include "katabasis/Globals.h"
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
AuthFlow::AuthFlow(AccountData * data, QObject *parent) :
|
||||
AccountTask(data, parent)
|
||||
AuthFlow::AuthFlow(AccountData* data, QObject* parent) : AccountTask(data, parent) {}
|
||||
|
||||
void AuthFlow::succeed()
|
||||
{
|
||||
}
|
||||
|
||||
void AuthFlow::succeed() {
|
||||
m_data->validity_ = Katabasis::Validity::Certain;
|
||||
changeState(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
tr("Finished all authentication steps")
|
||||
);
|
||||
changeState(AccountTaskState::STATE_SUCCEEDED, tr("Finished all authentication steps"));
|
||||
}
|
||||
|
||||
void AuthFlow::executeTask() {
|
||||
if(m_currentStep) {
|
||||
void AuthFlow::executeTask()
|
||||
{
|
||||
if (m_currentStep) {
|
||||
return;
|
||||
}
|
||||
changeState(AccountTaskState::STATE_WORKING, tr("Initializing"));
|
||||
nextStep();
|
||||
}
|
||||
|
||||
void AuthFlow::nextStep() {
|
||||
if(m_steps.size() == 0) {
|
||||
void AuthFlow::nextStep()
|
||||
{
|
||||
if (m_steps.size() == 0) {
|
||||
// we got to the end without an incident... assume this is all.
|
||||
m_currentStep.reset();
|
||||
succeed();
|
||||
@ -46,15 +43,13 @@ void AuthFlow::nextStep() {
|
||||
m_currentStep->perform();
|
||||
}
|
||||
|
||||
|
||||
QString AuthFlow::getStateMessage() const {
|
||||
switch (m_taskState)
|
||||
{
|
||||
QString AuthFlow::getStateMessage() const
|
||||
{
|
||||
switch (m_taskState) {
|
||||
case AccountTaskState::STATE_WORKING: {
|
||||
if(m_currentStep) {
|
||||
if (m_currentStep) {
|
||||
return m_currentStep->describe();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return tr("Working...");
|
||||
}
|
||||
}
|
||||
@ -64,8 +59,9 @@ QString AuthFlow::getStateMessage() const {
|
||||
}
|
||||
}
|
||||
|
||||
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message) {
|
||||
if(changeState(resultingState, message)) {
|
||||
void AuthFlow::stepFinished(AccountTaskState resultingState, QString message)
|
||||
{
|
||||
if (changeState(resultingState, message)) {
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QSet>
|
||||
#include <QNetworkReply>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include <katabasis/DeviceFlow.h>
|
||||
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
|
||||
class AuthFlow : public AccountTask
|
||||
{
|
||||
class AuthFlow : public AccountTask {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuthFlow(AccountData * data, QObject *parent = 0);
|
||||
public:
|
||||
explicit AuthFlow(AccountData* data, QObject* parent = 0);
|
||||
|
||||
Katabasis::Validity validity() {
|
||||
return m_data->validity_;
|
||||
};
|
||||
Katabasis::Validity validity() { return m_data->validity_; };
|
||||
|
||||
QString getStateMessage() const override;
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void activityChanged(Katabasis::Activity activity);
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void stepFinished(AccountTaskState resultingState, QString message);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
void succeed();
|
||||
void nextStep();
|
||||
|
||||
protected:
|
||||
protected:
|
||||
QList<AuthStep::Ptr> m_steps;
|
||||
AuthStep::Ptr m_currentStep;
|
||||
};
|
||||
|
@ -1,15 +1,16 @@
|
||||
#include "MSA.h"
|
||||
|
||||
#include "minecraft/auth/steps/MSAStep.h"
|
||||
#include "minecraft/auth/steps/XboxUserStep.h"
|
||||
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||
#include "minecraft/auth/steps/XboxProfileStep.h"
|
||||
#include "minecraft/auth/steps/EntitlementsStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
#include "minecraft/auth/steps/LauncherLoginStep.h"
|
||||
#include "minecraft/auth/steps/MSAStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
||||
#include "minecraft/auth/steps/XboxProfileStep.h"
|
||||
#include "minecraft/auth/steps/XboxUserStep.h"
|
||||
|
||||
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent) {
|
||||
MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Refresh));
|
||||
m_steps.append(makeShared<XboxUserStep>(m_data));
|
||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||
@ -21,10 +22,8 @@ MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
||||
|
||||
MSAInteractive::MSAInteractive(
|
||||
AccountData* data,
|
||||
QObject* parent
|
||||
) : AuthFlow(data, parent) {
|
||||
MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<MSAStep>(m_data, MSAStep::Action::Login));
|
||||
m_steps.append(makeShared<XboxUserStep>(m_data));
|
||||
m_steps.append(makeShared<XboxAuthorizationStep>(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||
|
@ -1,22 +1,14 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class MSAInteractive : public AuthFlow
|
||||
{
|
||||
class MSAInteractive : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MSAInteractive(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MSAInteractive(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
||||
class MSASilent : public AuthFlow
|
||||
{
|
||||
class MSASilent : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MSASilent(
|
||||
AccountData * data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MSASilent(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
@ -1,25 +1,20 @@
|
||||
#include "Mojang.h"
|
||||
|
||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
|
||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||
|
||||
MojangRefresh::MojangRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
|
||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
||||
|
||||
MojangLogin::MojangLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent
|
||||
): AuthFlow(data, parent), m_password(password) {
|
||||
MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
|
||||
{
|
||||
m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
|
||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
||||
m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
|
||||
|
@ -1,26 +1,17 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class MojangRefresh : public AuthFlow
|
||||
{
|
||||
class MojangRefresh : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MojangRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MojangRefresh(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
||||
class MojangLogin : public AuthFlow
|
||||
{
|
||||
class MojangLogin : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MojangLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
|
||||
|
||||
private:
|
||||
private:
|
||||
QString m_password;
|
||||
};
|
||||
|
@ -2,16 +2,12 @@
|
||||
|
||||
#include "minecraft/auth/steps/OfflineStep.h"
|
||||
|
||||
OfflineRefresh::OfflineRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
OfflineRefresh::OfflineRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
||||
}
|
||||
|
||||
OfflineLogin::OfflineLogin(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
OfflineLogin::OfflineLogin(AccountData* data, QObject* parent) : AuthFlow(data, parent)
|
||||
{
|
||||
m_steps.append(makeShared<OfflineStep>(m_data));
|
||||
}
|
||||
|
@ -1,22 +1,14 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class OfflineRefresh : public AuthFlow
|
||||
{
|
||||
class OfflineRefresh : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit OfflineRefresh(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
||||
class OfflineLogin : public AuthFlow
|
||||
{
|
||||
class OfflineLogin : public AuthFlow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineLogin(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
public:
|
||||
explicit OfflineLogin(AccountData* data, QObject* parent = 0);
|
||||
};
|
||||
|
@ -11,12 +11,13 @@ EntitlementsStep::EntitlementsStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
EntitlementsStep::~EntitlementsStep() noexcept = default;
|
||||
|
||||
QString EntitlementsStep::describe() {
|
||||
QString EntitlementsStep::describe()
|
||||
{
|
||||
return tr("Determining game ownership.");
|
||||
}
|
||||
|
||||
|
||||
void EntitlementsStep::perform() {
|
||||
void EntitlementsStep::perform()
|
||||
{
|
||||
auto uuid = QUuid::createUuid();
|
||||
m_entitlementsRequestId = uuid.toString().remove('{').remove('}');
|
||||
auto url = "https://api.minecraftservices.com/entitlements/license?requestId=" + m_entitlementsRequestId;
|
||||
@ -24,22 +25,20 @@ void EntitlementsStep::perform() {
|
||||
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);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &EntitlementsStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
qDebug() << "Getting entitlements...";
|
||||
}
|
||||
|
||||
void EntitlementsStep::rehydrate() {
|
||||
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());
|
||||
void EntitlementsStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class EntitlementsStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EntitlementsStep(AccountData *data);
|
||||
public:
|
||||
explicit EntitlementsStep(AccountData* data);
|
||||
virtual ~EntitlementsStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,9 +16,9 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
|
||||
private:
|
||||
private:
|
||||
QString m_entitlementsRequestId;
|
||||
};
|
||||
|
@ -6,34 +6,32 @@
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
GetSkinStep::GetSkinStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
GetSkinStep::~GetSkinStep() noexcept = default;
|
||||
|
||||
QString GetSkinStep::describe() {
|
||||
QString GetSkinStep::describe()
|
||||
{
|
||||
return tr("Getting skin.");
|
||||
}
|
||||
|
||||
void GetSkinStep::perform() {
|
||||
void GetSkinStep::perform()
|
||||
{
|
||||
auto url = QUrl(m_data->minecraftProfile.skin.url);
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &GetSkinStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void GetSkinStep::rehydrate() {
|
||||
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());
|
||||
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) {
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class GetSkinStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GetSkinStep(AccountData *data);
|
||||
public:
|
||||
explicit GetSkinStep(AccountData* data);
|
||||
virtual ~GetSkinStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -8,17 +8,17 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
LauncherLoginStep::~LauncherLoginStep() noexcept = default;
|
||||
|
||||
QString LauncherLoginStep::describe() {
|
||||
QString LauncherLoginStep::describe()
|
||||
{
|
||||
return tr("Accessing Mojang services.");
|
||||
}
|
||||
|
||||
void LauncherLoginStep::perform() {
|
||||
void LauncherLoginStep::perform()
|
||||
{
|
||||
auto requestURL = "https://api.minecraftservices.com/launcher/login";
|
||||
auto uhs = m_data->mojangservicesToken.extra["uhs"].toString();
|
||||
auto xToken = m_data->mojangservicesToken.token;
|
||||
@ -34,22 +34,20 @@ void LauncherLoginStep::perform() {
|
||||
QNetworkRequest request = QNetworkRequest(QUrl(requestURL));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
AuthRequest *requestor = new AuthRequest(this);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &LauncherLoginStep::onRequestDone);
|
||||
requestor->post(request, requestBody.toUtf8());
|
||||
qDebug() << "Getting Minecraft access token...";
|
||||
}
|
||||
|
||||
void LauncherLoginStep::rehydrate() {
|
||||
void LauncherLoginStep::rehydrate()
|
||||
{
|
||||
// TODO: check the token validity
|
||||
}
|
||||
|
||||
void LauncherLoginStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void LauncherLoginStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
@ -57,27 +55,17 @@ void LauncherLoginStep::onRequestDone(
|
||||
qWarning() << "Reply error:" << error;
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
||||
if (!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) {
|
||||
qWarning() << "Could not parse login_with_xbox response...";
|
||||
qCDebug(authCredentials()) << data;
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to parse the Minecraft access token response.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response."));
|
||||
return;
|
||||
}
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr(""));
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class LauncherLoginStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LauncherLoginStep(AccountData *data);
|
||||
public:
|
||||
explicit LauncherLoginStep(AccountData* data);
|
||||
virtual ~LauncherLoginStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -47,7 +47,8 @@
|
||||
using OAuth2 = Katabasis::DeviceFlow;
|
||||
using Activity = Katabasis::Activity;
|
||||
|
||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action) {
|
||||
MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(action)
|
||||
{
|
||||
m_clientId = APPLICATION->getMSAClientID();
|
||||
OAuth2::Options opts;
|
||||
opts.scope = "XboxLive.signin offline_access";
|
||||
@ -64,13 +65,14 @@ MSAStep::MSAStep(AccountData* data, Action action) : AuthStep(data), m_action(ac
|
||||
|
||||
MSAStep::~MSAStep() noexcept = default;
|
||||
|
||||
QString MSAStep::describe() {
|
||||
QString MSAStep::describe()
|
||||
{
|
||||
return tr("Logging in with Microsoft account.");
|
||||
}
|
||||
|
||||
|
||||
void MSAStep::rehydrate() {
|
||||
switch(m_action) {
|
||||
void MSAStep::rehydrate()
|
||||
{
|
||||
switch (m_action) {
|
||||
case Refresh: {
|
||||
// TODO: check the tokens and see if they are old (older than a day)
|
||||
return;
|
||||
@ -82,12 +84,14 @@ void MSAStep::rehydrate() {
|
||||
}
|
||||
}
|
||||
|
||||
void MSAStep::perform() {
|
||||
switch(m_action) {
|
||||
void MSAStep::perform()
|
||||
{
|
||||
switch (m_action) {
|
||||
case Refresh: {
|
||||
if (m_data->msaClientID != m_clientId) {
|
||||
emit hideVerificationUriAndCode();
|
||||
emit finished(AccountTaskState::STATE_DISABLED, tr("Microsoft user authentication failed - client identification has changed."));
|
||||
emit finished(AccountTaskState::STATE_DISABLED,
|
||||
tr("Microsoft user authentication failed - client identification has changed."));
|
||||
}
|
||||
m_oauth2->refresh();
|
||||
return;
|
||||
@ -105,8 +109,9 @@ void MSAStep::perform() {
|
||||
}
|
||||
}
|
||||
|
||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) {
|
||||
switch(activity) {
|
||||
void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity)
|
||||
{
|
||||
switch (activity) {
|
||||
case Katabasis::Activity::Idle:
|
||||
case Katabasis::Activity::LoggingIn:
|
||||
case Katabasis::Activity::Refreshing:
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -43,13 +43,11 @@
|
||||
|
||||
class MSAStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Action {
|
||||
Refresh,
|
||||
Login
|
||||
};
|
||||
public:
|
||||
explicit MSAStep(AccountData *data, Action action);
|
||||
public:
|
||||
enum Action { Refresh, Login };
|
||||
|
||||
public:
|
||||
explicit MSAStep(AccountData* data, Action action);
|
||||
virtual ~MSAStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -57,11 +55,11 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onOAuthActivityChanged(Katabasis::Activity activity);
|
||||
|
||||
private:
|
||||
Katabasis::DeviceFlow *m_oauth2 = nullptr;
|
||||
private:
|
||||
Katabasis::DeviceFlow* m_oauth2 = nullptr;
|
||||
Action m_action;
|
||||
QString m_clientId;
|
||||
};
|
||||
|
@ -5,37 +5,37 @@
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
|
||||
|
||||
QString MigrationEligibilityStep::describe() {
|
||||
QString MigrationEligibilityStep::describe()
|
||||
{
|
||||
return tr("Checking for migration eligibility.");
|
||||
}
|
||||
|
||||
void MigrationEligibilityStep::perform() {
|
||||
void MigrationEligibilityStep::perform()
|
||||
{
|
||||
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
|
||||
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);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void MigrationEligibilityStep::rehydrate() {
|
||||
void MigrationEligibilityStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void MigrationEligibilityStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void MigrationEligibilityStep::onRequestDone(QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError) {
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MigrationEligibilityStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MigrationEligibilityStep(AccountData *data);
|
||||
public:
|
||||
explicit MigrationEligibilityStep(AccountData* data);
|
||||
virtual ~MigrationEligibilityStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -7,52 +7,46 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
MinecraftProfileStep::MinecraftProfileStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
MinecraftProfileStep::~MinecraftProfileStep() noexcept = default;
|
||||
|
||||
QString MinecraftProfileStep::describe() {
|
||||
QString MinecraftProfileStep::describe()
|
||||
{
|
||||
return tr("Fetching the Minecraft profile.");
|
||||
}
|
||||
|
||||
|
||||
void MinecraftProfileStep::perform() {
|
||||
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);
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &MinecraftProfileStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void MinecraftProfileStep::rehydrate() {
|
||||
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());
|
||||
void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error == QNetworkReply::ContentNotFoundError) {
|
||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
if (m_data->type == AccountType::Mojang) {
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
||||
}
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
tr("Account has no Minecraft profile.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
||||
return;
|
||||
}
|
||||
if (error != QNetworkReply::NoError) {
|
||||
@ -65,35 +59,24 @@ void MinecraftProfileStep::onRequestDone(
|
||||
qWarning() << QString::fromUtf8(data);
|
||||
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!Parsers::parseMinecraftProfile(data, m_data->minecraftProfile)) {
|
||||
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")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
if (m_data->type == AccountType::Mojang) {
|
||||
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
|
||||
}
|
||||
emit finished(
|
||||
AccountTaskState::STATE_WORKING,
|
||||
tr("Minecraft Java profile acquisition succeeded.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MinecraftProfileStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MinecraftProfileStep(AccountData *data);
|
||||
public:
|
||||
explicit MinecraftProfileStep(AccountData* data);
|
||||
virtual ~MinecraftProfileStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -7,18 +7,17 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
|
||||
|
||||
QString MinecraftProfileStepMojang::describe() {
|
||||
QString MinecraftProfileStepMojang::describe()
|
||||
{
|
||||
return tr("Fetching the Minecraft profile.");
|
||||
}
|
||||
|
||||
|
||||
void MinecraftProfileStepMojang::perform() {
|
||||
void MinecraftProfileStepMojang::perform()
|
||||
{
|
||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
|
||||
return;
|
||||
@ -27,35 +26,32 @@ void MinecraftProfileStepMojang::perform() {
|
||||
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
|
||||
QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
|
||||
QNetworkRequest req = QNetworkRequest(url);
|
||||
AuthRequest *request = new AuthRequest(this);
|
||||
AuthRequest* request = new AuthRequest(this);
|
||||
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
|
||||
request->get(req);
|
||||
}
|
||||
|
||||
void MinecraftProfileStepMojang::rehydrate() {
|
||||
void MinecraftProfileStepMojang::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void MinecraftProfileStepMojang::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error == QNetworkReply::ContentNotFoundError) {
|
||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
if (m_data->type == AccountType::Mojang) {
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
||||
}
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
tr("Account has no Minecraft profile.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
||||
return;
|
||||
}
|
||||
if (error != QNetworkReply::NoError) {
|
||||
@ -68,35 +64,24 @@ void MinecraftProfileStepMojang::onRequestDone(
|
||||
qWarning() << QString::fromUtf8(data);
|
||||
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
||||
if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Minecraft Java profile response could not be parsed")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_data->type == AccountType::Mojang) {
|
||||
if (m_data->type == AccountType::Mojang) {
|
||||
auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = validProfile;
|
||||
}
|
||||
emit finished(
|
||||
AccountTaskState::STATE_WORKING,
|
||||
tr("Minecraft Java profile acquisition succeeded.")
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MinecraftProfileStepMojang : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MinecraftProfileStepMojang(AccountData *data);
|
||||
public:
|
||||
explicit MinecraftProfileStepMojang(AccountData* data);
|
||||
virtual ~MinecraftProfileStepMojang() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -5,14 +5,17 @@
|
||||
OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}
|
||||
OfflineStep::~OfflineStep() noexcept = default;
|
||||
|
||||
QString OfflineStep::describe() {
|
||||
QString OfflineStep::describe()
|
||||
{
|
||||
return tr("Creating offline account.");
|
||||
}
|
||||
|
||||
void OfflineStep::rehydrate() {
|
||||
void OfflineStep::rehydrate()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
void OfflineStep::perform() {
|
||||
void OfflineStep::perform()
|
||||
{
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account."));
|
||||
}
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
class OfflineStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OfflineStep(AccountData *data);
|
||||
public:
|
||||
explicit OfflineStep(AccountData* data);
|
||||
virtual ~OfflineStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
|
@ -1,33 +1,32 @@
|
||||
#include "XboxAuthorizationStep.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.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(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() {
|
||||
QString XboxAuthorizationStep::describe()
|
||||
{
|
||||
return tr("Getting authorization to access %1 services.").arg(m_authorizationKind);
|
||||
}
|
||||
|
||||
void XboxAuthorizationStep::rehydrate() {
|
||||
void XboxAuthorizationStep::rehydrate()
|
||||
{
|
||||
// FIXME: check if the tokens are good?
|
||||
}
|
||||
|
||||
void XboxAuthorizationStep::perform() {
|
||||
void XboxAuthorizationStep::perform()
|
||||
{
|
||||
QString xbox_auth_template = R"XXX(
|
||||
{
|
||||
"Properties": {
|
||||
@ -41,129 +40,98 @@ void XboxAuthorizationStep::perform() {
|
||||
}
|
||||
)XXX";
|
||||
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
|
||||
// http://xboxlive.com
|
||||
// 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);
|
||||
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());
|
||||
void XboxAuthorizationStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qWarning() << "Reply error:" << error;
|
||||
if (Net::isApplicationError(error)) {
|
||||
if(!processSTSError(error, data, headers)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error)
|
||||
);
|
||||
if (!processSTSError(error, data, headers)) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, error));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_)
|
||||
);
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, requestor->errorString_));
|
||||
}
|
||||
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)
|
||||
);
|
||||
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)
|
||||
);
|
||||
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;
|
||||
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) {
|
||||
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) {
|
||||
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())
|
||||
);
|
||||
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)
|
||||
);
|
||||
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>")
|
||||
);
|
||||
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.")
|
||||
);
|
||||
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>")
|
||||
);
|
||||
.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)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XSTS authentication ended with unrecognized error(s):\n\n%1").arg(errorCode));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#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);
|
||||
public:
|
||||
explicit XboxAuthorizationStep(AccountData* data, Katabasis::Token* token, QString relyingParty, QString authorizationKind);
|
||||
virtual ~XboxAuthorizationStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,18 +16,14 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private:
|
||||
bool processSTSError(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
);
|
||||
private:
|
||||
bool processSTSError(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
|
||||
private:
|
||||
Katabasis::Token *m_token;
|
||||
private:
|
||||
Katabasis::Token* m_token;
|
||||
QString m_relyingParty;
|
||||
QString m_authorizationKind;
|
||||
};
|
||||
|
@ -8,66 +8,56 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
XboxProfileStep::XboxProfileStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
XboxProfileStep::~XboxProfileStep() noexcept = default;
|
||||
|
||||
QString XboxProfileStep::describe() {
|
||||
QString XboxProfileStep::describe()
|
||||
{
|
||||
return tr("Fetching Xbox profile.");
|
||||
}
|
||||
|
||||
void XboxProfileStep::rehydrate() {
|
||||
void XboxProfileStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void XboxProfileStep::perform() {
|
||||
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"
|
||||
);
|
||||
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);
|
||||
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());
|
||||
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;
|
||||
qCDebug(authCredentials()) << data;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to retrieve the Xbox profile: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class XboxProfileStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XboxProfileStep(AccountData *data);
|
||||
public:
|
||||
explicit XboxProfileStep(AccountData* data);
|
||||
virtual ~XboxProfileStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -6,22 +6,22 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
XboxUserStep::XboxUserStep(AccountData* data) : AuthStep(data) {}
|
||||
|
||||
XboxUserStep::~XboxUserStep() noexcept = default;
|
||||
|
||||
QString XboxUserStep::describe() {
|
||||
QString XboxUserStep::describe()
|
||||
{
|
||||
return tr("Logging in as an Xbox user.");
|
||||
}
|
||||
|
||||
|
||||
void XboxUserStep::rehydrate() {
|
||||
void XboxUserStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void XboxUserStep::perform() {
|
||||
void XboxUserStep::perform()
|
||||
{
|
||||
QString xbox_auth_template = R"XXX(
|
||||
{
|
||||
"Properties": {
|
||||
@ -40,40 +40,31 @@ void XboxUserStep::perform() {
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
// set contract-version header (prevent err 400 bad-request?)
|
||||
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
||||
request.setRawHeader("x-xbl-contract-version", "1");
|
||||
request.setRawHeader("x-xbl-contract-version", "1");
|
||||
|
||||
auto *requestor = new AuthRequest(this);
|
||||
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());
|
||||
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;
|
||||
if (Net::isApplicationError(error)) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
}
|
||||
else {
|
||||
emit finished(
|
||||
AccountTaskState::STATE_OFFLINE,
|
||||
tr("XBox user authentication failed: %1").arg(requestor->errorString_)
|
||||
);
|
||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
||||
} else {
|
||||
emit finished(AccountTaskState::STATE_OFFLINE, tr("XBox user authentication failed: %1").arg(requestor->errorString_));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Katabasis::Token temp;
|
||||
if(!Parsers::parseXTokenResponse(data, temp, "UToken")) {
|
||||
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;
|
||||
|
@ -4,12 +4,11 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class XboxUserStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XboxUserStep(AccountData *data);
|
||||
public:
|
||||
explicit XboxUserStep(AccountData* data);
|
||||
virtual ~XboxUserStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -17,6 +16,6 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -4,7 +4,8 @@
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
|
||||
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password) {
|
||||
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
|
||||
{
|
||||
m_yggdrasil = new Yggdrasil(m_data, this);
|
||||
|
||||
connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
|
||||
@ -14,28 +15,32 @@ YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(dat
|
||||
|
||||
YggdrasilStep::~YggdrasilStep() noexcept = default;
|
||||
|
||||
QString YggdrasilStep::describe() {
|
||||
QString YggdrasilStep::describe()
|
||||
{
|
||||
return tr("Logging in with Mojang account.");
|
||||
}
|
||||
|
||||
void YggdrasilStep::rehydrate() {
|
||||
void YggdrasilStep::rehydrate()
|
||||
{
|
||||
// NOOP, for now.
|
||||
}
|
||||
|
||||
void YggdrasilStep::perform() {
|
||||
if(m_password.size()) {
|
||||
void YggdrasilStep::perform()
|
||||
{
|
||||
if (m_password.size()) {
|
||||
m_yggdrasil->login(m_password);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
m_yggdrasil->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthSucceeded() {
|
||||
void YggdrasilStep::onAuthSucceeded()
|
||||
{
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthFailed() {
|
||||
void YggdrasilStep::onAuthFailed()
|
||||
{
|
||||
// TODO: hook these in again, expand to MSA
|
||||
// m_error = m_yggdrasil->m_error;
|
||||
// m_aborted = m_yggdrasil->m_aborted;
|
||||
@ -44,7 +49,7 @@ void YggdrasilStep::onAuthFailed() {
|
||||
QString errorMessage = tr("Mojang user authentication failed.");
|
||||
|
||||
// NOTE: soft error in the first step means 'offline'
|
||||
if(state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
if (state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
state = AccountTaskState::STATE_OFFLINE;
|
||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ class Yggdrasil;
|
||||
class YggdrasilStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit YggdrasilStep(AccountData *data, QString password);
|
||||
public:
|
||||
explicit YggdrasilStep(AccountData* data, QString password);
|
||||
virtual ~YggdrasilStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
@ -18,11 +18,11 @@ public:
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onAuthSucceeded();
|
||||
void onAuthFailed();
|
||||
|
||||
private:
|
||||
Yggdrasil *m_yggdrasil = nullptr;
|
||||
private:
|
||||
Yggdrasil* m_yggdrasil = nullptr;
|
||||
QString m_password;
|
||||
};
|
||||
|
Reference in New Issue
Block a user