Add custom login support
This commit is contained in:
parent
a3e64b6f1b
commit
94f3d61302
12
PollyMC.iml
Normal file
12
PollyMC.iml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/libraries/javacheck" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/libraries/launcher" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -222,6 +222,8 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/flows/Offline.h
|
||||
minecraft/auth/flows/Elyby.cpp
|
||||
minecraft/auth/flows/Elyby.h
|
||||
minecraft/auth/flows/Custom.cpp
|
||||
minecraft/auth/flows/Custom.h
|
||||
|
||||
minecraft/auth/steps/EntitlementsStep.cpp
|
||||
minecraft/auth/steps/EntitlementsStep.h
|
||||
@ -229,6 +231,10 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/steps/ElybyProfileStep.h
|
||||
minecraft/auth/steps/ElybyStep.cpp
|
||||
minecraft/auth/steps/ElybyStep.h
|
||||
minecraft/auth/steps/CustomProfileStep.cpp
|
||||
minecraft/auth/steps/CustomProfileStep.h
|
||||
minecraft/auth/steps/CustomStep.cpp
|
||||
minecraft/auth/steps/CustomStep.h
|
||||
minecraft/auth/steps/GetSkinStep.cpp
|
||||
minecraft/auth/steps/GetSkinStep.h
|
||||
minecraft/auth/steps/LauncherLoginStep.cpp
|
||||
|
@ -1031,7 +1031,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
// authlib patch
|
||||
if (session->user_type == "elyby")
|
||||
{
|
||||
process->appendStep(makeShared<InjectAuthlib>(pptr, &m_injector));
|
||||
process->appendStep(makeShared<InjectAuthlib>(pptr, &m_injector, "ely.by"));
|
||||
}
|
||||
else if (session->user_type == "custom")
|
||||
{
|
||||
process->appendStep(makeShared<InjectAuthlib>(pptr, &m_injector, session.url));
|
||||
}
|
||||
process->appendStep(makeShared<Update>(pptr, Net::Mode::Online));
|
||||
|
||||
|
@ -354,6 +354,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) {
|
||||
type = AccountType::Offline;
|
||||
} else if (typeS == "Elyby") {
|
||||
type = AccountType::Elyby;
|
||||
} else if (typeS == "Custom") {
|
||||
type = AccountType::Custom
|
||||
} else {
|
||||
qWarning() << "Failed to parse account data: type is not recognized.";
|
||||
return false;
|
||||
@ -414,6 +416,9 @@ QJsonObject AccountData::saveState() const {
|
||||
else if (type == AccountType::Elyby) {
|
||||
output["type"] = "Elyby";
|
||||
}
|
||||
else if (type == AccountType::Custom) {
|
||||
output["type"] = "Custom";
|
||||
}
|
||||
|
||||
tokenToJSONV3(output, yggdrasilToken, "ygg");
|
||||
profileToJSONV3(output, minecraftProfile, "profile");
|
||||
@ -433,14 +438,14 @@ QString AccountData::accessToken() const {
|
||||
}
|
||||
|
||||
QString AccountData::clientToken() const {
|
||||
if(type != AccountType::Mojang && type != AccountType::Elyby) {
|
||||
if(type != AccountType::Mojang && type != AccountType::Elyby && type != AccountType::Custom) {
|
||||
return QString();
|
||||
}
|
||||
return yggdrasilToken.extra["clientToken"].toString();
|
||||
}
|
||||
|
||||
void AccountData::setClientToken(QString clientToken) {
|
||||
if(type != AccountType::Mojang && type != AccountType::Elyby) {
|
||||
if(type != AccountType::Mojang && type != AccountType::Elyby && type != AccountType::Custom) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = clientToken;
|
||||
@ -454,7 +459,7 @@ void AccountData::generateClientTokenIfMissing() {
|
||||
}
|
||||
|
||||
void AccountData::invalidateClientToken() {
|
||||
if(type != AccountType::Mojang && type != AccountType::Elyby) {
|
||||
if(type != AccountType::Mojang && type != AccountType::Elyby && type != AccountType::Custom) {
|
||||
return;
|
||||
}
|
||||
yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
|
||||
@ -475,12 +480,11 @@ QString AccountData::profileName() const {
|
||||
|
||||
QString AccountData::accountDisplayString() const {
|
||||
switch(type) {
|
||||
case AccountType::Custom:
|
||||
case AccountType::Elyby:
|
||||
case AccountType::Mojang: {
|
||||
return userName();
|
||||
}
|
||||
case AccountType::Elyby: {
|
||||
return userName();
|
||||
}
|
||||
case AccountType::Offline: {
|
||||
return QObject::tr("<Offline>");
|
||||
}
|
||||
|
@ -75,7 +75,8 @@ enum class AccountType {
|
||||
MSA,
|
||||
Mojang,
|
||||
Offline,
|
||||
Elyby
|
||||
Elyby,
|
||||
Custom
|
||||
};
|
||||
|
||||
enum class AccountState {
|
||||
@ -114,6 +115,8 @@ struct AccountData {
|
||||
|
||||
QString lastError() const;
|
||||
|
||||
QString customUrl const;
|
||||
|
||||
AccountType type = AccountType::MSA;
|
||||
bool legacy = false;
|
||||
bool canMigrateToMSA = false;
|
||||
|
@ -40,6 +40,8 @@ struct AuthSession
|
||||
QString uuid;
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
QString user_type;
|
||||
// Yggdrasil server url
|
||||
QString url;
|
||||
// Did the auth server reply?
|
||||
bool auth_server_online = false;
|
||||
// Did the user request online mode?
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include "flows/Mojang.h"
|
||||
#include "flows/Offline.h"
|
||||
#include "flows/Elyby.h"
|
||||
#include "flows/Custom.h"
|
||||
|
||||
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) {
|
||||
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
@ -118,6 +119,17 @@ MinecraftAccountPtr MinecraftAccount::createElyby(const QString &username)
|
||||
return account;
|
||||
}
|
||||
|
||||
MinecraftAccountPtr MinecraftAccount::createCustom(const QString &username, const QString &url)
|
||||
{
|
||||
MinecraftAccountPtr account = makeShared<MinecraftAccount>();
|
||||
|
||||
account->data.type = AccountType::Custom;
|
||||
account->data.yggdrasilToken.extra["userName"] = username;
|
||||
account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
|
||||
account->data.minecraftEntitlement.ownsMinecraft = true;
|
||||
account->data.minecraftEntitlement.canPlayMinecraft = true;
|
||||
return account;
|
||||
}
|
||||
|
||||
QJsonObject MinecraftAccount::saveToJson() const
|
||||
{
|
||||
@ -185,6 +197,17 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginElyby(QString password) {
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::loginCustom(QString password, QString url) {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new customLogin(&data, password, url));
|
||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
if(m_currentTask) {
|
||||
return m_currentTask;
|
||||
@ -199,6 +222,9 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
else if(data.type == AccountType::Elyby) {
|
||||
m_currentTask.reset(new ElybyRefresh(&data));
|
||||
}
|
||||
else if (data.type == AccountType::Custom) {
|
||||
m_currentTask.reset(new CustomRefresh(&data));
|
||||
}
|
||||
else {
|
||||
m_currentTask.reset(new MojangRefresh(&data));
|
||||
}
|
||||
@ -328,6 +354,8 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
|
||||
session->uuid = data.profileId();
|
||||
// 'legacy' or 'mojang', depending on account type
|
||||
session->user_type = typeString();
|
||||
session->url = data.customUrl;
|
||||
|
||||
if (!session->access_token.isEmpty())
|
||||
{
|
||||
session->session = "token:" + data.accessToken() + ":" + data.profileId();
|
||||
|
@ -97,6 +97,8 @@ public: /* construction */
|
||||
|
||||
static MinecraftAccountPtr createElyby(const QString &username);
|
||||
|
||||
static MinecraftAccountPtr createCustom(const QString &username, const QString &url);
|
||||
|
||||
static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json);
|
||||
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json);
|
||||
|
||||
@ -117,6 +119,8 @@ public: /* manipulation */
|
||||
|
||||
shared_qobject_ptr<AccountTask> loginElyby(QString password);
|
||||
|
||||
shared_qobject_ptr<AccountTask> loginCustom(QString password, QString url);
|
||||
|
||||
shared_qobject_ptr<AccountTask> refresh();
|
||||
|
||||
shared_qobject_ptr<AccountTask> currentTask();
|
||||
@ -146,6 +150,10 @@ public: /* queries */
|
||||
return data.profileName();
|
||||
}
|
||||
|
||||
qString customUrl() const {
|
||||
return data.customUrl();
|
||||
}
|
||||
|
||||
bool isActive() const;
|
||||
|
||||
bool canMigrate() const {
|
||||
@ -168,6 +176,10 @@ public: /* queries */
|
||||
return data.type == AccountType::Elyby;
|
||||
}
|
||||
|
||||
bool isCustom() const {
|
||||
return data.type == AccountType::Custom;
|
||||
}
|
||||
|
||||
bool ownsMinecraft() const {
|
||||
return data.minecraftEntitlement.ownsMinecraft;
|
||||
}
|
||||
@ -192,10 +204,15 @@ public: /* queries */
|
||||
case AccountType::Offline: {
|
||||
return "offline";
|
||||
}
|
||||
break;
|
||||
case AccountType::Elyby: {
|
||||
return "elyby";
|
||||
}
|
||||
break;
|
||||
case AccountType::Custom {
|
||||
return "custom";
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
return "unknown";
|
||||
}
|
||||
|
@ -129,6 +129,8 @@ void Yggdrasil::login(QString password, QString baseUrl) {
|
||||
|
||||
QJsonDocument doc(req);
|
||||
|
||||
this->customUrl = baseUrl;
|
||||
|
||||
QUrl reqUrl(baseUrl + "authenticate");
|
||||
QNetworkRequest netRequest(reqUrl);
|
||||
QByteArray requestData = doc.toJson();
|
||||
|
@ -97,6 +97,8 @@ protected:
|
||||
QTimer counter;
|
||||
int count = 0; // num msec since time reset
|
||||
|
||||
QString customUrl;
|
||||
|
||||
const int timeout_max = 30000;
|
||||
const int time_step = 50;
|
||||
};
|
||||
|
25
launcher/minecraft/auth/flows/Custom.cpp
Normal file
25
launcher/minecraft/auth/flows/Custom.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "Custom.h"
|
||||
|
||||
#include "minecraft/auth/steps/CustomStep.h"
|
||||
#include "minecraft/auth/steps/CustomProfileStep.h"
|
||||
#include "minecraft/auth/steps/GetSkinStep.h"
|
||||
|
||||
customRefresh::customRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent
|
||||
) : AuthFlow(data, parent) {
|
||||
m_steps.append(makeShared<CustomStep>(m_data, QString()));
|
||||
m_steps.append(makeShared<CustomProfileStep>(m_data));
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
||||
|
||||
customLogin::customLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QString url,
|
||||
QObject *parent
|
||||
): AuthFlow(data, parent), m_password(password), m_url(url) {
|
||||
m_steps.append(makeShared<CustomStep>(m_data, m_password, m_url));
|
||||
m_steps.append(makeShared<CustomProfileStep>(m_data));
|
||||
m_steps.append(makeShared<GetSkinStep>(m_data));
|
||||
}
|
27
launcher/minecraft/auth/flows/Custom.h
Normal file
27
launcher/minecraft/auth/flows/Custom.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "AuthFlow.h"
|
||||
|
||||
class customRefresh : public AuthFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit customRefresh(
|
||||
AccountData *data,
|
||||
QObject *parent = 0
|
||||
);
|
||||
};
|
||||
|
||||
class customLogin : public AuthFlow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit customLogin(
|
||||
AccountData *data,
|
||||
QString password,
|
||||
QString url,
|
||||
QObject *parent = 0
|
||||
);
|
||||
|
||||
private:
|
||||
QString m_password;
|
||||
};
|
94
launcher/minecraft/auth/steps/CustomProfileStep.cpp
Normal file
94
launcher/minecraft/auth/steps/CustomProfileStep.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include "CustomProfileStep.h"
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/NetUtils.h"
|
||||
|
||||
CustomProfileStep::CustomProfileStep(AccountData* data) : AuthStep(data) {
|
||||
|
||||
}
|
||||
|
||||
CustomProfileStep::~CustomProfileStep() noexcept = default;
|
||||
|
||||
QString CustomProfileStep::describe() {
|
||||
return tr("Fetching the Minecraft profile.");
|
||||
}
|
||||
|
||||
|
||||
void CustomProfileStep::perform() {
|
||||
if (m_data->minecraftProfile.id.isEmpty()) {
|
||||
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
|
||||
return;
|
||||
}
|
||||
|
||||
// m_data->
|
||||
|
||||
QUrl url = QUrl(m_data.customUrl + "/session/profile/" + m_data->minecraftProfile.id);
|
||||
QNetworkRequest req = QNetworkRequest(url);
|
||||
AuthRequest *request = new AuthRequest(this);
|
||||
connect(request, &AuthRequest::finished, this, &CustomProfileStep::onRequestDone);
|
||||
request->get(req);
|
||||
}
|
||||
|
||||
void CustomProfileStep::rehydrate() {
|
||||
// NOOP, for now. We only save bools and there's nothing to check.
|
||||
}
|
||||
|
||||
void CustomProfileStep::onRequestDone(
|
||||
QNetworkReply::NetworkError error,
|
||||
QByteArray data,
|
||||
QList<QNetworkReply::RawHeaderPair> headers
|
||||
) {
|
||||
auto requestor = qobject_cast<AuthRequest *>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
#ifndef NDEBUG
|
||||
qDebug() << data;
|
||||
#endif
|
||||
if (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_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;
|
||||
}
|
||||
|
||||
emit finished(
|
||||
AccountTaskState::STATE_WORKING,
|
||||
tr("Minecraft Java profile acquisition succeeded.")
|
||||
);
|
||||
}
|
22
launcher/minecraft/auth/steps/CustomProfileStep.h
Normal file
22
launcher/minecraft/auth/steps/CustomProfileStep.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
|
||||
class CustomProfileStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CustomProfileStep(AccountData *data);
|
||||
virtual ~CustomProfileStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
|
||||
};
|
52
launcher/minecraft/auth/steps/CustomStep.cpp
Normal file
52
launcher/minecraft/auth/steps/CustomStep.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "CustomStep.h"
|
||||
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/auth/Yggdrasil.h"
|
||||
|
||||
CustomStep::CustomStep(AccountData* data, QString password, QString url) : AuthStep(data), m_password(password), m_url(url) {
|
||||
m_yggdrasil = new Yggdrasil(m_data, this);
|
||||
|
||||
connect(m_yggdrasil, &Task::failed, this, &CustomStep::onAuthFailed);
|
||||
connect(m_yggdrasil, &Task::succeeded, this, &CustomStep::onAuthSucceeded);
|
||||
connect(m_yggdrasil, &Task::aborted, this, &CustomStep::onAuthFailed);
|
||||
}
|
||||
|
||||
CustomStep::~CustomStep() noexcept = default;
|
||||
|
||||
QString CustomStep::describe() {
|
||||
return tr("Logging in with Custom account.");
|
||||
}
|
||||
|
||||
void CustomStep::rehydrate() {
|
||||
// NOOP, for now.
|
||||
}
|
||||
|
||||
void CustomStep::perform() {
|
||||
if(m_password.size()) {
|
||||
m_yggdrasil->login(m_password, m_url + "/auth/");
|
||||
}
|
||||
else {
|
||||
m_yggdrasil->refresh(m_url + "/auth/");
|
||||
}
|
||||
}
|
||||
|
||||
void CustomStep::onAuthSucceeded() {
|
||||
emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Custom"));
|
||||
}
|
||||
|
||||
void CustomStep::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("Custom user authentication failed.");
|
||||
|
||||
// NOTE: soft error in the first step means 'offline'
|
||||
if(state == AccountTaskState::STATE_FAILED_SOFT) {
|
||||
state = AccountTaskState::STATE_OFFLINE;
|
||||
errorMessage = tr("Custom user authentication ended with a network error. Is MutliFactor Auth current?");
|
||||
}
|
||||
emit finished(state, errorMessage);
|
||||
}
|
28
launcher/minecraft/auth/steps/CustomStep.h
Normal file
28
launcher/minecraft/auth/steps/CustomStep.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/auth/AuthStep.h"
|
||||
|
||||
class Yggdrasil;
|
||||
|
||||
class CustomStep : public AuthStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CustomStep(AccountData *data, QString password, QString url);
|
||||
virtual ~CustomStep() noexcept;
|
||||
|
||||
void perform() override;
|
||||
void rehydrate() override;
|
||||
|
||||
QString describe() override;
|
||||
|
||||
private slots:
|
||||
void onAuthSucceeded();
|
||||
void onAuthFailed();
|
||||
|
||||
private:
|
||||
Yggdrasil *m_yggdrasil = nullptr;
|
||||
QString m_password;
|
||||
};
|
@ -20,9 +20,10 @@
|
||||
#include <Application.h>
|
||||
#include <Json.h>
|
||||
|
||||
InjectAuthlib::InjectAuthlib(LaunchTask *parent, AuthlibInjectorPtr* injector) : LaunchStep(parent)
|
||||
InjectAuthlib::InjectAuthlib(LaunchTask *parent, AuthlibInjectorPtr* injector, QString url) : LaunchStep(parent)
|
||||
{
|
||||
m_injector = injector;
|
||||
m_url = url;
|
||||
}
|
||||
|
||||
void InjectAuthlib::executeTask()
|
||||
@ -130,7 +131,7 @@ void InjectAuthlib::onVersionDownloadSucceeded()
|
||||
|
||||
void InjectAuthlib::onDownloadSucceeded()
|
||||
{
|
||||
QString injector = QString("%1=%2").arg(QDir("injectors").absoluteFilePath(m_versionName)).arg("ely.by");
|
||||
QString injector = QString("%1=%2").arg(QDir("injectors").absoluteFilePath(m_versionName)).arg(m_url);
|
||||
|
||||
qDebug()
|
||||
<< "Injecting " << injector;
|
||||
|
@ -71,5 +71,6 @@ private:
|
||||
bool m_offlineMode;
|
||||
QString m_versionName;
|
||||
QString m_authServer;
|
||||
QString m_url;
|
||||
AuthlibInjectorPtr *m_injector;
|
||||
};
|
||||
|
131
launcher/ui/dialogs/CustomLoginDialog.cpp
Normal file
131
launcher/ui/dialogs/CustomLoginDialog.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
/* 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 "CustomLoginDialog.h"
|
||||
#include "ui_CustomLoginDialog.h"
|
||||
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
CustomLoginDialog::CustomLoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::CustomLoginDialog)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
CustomLoginDialog::~CustomLoginDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
// Stage 1: User interaction
|
||||
void CustomLoginDialog::accept()
|
||||
{
|
||||
setUserInputsEnabled(false);
|
||||
ui->progressBar->setVisible(true);
|
||||
|
||||
// Setup the login task and start it
|
||||
m_account = MinecraftAccount::createCustom(ui->userTextBox->text());
|
||||
if (ui->mfaTextBox->text().length() > 0) {
|
||||
m_loginTask = m_account->loginCustom(ui->passTextBox->text() + ':' + ui->mfaTextBox->text(), ui->urlTextBox->text());
|
||||
}
|
||||
else {
|
||||
m_loginTask = m_account->loginCustom(ui->passTextBox->text(), ui->urlTextBox->text());
|
||||
}
|
||||
connect(m_loginTask.get(), &Task::failed, this, &CustomLoginDialog::onTaskFailed);
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &CustomLoginDialog::onTaskSucceeded);
|
||||
connect(m_loginTask.get(), &Task::status, this, &CustomLoginDialog::onTaskStatus);
|
||||
connect(m_loginTask.get(), &Task::progress, this, &CustomLoginDialog::onTaskProgress);
|
||||
m_loginTask->start();
|
||||
}
|
||||
|
||||
void CustomLoginDialog::setUserInputsEnabled(bool enable)
|
||||
{
|
||||
ui->userTextBox->setEnabled(enable);
|
||||
ui->passTextBox->setEnabled(enable);
|
||||
ui->urlTextBox->setEnabled(enable);
|
||||
ui->mfaTextBox->setEnabled(enable);
|
||||
ui->buttonBox->setEnabled(enable);
|
||||
}
|
||||
|
||||
// Enable the OK button only when both textboxes contain something.
|
||||
void CustomLoginDialog::on_userTextBox_textEdited(const QString &newText)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty() && !ui->urlTextBox->text().isEmpty());
|
||||
}
|
||||
void CustomLoginDialog::on_passTextBox_textEdited(const QString &newText)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty() && !ui->urlTextBox->text().isEmpty());
|
||||
}
|
||||
void CustomLoginDialog::on_urlTextBox_textEdited(const QString &newText)
|
||||
{
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty() && !ui->passTextBox->text().isEmpty());
|
||||
}
|
||||
|
||||
void CustomLoginDialog::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 CustomLoginDialog::onTaskSucceeded()
|
||||
{
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void CustomLoginDialog::onTaskStatus(const QString &status)
|
||||
{
|
||||
ui->label->setText(status);
|
||||
}
|
||||
|
||||
void CustomLoginDialog::onTaskProgress(qint64 current, qint64 total)
|
||||
{
|
||||
ui->progressBar->setMaximum(total);
|
||||
ui->progressBar->setValue(current);
|
||||
}
|
||||
|
||||
// Public interface
|
||||
MinecraftAccountPtr CustomLoginDialog::newAccount(QWidget *parent, QString msg)
|
||||
{
|
||||
CustomLoginDialog dlg(parent);
|
||||
dlg.ui->label->setText(msg);
|
||||
if (dlg.exec() == QDialog::Accepted)
|
||||
{
|
||||
return dlg.m_account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
60
launcher/ui/dialogs/CustomLoginDialog.h
Normal file
60
launcher/ui/dialogs/CustomLoginDialog.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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 CustomLoginDialog;
|
||||
}
|
||||
|
||||
class CustomLoginDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~CustomLoginDialog();
|
||||
|
||||
static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
|
||||
|
||||
private:
|
||||
explicit CustomLoginDialog(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);
|
||||
void on_urlTextBox_textEdited(const QString &newText);
|
||||
|
||||
private:
|
||||
Ui::CustomLoginDialog *ui;
|
||||
MinecraftAccountPtr m_account;
|
||||
Task::Ptr m_loginTask;
|
||||
};
|
94
launcher/ui/dialogs/CustomLoginDialog.ui
Normal file
94
launcher/ui/dialogs/CustomLoginDialog.ui
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CustomLoginDialog</class>
|
||||
<widget class="QDialog" name="CustomLoginDialog">
|
||||
<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="QLineEdit" name="urlTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>Url</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="mfaTextBox">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>2FA Code (Optional)</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>
|
@ -49,6 +49,7 @@
|
||||
#include "ui/dialogs/LoginDialog.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
#include "ui/dialogs/ElybyLoginDialog.h"
|
||||
#include "ui/dialogs/CustomLoginDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/SkinUploadDialog.h"
|
||||
|
||||
@ -219,6 +220,22 @@ void AccountListPage::on_actionAddElyby_triggered()
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddCustom_triggered()
|
||||
{
|
||||
MinecraftAccountPtr account = CustomLoginDialog::newAccount(
|
||||
this,
|
||||
tr("Enter the custom yggdrasil server url, along with your username and password to add your account.")
|
||||
);
|
||||
|
||||
if (account)
|
||||
{
|
||||
m_accounts->addAccount(account);
|
||||
if (m_accounts->count() == 1) {
|
||||
m_account->setDefaultAccount(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionRemove_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
@ -263,6 +280,7 @@ void AccountListPage::updateButtonStates()
|
||||
bool accountIsReady = false;
|
||||
bool accountIsOnline = false;
|
||||
bool accountIsElyby = false;
|
||||
bool AccountIsCustom = false;
|
||||
if (hasSelection)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
@ -270,12 +288,13 @@ void AccountListPage::updateButtonStates()
|
||||
accountIsReady = !account->isActive();
|
||||
accountIsOnline = !account->isOffline();
|
||||
accountIsElyby = account->isElyby();
|
||||
accountIsCustom = account->isCustom();
|
||||
|
||||
}
|
||||
ui->actionRemove->setEnabled(accountIsReady);
|
||||
ui->actionSetDefault->setEnabled(accountIsReady);
|
||||
ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline && !accountIsElyby);
|
||||
ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline && !accountIsElyby);
|
||||
ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline && !accountIsElyby && !accountIsCustom);
|
||||
ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline && !accountIsElyby && !accountIsCustom);
|
||||
ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline);
|
||||
|
||||
if(m_accounts->defaultAccount().get() == nullptr) {
|
||||
|
@ -86,6 +86,7 @@ public slots:
|
||||
void on_actionAddMicrosoft_triggered();
|
||||
void on_actionAddOffline_triggered();
|
||||
void on_actionAddElyby_triggered();
|
||||
void on_actionAddCustom_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionRefresh_triggered();
|
||||
void on_actionSetDefault_triggered();
|
||||
|
@ -56,6 +56,7 @@
|
||||
<addaction name="actionAddMojang"/>
|
||||
<addaction name="actionAddOffline"/>
|
||||
<addaction name="actionAddElyby"/>
|
||||
<addaction name="actionAddCustom"/>
|
||||
<addaction name="actionRefresh"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionSetDefault"/>
|
||||
@ -115,6 +116,11 @@
|
||||
<string>Add &Ely.by</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddCustom">
|
||||
<property name="text">
|
||||
<string>Add &Custom</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRefresh">
|
||||
<property name="text">
|
||||
<string>&Refresh</string>
|
||||
|
Loading…
Reference in New Issue
Block a user