mirror of
https://github.com/MultiMC/MultiMC5.git
synced 2024-10-06 06:50:16 +00:00
NOISSUE Remove Mojang accounts
They are being phased out on September 19 2023
This commit is contained in:
parent
3cf0a37a28
commit
d711feedf7
@ -212,26 +212,18 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/MinecraftAccount.h
|
||||
minecraft/auth/Parsers.cpp
|
||||
minecraft/auth/Parsers.h
|
||||
minecraft/auth/Yggdrasil.cpp
|
||||
minecraft/auth/Yggdrasil.h
|
||||
|
||||
minecraft/auth/flows/AuthFlow.cpp
|
||||
minecraft/auth/flows/AuthFlow.h
|
||||
minecraft/auth/flows/Mojang.cpp
|
||||
minecraft/auth/flows/Mojang.h
|
||||
minecraft/auth/flows/MSA.cpp
|
||||
minecraft/auth/flows/MSA.h
|
||||
|
||||
minecraft/auth/steps/EntitlementsStep.cpp
|
||||
minecraft/auth/steps/EntitlementsStep.h
|
||||
minecraft/auth/steps/ForcedMigrationStep.cpp
|
||||
minecraft/auth/steps/ForcedMigrationStep.h
|
||||
minecraft/auth/steps/GetSkinStep.cpp
|
||||
minecraft/auth/steps/GetSkinStep.h
|
||||
minecraft/auth/steps/LauncherLoginStep.cpp
|
||||
minecraft/auth/steps/LauncherLoginStep.h
|
||||
minecraft/auth/steps/MigrationEligibilityStep.cpp
|
||||
minecraft/auth/steps/MigrationEligibilityStep.h
|
||||
minecraft/auth/steps/MinecraftProfileStep.cpp
|
||||
minecraft/auth/steps/MinecraftProfileStep.h
|
||||
minecraft/auth/steps/MSAStep.cpp
|
||||
@ -242,8 +234,6 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/steps/XboxProfileStep.h
|
||||
minecraft/auth/steps/XboxUserStep.cpp
|
||||
minecraft/auth/steps/XboxUserStep.h
|
||||
minecraft/auth/steps/YggdrasilStep.cpp
|
||||
minecraft/auth/steps/YggdrasilStep.h
|
||||
|
||||
minecraft/gameoptions/GameOptions.h
|
||||
minecraft/gameoptions/GameOptions.cpp
|
||||
@ -776,8 +766,6 @@ SET(LAUNCHER_SOURCES
|
||||
ui/dialogs/ExportInstanceDialog.h
|
||||
ui/dialogs/IconPickerDialog.cpp
|
||||
ui/dialogs/IconPickerDialog.h
|
||||
ui/dialogs/LoginDialog.cpp
|
||||
ui/dialogs/LoginDialog.h
|
||||
ui/dialogs/MSALoginDialog.cpp
|
||||
ui/dialogs/MSALoginDialog.h
|
||||
ui/dialogs/NewComponentDialog.cpp
|
||||
@ -895,7 +883,6 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/IconPickerDialog.ui
|
||||
ui/dialogs/MSALoginDialog.ui
|
||||
ui/dialogs/AboutDialog.ui
|
||||
ui/dialogs/LoginDialog.ui
|
||||
ui/dialogs/EditAccountDialog.ui
|
||||
ui/dialogs/CreateShortcutDialog.ui
|
||||
ui/dialogs/ModrinthExportDialog.ui
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/EditAccountDialog.h"
|
||||
#include "ui/dialogs/ProfileSetupDialog.h"
|
||||
#include "ui/dialogs/LoginDialog.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
@ -236,33 +235,12 @@ void LaunchController::login() {
|
||||
if (button == QMessageBox::StandardButton::Ok) {
|
||||
auto accounts = APPLICATION->accounts();
|
||||
bool isDefault = accounts->defaultAccount() == m_accountToUse;
|
||||
bool msa = m_accountToUse->isMSA();
|
||||
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId())));
|
||||
MinecraftAccountPtr newAccount = nullptr;
|
||||
if (msa) {
|
||||
if(BuildConfig.BUILD_PLATFORM == "osx64") {
|
||||
CustomMessageBox::selectable(
|
||||
m_parentWidget,
|
||||
tr("Microsoft Accounts not available"),
|
||||
tr(
|
||||
"Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated MultiMC.\n\n"
|
||||
"Please update both your operating system and MultiMC."
|
||||
),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
emitFailed(tr("Attempted to re-login to a Microsoft account on an unsupported platform"));
|
||||
return;
|
||||
}
|
||||
newAccount = MSALoginDialog::newAccount(
|
||||
m_parentWidget,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
);
|
||||
} else {
|
||||
newAccount = LoginDialog::newAccount(
|
||||
m_parentWidget,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
);
|
||||
}
|
||||
if (newAccount) {
|
||||
accounts->addAccount(newAccount);
|
||||
if (isDefault) {
|
||||
|
@ -238,71 +238,6 @@ bool entitlementFromJSONV3(const QJsonObject &parent, MinecraftEntitlement & 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()) {
|
||||
@ -310,28 +245,15 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
return false;
|
||||
}
|
||||
auto typeS = typeV.toString();
|
||||
if(typeS == "MSA") {
|
||||
type = AccountType::MSA;
|
||||
} else if (typeS == "Mojang") {
|
||||
type = AccountType::Mojang;
|
||||
} else {
|
||||
if(typeS != "MSA") {
|
||||
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);
|
||||
mustMigrateToMSA = data.value("mustMigrateToMSA").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");
|
||||
if(!entitlementFromJSONV3(data, minecraftEntitlement)) {
|
||||
@ -348,25 +270,12 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
|
||||
QJsonObject AccountData::saveState() const {
|
||||
QJsonObject output;
|
||||
if(type == AccountType::Mojang) {
|
||||
output["type"] = "Mojang";
|
||||
if(legacy) {
|
||||
output["legacy"] = true;
|
||||
}
|
||||
if(canMigrateToMSA) {
|
||||
output["canMigrateToMSA"] = true;
|
||||
}
|
||||
if(mustMigrateToMSA) {
|
||||
output["mustMigrateToMSA"] = 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");
|
||||
@ -374,45 +283,10 @@ QJsonObject AccountData::saveState() const {
|
||||
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(QRegularExpression("[{-}]"));
|
||||
}
|
||||
|
||||
QString AccountData::profileId() const {
|
||||
return minecraftProfile.id;
|
||||
}
|
||||
@ -427,20 +301,10 @@ QString AccountData::profileName() const {
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString AccountData::lastError() const {
|
||||
|
@ -54,21 +54,11 @@ enum class AccountState {
|
||||
|
||||
struct AccountData {
|
||||
QJsonObject saveState() const;
|
||||
bool resumeStateFromV2(QJsonObject data);
|
||||
bool resumeStateFromV3(QJsonObject data);
|
||||
|
||||
//! userName for Mojang accounts, gamertag for MSA
|
||||
//! Xbox gamertag
|
||||
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;
|
||||
|
||||
@ -77,11 +67,6 @@ struct AccountData {
|
||||
|
||||
QString lastError() const;
|
||||
|
||||
AccountType type = AccountType::MSA;
|
||||
bool legacy = false;
|
||||
bool canMigrateToMSA = false;
|
||||
bool mustMigrateToMSA = false;
|
||||
|
||||
Katabasis::Token msaToken;
|
||||
Katabasis::Token userToken;
|
||||
Katabasis::Token xboxApiToken;
|
||||
|
@ -265,12 +265,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
case NameColumn:
|
||||
return account->accountDisplayString();
|
||||
|
||||
case TypeColumn: {
|
||||
auto typeStr = account->typeString();
|
||||
typeStr[0] = typeStr[0].toUpper();
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
case StatusColumn: {
|
||||
switch(account->accountState()) {
|
||||
case AccountState::Unchecked: {
|
||||
@ -304,18 +298,6 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
return account->profileName();
|
||||
}
|
||||
|
||||
case MigrationColumn: {
|
||||
if(account->isMSA()) {
|
||||
return tr("N/A", "Can Migrate?");
|
||||
}
|
||||
if (account->canMigrate()) {
|
||||
return tr("Yes", "Can Migrate?");
|
||||
}
|
||||
else {
|
||||
return tr("No", "Can Migrate?");
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -347,12 +329,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
|
||||
{
|
||||
case NameColumn:
|
||||
return tr("Account");
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
case StatusColumn:
|
||||
return tr("Status");
|
||||
case MigrationColumn:
|
||||
return tr("Can Migrate?");
|
||||
case ProfileNameColumn:
|
||||
return tr("Profile");
|
||||
default:
|
||||
@ -364,12 +342,8 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
|
||||
{
|
||||
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 Microsoft account?");
|
||||
case ProfileNameColumn:
|
||||
return tr("Name of the Minecraft profile associated with the account.");
|
||||
default:
|
||||
@ -469,7 +443,8 @@ bool AccountList::loadList()
|
||||
auto listVersion = root.value("formatVersion").toVariant().toInt();
|
||||
switch(listVersion) {
|
||||
case AccountListVersion::MojangOnly: {
|
||||
return loadV2(root);
|
||||
// NOTE: Mojang-only version of the account list format is gone.
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case AccountListVersion::MojangMSA: {
|
||||
@ -486,39 +461,6 @@ bool AccountList::loadList()
|
||||
}
|
||||
}
|
||||
|
||||
bool AccountList::loadV2(QJsonObject& root) {
|
||||
beginResetModel();
|
||||
auto defaultUserName = root.value("activeAccount").toString("");
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
for (QJsonValue accountVal : accounts)
|
||||
{
|
||||
QJsonObject accountObj = accountVal.toObject();
|
||||
MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
|
||||
if (account.get() != nullptr)
|
||||
{
|
||||
auto profileId = account->profileId();
|
||||
if(!profileId.size()) {
|
||||
continue;
|
||||
}
|
||||
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 (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
|
||||
m_defaultAccount = account;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Failed to load an account.";
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccountList::loadV3(QJsonObject& root) {
|
||||
beginResetModel();
|
||||
QJsonArray accounts = root.value("accounts").toArray();
|
||||
|
@ -40,8 +40,6 @@ public:
|
||||
// TODO: Add icon column.
|
||||
NameColumn = 0,
|
||||
ProfileNameColumn,
|
||||
MigrationColumn,
|
||||
TypeColumn,
|
||||
StatusColumn,
|
||||
|
||||
NUM_COLUMNS
|
||||
|
@ -28,7 +28,7 @@ struct AuthSession
|
||||
|
||||
// client token
|
||||
QString client_token;
|
||||
// account user name
|
||||
// account user name FIXME: gone
|
||||
QString username;
|
||||
// combined session ID
|
||||
QString session;
|
||||
|
@ -29,20 +29,12 @@
|
||||
#include <QPainter>
|
||||
|
||||
#include "flows/MSA.h"
|
||||
#include "flows/Mojang.h"
|
||||
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
|
||||
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
||||
}
|
||||
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json) {
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
if(account->data.resumeStateFromV2(json)) {
|
||||
return account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
@ -52,19 +44,9 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString &username)
|
||||
{
|
||||
MinecraftAccountPtr account = new MinecraftAccount();
|
||||
account->data.type = AccountType::Mojang;
|
||||
account->data.yggdrasilToken.extra["userName"] = username;
|
||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
||||
return account;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
|
||||
MinecraftAccountPtr MinecraftAccount::createBlank()
|
||||
{
|
||||
MinecraftAccountPtr account(new MinecraftAccount());
|
||||
account->data.type = AccountType::MSA;
|
||||
return account;
|
||||
}
|
||||
|
||||
@ -99,13 +81,6 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::setCurrentTask(AccountTask * t
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
return setCurrentTask(new MojangLogin(&data, password));
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
@ -117,12 +92,7 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
if(data.type == AccountType::MSA) {
|
||||
return setCurrentTask(new MSASilent(&data));
|
||||
}
|
||||
else {
|
||||
return setCurrentTask(new MojangRefresh(&data));
|
||||
}
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::currentTask() {
|
||||
@ -147,17 +117,10 @@ void MinecraftAccount::authFailed(QString reason)
|
||||
}
|
||||
break;
|
||||
case AccountTaskState::STATE_FAILED_HARD: {
|
||||
if(isMSA()) {
|
||||
data.msaToken.token = QString();
|
||||
data.msaToken.refresh_token = QString();
|
||||
data.msaToken.validity = Katabasis::Validity::None;
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
}
|
||||
else {
|
||||
data.yggdrasilToken.token = QString();
|
||||
data.yggdrasilToken.validity = Katabasis::Validity::None;
|
||||
data.validity_ = Katabasis::Validity::None;
|
||||
}
|
||||
emit changed();
|
||||
}
|
||||
break;
|
||||
@ -230,16 +193,16 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
|
||||
// the user name. you have to have an user name
|
||||
// FIXME: not with MSA
|
||||
session->username = data.userName();
|
||||
// session->username = data.userName();
|
||||
// volatile auth token
|
||||
session->access_token = data.accessToken();
|
||||
// the semi-permanent client token
|
||||
session->client_token = data.clientToken();
|
||||
// session->client_token = data.clientToken();
|
||||
// profile name
|
||||
session->player_name = data.profileName();
|
||||
// profile ID
|
||||
session->uuid = data.profileId();
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
// 'msa' from september 19th 2023
|
||||
session->user_type = typeString();
|
||||
if (!session->access_token.isEmpty())
|
||||
{
|
||||
|
@ -69,9 +69,7 @@ public: /* construction */
|
||||
//! Default constructor
|
||||
explicit MinecraftAccount(QObject *parent = 0);
|
||||
|
||||
static MinecraftAccountPtr createFromUsername(const QString &username);
|
||||
|
||||
static MinecraftAccountPtr createBlankMSA();
|
||||
static MinecraftAccountPtr createBlank();
|
||||
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
||||
@ -84,12 +82,6 @@ private:
|
||||
|
||||
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.
|
||||
*/
|
||||
shared_qobject_ptr<AccountTask> login(QString password);
|
||||
|
||||
shared_qobject_ptr<AccountTask> loginMSA();
|
||||
|
||||
shared_qobject_ptr<AccountTask> refresh();
|
||||
@ -105,10 +97,6 @@ public: /* queries */
|
||||
return data.accountDisplayString();
|
||||
}
|
||||
|
||||
QString mojangUserName() const {
|
||||
return data.userName();
|
||||
}
|
||||
|
||||
QString accessToken() const {
|
||||
return data.accessToken();
|
||||
}
|
||||
@ -123,14 +111,6 @@ public: /* queries */
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
bool canMigrate() const {
|
||||
return data.canMigrateToMSA;
|
||||
}
|
||||
|
||||
bool isMSA() const {
|
||||
return data.type == AccountType::MSA;
|
||||
}
|
||||
|
||||
bool ownsMinecraft() const {
|
||||
return data.minecraftEntitlement.ownsMinecraft;
|
||||
}
|
||||
@ -140,23 +120,8 @@ public: /* queries */
|
||||
}
|
||||
|
||||
QString typeString() const {
|
||||
switch(data.type) {
|
||||
case AccountType::Mojang: {
|
||||
if(data.legacy) {
|
||||
return "legacy";
|
||||
}
|
||||
return "mojang";
|
||||
}
|
||||
break;
|
||||
case AccountType::MSA: {
|
||||
return "msa";
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap getFace() const;
|
||||
|
||||
|
@ -247,36 +247,6 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseForcedMigrationResponse(QByteArray & data, bool& result) {
|
||||
qDebug() << "Parsing Rollout response...";
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
|
||||
if(jsonError.error) {
|
||||
qWarning() << "Failed to parse response from https://api.minecraftservices.com/rollout/v1/msamigrationforced as JSON: " << jsonError.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
QString feature;
|
||||
if(!getString(obj.value("feature"), feature)) {
|
||||
qWarning() << "Rollout feature is not a string";
|
||||
return false;
|
||||
}
|
||||
if(feature != "msamigrationforced") {
|
||||
qWarning() << "Rollout feature is not what we expected (msamigrationforced), but is instead \"" << feature << "\"";
|
||||
return false;
|
||||
}
|
||||
if(!getBool(obj.value("rollout"), result)) {
|
||||
qWarning() << "Rollout feature is not a string";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parseRolloutResponse(QByteArray & data, bool& result) {
|
||||
qDebug() << "Parsing Rollout response...";
|
||||
#ifndef NDEBUG
|
||||
|
@ -1,331 +0,0 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Yggdrasil.h"
|
||||
#include "AccountData.h"
|
||||
|
||||
#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)
|
||||
{
|
||||
changeState(AccountTaskState::STATE_CREATED);
|
||||
}
|
||||
|
||||
void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content) {
|
||||
changeState(AccountTaskState::STATE_WORKING);
|
||||
|
||||
QNetworkRequest netRequest(endpoint);
|
||||
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
m_netReply = APPLICATION->network()->post(netRequest, content);
|
||||
connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply);
|
||||
connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers);
|
||||
connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers);
|
||||
connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors);
|
||||
timeout_keeper.setSingleShot(true);
|
||||
timeout_keeper.start(timeout_max);
|
||||
counter.setSingleShot(false);
|
||||
counter.start(time_step);
|
||||
progress(0, timeout_max);
|
||||
connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout);
|
||||
connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
|
||||
}
|
||||
|
||||
void Yggdrasil::executeTask() {
|
||||
}
|
||||
|
||||
void Yggdrasil::refresh() {
|
||||
start();
|
||||
/*
|
||||
* {
|
||||
* "clientToken": "client identifier"
|
||||
* "accessToken": "current access token to be refreshed"
|
||||
* "selectedProfile": // specifying this causes errors
|
||||
* {
|
||||
* "id": "profile ID"
|
||||
* "name": "profile name"
|
||||
* }
|
||||
* "requestUser": true/false // request the user structure
|
||||
* }
|
||||
*/
|
||||
QJsonObject req;
|
||||
req.insert("clientToken", m_data->clientToken());
|
||||
req.insert("accessToken", m_data->accessToken());
|
||||
/*
|
||||
{
|
||||
auto currentProfile = m_account->currentProfile();
|
||||
QJsonObject profile;
|
||||
profile.insert("id", currentProfile->id());
|
||||
profile.insert("name", currentProfile->name());
|
||||
req.insert("selectedProfile", profile);
|
||||
}
|
||||
*/
|
||||
req.insert("requestUser", false);
|
||||
QJsonDocument doc(req);
|
||||
|
||||
QUrl reqUrl("https://authserver.mojang.com/refresh");
|
||||
QByteArray requestData = doc.toJson();
|
||||
|
||||
sendRequest(reqUrl, requestData);
|
||||
}
|
||||
|
||||
void Yggdrasil::login(QString password) {
|
||||
start();
|
||||
/*
|
||||
* {
|
||||
* "agent": { // optional
|
||||
* "name": "Minecraft", // So far this is the only encountered value
|
||||
* "version": 1 // This number might be increased
|
||||
* // by the vanilla client in the future
|
||||
* },
|
||||
* "username": "mojang account name", // Can be an email address or player name for
|
||||
* // unmigrated accounts
|
||||
* "password": "mojang account password",
|
||||
* "clientToken": "client identifier", // optional
|
||||
* "requestUser": true/false // request the user structure
|
||||
* }
|
||||
*/
|
||||
QJsonObject req;
|
||||
|
||||
{
|
||||
QJsonObject agent;
|
||||
// C++ makes string literals void* for some stupid reason, so we have to tell it
|
||||
// QString... Thanks Obama.
|
||||
agent.insert("name", QString("Minecraft"));
|
||||
agent.insert("version", 1);
|
||||
req.insert("agent", agent);
|
||||
}
|
||||
|
||||
req.insert("username", m_data->userName());
|
||||
req.insert("password", password);
|
||||
req.insert("requestUser", false);
|
||||
|
||||
// If we already have a client token, give it to the server.
|
||||
// Otherwise, let the server give us one.
|
||||
|
||||
m_data->generateClientTokenIfMissing();
|
||||
req.insert("clientToken", m_data->clientToken());
|
||||
|
||||
QJsonDocument doc(req);
|
||||
|
||||
QUrl reqUrl("https://authserver.mojang.com/authenticate");
|
||||
QNetworkRequest netRequest(reqUrl);
|
||||
QByteArray requestData = doc.toJson();
|
||||
|
||||
sendRequest(reqUrl, requestData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Yggdrasil::refreshTimers(qint64, qint64) {
|
||||
timeout_keeper.stop();
|
||||
timeout_keeper.start(timeout_max);
|
||||
progress(count = 0, timeout_max);
|
||||
}
|
||||
|
||||
void Yggdrasil::heartbeat() {
|
||||
count += time_step;
|
||||
progress(count, timeout_max);
|
||||
}
|
||||
|
||||
bool Yggdrasil::abort() {
|
||||
progress(timeout_max, timeout_max);
|
||||
// TODO: actually use this in a meaningful way
|
||||
m_aborted = Yggdrasil::BY_USER;
|
||||
m_netReply->abort();
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
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.";
|
||||
|
||||
// qDebug() << responseData;
|
||||
// If we already have a client token, make sure the one the server gave us matches our
|
||||
// existing one.
|
||||
QString clientToken = responseData.value("clientToken").toString("");
|
||||
if (clientToken.isEmpty()) {
|
||||
// Fail if the server gave us an empty client token
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
|
||||
return;
|
||||
}
|
||||
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."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, we set the access token.
|
||||
qDebug() << "Getting access token.";
|
||||
QString accessToken = responseData.value("accessToken").toString("");
|
||||
if (accessToken.isEmpty()) {
|
||||
// Fail if the server didn't give us an access token.
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
|
||||
return;
|
||||
}
|
||||
// Set the access token.
|
||||
m_data->yggdrasilToken.token = accessToken;
|
||||
m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
|
||||
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||
|
||||
// We've made it through the minefield of possible errors. Return true to indicate that
|
||||
// we've succeeded.
|
||||
qDebug() << "Finished reading authentication response.";
|
||||
changeState(AccountTaskState::STATE_SUCCEEDED);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Try to parse the response regardless of the response code.
|
||||
// Sometimes the auth server will give more information and an error code.
|
||||
QJsonParseError jsonError;
|
||||
QByteArray replyData = m_netReply->readAll();
|
||||
QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
|
||||
// Check the response code.
|
||||
int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (responseCode == 200) {
|
||||
// If the response code was 200, then there shouldn't be an error. Make sure
|
||||
// anyways.
|
||||
// Also, sometimes an empty reply indicates success. If there was no data received,
|
||||
// pass an empty json object to the processResponse function.
|
||||
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)
|
||||
);
|
||||
qCritical() << replyData;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the response code was not 200, then Yggdrasil may have given us information
|
||||
// about the error.
|
||||
// If we can parse the response, then get information from it. Otherwise just say
|
||||
// there was an unknown error.
|
||||
if (jsonError.error == QJsonParseError::NoError) {
|
||||
// We were able to parse the server's response. Woo!
|
||||
// Call processError. If a subclass has overridden it then they'll handle their
|
||||
// stuff there.
|
||||
qDebug() << "The request failed, but the server gave us an error message. Processing error.";
|
||||
processError(doc.object());
|
||||
}
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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("")
|
||||
}
|
||||
);
|
||||
changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
|
||||
}
|
||||
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."));
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AccountTask.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
#include <qsslerror.h>
|
||||
|
||||
#include "MinecraftAccount.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
/**
|
||||
* A Yggdrasil task is a task that performs an operation on a given mojang account.
|
||||
*/
|
||||
class Yggdrasil : public AccountTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Yggdrasil(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
virtual ~Yggdrasil() = default;
|
||||
|
||||
void refresh();
|
||||
void login(QString password);
|
||||
|
||||
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;
|
||||
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
||||
/**
|
||||
* Processes the response received from the server.
|
||||
* If an error occurred, this should emit a failed signal.
|
||||
* If Yggdrasil gave an error response, it should call setError() first, and then return false.
|
||||
* Otherwise, it should return true.
|
||||
* Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
|
||||
* an empty QJsonObject.
|
||||
*/
|
||||
void processResponse(QJsonObject responseData);
|
||||
|
||||
/**
|
||||
* Processes an error response received from the server.
|
||||
* The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
|
||||
* \returns a QString error message that will be passed to emitFailed.
|
||||
*/
|
||||
virtual void processError(QJsonObject responseData);
|
||||
|
||||
protected slots:
|
||||
void processReply();
|
||||
void refreshTimers(qint64, qint64);
|
||||
void heartbeat();
|
||||
void sslErrors(QList<QSslError>);
|
||||
void abortByTimeout();
|
||||
|
||||
public slots:
|
||||
virtual bool abort() override;
|
||||
|
||||
private:
|
||||
void sendRequest(QUrl endpoint, QByteArray content);
|
||||
|
||||
protected:
|
||||
QNetworkReply *m_netReply = nullptr;
|
||||
QTimer timeout_keeper;
|
||||
QTimer counter;
|
||||
int count = 0; // num msec since time reset
|
||||
|
||||
const int timeout_max = 30000;
|
||||
const int time_step = 50;
|
||||
};
|
@ -9,7 +9,6 @@
|
||||
|
||||
#include <katabasis/DeviceFlow.h>
|
||||
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
@ -21,10 +21,7 @@ MSASilent::MSASilent(AccountData* data, QObject* parent) : AuthFlow(data, parent
|
||||
m_steps.append(new GetSkinStep(m_data));
|
||||
}
|
||||
|
||||
MSAInteractive::MSAInteractive(
|
||||
AccountData* data,
|
||||
QObject* parent
|
||||
) : AuthFlow(data, parent) {
|
||||
MSAInteractive::MSAInteractive(AccountData* data, QObject* parent) : AuthFlow(data, parent) {
|
||||
m_steps.append(new MSAStep(m_data, MSAStep::Action::Login));
|
||||
m_steps.append(new XboxUserStep(m_data));
|
||||
m_steps.append(new XboxAuthorizationStep(m_data, &m_data->xboxApiToken, "http://xboxlive.com", "Xbox"));
|
||||
|
@ -1,30 +0,0 @@
|
||||
#include "Mojang.h"
|
||||
|
||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||
#include "minecraft/auth/steps/MinecraftProfileStep.h"
|
||||
#include "minecraft/auth/steps/ForcedMigrationStep.h"
|
||||
#include "minecraft/auth/steps/MigrationEligibilityStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
|
||||
MojangRefresh::MojangRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
m_steps.append(new YggdrasilStep(m_data, QString()));
|
||||
m_steps.append(new ForcedMigrationStep(m_data));
|
||||
m_steps.append(new MinecraftProfileStep(m_data));
|
||||
m_steps.append(new MigrationEligibilityStep(m_data));
|
||||
m_steps.append(new GetSkinStep(m_data));
|
||||
}
|
||||
|
||||
MojangLogin::MojangLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent
|
||||
): AuthFlow(data, parent), m_password(password) {
|
||||
m_steps.append(new YggdrasilStep(m_data, m_password));
|
||||
m_steps.append(new ForcedMigrationStep(m_data));
|
||||
m_steps.append(new MinecraftProfileStep(m_data));
|
||||
m_steps.append(new MigrationEligibilityStep(m_data));
|
||||
m_steps.append(new GetSkinStep(m_data));
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class MojangRefresh : public AuthFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MojangRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
};
|
||||
|
||||
class MojangLogin : public AuthFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MojangLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QObject *parent = 0
|
||||
);
|
||||
|
||||
private:
|
||||
QString m_password;
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
#include "ForcedMigrationStep.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
ForcedMigrationStep::ForcedMigrationStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
|
||||
ForcedMigrationStep::~ForcedMigrationStep() noexcept = default;
|
||||
|
||||
QString ForcedMigrationStep::describe() {
|
||||
return tr("Checking for migration eligibility.");
|
||||
}
|
||||
|
||||
void ForcedMigrationStep::perform() {
|
||||
auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigrationforced");
|
||||
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);
|
||||
connect(requestor, &AuthRequest::finished, this, &ForcedMigrationStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
void ForcedMigrationStep::rehydrate() {
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void ForcedMigrationStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError) {
|
||||
Parsers::parseForcedMigrationResponse(data, m_data->mustMigrateToMSA);
|
||||
}
|
||||
if(m_data->mustMigrateToMSA) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_MUST_MIGRATE, tr("The account must be migrated to a Microsoft account."));
|
||||
}
|
||||
else {
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got forced migration flags"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class ForcedMigrationStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ForcedMigrationStep(AccountData *data);
|
||||
virtual ~ForcedMigrationStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
||||
|
@ -1,45 +0,0 @@
|
||||
#include "MigrationEligibilityStep.h"
|
||||
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
|
||||
MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
|
||||
MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
|
||||
|
||||
QString MigrationEligibilityStep::describe() {
|
||||
return tr("Checking for migration eligibility.");
|
||||
}
|
||||
|
||||
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);
|
||||
connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
|
||||
requestor->get(request);
|
||||
}
|
||||
|
||||
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());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError) {
|
||||
Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
|
||||
}
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Got migration flags"));
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class MigrationEligibilityStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MigrationEligibilityStep(AccountData *data);
|
||||
virtual ~MigrationEligibilityStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
@ -43,11 +43,6 @@ void MinecraftProfileStep::onRequestDone(
|
||||
qDebug() << data;
|
||||
#endif
|
||||
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) {
|
||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
||||
}
|
||||
m_data->minecraftProfile = MinecraftProfile();
|
||||
emit finished(
|
||||
AccountTaskState::STATE_SUCCEEDED,
|
||||
@ -79,11 +74,6 @@ void MinecraftProfileStep::onRequestDone(
|
||||
return;
|
||||
}
|
||||
|
||||
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.")
|
||||
|
@ -1,51 +0,0 @@
|
||||
#include "YggdrasilStep.h"
|
||||
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
|
||||
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);
|
||||
connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded);
|
||||
}
|
||||
|
||||
YggdrasilStep::~YggdrasilStep() noexcept = default;
|
||||
|
||||
QString YggdrasilStep::describe() {
|
||||
return tr("Logging in with Mojang account.");
|
||||
}
|
||||
|
||||
void YggdrasilStep::rehydrate() {
|
||||
// NOOP, for now.
|
||||
}
|
||||
|
||||
void YggdrasilStep::perform() {
|
||||
if(m_password.size()) {
|
||||
m_yggdrasil->login(m_password);
|
||||
}
|
||||
else {
|
||||
m_yggdrasil->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthSucceeded() {
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
|
||||
}
|
||||
|
||||
void YggdrasilStep::onAuthFailed() {
|
||||
// TODO: hook these in again, expand to MSA
|
||||
// m_error = m_yggdrasil->m_error;
|
||||
// m_aborted = m_yggdrasil->m_aborted;
|
||||
|
||||
auto state = m_yggdrasil->taskState();
|
||||
QString errorMessage = tr("Mojang user authentication failed.");
|
||||
|
||||
// NOTE: soft error in the first step means 'offline'
|
||||
if(state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
state = AccountTaskState::STATE_OFFLINE;
|
||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
||||
}
|
||||
emit finished(state, errorMessage);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
class Yggdrasil;
|
||||
|
||||
class YggdrasilStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit YggdrasilStep(AccountData *data, QString password);
|
||||
virtual ~YggdrasilStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onAuthSucceeded();
|
||||
void onAuthFailed();
|
||||
|
||||
private:
|
||||
Yggdrasil *m_yggdrasil = nullptr;
|
||||
QString m_password;
|
||||
};
|
@ -1,119 +0,0 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "LoginDialog.h"
|
||||
#include "ui_LoginDialog.h"
|
||||
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->progressBar->setVisible(false);
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
LoginDialog::~LoginDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
// Stage 1: User interaction
|
||||
void LoginDialog::accept()
|
||||
{
|
||||
setUserInputsEnabled(false);
|
||||
ui->progressBar->setVisible(true);
|
||||
|
||||
// Setup the login task and start it
|
||||
m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
|
||||
m_loginTask = m_account->login(ui->passTextBox->text());
|
||||
connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded);
|
||||
connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
|
||||
connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
|
||||
m_loginTask->start();
|
||||
}
|
||||
|
||||
void LoginDialog::setUserInputsEnabled(bool enable)
|
||||
{
|
||||
ui->userTextBox->setEnabled(enable);
|
||||
ui->passTextBox->setEnabled(enable);
|
||||
ui->buttonBox->setEnabled(enable);
|
||||
}
|
||||
|
||||
// Enable the OK button only when both textboxes contain something.
|
||||
void LoginDialog::on_userTextBox_textEdited(const QString &newText)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
|
||||
}
|
||||
void LoginDialog::on_passTextBox_textEdited(const QString &newText)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
|
||||
}
|
||||
|
||||
void LoginDialog::onTaskFailed(const QString &reason)
|
||||
{
|
||||
// Set message
|
||||
auto lines = reason.split('\n');
|
||||
QString processed;
|
||||
for(auto line: lines) {
|
||||
if(line.size()) {
|
||||
processed += "<font color='red'>" + line + "</font><br />";
|
||||
}
|
||||
else {
|
||||
processed += "<br />";
|
||||
}
|
||||
}
|
||||
ui->label->setText(processed);
|
||||
|
||||
// Re-enable user-interaction
|
||||
setUserInputsEnabled(true);
|
||||
ui->progressBar->setVisible(false);
|
||||
}
|
||||
|
||||
void LoginDialog::onTaskSucceeded()
|
||||
{
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void LoginDialog::onTaskStatus(const QString &status)
|
||||
{
|
||||
ui->label->setText(status);
|
||||
}
|
||||
|
||||
void LoginDialog::onTaskProgress(qint64 current, qint64 total)
|
||||
{
|
||||
ui->progressBar->setMaximum(total);
|
||||
ui->progressBar->setValue(current);
|
||||
}
|
||||
|
||||
// Public interface
|
||||
MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)
|
||||
{
|
||||
LoginDialog dlg(parent);
|
||||
dlg.ui->label->setText(msg);
|
||||
if (dlg.exec() == QDialog::Accepted)
|
||||
{
|
||||
return dlg.m_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtCore/QEventLoop>
|
||||
|
||||
#include "minecraft/auth/MinecraftAccount.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class LoginDialog;
|
||||
}
|
||||
|
||||
class LoginDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~LoginDialog();
|
||||
|
||||
static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
|
||||
|
||||
private:
|
||||
explicit LoginDialog(QWidget *parent = 0);
|
||||
|
||||
void setUserInputsEnabled(bool enable);
|
||||
|
||||
protected
|
||||
slots:
|
||||
void accept();
|
||||
|
||||
void onTaskFailed(const QString &reason);
|
||||
void onTaskSucceeded();
|
||||
void onTaskStatus(const QString &status);
|
||||
void onTaskProgress(qint64 current, qint64 total);
|
||||
|
||||
void on_userTextBox_textEdited(const QString &newText);
|
||||
void on_passTextBox_textEdited(const QString &newText);
|
||||
|
||||
private:
|
||||
Ui::LoginDialog *ui;
|
||||
MinecraftAccountPtr m_account;
|
||||
Task::Ptr m_loginTask;
|
||||
};
|
@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LoginDialog</class>
|
||||
<widget class="QDialog" name="LoginDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>421</width>
|
||||
<height>198</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Add Account</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string notr="true">Message label placeholder.</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>Email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="passTextBox">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -38,7 +38,7 @@ int MSALoginDialog::exec() {
|
||||
ui->copyCodeButton->setVisible(false);
|
||||
|
||||
// Setup the login task and start it
|
||||
m_account = MinecraftAccount::createBlankMSA();
|
||||
m_account = MinecraftAccount::createBlank();
|
||||
m_loginTask = m_account->loginMSA();
|
||||
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/LoginDialog.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/SkinUploadDialog.h"
|
||||
@ -74,7 +73,7 @@ AccountListPage::AccountListPage(QWidget *parent)
|
||||
updateButtonStates();
|
||||
|
||||
// Xbox authentication won't work without a client identifier, so disable the button if it is missing
|
||||
ui->actionAddMicrosoft->setVisible(Secrets::hasMSAClientID());
|
||||
ui->actionAddAccount->setVisible(Secrets::hasMSAClientID());
|
||||
}
|
||||
|
||||
AccountListPage::~AccountListPage()
|
||||
@ -111,36 +110,8 @@ void AccountListPage::listChanged()
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddMojang_triggered()
|
||||
void AccountListPage::on_actionAddAccount_triggered()
|
||||
{
|
||||
MinecraftAccountPtr account = LoginDialog::newAccount(
|
||||
this,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
);
|
||||
|
||||
if (account)
|
||||
{
|
||||
m_accounts->addAccount(account);
|
||||
if (m_accounts->count() == 1) {
|
||||
m_accounts->setDefaultAccount(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddMicrosoft_triggered()
|
||||
{
|
||||
if(BuildConfig.BUILD_PLATFORM == "osx64") {
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Microsoft Accounts not available"),
|
||||
tr(
|
||||
"Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated MultiMC.\n\n"
|
||||
"Please update both your operating system and MultiMC."
|
||||
),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
return;
|
||||
}
|
||||
MinecraftAccountPtr account = MSALoginDialog::newAccount(
|
||||
this,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
|
@ -60,8 +60,7 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
void on_actionAddMojang_triggered();
|
||||
void on_actionAddMicrosoft_triggered();
|
||||
void on_actionAddAccount_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionRefresh_triggered();
|
||||
void on_actionSetDefault_triggered();
|
||||
|
@ -52,8 +52,7 @@
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAddMicrosoft"/>
|
||||
<addaction name="actionAddMojang"/>
|
||||
<addaction name="actionAddAccount"/>
|
||||
<addaction name="actionRefresh"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionSetDefault"/>
|
||||
@ -62,11 +61,6 @@
|
||||
<addaction name="actionUploadSkin"/>
|
||||
<addaction name="actionDeleteSkin"/>
|
||||
</widget>
|
||||
<action name="actionAddMojang">
|
||||
<property name="text">
|
||||
<string>Add Mojang</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
@ -98,9 +92,12 @@
|
||||
<string>Delete the currently active skin and go back to the default one</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddMicrosoft">
|
||||
<action name="actionAddAccount">
|
||||
<property name="text">
|
||||
<string>Add Microsoft</string>
|
||||
<string>Add Account</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add a Microsoft Account</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRefresh">
|
||||
|
Loading…
Reference in New Issue
Block a user