aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/auth
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/auth')
-rw-r--r--launcher/minecraft/auth/AccountData.cpp387
-rw-r--r--launcher/minecraft/auth/AccountData.h73
-rw-r--r--launcher/minecraft/auth/AccountList.cpp (renamed from launcher/minecraft/auth/MojangAccountList.cpp)321
-rw-r--r--launcher/minecraft/auth/AccountList.h118
-rw-r--r--launcher/minecraft/auth/AccountTask.cpp69
-rw-r--r--launcher/minecraft/auth/AccountTask.h (renamed from launcher/minecraft/auth/YggdrasilTask.h)78
-rw-r--r--launcher/minecraft/auth/AuthSession.cpp2
-rw-r--r--launcher/minecraft/auth/AuthSession.h13
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp303
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.h (renamed from launcher/minecraft/auth/MojangAccount.h)120
-rw-r--r--launcher/minecraft/auth/MojangAccount.cpp315
-rw-r--r--launcher/minecraft/auth/MojangAccountList.h199
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.cpp752
-rw-r--r--launcher/minecraft/auth/flows/AuthContext.h94
-rw-r--r--launcher/minecraft/auth/flows/AuthenticateTask.cpp202
-rw-r--r--launcher/minecraft/auth/flows/AuthenticateTask.h46
-rw-r--r--launcher/minecraft/auth/flows/MSAHelper.txt51
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.cpp20
-rw-r--r--launcher/minecraft/auth/flows/MSAInteractive.h10
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.cpp16
-rw-r--r--launcher/minecraft/auth/flows/MSASilent.h10
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.cpp14
-rw-r--r--launcher/minecraft/auth/flows/MojangLogin.h13
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.cpp14
-rw-r--r--launcher/minecraft/auth/flows/MojangRefresh.h10
-rw-r--r--launcher/minecraft/auth/flows/RefreshTask.cpp144
-rw-r--r--launcher/minecraft/auth/flows/RefreshTask.h44
-rw-r--r--launcher/minecraft/auth/flows/ValidateTask.cpp61
-rw-r--r--launcher/minecraft/auth/flows/ValidateTask.h47
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.cpp (renamed from launcher/minecraft/auth/YggdrasilTask.cpp)224
-rw-r--r--launcher/minecraft/auth/flows/Yggdrasil.h82
31 files changed, 2452 insertions, 1400 deletions
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
new file mode 100644
index 00000000..77c73c1b
--- /dev/null
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -0,0 +1,387 @@
+#include "AccountData.h"
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QDebug>
+#include <QUuid>
+
+namespace {
+void tokenToJSONV3(QJsonObject &parent, Katabasis::Token t, const char * tokenName) {
+ if(!t.persistent) {
+ return;
+ }
+ QJsonObject out;
+ if(t.issueInstant.isValid()) {
+ out["iat"] = QJsonValue(t.issueInstant.toMSecsSinceEpoch() / 1000);
+ }
+
+ if(t.notAfter.isValid()) {
+ out["exp"] = QJsonValue(t.notAfter.toMSecsSinceEpoch() / 1000);
+ }
+
+ bool save = false;
+ if(!t.token.isEmpty()) {
+ out["token"] = QJsonValue(t.token);
+ save = true;
+ }
+ if(!t.refresh_token.isEmpty()) {
+ out["refresh_token"] = QJsonValue(t.refresh_token);
+ save = true;
+ }
+ if(t.extra.size()) {
+ out["extra"] = QJsonObject::fromVariantMap(t.extra);
+ save = true;
+ }
+ if(save) {
+ parent[tokenName] = out;
+ }
+}
+
+Katabasis::Token tokenFromJSONV3(const QJsonObject &parent, const char * tokenName) {
+ Katabasis::Token out;
+ auto tokenObject = parent.value(tokenName).toObject();
+ if(tokenObject.isEmpty()) {
+ return out;
+ }
+ auto issueInstant = tokenObject.value("iat");
+ if(issueInstant.isDouble()) {
+ out.issueInstant = QDateTime::fromMSecsSinceEpoch(((int64_t) issueInstant.toDouble()) * 1000);
+ }
+
+ auto notAfter = tokenObject.value("exp");
+ if(notAfter.isDouble()) {
+ out.notAfter = QDateTime::fromMSecsSinceEpoch(((int64_t) notAfter.toDouble()) * 1000);
+ }
+
+ auto token = tokenObject.value("token");
+ if(token.isString()) {
+ out.token = token.toString();
+ out.validity = Katabasis::Validity::Assumed;
+ }
+
+ auto refresh_token = tokenObject.value("refresh_token");
+ if(refresh_token.isString()) {
+ out.refresh_token = refresh_token.toString();
+ }
+
+ auto extra = tokenObject.value("extra");
+ if(extra.isObject()) {
+ out.extra = extra.toObject().toVariantMap();
+ }
+ return out;
+}
+
+void profileToJSONV3(QJsonObject &parent, MinecraftProfile p, const char * tokenName) {
+ if(p.id.isEmpty()) {
+ return;
+ }
+ QJsonObject out;
+ out["id"] = QJsonValue(p.id);
+ out["name"] = QJsonValue(p.name);
+ if(p.currentCape != -1) {
+ out["cape"] = p.capes[p.currentCape].id;
+ }
+
+ {
+ QJsonObject skinObj;
+ skinObj["id"] = p.skin.id;
+ skinObj["url"] = p.skin.url;
+ skinObj["variant"] = p.skin.variant;
+ if(p.skin.data.size()) {
+ skinObj["data"] = QString::fromLatin1(p.skin.data.toBase64());
+ }
+ out["skin"] = skinObj;
+ }
+
+ QJsonArray capesArray;
+ for(auto & cape: p.capes) {
+ QJsonObject capeObj;
+ capeObj["id"] = cape.id;
+ capeObj["url"] = cape.url;
+ capeObj["alias"] = cape.alias;
+ if(cape.data.size()) {
+ capeObj["data"] = QString::fromLatin1(cape.data.toBase64());
+ }
+ capesArray.push_back(capeObj);
+ }
+ out["capes"] = capesArray;
+ parent[tokenName] = out;
+}
+
+MinecraftProfile profileFromJSONV3(const QJsonObject &parent, const char * tokenName) {
+ MinecraftProfile out;
+ auto tokenObject = parent.value(tokenName).toObject();
+ if(tokenObject.isEmpty()) {
+ return out;
+ }
+ {
+ auto idV = tokenObject.value("id");
+ auto nameV = tokenObject.value("name");
+ if(!idV.isString() || !nameV.isString()) {
+ qWarning() << "mandatory profile attributes are missing or of unexpected type";
+ return MinecraftProfile();
+ }
+ out.name = nameV.toString();
+ out.id = idV.toString();
+ }
+
+ {
+ auto skinV = tokenObject.value("skin");
+ if(!skinV.isObject()) {
+ qWarning() << "skin is missing";
+ return MinecraftProfile();
+ }
+ auto skinObj = skinV.toObject();
+ auto idV = skinObj.value("id");
+ auto urlV = skinObj.value("url");
+ auto variantV = skinObj.value("variant");
+ if(!idV.isString() || !urlV.isString() || !variantV.isString()) {
+ qWarning() << "mandatory skin attributes are missing or of unexpected type";
+ return MinecraftProfile();
+ }
+ out.skin.id = idV.toString();
+ out.skin.url = urlV.toString();
+ out.skin.variant = variantV.toString();
+
+ // data for skin is optional
+ auto dataV = skinObj.value("data");
+ if(dataV.isString()) {
+ // TODO: validate base64
+ out.skin.data = QByteArray::fromBase64(dataV.toString().toLatin1());
+ }
+ else if (!dataV.isUndefined()) {
+ qWarning() << "skin data is something unexpected";
+ return MinecraftProfile();
+ }
+ }
+
+ auto capesV = tokenObject.value("capes");
+ if(!capesV.isArray()) {
+ qWarning() << "capes is not an array!";
+ return MinecraftProfile();
+ }
+ auto capesArray = capesV.toArray();
+ for(auto capeV: capesArray) {
+ if(!capeV.isObject()) {
+ qWarning() << "cape is not an object!";
+ return MinecraftProfile();
+ }
+ auto capeObj = capeV.toObject();
+ auto idV = capeObj.value("id");
+ auto urlV = capeObj.value("url");
+ auto aliasV = capeObj.value("alias");
+ if(!idV.isString() || !urlV.isString() || !aliasV.isString()) {
+ qWarning() << "mandatory skin attributes are missing or of unexpected type";
+ return MinecraftProfile();
+ }
+ Cape cape;
+ cape.id = idV.toString();
+ cape.url = urlV.toString();
+ cape.alias = aliasV.toString();
+
+ // data for cape is optional.
+ auto dataV = capeObj.value("data");
+ if(dataV.isString()) {
+ // TODO: validate base64
+ cape.data = QByteArray::fromBase64(dataV.toString().toLatin1());
+ }
+ else if (!dataV.isUndefined()) {
+ qWarning() << "cape data is something unexpected";
+ return MinecraftProfile();
+ }
+ out.capes.push_back(cape);
+ }
+ out.validity = Katabasis::Validity::Assumed;
+ return out;
+}
+
+}
+
+bool AccountData::resumeStateFromV2(QJsonObject data) {
+ // The JSON object must at least have a username for it to be valid.
+ if (!data.value("username").isString())
+ {
+ qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
+ return false;
+ }
+
+ QString userName = data.value("username").toString("");
+ QString clientToken = data.value("clientToken").toString("");
+ QString accessToken = data.value("accessToken").toString("");
+
+ QJsonArray profileArray = data.value("profiles").toArray();
+ if (profileArray.size() < 1)
+ {
+ qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
+ return false;
+ }
+
+ struct AccountProfile
+ {
+ QString id;
+ QString name;
+ bool legacy;
+ };
+
+ QList<AccountProfile> profiles;
+ int currentProfileIndex = 0;
+ int index = -1;
+ QString currentProfile = data.value("activeProfile").toString("");
+ for (QJsonValue profileVal : profileArray)
+ {
+ index++;
+ QJsonObject profileObject = profileVal.toObject();
+ QString id = profileObject.value("id").toString("");
+ QString name = profileObject.value("name").toString("");
+ bool legacy = profileObject.value("legacy").toBool(false);
+ if (id.isEmpty() || name.isEmpty())
+ {
+ qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
+ continue;
+ }
+ if(id == currentProfile) {
+ currentProfileIndex = index;
+ }
+ profiles.append({id, name, legacy});
+ }
+ auto & profile = profiles[currentProfileIndex];
+
+ type = AccountType::Mojang;
+ legacy = profile.legacy;
+
+ minecraftProfile.id = profile.id;
+ minecraftProfile.name = profile.name;
+ minecraftProfile.validity = Katabasis::Validity::Assumed;
+
+ yggdrasilToken.token = accessToken;
+ yggdrasilToken.extra["clientToken"] = clientToken;
+ yggdrasilToken.extra["userName"] = userName;
+ yggdrasilToken.validity = Katabasis::Validity::Assumed;
+
+ validity_ = minecraftProfile.validity;
+ return true;
+}
+
+bool AccountData::resumeStateFromV3(QJsonObject data) {
+ auto typeV = data.value("type");
+ if(!typeV.isString()) {
+ qWarning() << "Failed to parse account data: type is missing.";
+ return false;
+ }
+ auto typeS = typeV.toString();
+ if(typeS == "MSA") {
+ type = AccountType::MSA;
+ } else if (typeS == "Mojang") {
+ type = AccountType::Mojang;
+ } else {
+ qWarning() << "Failed to parse account data: type is not recognized.";
+ return false;
+ }
+
+ if(type == AccountType::Mojang) {
+ legacy = data.value("legacy").toBool(false);
+ canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
+ }
+
+ if(type == AccountType::MSA) {
+ msaToken = tokenFromJSONV3(data, "msa");
+ userToken = tokenFromJSONV3(data, "utoken");
+ xboxApiToken = tokenFromJSONV3(data, "xrp-main");
+ mojangservicesToken = tokenFromJSONV3(data, "xrp-mc");
+ }
+
+ yggdrasilToken = tokenFromJSONV3(data, "ygg");
+ minecraftProfile = profileFromJSONV3(data, "profile");
+
+ validity_ = minecraftProfile.validity;
+
+ return true;
+}
+
+QJsonObject AccountData::saveState() const {
+ QJsonObject output;
+ if(type == AccountType::Mojang) {
+ output["type"] = "Mojang";
+ if(legacy) {
+ output["legacy"] = true;
+ }
+ if(canMigrateToMSA) {
+ output["canMigrateToMSA"] = true;
+ }
+ }
+ else if (type == AccountType::MSA) {
+ output["type"] = "MSA";
+ tokenToJSONV3(output, msaToken, "msa");
+ tokenToJSONV3(output, userToken, "utoken");
+ tokenToJSONV3(output, xboxApiToken, "xrp-main");
+ tokenToJSONV3(output, mojangservicesToken, "xrp-mc");
+ }
+
+ tokenToJSONV3(output, yggdrasilToken, "ygg");
+ profileToJSONV3(output, minecraftProfile, "profile");
+ return output;
+}
+
+QString AccountData::userName() const {
+ if(type != AccountType::Mojang) {
+ return QString();
+ }
+ return yggdrasilToken.extra["userName"].toString();
+}
+
+QString AccountData::accessToken() const {
+ return yggdrasilToken.token;
+}
+
+QString AccountData::clientToken() const {
+ if(type != AccountType::Mojang) {
+ return QString();
+ }
+ return yggdrasilToken.extra["clientToken"].toString();
+}
+
+void AccountData::setClientToken(QString clientToken) {
+ if(type != AccountType::Mojang) {
+ return;
+ }
+ yggdrasilToken.extra["clientToken"] = clientToken;
+}
+
+void AccountData::generateClientTokenIfMissing() {
+ if(yggdrasilToken.extra.contains("clientToken")) {
+ return;
+ }
+ invalidateClientToken();
+}
+
+void AccountData::invalidateClientToken() {
+ if(type != AccountType::Mojang) {
+ return;
+ }
+ yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{-}]"));
+}
+
+QString AccountData::profileId() const {
+ return minecraftProfile.id;
+}
+
+QString AccountData::profileName() const {
+ return minecraftProfile.name;
+}
+
+QString AccountData::accountDisplayString() const {
+ switch(type) {
+ case AccountType::Mojang: {
+ return userName();
+ }
+ case AccountType::MSA: {
+ if(xboxApiToken.extra.contains("gtg")) {
+ return xboxApiToken.extra["gtg"].toString();
+ }
+ return "Xbox profile missing";
+ }
+ default: {
+ return "Invalid Account";
+ }
+ }
+}
diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h
new file mode 100644
index 00000000..b2d09cb0
--- /dev/null
+++ b/launcher/minecraft/auth/AccountData.h
@@ -0,0 +1,73 @@
+#pragma once
+#include <QString>
+#include <QByteArray>
+#include <QVector>
+#include <katabasis/Bits.h>
+#include <QJsonObject>
+
+struct Skin {
+ QString id;
+ QString url;
+ QString variant;
+
+ QByteArray data;
+};
+
+struct Cape {
+ QString id;
+ QString url;
+ QString alias;
+
+ QByteArray data;
+};
+
+struct MinecraftProfile {
+ QString id;
+ QString name;
+ Skin skin;
+ int currentCape = -1;
+ QVector<Cape> capes;
+ Katabasis::Validity validity = Katabasis::Validity::None;
+};
+
+enum class AccountType {
+ MSA,
+ Mojang
+};
+
+struct AccountData {
+ QJsonObject saveState() const;
+ bool resumeStateFromV2(QJsonObject data);
+ bool resumeStateFromV3(QJsonObject data);
+
+ //! userName for Mojang accounts, gamertag for MSA
+ QString accountDisplayString() const;
+
+ //! Only valid for Mojang accounts. MSA does not preserve this information
+ QString userName() const;
+
+ //! Only valid for Mojang accounts.
+ QString clientToken() const;
+ void setClientToken(QString clientToken);
+ void invalidateClientToken();
+ void generateClientTokenIfMissing();
+
+ //! Yggdrasil access token, as passed to the game.
+ QString accessToken() const;
+
+ QString profileId() const;
+ QString profileName() const;
+
+ AccountType type = AccountType::MSA;
+ bool legacy = false;
+ bool canMigrateToMSA = false;
+
+ Katabasis::Token msaToken;
+ Katabasis::Token userToken;
+ Katabasis::Token xboxApiToken;
+ Katabasis::Token mojangservicesToken;
+
+ Katabasis::Token yggdrasilToken;
+ MinecraftProfile minecraftProfile;
+ Katabasis::Validity validity_ = Katabasis::Validity::None;
+};
diff --git a/launcher/minecraft/auth/MojangAccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index e584cb3b..59028b60 100644
--- a/launcher/minecraft/auth/MojangAccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -13,8 +13,8 @@
* limitations under the License.
*/
-#include "MojangAccountList.h"
-#include "MojangAccount.h"
+#include "AccountList.h"
+#include "AccountData.h"
#include <QIODevice>
#include <QFile>
@@ -28,31 +28,49 @@
#include <QDebug>
#include <FileSystem.h>
+#include <QSaveFile>
-#define ACCOUNT_LIST_FORMAT_VERSION 2
+enum AccountListVersion {
+ MojangOnly = 2,
+ MojangMSA = 3
+};
-MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(parent)
-{
-}
+AccountList::AccountList(QObject *parent) : QAbstractListModel(parent) { }
-MojangAccountPtr MojangAccountList::findAccount(const QString &username) const
-{
- for (int i = 0; i < count(); i++)
- {
- MojangAccountPtr account = at(i);
- if (account->username() == username)
- return account;
+int AccountList::findAccountByProfileId(const QString& profileId) const {
+ for (int i = 0; i < count(); i++) {
+ MinecraftAccountPtr account = at(i);
+ if (account->profileId() == profileId) {
+ return i;
+ }
}
- return nullptr;
+ return -1;
}
-const MojangAccountPtr MojangAccountList::at(int i) const
+const MinecraftAccountPtr AccountList::at(int i) const
{
- return MojangAccountPtr(m_accounts.at(i));
+ return MinecraftAccountPtr(m_accounts.at(i));
}
-void MojangAccountList::addAccount(const MojangAccountPtr account)
+void AccountList::addAccount(const MinecraftAccountPtr account)
{
+ // We only ever want accounts with valid profiles.
+ // Keeping profile-less accounts is pointless and serves no purpose.
+ auto profileId = account->profileId();
+ if(!profileId.size()) {
+ return;
+ }
+
+ // override/replace existing account with the same profileId
+ auto existingAccount = findAccountByProfileId(profileId);
+ if(existingAccount != -1) {
+ m_accounts[existingAccount] = account;
+ emit dataChanged(index(existingAccount), index(existingAccount, columnCount(QModelIndex()) - 1));
+ onListChanged();
+ return;
+ }
+
+ // if we don't have this porfileId yet, add the account to the end
int row = m_accounts.count();
beginInsertRows(QModelIndex(), row, row);
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
@@ -61,24 +79,7 @@ void MojangAccountList::addAccount(const MojangAccountPtr account)
onListChanged();
}
-void MojangAccountList::removeAccount(const QString &username)
-{
- int idx = 0;
- for (auto account : m_accounts)
- {
- if (account->username() == username)
- {
- beginRemoveRows(QModelIndex(), idx, idx);
- m_accounts.removeOne(account);
- endRemoveRows();
- return;
- }
- idx++;
- }
- onListChanged();
-}
-
-void MojangAccountList::removeAccount(QModelIndex index)
+void AccountList::removeAccount(QModelIndex index)
{
int row = index.row();
if(index.isValid() && row >= 0 && row < m_accounts.size())
@@ -96,19 +97,19 @@ void MojangAccountList::removeAccount(QModelIndex index)
}
}
-MojangAccountPtr MojangAccountList::activeAccount() const
+MinecraftAccountPtr AccountList::activeAccount() const
{
return m_activeAccount;
}
-void MojangAccountList::setActiveAccount(const QString &username)
+void AccountList::setActiveAccount(const QString &profileId)
{
- if (username.isEmpty() && m_activeAccount)
+ if (profileId.isEmpty() && m_activeAccount)
{
int idx = 0;
auto prevActiveAcc = m_activeAccount;
m_activeAccount = nullptr;
- for (MojangAccountPtr account : m_accounts)
+ for (MinecraftAccountPtr account : m_accounts)
{
if (account == prevActiveAcc)
{
@@ -125,9 +126,9 @@ void MojangAccountList::setActiveAccount(const QString &username)
auto newActiveAccount = m_activeAccount;
int newActiveAccountIdx = -1;
int idx = 0;
- for (MojangAccountPtr account : m_accounts)
+ for (MinecraftAccountPtr account : m_accounts)
{
- if (account->username() == username)
+ if (account->profileId() == profileId)
{
newActiveAccount = account;
newActiveAccountIdx = idx;
@@ -148,13 +149,13 @@ void MojangAccountList::setActiveAccount(const QString &username)
}
}
-void MojangAccountList::accountChanged()
+void AccountList::accountChanged()
{
// the list changed. there is no doubt.
onListChanged();
}
-void MojangAccountList::onListChanged()
+void AccountList::onListChanged()
{
if (m_autosave)
// TODO: Alert the user if this fails.
@@ -163,7 +164,7 @@ void MojangAccountList::onListChanged()
emit listChanged();
}
-void MojangAccountList::onActiveChanged()
+void AccountList::onActiveChanged()
{
if (m_autosave)
saveList();
@@ -171,12 +172,12 @@ void MojangAccountList::onActiveChanged()
emit activeAccountChanged();
}
-int MojangAccountList::count() const
+int AccountList::count() const
{
return m_accounts.count();
}
-QVariant MojangAccountList::data(const QModelIndex &index, int role) const
+QVariant AccountList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@@ -184,51 +185,61 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const
if (index.row() > count())
return QVariant();
- MojangAccountPtr account = at(index.row());
+ MinecraftAccountPtr account = at(index.row());
switch (role)
{
- case Qt::DisplayRole:
- switch (index.column())
- {
- case NameColumn:
- return account->username();
+ case Qt::DisplayRole:
+ switch (index.column())
+ {
+ case NameColumn:
+ return account->accountDisplayString();
- default:
- return QVariant();
- }
+ case TypeColumn: {
+ auto typeStr = account->typeString();
+ typeStr[0] = typeStr[0].toUpper();
+ return typeStr;
+ }
- case Qt::ToolTipRole:
- return account->username();
+ case ProfileNameColumn: {
+ return account->profileName();
+ }
- case PointerRole:
- return qVariantFromValue(account);
+ default:
+ return QVariant();
+ }
- case Qt::CheckStateRole:
- switch (index.column())
- {
- case ActiveColumn:
- return account == m_activeAccount ? Qt::Checked : Qt::Unchecked;
- }
+ case Qt::ToolTipRole:
+ return account->accountDisplayString();
- default:
- return QVariant();
+ case PointerRole:
+ return qVariantFromValue(account);
+
+ case Qt::CheckStateRole:
+ switch (index.column())
+ {
+ case NameColumn:
+ return account == m_activeAccount ? Qt::Checked : Qt::Unchecked;
+ }
+
+ default:
+ return QVariant();
}
}
-QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant AccountList::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
case Qt::DisplayRole:
switch (section)
{
- case ActiveColumn:
- return tr("Active?");
-
case NameColumn:
- return tr("Name");
-
+ return tr("Account");
+ case TypeColumn:
+ return tr("Type");
+ case ProfileNameColumn:
+ return tr("Profile");
default:
return QVariant();
}
@@ -237,8 +248,11 @@ QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation,
switch (section)
{
case NameColumn:
- return tr("The name of the version.");
-
+ return tr("User name of the account.");
+ case TypeColumn:
+ return tr("Type of the account - Mojang or MSA.");
+ case ProfileNameColumn:
+ return tr("Name of the Minecraft profile associated with the account.");
default:
return QVariant();
}
@@ -248,18 +262,18 @@ QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation,
}
}
-int MojangAccountList::rowCount(const QModelIndex &) const
+int AccountList::rowCount(const QModelIndex &) const
{
// Return count
return count();
}
-int MojangAccountList::columnCount(const QModelIndex &) const
+int AccountList::columnCount(const QModelIndex &) const
{
- return 2;
+ return NUM_COLUMNS;
}
-Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const
+Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
@@ -269,7 +283,7 @@ Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const
return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
-bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role)
+bool AccountList::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
@@ -280,8 +294,8 @@ bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value,
{
if(value == Qt::Checked)
{
- MojangAccountPtr account = this->at(index.row());
- this->setActiveAccount(account->username());
+ MinecraftAccountPtr account = at(index.row());
+ setActiveAccount(account->profileId());
}
}
@@ -289,31 +303,21 @@ bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value,
return true;
}
-void MojangAccountList::updateListData(QList<MojangAccountPtr> versions)
-{
- beginResetModel();
- m_accounts = versions;
- endResetModel();
-}
-
-bool MojangAccountList::loadList(const QString &filePath)
+bool AccountList::loadList()
{
- QString path = filePath;
- if (path.isEmpty())
- path = m_listFilePath;
- if (path.isEmpty())
+ if (m_listFilePath.isEmpty())
{
qCritical() << "Can't load Mojang account list. No file path given and no default set.";
return false;
}
- QFile file(path);
+ QFile file(m_listFilePath);
// 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))
{
- qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8();
+ qCritical() << QString("Failed to read the account list file (%1).").arg(m_listFilePath).toUtf8();
return false;
}
@@ -343,121 +347,168 @@ bool MojangAccountList::loadList(const QString &filePath)
QJsonObject root = jsonDoc.object();
// Make sure the format version matches.
- if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION)
- {
- QString newName = "accounts-old.json";
- qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to"
- << newName;
+ auto listVersion = root.value("formatVersion").toVariant().toInt();
+ switch(listVersion) {
+ case AccountListVersion::MojangOnly: {
+ return loadV2(root);
+ }
+ break;
+ case AccountListVersion::MojangMSA: {
+ return loadV3(root);
+ }
+ break;
+ default: {
+ QString newName = "accounts-old.json";
+ qWarning() << "Unknown f