Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into skin_selector

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2024-05-18 13:27:31 +03:00
111 changed files with 1378 additions and 2860 deletions

View File

@@ -37,7 +37,7 @@
#include "ui_MSALoginDialog.h"
#include "DesktopServices.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthFlow.h"
#include <QApplication>
#include <QClipboard>
@@ -47,30 +47,29 @@
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
{
ui->setupUi(this);
ui->progressBar->setVisible(false);
ui->actionButton->setVisible(false);
// ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
ui->cancel->setEnabled(false);
ui->link->setVisible(false);
ui->copy->setVisible(false);
ui->progressBar->setVisible(false);
connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject);
connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl);
}
int MSALoginDialog::exec()
{
setUserInputsEnabled(false);
ui->progressBar->setVisible(true);
// Setup the login task and start it
m_account = MinecraftAccount::createBlankMSA();
m_loginTask = m_account->loginMSA();
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
m_loginTask->start();
m_task = m_account->login(m_using_device_code);
connect(m_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
connect(m_task.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
connect(m_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
connect(m_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
connect(m_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
connect(ui->cancel, &QPushButton::pressed, m_task.get(), &Task::abort);
connect(&m_external_timer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
m_task->start();
return QDialog::exec();
}
@@ -80,60 +79,6 @@ MSALoginDialog::~MSALoginDialog()
delete ui;
}
void MSALoginDialog::externalLoginTick()
{
m_externalLoginElapsed++;
ui->progressBar->setValue(m_externalLoginElapsed);
ui->progressBar->repaint();
if (m_externalLoginElapsed >= m_externalLoginTimeout) {
m_externalLoginTimer.stop();
}
}
void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn)
{
m_externalLoginElapsed = 0;
m_externalLoginTimeout = expiresIn;
m_externalLoginTimer.setInterval(1000);
m_externalLoginTimer.setSingleShot(false);
m_externalLoginTimer.start();
ui->progressBar->setMaximum(expiresIn);
ui->progressBar->setValue(m_externalLoginElapsed);
QString urlString = uri.toString();
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
if (urlString == "https://www.microsoft.com/link" && !code.isEmpty()) {
urlString += QString("?otc=%1").arg(code);
DesktopServices::openUrl(urlString);
ui->label->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
.arg(linkString, code));
} else {
ui->label->setText(
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
}
ui->actionButton->setVisible(true);
connect(ui->actionButton, &QPushButton::clicked, [=]() {
DesktopServices::openUrl(uri);
QClipboard* cb = QApplication::clipboard();
cb->setText(code);
});
}
void MSALoginDialog::hideVerificationUriAndCode()
{
m_externalLoginTimer.stop();
ui->actionButton->setVisible(false);
}
void MSALoginDialog::setUserInputsEnabled(bool enable)
{
ui->buttonBox->setEnabled(enable);
}
void MSALoginDialog::onTaskFailed(const QString& reason)
{
// Set message
@@ -146,12 +91,7 @@ void MSALoginDialog::onTaskFailed(const QString& reason)
processed += "<br />";
}
}
ui->label->setText(processed);
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
ui->actionButton->setVisible(false);
ui->message->setText(processed);
}
void MSALoginDialog::onTaskSucceeded()
@@ -161,22 +101,81 @@ void MSALoginDialog::onTaskSucceeded()
void MSALoginDialog::onTaskStatus(const QString& status)
{
ui->label->setText(status);
}
void MSALoginDialog::onTaskProgress(qint64 current, qint64 total)
{
ui->progressBar->setMaximum(total);
ui->progressBar->setValue(current);
ui->message->setText(status);
ui->cancel->setEnabled(false);
ui->link->setVisible(false);
ui->copy->setVisible(false);
ui->progressBar->setVisible(false);
}
// Public interface
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg)
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg, bool usingDeviceCode)
{
MSALoginDialog dlg(parent);
dlg.ui->label->setText(msg);
dlg.m_using_device_code = usingDeviceCode;
dlg.ui->message->setText(msg);
if (dlg.exec() == QDialog::Accepted) {
return dlg.m_account;
}
return nullptr;
}
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
{
ui->cancel->setEnabled(true);
ui->link->setVisible(true);
ui->copy->setVisible(true);
DesktopServices::openUrl(url);
ui->link->setText(url.toDisplayString());
ui->message->setText(
tr("Browser opened to complete the login process."
"<br /><br />"
"If your browser hasn't opened, please manually open the below link in your browser:"));
}
void MSALoginDialog::copyUrl()
{
QClipboard* cb = QApplication::clipboard();
cb->setText(ui->link->text());
}
void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn)
{
m_external_elapsed = 0;
m_external_timeout = expiresIn;
m_external_timer.setInterval(1000);
m_external_timer.setSingleShot(false);
m_external_timer.start();
ui->progressBar->setMaximum(expiresIn);
ui->progressBar->setValue(m_external_elapsed);
QString linkString = QString("<a href=\"%1\">%2</a>").arg(url, url);
if (url == "https://www.microsoft.com/link" && !code.isEmpty()) {
url += QString("?otc=%1").arg(code);
ui->message->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
.arg(linkString, code));
} else {
ui->message->setText(
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
}
ui->cancel->setEnabled(true);
ui->link->setVisible(true);
ui->copy->setVisible(true);
ui->progressBar->setVisible(true);
DesktopServices::openUrl(url);
ui->link->setText(code);
}
void MSALoginDialog::externalLoginTick()
{
m_external_elapsed++;
ui->progressBar->setValue(m_external_elapsed);
ui->progressBar->repaint();
if (m_external_elapsed >= m_external_timeout) {
m_external_timer.stop();
}
}

View File

@@ -19,6 +19,7 @@
#include <QtCore/QEventLoop>
#include <QtWidgets/QDialog>
#include "minecraft/auth/AuthFlow.h"
#include "minecraft/auth/MinecraftAccount.h"
namespace Ui {
@@ -31,29 +32,29 @@ class MSALoginDialog : public QDialog {
public:
~MSALoginDialog();
static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
static MinecraftAccountPtr newAccount(QWidget* parent, QString message, bool usingDeviceCode = false);
int exec() override;
private:
explicit MSALoginDialog(QWidget* parent = 0);
void setUserInputsEnabled(bool enable);
protected slots:
void onTaskFailed(const QString& reason);
void onTaskSucceeded();
void onTaskStatus(const QString& status);
void onTaskProgress(qint64 current, qint64 total);
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
void hideVerificationUriAndCode();
void authorizeWithBrowser(const QUrl& url);
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
void copyUrl();
void externalLoginTick();
private:
Ui::MSALoginDialog* ui;
MinecraftAccountPtr m_account;
shared_qobject_ptr<AccountTask> m_loginTask;
QTimer m_externalLoginTimer;
int m_externalLoginElapsed = 0;
int m_externalLoginTimeout = 0;
shared_qobject_ptr<AuthFlow> m_task;
int m_external_elapsed;
int m_external_timeout;
QTimer m_external_timer;
bool m_using_device_code = false;
};

View File

@@ -7,11 +7,11 @@
<x>0</x>
<y>0</y>
<width>491</width>
<height>143</height>
<height>208</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -21,15 +21,28 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<widget class="QLabel" name="message">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>500</width>
<height>500</height>
</size>
</property>
<property name="text">
<string notr="true">Message label placeholder.
aaaaa</string>
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
@@ -38,6 +51,28 @@ aaaaa</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="linkLayout">
<item>
<widget class="QLineEdit" name="link">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="copy">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="copy">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
@@ -49,25 +84,11 @@ aaaaa</string>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="actionButton">
<property name="text">
<string>Open page and copy code</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
<widget class="QPushButton" name="cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</widget>

View File

@@ -1,8 +1,6 @@
#include "OfflineLoginDialog.h"
#include "ui_OfflineLoginDialog.h"
#include "minecraft/auth/AccountTask.h"
#include <QtWidgets/QPushButton>
OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog)
@@ -28,7 +26,7 @@ void OfflineLoginDialog::accept()
// Setup the login task and start it
m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
m_loginTask = m_account->loginOffline();
m_loginTask = m_account->login();
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);

View File

@@ -45,8 +45,9 @@
#include "ui/dialogs/ProgressDialog.h"
#include <Application.h>
#include "minecraft/auth/AuthRequest.h"
#include "minecraft/auth/Parsers.h"
#include "net/StaticHeaderProxy.h"
#include "net/Upload.h"
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
@@ -150,28 +151,27 @@ void ProfileSetupDialog::checkName(const QString& name)
currentCheck = name;
isChecking = true;
auto token = m_accountToSetup->accessToken();
QUrl url(QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name));
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
{ "Accept", "application/json" },
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name);
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
m_check_response.reset(new QByteArray());
if (m_check_task)
disconnect(m_check_task.get(), nullptr, this, nullptr);
m_check_task = Net::Download::makeByteArray(url, m_check_response);
m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
requestor->get(request);
connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished);
m_check_task->setNetwork(APPLICATION->network());
m_check_task->start();
}
void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error,
QByteArray profileData,
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
void ProfileSetupDialog::checkFinished()
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error == QNetworkReply::NoError) {
auto doc = QJsonDocument::fromJson(profileData);
if (m_check_task->error() == QNetworkReply::NoError) {
auto doc = QJsonDocument::fromJson(*m_check_response);
auto root = doc.object();
auto statusValue = root.value("status").toString("INVALID");
if (statusValue == "AVAILABLE") {
@@ -195,20 +195,22 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
return;
}
auto token = m_accountToSetup->accessToken();
auto url = QString("https://api.minecraftservices.com/minecraft/profile");
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
QString payloadTemplate("{\"profileName\":\"%1\"}");
auto profileData = payloadTemplate.arg(profileName).toUtf8();
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
requestor->post(request, profileData);
QUrl url("https://api.minecraftservices.com/minecraft/profile");
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
{ "Accept", "application/json" },
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
m_profile_response.reset(new QByteArray());
m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8());
m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished);
m_profile_task->setNetwork(APPLICATION->network());
m_profile_task->start();
isWorking = true;
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
@@ -244,22 +246,17 @@ struct MojangError {
} // namespace
void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error,
QByteArray errorData,
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
void ProfileSetupDialog::setupProfileFinished()
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
isWorking = false;
if (error == QNetworkReply::NoError) {
if (m_profile_task->error() == QNetworkReply::NoError) {
/*
* data contains the profile in the response
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
*/
accept();
} else {
auto parsedError = MojangError::fromJSON(errorData);
auto parsedError = MojangError::fromJSON(*m_profile_response);
ui->errorLabel->setVisible(true);
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
qDebug() << parsedError.rawError;

View File

@@ -22,6 +22,8 @@
#include <minecraft/auth/MinecraftAccount.h>
#include <memory>
#include "net/Download.h"
#include "net/Upload.h"
namespace Ui {
class ProfileSetupDialog;
@@ -40,10 +42,10 @@ class ProfileSetupDialog : public QDialog {
void on_buttonBox_rejected();
void nameEdited(const QString& name);
void checkFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
void startCheck();
void setupProfileFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
void checkFinished();
void setupProfileFinished();
protected:
void scheduleCheck(const QString& name);
@@ -67,4 +69,10 @@ class ProfileSetupDialog : public QDialog {
QString currentCheck;
QTimer checkStartTimer;
std::shared_ptr<QByteArray> m_check_response;
Net::Download::Ptr m_check_task;
std::shared_ptr<QByteArray> m_profile_response;
Net::Upload::Ptr m_profile_task;
};

View File

@@ -36,7 +36,6 @@
#include "Json.h"
#include "QObjectPtr.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/Parsers.h"
#include "minecraft/skins/CapeChange.h"
#include "minecraft/skins/SkinDelete.h"

View File

@@ -40,6 +40,7 @@
#include <QItemSelectionModel>
#include <QMenu>
#include <QPushButton>
#include <QDebug>
@@ -129,8 +130,19 @@ void AccountListPage::listChanged()
void AccountListPage::on_actionAddMicrosoft_triggered()
{
MinecraftAccountPtr account =
MSALoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account."));
QMessageBox box(this);
box.setWindowTitle(tr("Add account"));
box.setText(tr("How do you want to login?"));
box.setIcon(QMessageBox::Question);
auto deviceCode = box.addButton(tr("Legacy"), QMessageBox::ButtonRole::YesRole);
auto authCode = box.addButton(tr("Recommended"), QMessageBox::ButtonRole::NoRole);
auto cancel = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
box.setDefaultButton(authCode);
box.exec();
if ((box.clickedButton() != deviceCode && box.clickedButton() != authCode) || box.clickedButton() == cancel)
return;
MinecraftAccountPtr account = MSALoginDialog::newAccount(
this, tr("Please enter your Mojang account email and password to add your account."), box.clickedButton() == deviceCode);
if (account) {
m_accounts->addAccount(account);

View File

@@ -88,6 +88,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
};
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);

View File

@@ -331,7 +331,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
auto full_file_path = cache_entry->getFullPath();
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
connect(icon_fetch_action.get(), &Task::succeeded, this, [=] {
auto icon = QIcon(full_file_path);
QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
@@ -339,7 +339,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
emit dataChanged(index, index, { Qt::DecorationRole });
});
connect(icon_fetch_action.get(), &NetAction::failed, this, [=] {
connect(icon_fetch_action.get(), &Task::failed, this, [=] {
m_currently_running_icon_actions.remove(url);
m_failed_icon_actions.insert(url);
});

View File

@@ -352,10 +352,10 @@ void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
void ModpackListModel::searchRequestFailed(QString reason)
{
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) {
if (failed_action->replyStatusCode() == -1) {
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
} else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) {
} else if (failed_action->replyStatusCode() == 409) {
// 409 Gone, notify user to update
QMessageBox::critical(nullptr, tr("Error"),
//: %1 refers to the launcher itself