@ -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");
|
||||
@ -379,8 +380,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
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;
|
||||
@ -391,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";
|
||||
}
|
||||
|
||||
@ -420,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();
|
||||
}
|
||||
@ -481,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";
|
||||
@ -492,6 +500,7 @@ QString AccountData::accountDisplayString() const {
|
||||
}
|
||||
}
|
||||
|
||||
QString AccountData::lastError() const {
|
||||
QString AccountData::lastError() const
|
||||
{
|
||||
return errorString;
|
||||
}
|
||||
|
Reference in New Issue
Block a user