It compiles, Yggdrasil login working except error handling
This commit is contained in:
parent
7dbaa896a6
commit
c32aaf244d
@ -109,6 +109,8 @@
|
|||||||
#include "icons/IconList.h"
|
#include "icons/IconList.h"
|
||||||
#include "net/HttpMetaCache.h"
|
#include "net/HttpMetaCache.h"
|
||||||
|
|
||||||
|
#include "ui/GuiUtil.h"
|
||||||
|
|
||||||
#include "java/JavaInstallList.h"
|
#include "java/JavaInstallList.h"
|
||||||
|
|
||||||
#include "updater/ExternalUpdater.h"
|
#include "updater/ExternalUpdater.h"
|
||||||
|
@ -233,8 +233,6 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/MinecraftAccount.h
|
minecraft/auth/MinecraftAccount.h
|
||||||
minecraft/auth/Parsers.cpp
|
minecraft/auth/Parsers.cpp
|
||||||
minecraft/auth/Parsers.h
|
minecraft/auth/Parsers.h
|
||||||
minecraft/auth/Yggdrasil.cpp
|
|
||||||
minecraft/auth/Yggdrasil.h
|
|
||||||
|
|
||||||
minecraft/auth/AuthFlow.cpp
|
minecraft/auth/AuthFlow.cpp
|
||||||
minecraft/auth/AuthFlow.h
|
minecraft/auth/AuthFlow.h
|
||||||
@ -257,6 +255,8 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/auth/steps/XboxProfileStep.h
|
minecraft/auth/steps/XboxProfileStep.h
|
||||||
minecraft/auth/steps/XboxUserStep.cpp
|
minecraft/auth/steps/XboxUserStep.cpp
|
||||||
minecraft/auth/steps/XboxUserStep.h
|
minecraft/auth/steps/XboxUserStep.h
|
||||||
|
minecraft/auth/steps/YggdrasilMinecraftProfileStep.cpp
|
||||||
|
minecraft/auth/steps/YggdrasilMinecraftProfileStep.h
|
||||||
minecraft/auth/steps/YggdrasilStep.cpp
|
minecraft/auth/steps/YggdrasilStep.cpp
|
||||||
minecraft/auth/steps/YggdrasilStep.h
|
minecraft/auth/steps/YggdrasilStep.h
|
||||||
|
|
||||||
@ -1071,8 +1071,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/IconPickerDialog.h
|
ui/dialogs/IconPickerDialog.h
|
||||||
ui/dialogs/ImportResourceDialog.cpp
|
ui/dialogs/ImportResourceDialog.cpp
|
||||||
ui/dialogs/ImportResourceDialog.h
|
ui/dialogs/ImportResourceDialog.h
|
||||||
ui/dialogs/LoginDialog.cpp
|
|
||||||
ui/dialogs/LoginDialog.h
|
|
||||||
ui/dialogs/AuthlibInjectorLoginDialog.cpp
|
ui/dialogs/AuthlibInjectorLoginDialog.cpp
|
||||||
ui/dialogs/AuthlibInjectorLoginDialog.h
|
ui/dialogs/AuthlibInjectorLoginDialog.h
|
||||||
ui/dialogs/MSALoginDialog.cpp
|
ui/dialogs/MSALoginDialog.cpp
|
||||||
|
@ -24,13 +24,16 @@
|
|||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include "net/ByteArraySink.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
|
||||||
|
|
||||||
CreateAuthlibInjectorAccount::CreateAuthlibInjectorAccount(QUrl url, MinecraftAccountPtr account, QString username)
|
CreateAuthlibInjectorAccount::CreateAuthlibInjectorAccount(QUrl url, MinecraftAccountPtr account, QString username)
|
||||||
: NetRequest(), m_url(url), m_account(account), m_username(username)
|
: NetRequest(), m_account(account), m_username(username)
|
||||||
{}
|
{
|
||||||
|
m_url = url;
|
||||||
|
m_sink.reset(new Sink(*this));
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkReply* CreateAuthlibInjectorAccount::getReply(QNetworkRequest& request)
|
QNetworkReply* CreateAuthlibInjectorAccount::getReply(QNetworkRequest& request)
|
||||||
{
|
{
|
||||||
@ -43,26 +46,34 @@ CreateAuthlibInjectorAccount::Ptr CreateAuthlibInjectorAccount::make(QUrl url, M
|
|||||||
return CreateAuthlibInjectorAccount::Ptr(new CreateAuthlibInjectorAccount(url, account, username));
|
return CreateAuthlibInjectorAccount::Ptr(new CreateAuthlibInjectorAccount(url, account, username));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateAuthlibInjectorAccount::downloadFinished()
|
auto CreateAuthlibInjectorAccount::Sink::init(QNetworkRequest& request) -> Task::State
|
||||||
{
|
{
|
||||||
if (m_state != State::Failed) {
|
return Task::State::Running;
|
||||||
QVariant header = m_reply->rawHeader("X-Authlib-Injector-API-Location");
|
}
|
||||||
|
|
||||||
|
auto CreateAuthlibInjectorAccount::Sink::write(QByteArray& data) -> Task::State
|
||||||
|
{
|
||||||
|
return Task::State::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CreateAuthlibInjectorAccount::Sink::abort() -> Task::State
|
||||||
|
{
|
||||||
|
return Task::State::Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CreateAuthlibInjectorAccount::Sink::finalize(QNetworkReply& reply) -> Task::State
|
||||||
|
{
|
||||||
|
QVariant header = reply.rawHeader("X-Authlib-Injector-API-Location");
|
||||||
|
QUrl url = m_outer.m_url;
|
||||||
if (header.isValid()) {
|
if (header.isValid()) {
|
||||||
auto location = header.toString();
|
auto location = header.toString();
|
||||||
m_url = m_url.resolved(location);
|
url = url.resolved(location);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "X-Authlib-Injector-API-Location header not found!";
|
qDebug() << "X-Authlib-Injector-API-Location header not found!";
|
||||||
}
|
}
|
||||||
m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_username, m_url.toString()));
|
|
||||||
m_state = State::Succeeded;
|
m_outer.m_account.reset(MinecraftAccount::createFromUsernameAuthlibInjector(m_outer.m_username, url.toString()));
|
||||||
emit succeeded();
|
return Task::State::Succeeded;
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
qDebug() << m_reply->readAll();
|
|
||||||
m_reply.reset();
|
|
||||||
emitFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftAccountPtr CreateAuthlibInjectorAccount::getAccount()
|
MinecraftAccountPtr CreateAuthlibInjectorAccount::getAccount()
|
||||||
|
@ -32,12 +32,26 @@ class CreateAuthlibInjectorAccount : public Net::NetRequest {
|
|||||||
|
|
||||||
MinecraftAccountPtr getAccount();
|
MinecraftAccountPtr getAccount();
|
||||||
|
|
||||||
protected slots:
|
class Sink : public Net::Sink {
|
||||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
public:
|
||||||
void downloadFinished();
|
Sink(CreateAuthlibInjectorAccount& outer) : m_outer(outer) {}
|
||||||
|
virtual ~Sink() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto init(QNetworkRequest& request) -> Task::State override;
|
||||||
|
auto write(QByteArray& data) -> Task::State override;
|
||||||
|
auto abort() -> Task::State override;
|
||||||
|
auto finalize(QNetworkReply& reply) -> Task::State override;
|
||||||
|
auto hasLocalData() -> bool override { return false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CreateAuthlibInjectorAccount& m_outer;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl m_url;
|
|
||||||
MinecraftAccountPtr m_account;
|
MinecraftAccountPtr m_account;
|
||||||
QString m_username;
|
QString m_username;
|
||||||
};
|
};
|
||||||
|
@ -35,12 +35,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "LaunchController.h"
|
#include "LaunchController.h"
|
||||||
|
#include <meta/Index.h>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "launch/steps/PrintServers.h"
|
#include "launch/steps/PrintServers.h"
|
||||||
#include "minecraft/auth/AccountData.h"
|
#include "minecraft/auth/AccountData.h"
|
||||||
#include "minecraft/auth/AccountList.h"
|
#include "minecraft/auth/AccountList.h"
|
||||||
#include "settings/MissingAuthlibInjectorBehavior.h"
|
#include "settings/MissingAuthlibInjectorBehavior.h"
|
||||||
#include "ui/pages/instance/VersionPage.h"
|
|
||||||
|
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
|
@ -72,7 +72,7 @@ int main(int argc, char* argv[])
|
|||||||
Q_INIT_RESOURCE(multimc);
|
Q_INIT_RESOURCE(multimc);
|
||||||
Q_INIT_RESOURCE(backgrounds);
|
Q_INIT_RESOURCE(backgrounds);
|
||||||
Q_INIT_RESOURCE(documents);
|
Q_INIT_RESOURCE(documents);
|
||||||
Q_INIT_RESOURCE(prismlauncher);
|
Q_INIT_RESOURCE(fjordlauncher);
|
||||||
|
|
||||||
Q_INIT_RESOURCE(pe_dark);
|
Q_INIT_RESOURCE(pe_dark);
|
||||||
Q_INIT_RESOURCE(pe_light);
|
Q_INIT_RESOURCE(pe_light);
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
/*
|
|
||||||
* Prism Launcher - Minecraft Launcher
|
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* This file incorporates work covered by the following copyright and
|
|
||||||
* permission notice:
|
|
||||||
*
|
|
||||||
* 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 <tasks/Task.h>
|
|
||||||
|
|
||||||
#include <qsslerror.h>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include "MinecraftAccount.h"
|
|
||||||
|
|
||||||
class QNetworkReply;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum for describing the state of the current task.
|
|
||||||
* Used by the getStateMessage function to determine what the status message should be.
|
|
||||||
*/
|
|
||||||
enum class AccountTaskState {
|
|
||||||
STATE_CREATED,
|
|
||||||
STATE_WORKING,
|
|
||||||
STATE_SUCCEEDED,
|
|
||||||
STATE_DISABLED, //!< MSA Client ID has changed. Tell user to reloginn
|
|
||||||
STATE_FAILED_SOFT, //!< soft failure. authentication went through partially
|
|
||||||
STATE_FAILED_HARD, //!< hard failure. main tokens are invalid
|
|
||||||
STATE_FAILED_GONE, //!< hard failure. main tokens are invalid, and the account no longer exists
|
|
||||||
STATE_OFFLINE //!< soft failure. authentication failed in the first step in a 'soft' way
|
|
||||||
};
|
|
||||||
|
|
||||||
class AccountTask : public Task {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit AccountTask(AccountData* data, QObject* parent = 0);
|
|
||||||
virtual ~AccountTask() {};
|
|
||||||
|
|
||||||
AccountTaskState m_taskState = AccountTaskState::STATE_CREATED;
|
|
||||||
|
|
||||||
AccountTaskState taskState() { return m_taskState; }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
|
||||||
void hideVerificationUriAndCode();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* Returns the state message for the given state.
|
|
||||||
* Used to set the status message for the task.
|
|
||||||
* Should be overridden by subclasses that want to change messages for a given state.
|
|
||||||
*/
|
|
||||||
virtual QString getStateMessage() const;
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
// NOTE: true -> non-terminal state, false -> terminal state
|
|
||||||
bool changeState(AccountTaskState newState, QString reason = QString());
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AccountData* m_data = nullptr;
|
|
||||||
};
|
|
@ -13,6 +13,7 @@
|
|||||||
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
#include "minecraft/auth/steps/XboxAuthorizationStep.h"
|
||||||
#include "minecraft/auth/steps/XboxProfileStep.h"
|
#include "minecraft/auth/steps/XboxProfileStep.h"
|
||||||
#include "minecraft/auth/steps/XboxUserStep.h"
|
#include "minecraft/auth/steps/XboxUserStep.h"
|
||||||
|
#include "minecraft/auth/steps/YggdrasilMinecraftProfileStep.h"
|
||||||
#include "minecraft/auth/steps/YggdrasilStep.h"
|
#include "minecraft/auth/steps/YggdrasilStep.h"
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
@ -20,7 +21,7 @@
|
|||||||
|
|
||||||
#include <Application.h>
|
#include <Application.h>
|
||||||
|
|
||||||
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(parent), m_data(data)
|
AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent, const std::optional<QString> password) : Task(parent), m_data(data)
|
||||||
{
|
{
|
||||||
if (data->type == AccountType::MSA) {
|
if (data->type == AccountType::MSA) {
|
||||||
if (action == Action::DeviceCode) {
|
if (action == Action::DeviceCode) {
|
||||||
@ -43,8 +44,8 @@ AuthFlow::AuthFlow(AccountData* data, Action action, QObject* parent) : Task(par
|
|||||||
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
m_steps.append(makeShared<MinecraftProfileStep>(m_data));
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||||
} else if (data->type == AccountType::AuthlibInjector) {
|
} else if (data->type == AccountType::AuthlibInjector) {
|
||||||
m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
|
m_steps.append(makeShared<YggdrasilStep>(m_data, password));
|
||||||
m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
|
m_steps.append(makeShared<YggdrasilMinecraftProfileStep>(m_data));
|
||||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||||
}
|
}
|
||||||
changeState(AccountTaskState::STATE_CREATED);
|
changeState(AccountTaskState::STATE_CREATED);
|
||||||
|
@ -17,7 +17,10 @@ class AuthFlow : public Task {
|
|||||||
public:
|
public:
|
||||||
enum class Action { Refresh, Login, DeviceCode };
|
enum class Action { Refresh, Login, DeviceCode };
|
||||||
|
|
||||||
explicit AuthFlow(AccountData* data, Action action = Action::Refresh, QObject* parent = 0);
|
explicit AuthFlow(AccountData* data,
|
||||||
|
Action action = Action::Refresh,
|
||||||
|
QObject* parent = 0,
|
||||||
|
std::optional<QString> password = std::nullopt);
|
||||||
virtual ~AuthFlow() = default;
|
virtual ~AuthFlow() = default;
|
||||||
|
|
||||||
void executeTask() override;
|
void executeTask() override;
|
||||||
|
@ -67,15 +67,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
|
|
||||||
{
|
|
||||||
auto account = makeShared<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::createFromUsernameAuthlibInjector(const QString& username, const QString& authlibInjectorUrl)
|
MinecraftAccountPtr MinecraftAccount::createFromUsernameAuthlibInjector(const QString& username, const QString& authlibInjectorUrl)
|
||||||
{
|
{
|
||||||
auto account = makeShared<MinecraftAccount>();
|
auto account = makeShared<MinecraftAccount>();
|
||||||
@ -143,11 +134,11 @@ QPixmap MinecraftAccount::getFace() const
|
|||||||
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
return skin.scaled(64, 64, Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode)
|
shared_qobject_ptr<AuthFlow> MinecraftAccount::login(bool useDeviceCode, std::optional<QString> password)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this));
|
m_currentTask.reset(new AuthFlow(&data, useDeviceCode ? AuthFlow::Action::DeviceCode : AuthFlow::Action::Login, this, password));
|
||||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
|
||||||
|
@ -83,7 +83,6 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
//! Default constructor
|
//! Default constructor
|
||||||
explicit MinecraftAccount(QObject* parent = 0);
|
explicit MinecraftAccount(QObject* parent = 0);
|
||||||
|
|
||||||
static MinecraftAccountPtr createFromUsername(const QString& username);
|
|
||||||
static MinecraftAccountPtr createFromUsernameAuthlibInjector(const QString& username, const QString& authlibInjectorUrl);
|
static MinecraftAccountPtr createFromUsernameAuthlibInjector(const QString& username, const QString& authlibInjectorUrl);
|
||||||
|
|
||||||
static MinecraftAccountPtr createBlankMSA();
|
static MinecraftAccountPtr createBlankMSA();
|
||||||
@ -98,7 +97,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||||||
QJsonObject saveToJson() const;
|
QJsonObject saveToJson() const;
|
||||||
|
|
||||||
public: /* manipulation */
|
public: /* manipulation */
|
||||||
shared_qobject_ptr<AuthFlow> login(bool useDeviceCode = false);
|
shared_qobject_ptr<AuthFlow> login(bool useDeviceCode = false, std::optional<QString> password = std::nullopt);
|
||||||
|
|
||||||
shared_qobject_ptr<AuthFlow> refresh();
|
shared_qobject_ptr<AuthFlow> refresh();
|
||||||
|
|
||||||
|
@ -1,354 +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 <QByteArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "BuildConfig.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(m_data->authServerUrl() + "/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(m_data->authServerUrl() + "/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();
|
|
||||||
|
|
||||||
// Get UUID here since we need it for later
|
|
||||||
// FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided
|
|
||||||
auto profile = responseData.value("selectedProfile");
|
|
||||||
if (!profile.isObject()) {
|
|
||||||
auto profiles = responseData.value("availableProfiles");
|
|
||||||
if (!profiles.isArray()) {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles."));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (profiles.toArray().isEmpty()) {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile."));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
profile = profiles.toArray().first();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto profileObj = profile.toObject();
|
|
||||||
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
|
|
||||||
if (i.key() == "name" && i.value().isString()) {
|
|
||||||
m_data->minecraftProfile.name = i->toString();
|
|
||||||
} else if (i.key() == "id" && i.value().isString()) {
|
|
||||||
m_data->minecraftProfile.id = i->toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
|
|
||||||
.arg(m_netReply->errorString())
|
|
||||||
.arg(m_netReply->error()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to parse the response regardless of the response code.
|
|
||||||
// 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,92 +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 <qsslerror.h>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#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;
|
|
||||||
};
|
|
@ -1,88 +0,0 @@
|
|||||||
#include "MinecraftProfileStepMojang.h"
|
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "Logging.h"
|
|
||||||
#include "minecraft/auth/AuthRequest.h"
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
|
||||||
#include "net/NetUtils.h"
|
|
||||||
|
|
||||||
MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
|
|
||||||
|
|
||||||
MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
|
|
||||||
|
|
||||||
QString MinecraftProfileStepMojang::describe()
|
|
||||||
{
|
|
||||||
return tr("Fetching the Minecraft profile.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStepMojang::perform()
|
|
||||||
{
|
|
||||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
|
|
||||||
QUrl url = QUrl(m_data->sessionServerUrl() + "/session/minecraft/profile/" + m_data->minecraftProfile.id);
|
|
||||||
QNetworkRequest req = QNetworkRequest(url);
|
|
||||||
AuthRequest* request = new AuthRequest(this);
|
|
||||||
connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
|
|
||||||
request->get(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStepMojang::rehydrate()
|
|
||||||
{
|
|
||||||
// NOOP, for now. We only save bools and there's nothing to check.
|
|
||||||
}
|
|
||||||
|
|
||||||
void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
|
|
||||||
QByteArray data,
|
|
||||||
QList<QNetworkReply::RawHeaderPair> headers)
|
|
||||||
{
|
|
||||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
|
||||||
requestor->deleteLater();
|
|
||||||
|
|
||||||
qCDebug(authCredentials()) << data;
|
|
||||||
if (error == QNetworkReply::ContentNotFoundError) {
|
|
||||||
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
|
||||||
if (m_data->type == AccountType::Mojang) {
|
|
||||||
m_data->minecraftEntitlement.canPlayMinecraft = false;
|
|
||||||
m_data->minecraftEntitlement.ownsMinecraft = false;
|
|
||||||
}
|
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
|
||||||
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (error != QNetworkReply::NoError) {
|
|
||||||
qWarning() << "Error getting profile:";
|
|
||||||
qWarning() << " HTTP Status: " << requestor->httpStatus_;
|
|
||||||
qWarning() << " Internal error no.: " << error;
|
|
||||||
qWarning() << " Error string: " << requestor->errorString_;
|
|
||||||
|
|
||||||
qWarning() << " Response:";
|
|
||||||
qWarning() << QString::fromUtf8(data);
|
|
||||||
|
|
||||||
if (Net::isApplicationError(error)) {
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
|
||||||
} else {
|
|
||||||
emit finished(AccountTaskState::STATE_OFFLINE,
|
|
||||||
tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
|
|
||||||
m_data->minecraftProfile = MinecraftProfile();
|
|
||||||
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
|
||||||
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,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
|
||||||
|
|
||||||
class MinecraftProfileStepMojang : public AuthStep {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MinecraftProfileStepMojang(AccountData* data);
|
|
||||||
virtual ~MinecraftProfileStepMojang() noexcept;
|
|
||||||
|
|
||||||
void perform() override;
|
|
||||||
void rehydrate() override;
|
|
||||||
|
|
||||||
QString describe() override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
|
||||||
};
|
|
@ -0,0 +1,73 @@
|
|||||||
|
#include "YggdrasilMinecraftProfileStep.h"
|
||||||
|
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "minecraft/auth/Parsers.h"
|
||||||
|
#include "net/NetUtils.h"
|
||||||
|
#include "net/RawHeaderProxy.h"
|
||||||
|
|
||||||
|
YggdrasilMinecraftProfileStep::YggdrasilMinecraftProfileStep(AccountData* data) : AuthStep(data) {}
|
||||||
|
|
||||||
|
QString YggdrasilMinecraftProfileStep::describe()
|
||||||
|
{
|
||||||
|
return tr("Fetching the Minecraft profile.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilMinecraftProfileStep::perform()
|
||||||
|
{
|
||||||
|
if (m_data->minecraftProfile.id.isEmpty()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl url = QUrl(m_data->sessionServerUrl() + "/session/minecraft/profile/" + m_data->minecraftProfile.id);
|
||||||
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" }, { "Accept", "application/json" } };
|
||||||
|
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_request = Net::Download::makeByteArray(url, m_response);
|
||||||
|
m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
|
||||||
|
|
||||||
|
m_task.reset(new NetJob("MinecraftProfileStep", APPLICATION->network()));
|
||||||
|
m_task->setAskRetry(false);
|
||||||
|
m_task->addNetAction(m_request);
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &YggdrasilMinecraftProfileStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilMinecraftProfileStep::onRequestDone()
|
||||||
|
{
|
||||||
|
if (m_request->error() == QNetworkReply::ContentNotFoundError) {
|
||||||
|
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
|
||||||
|
m_data->minecraftProfile = MinecraftProfile();
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, tr("Account has no Minecraft profile."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_request->error() != QNetworkReply::NoError) {
|
||||||
|
qWarning() << "Error getting profile:";
|
||||||
|
qWarning() << " HTTP Status: " << m_request->replyStatusCode();
|
||||||
|
qWarning() << " Internal error no.: " << m_request->error();
|
||||||
|
qWarning() << " Error string: " << m_request->errorString();
|
||||||
|
|
||||||
|
qWarning() << " Response:";
|
||||||
|
qWarning() << QString::fromUtf8(*m_response);
|
||||||
|
|
||||||
|
if (Net::isApplicationError(m_request->error())) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT,
|
||||||
|
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
|
||||||
|
} else {
|
||||||
|
emit finished(AccountTaskState::STATE_OFFLINE,
|
||||||
|
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Parsers::parseMinecraftProfileMojang(*m_response, m_data->minecraftProfile)) {
|
||||||
|
m_data->minecraftProfile = MinecraftProfile();
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/Download.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
|
class YggdrasilMinecraftProfileStep : public AuthStep {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit YggdrasilMinecraftProfileStep(AccountData* data);
|
||||||
|
virtual ~YggdrasilMinecraftProfileStep() noexcept = default;
|
||||||
|
|
||||||
|
void perform() override;
|
||||||
|
|
||||||
|
QString describe() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onRequestDone();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Download::Ptr m_request;
|
||||||
|
NetJob::Ptr m_task;
|
||||||
|
};
|
@ -1,16 +1,9 @@
|
|||||||
#include "YggdrasilStep.h"
|
#include "YggdrasilStep.h"
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
#include "minecraft/auth/Parsers.h"
|
#include "net/RawHeaderProxy.h"
|
||||||
#include "minecraft/auth/Yggdrasil.h"
|
|
||||||
|
|
||||||
YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
|
YggdrasilStep::YggdrasilStep(AccountData* data, std::optional<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);
|
|
||||||
connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString YggdrasilStep::describe()
|
QString YggdrasilStep::describe()
|
||||||
{
|
{
|
||||||
@ -19,27 +12,195 @@ QString YggdrasilStep::describe()
|
|||||||
|
|
||||||
void YggdrasilStep::perform()
|
void YggdrasilStep::perform()
|
||||||
{
|
{
|
||||||
if (m_password.size()) {
|
if (m_password) {
|
||||||
m_yggdrasil->login(m_password);
|
login(*m_password);
|
||||||
} else {
|
} else {
|
||||||
m_yggdrasil->refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void YggdrasilStep::onAuthSucceeded()
|
void YggdrasilStep::login(QString password)
|
||||||
{
|
{
|
||||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
|
QUrl url(m_data->authServerUrl() + "/authenticate");
|
||||||
}
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" }, { "Accept", "application/json" } };
|
||||||
|
|
||||||
void YggdrasilStep::onAuthFailed()
|
/*
|
||||||
|
* {
|
||||||
|
* "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;
|
||||||
{
|
{
|
||||||
auto state = m_yggdrasil->taskState();
|
QJsonObject agent;
|
||||||
QString errorMessage = tr("Mojang user authentication failed.");
|
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.
|
||||||
|
|
||||||
// NOTE: soft error in the first step means 'offline'
|
m_data->generateClientTokenIfMissing();
|
||||||
if (state == AccountTaskState::STATE_FAILED_SOFT) {
|
req.insert("clientToken", m_data->clientToken());
|
||||||
state = AccountTaskState::STATE_OFFLINE;
|
|
||||||
errorMessage = tr("Mojang user authentication ended with a network error.");
|
QJsonDocument doc(req);
|
||||||
|
QByteArray requestData = doc.toJson();
|
||||||
|
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_request = Net::Upload::makeByteArray(url, m_response, requestData);
|
||||||
|
m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
|
||||||
|
|
||||||
|
m_task.reset(new NetJob("YggdrasilStep", APPLICATION->network()));
|
||||||
|
m_task->setAskRetry(false);
|
||||||
|
m_task->addNetAction(m_request);
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &YggdrasilStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->start();
|
||||||
}
|
}
|
||||||
emit finished(state, errorMessage);
|
|
||||||
|
void YggdrasilStep::refresh()
|
||||||
|
{
|
||||||
|
QUrl url(m_data->authServerUrl() + "/refresh");
|
||||||
|
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" }, { "Accept", "application/json" } };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* {
|
||||||
|
* "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());
|
||||||
|
req.insert("requestUser", false);
|
||||||
|
|
||||||
|
QJsonDocument doc(req);
|
||||||
|
QByteArray requestData = doc.toJson();
|
||||||
|
|
||||||
|
m_response.reset(new QByteArray());
|
||||||
|
m_request = Net::Upload::makeByteArray(url, m_response, requestData);
|
||||||
|
m_request->addHeaderProxy(new Net::RawHeaderProxy(headers));
|
||||||
|
|
||||||
|
m_task.reset(new NetJob("YggdrasilStep", APPLICATION->network()));
|
||||||
|
m_task->setAskRetry(false);
|
||||||
|
m_task->addNetAction(m_request);
|
||||||
|
|
||||||
|
connect(m_task.get(), &Task::finished, this, &YggdrasilStep::onRequestDone);
|
||||||
|
|
||||||
|
m_task->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilStep::onRequestDone()
|
||||||
|
{
|
||||||
|
// TODO handle errors
|
||||||
|
|
||||||
|
QJsonParseError jsonError;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(*m_response, &jsonError);
|
||||||
|
YggdrasilStep::processResponse(doc.object());
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilStep::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
|
||||||
|
emit finished(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()) {
|
||||||
|
emit finished(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.
|
||||||
|
emit finished(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 = Validity::Certain;
|
||||||
|
m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
|
||||||
|
|
||||||
|
// Get UUID here since we need it for later
|
||||||
|
// FIXME: Here is a simple workaround for now,, which uses the first available profile when selectedProfile is not provided
|
||||||
|
auto profile = responseData.value("selectedProfile");
|
||||||
|
if (!profile.isObject()) {
|
||||||
|
auto profiles = responseData.value("availableProfiles");
|
||||||
|
if (!profiles.isArray()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send available profiles."));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (profiles.toArray().isEmpty()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Account has no available profile."));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
profile = profiles.toArray().first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto profileObj = profile.toObject();
|
||||||
|
for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
|
||||||
|
if (i.key() == "name" && i.value().isString()) {
|
||||||
|
m_data->minecraftProfile.name = i->toString();
|
||||||
|
} else if (i.key() == "id" && i.value().isString()) {
|
||||||
|
m_data->minecraftProfile.id = i->toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_data->minecraftProfile.id.isEmpty()) {
|
||||||
|
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit finished(AccountTaskState::STATE_WORKING, "Logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
void YggdrasilStep::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,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "minecraft/auth/AuthStep.h"
|
#include "minecraft/auth/AuthStep.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "net/Upload.h"
|
||||||
|
|
||||||
class Yggdrasil;
|
class Yggdrasil;
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ class YggdrasilStep : public AuthStep {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit YggdrasilStep(AccountData* data, QString password);
|
explicit YggdrasilStep(AccountData* data, std::optional<QString> password);
|
||||||
virtual ~YggdrasilStep() noexcept = default;
|
virtual ~YggdrasilStep() noexcept = default;
|
||||||
|
|
||||||
void perform() override;
|
void perform() override;
|
||||||
@ -18,10 +19,17 @@ class YggdrasilStep : public AuthStep {
|
|||||||
QString describe() override;
|
QString describe() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onAuthSucceeded();
|
void onRequestDone();
|
||||||
void onAuthFailed();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Yggdrasil* m_yggdrasil = nullptr;
|
void login(QString password);
|
||||||
QString m_password;
|
void refresh();
|
||||||
|
|
||||||
|
void processResponse(QJsonObject responseData);
|
||||||
|
void processError(QJsonObject responseData);
|
||||||
|
|
||||||
|
std::optional<QString> m_password;
|
||||||
|
std::shared_ptr<QByteArray> m_response;
|
||||||
|
Net::Upload::Ptr m_request;
|
||||||
|
NetJob::Ptr m_task;
|
||||||
};
|
};
|
||||||
|
@ -139,7 +139,6 @@ void PackInstallTask::resolveMods()
|
|||||||
m_file_id_map.clear();
|
m_file_id_map.clear();
|
||||||
|
|
||||||
Flame::Manifest manifest;
|
Flame::Manifest manifest;
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
for (auto const& file : m_version.files) {
|
for (auto const& file : m_version.files) {
|
||||||
if (!file.serverOnly && file.url.isEmpty()) {
|
if (!file.serverOnly && file.url.isEmpty()) {
|
||||||
@ -151,15 +150,12 @@ void PackInstallTask::resolveMods()
|
|||||||
Flame::File flame_file;
|
Flame::File flame_file;
|
||||||
flame_file.projectId = file.curseforge.project_id;
|
flame_file.projectId = file.curseforge.project_id;
|
||||||
flame_file.fileId = file.curseforge.file_id;
|
flame_file.fileId = file.curseforge.file_id;
|
||||||
flame_file.hash = file.sha1;
|
|
||||||
|
|
||||||
manifest.files.insert(flame_file.fileId, flame_file);
|
manifest.files.insert(flame_file.fileId, flame_file);
|
||||||
m_file_id_map.append(flame_file.fileId);
|
m_file_id_map.append(flame_file.fileId);
|
||||||
} else {
|
} else {
|
||||||
m_file_id_map.append(-1);
|
m_file_id_map.append(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_mod_id_resolver_task.reset(new Flame::FileResolvingTask(APPLICATION->network(), manifest));
|
m_mod_id_resolver_task.reset(new Flame::FileResolvingTask(APPLICATION->network(), manifest));
|
||||||
@ -188,11 +184,11 @@ void PackInstallTask::onResolveModsSucceeded()
|
|||||||
VersionFile& local_file = m_version.files[index];
|
VersionFile& local_file = m_version.files[index];
|
||||||
|
|
||||||
// First check for blocked mods
|
// First check for blocked mods
|
||||||
if (!results_file.resolved || results_file.url.isEmpty()) {
|
if (!results_file.version.downloadUrl.isEmpty()) {
|
||||||
BlockedMod blocked_mod;
|
BlockedMod blocked_mod;
|
||||||
blocked_mod.name = local_file.name;
|
blocked_mod.name = local_file.name;
|
||||||
blocked_mod.websiteUrl = results_file.websiteUrl;
|
blocked_mod.websiteUrl = results_file.pack.websiteUrl;
|
||||||
blocked_mod.hash = results_file.hash;
|
blocked_mod.hash = results_file.version.hash;
|
||||||
blocked_mod.matched = false;
|
blocked_mod.matched = false;
|
||||||
blocked_mod.localPath = "";
|
blocked_mod.localPath = "";
|
||||||
blocked_mod.targetFolder = results_file.targetFolder;
|
blocked_mod.targetFolder = results_file.targetFolder;
|
||||||
@ -201,7 +197,7 @@ void PackInstallTask::onResolveModsSucceeded()
|
|||||||
|
|
||||||
anyBlocked = true;
|
anyBlocked = true;
|
||||||
} else {
|
} else {
|
||||||
local_file.url = results_file.url.toString();
|
local_file.url = results_file.version.downloadUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,14 +21,13 @@
|
|||||||
#include "ui_AuthlibInjectorLoginDialog.h"
|
#include "ui_AuthlibInjectorLoginDialog.h"
|
||||||
|
|
||||||
#include "CreateAuthlibInjectorAccount.h"
|
#include "CreateAuthlibInjectorAccount.h"
|
||||||
#include "minecraft/auth/AccountTask.h"
|
|
||||||
|
|
||||||
#include <QtWidgets/QPushButton>
|
#include <QtWidgets/QPushButton>
|
||||||
|
|
||||||
AuthlibInjectorLoginDialog::AuthlibInjectorLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AuthlibInjectorLoginDialog)
|
AuthlibInjectorLoginDialog::AuthlibInjectorLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AuthlibInjectorLoginDialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->progressBar->setVisible(false);
|
ui->loadingLabel->setVisible(false);
|
||||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
|
|
||||||
@ -104,10 +103,12 @@ void AuthlibInjectorLoginDialog::accept()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
setUserInputsEnabled(false);
|
setUserInputsEnabled(false);
|
||||||
ui->progressBar->setVisible(true);
|
ui->loadingLabel->setVisible(true);
|
||||||
|
|
||||||
// Get the authlib-injector API root
|
// Get the authlib-injector API root
|
||||||
auto netJob = NetJob::Ptr(new NetJob("Get authlib-injector API root", APPLICATION->network()));
|
auto netJob = NetJob::Ptr(new NetJob("Get authlib-injector API root", APPLICATION->network()));
|
||||||
|
netJob->setAskRetry(false);
|
||||||
|
|
||||||
auto username = ui->userTextBox->text();
|
auto username = ui->userTextBox->text();
|
||||||
m_createAuthlibInjectorAccountTask = CreateAuthlibInjectorAccount::make(fixedAuthlibInjectorUrl, m_account, username);
|
m_createAuthlibInjectorAccountTask = CreateAuthlibInjectorAccount::make(fixedAuthlibInjectorUrl, m_account, username);
|
||||||
netJob->addNetAction(m_createAuthlibInjectorAccountTask);
|
netJob->addNetAction(m_createAuthlibInjectorAccountTask);
|
||||||
@ -159,17 +160,16 @@ void AuthlibInjectorLoginDialog::onTaskFailed(const QString& reason)
|
|||||||
|
|
||||||
// Re-enable user-interaction
|
// Re-enable user-interaction
|
||||||
setUserInputsEnabled(true);
|
setUserInputsEnabled(true);
|
||||||
ui->progressBar->setVisible(false);
|
ui->loadingLabel->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthlibInjectorLoginDialog::onUrlTaskSucceeded()
|
void AuthlibInjectorLoginDialog::onUrlTaskSucceeded()
|
||||||
{
|
{
|
||||||
m_account = m_createAuthlibInjectorAccountTask->getAccount();
|
m_account = m_createAuthlibInjectorAccountTask->getAccount();
|
||||||
m_loginTask = m_account->loginAuthlibInjector(ui->passTextBox->text());
|
m_loginTask = m_account->login(false, ui->passTextBox->text());
|
||||||
connect(m_loginTask.get(), &Task::failed, this, &AuthlibInjectorLoginDialog::onTaskFailed);
|
connect(m_loginTask.get(), &Task::failed, this, &AuthlibInjectorLoginDialog::onTaskFailed);
|
||||||
connect(m_loginTask.get(), &Task::succeeded, this, &AuthlibInjectorLoginDialog::onTaskSucceeded);
|
connect(m_loginTask.get(), &Task::succeeded, this, &AuthlibInjectorLoginDialog::onTaskSucceeded);
|
||||||
connect(m_loginTask.get(), &Task::status, this, &AuthlibInjectorLoginDialog::onTaskStatus);
|
connect(m_loginTask.get(), &Task::status, this, &AuthlibInjectorLoginDialog::onTaskStatus);
|
||||||
connect(m_loginTask.get(), &Task::progress, this, &AuthlibInjectorLoginDialog::onTaskProgress);
|
|
||||||
m_loginTask->start();
|
m_loginTask->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,12 +183,6 @@ void AuthlibInjectorLoginDialog::onTaskStatus(const QString& status)
|
|||||||
ui->label->setText(status);
|
ui->label->setText(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AuthlibInjectorLoginDialog::onTaskProgress(qint64 current, qint64 total)
|
|
||||||
{
|
|
||||||
ui->progressBar->setMaximum(total);
|
|
||||||
ui->progressBar->setValue(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public interface
|
// Public interface
|
||||||
MinecraftAccountPtr AuthlibInjectorLoginDialog::newAccount(QWidget* parent, QString msg)
|
MinecraftAccountPtr AuthlibInjectorLoginDialog::newAccount(QWidget* parent, QString msg)
|
||||||
{
|
{
|
||||||
|
@ -51,7 +51,6 @@ class AuthlibInjectorLoginDialog : public QDialog {
|
|||||||
void onUrlTaskSucceeded();
|
void onUrlTaskSucceeded();
|
||||||
void onTaskSucceeded();
|
void onTaskSucceeded();
|
||||||
void onTaskStatus(const QString& status);
|
void onTaskStatus(const QString& status);
|
||||||
void onTaskProgress(qint64 current, qint64 total);
|
|
||||||
|
|
||||||
void on_userTextBox_textEdited(const QString& newText);
|
void on_userTextBox_textEdited(const QString& newText);
|
||||||
void on_passTextBox_textEdited(const QString& newText);
|
void on_passTextBox_textEdited(const QString& newText);
|
||||||
|
@ -61,12 +61,22 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QProgressBar" name="progressBar">
|
<widget class="QLabel" name="loadingLabel">
|
||||||
<property name="value">
|
<property name="font">
|
||||||
<number>24</number>
|
<font>
|
||||||
|
<pointsize>16</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="textVisible">
|
<property name="text">
|
||||||
<bool>false</bool>
|
<string>Please wait...</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -1,115 +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 nullptr;
|
|
||||||
}
|
|
@ -133,19 +133,6 @@ void AccountListPage::listChanged()
|
|||||||
updateButtonStates();
|
updateButtonStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountListPage::on_actionAddMojang_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_actionAddAuthlibInjector_triggered()
|
void AccountListPage::on_actionAddAuthlibInjector_triggered()
|
||||||
{
|
{
|
||||||
if (!m_accounts->anyAccountIsValid()) {
|
if (!m_accounts->anyAccountIsValid()) {
|
||||||
|
@ -70,7 +70,6 @@ class AccountListPage : public QMainWindow, public BasePage {
|
|||||||
void retranslate() override;
|
void retranslate() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void on_actionAddMojang_triggered();
|
|
||||||
void on_actionAddAuthlibInjector_triggered();
|
void on_actionAddAuthlibInjector_triggered();
|
||||||
void on_actionAddMicrosoft_triggered();
|
void on_actionAddMicrosoft_triggered();
|
||||||
void on_actionAddOffline_triggered();
|
void on_actionAddOffline_triggered();
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<addaction name="actionAddMicrosoft"/>
|
<addaction name="actionAddMicrosoft"/>
|
||||||
<addaction name="actionAddMojang"/>
|
|
||||||
<addaction name="actionAddAuthlibInjector"/>
|
<addaction name="actionAddAuthlibInjector"/>
|
||||||
<addaction name="actionAddOffline"/>
|
<addaction name="actionAddOffline"/>
|
||||||
<addaction name="actionRefresh"/>
|
<addaction name="actionRefresh"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user