From d4b522b6cb5281df02da54cd9e0f6445770e7ec7 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 10:36:26 +0100 Subject: [PATCH 01/45] Add offline mode UI --- launcher/CMakeLists.txt | 3 + launcher/ui/dialogs/OfflineLoginDialog.cpp | 113 +++++++++++++++++++ launcher/ui/dialogs/OfflineLoginDialog.h | 58 ++++++++++ launcher/ui/dialogs/OfflineLoginDialog.ui | 67 +++++++++++ launcher/ui/pages/global/AccountListPage.cpp | 17 +++ launcher/ui/pages/global/AccountListPage.h | 1 + launcher/ui/pages/global/AccountListPage.ui | 6 + 7 files changed, 265 insertions(+) create mode 100644 launcher/ui/dialogs/OfflineLoginDialog.cpp create mode 100644 launcher/ui/dialogs/OfflineLoginDialog.h create mode 100644 launcher/ui/dialogs/OfflineLoginDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b5c52afa..0052f0e2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -769,6 +769,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/LoginDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h + ui/dialogs/OfflineLoginDialog.cpp + ui/dialogs/OfflineLoginDialog.h ui/dialogs/NewComponentDialog.cpp ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp @@ -880,6 +882,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/ExportInstanceDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/MSALoginDialog.ui + ui/dialogs/OfflineLoginDialog.ui ui/dialogs/AboutDialog.ui ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp new file mode 100644 index 00000000..f6ecc4e9 --- /dev/null +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -0,0 +1,113 @@ +/* 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 "OfflineLoginDialog.h" +#include "ui_OfflineLoginDialog.h" + +#include "minecraft/auth/AccountTask.h" + +#include + +OfflineLoginDialog::OfflineLoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog) +{ + 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); +} + +OfflineLoginDialog::~OfflineLoginDialog() +{ + delete ui; +} + +// Stage 1: User interaction +void OfflineLoginDialog::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("TODO: create offline mode account flow"); + 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); + connect(m_loginTask.get(), &Task::progress, this, &OfflineLoginDialog::onTaskProgress); + m_loginTask->start(); +} + +void OfflineLoginDialog::setUserInputsEnabled(bool enable) +{ + ui->userTextBox->setEnabled(enable); + ui->buttonBox->setEnabled(enable); +} + +// Enable the OK button only when the textbox contains something. +void OfflineLoginDialog::on_userTextBox_textEdited(const QString &newText) +{ + ui->buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(!newText.isEmpty()); +} + +void OfflineLoginDialog::onTaskFailed(const QString &reason) +{ + // Set message + auto lines = reason.split('\n'); + QString processed; + for(auto line: lines) { + if(line.size()) { + processed += "" + line + "
"; + } + else { + processed += "
"; + } + } + ui->label->setText(processed); + + // Re-enable user-interaction + setUserInputsEnabled(true); + ui->progressBar->setVisible(false); +} + +void OfflineLoginDialog::onTaskSucceeded() +{ + QDialog::accept(); +} + +void OfflineLoginDialog::onTaskStatus(const QString &status) +{ + ui->label->setText(status); +} + +void OfflineLoginDialog::onTaskProgress(qint64 current, qint64 total) +{ + ui->progressBar->setMaximum(total); + ui->progressBar->setValue(current); +} + +// Public interface +MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg) +{ + OfflineLoginDialog dlg(parent); + dlg.ui->label->setText(msg); + if (dlg.exec() == QDialog::Accepted) + { + return dlg.m_account; + } + return 0; +} diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h new file mode 100644 index 00000000..ba0c1823 --- /dev/null +++ b/launcher/ui/dialogs/OfflineLoginDialog.h @@ -0,0 +1,58 @@ +/* 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 +#include + +#include "minecraft/auth/MinecraftAccount.h" +#include "tasks/Task.h" + +namespace Ui +{ +class OfflineLoginDialog; +} + +class OfflineLoginDialog : public QDialog +{ + Q_OBJECT + +public: + ~OfflineLoginDialog(); + + static MinecraftAccountPtr newAccount(QWidget *parent, QString message); + +private: + explicit OfflineLoginDialog(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); + +private: + Ui::OfflineLoginDialog *ui; + MinecraftAccountPtr m_account; + Task::Ptr m_loginTask; +}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui new file mode 100644 index 00000000..d8964a2e --- /dev/null +++ b/launcher/ui/dialogs/OfflineLoginDialog.ui @@ -0,0 +1,67 @@ + + + OfflineLoginDialog + + + + 0 + 0 + 400 + 150 + + + + + 0 + 0 + + + + Add Account + + + + + + Message label placeholder. + + + Qt::RichText + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Username + + + + + + + 69 + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index b8da6c75..b9aa7628 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -24,6 +24,7 @@ #include "net/NetJob.h" #include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/OfflineLoginDialog.h" #include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -153,6 +154,22 @@ void AccountListPage::on_actionAddMicrosoft_triggered() } } +void AccountListPage::on_actionAddOffline_triggered() +{ + MinecraftAccountPtr account = OfflineLoginDialog::newAccount( + this, + tr("Please enter your desired username to add your offline account.") + ); + + if (account) + { + m_accounts->addAccount(account); + if (m_accounts->count() == 1) { + m_accounts->setDefaultAccount(account); + } + } +} + void AccountListPage::on_actionRemove_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 1c65e708..841c3fd2 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -62,6 +62,7 @@ public: public slots: void on_actionAddMojang_triggered(); void on_actionAddMicrosoft_triggered(); + void on_actionAddOffline_triggered(); void on_actionRemove_triggered(); void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index 29738c02..d21a92e2 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -54,6 +54,7 @@ + @@ -103,6 +104,11 @@ Add Microsoft + + + Add Offline + + Refresh From a1ff3b1ee34c302ba52d773816207d30badab1eb Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 14:26:02 +0100 Subject: [PATCH 02/45] Add offline mode support --- launcher/CMakeLists.txt | 4 +++ launcher/LaunchController.cpp | 6 ++++ launcher/minecraft/auth/AccountData.cpp | 10 +++++- launcher/minecraft/auth/AccountData.h | 3 +- launcher/minecraft/auth/AccountList.cpp | 2 +- launcher/minecraft/auth/MinecraftAccount.cpp | 31 +++++++++++++++++++ launcher/minecraft/auth/MinecraftAccount.h | 12 +++++++ launcher/minecraft/auth/flows/Offline.cpp | 17 ++++++++++ launcher/minecraft/auth/flows/Offline.h | 22 +++++++++++++ launcher/minecraft/auth/steps/OfflineStep.cpp | 18 +++++++++++ launcher/minecraft/auth/steps/OfflineStep.h | 20 ++++++++++++ launcher/ui/dialogs/OfflineLoginDialog.cpp | 4 +-- 12 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 launcher/minecraft/auth/flows/Offline.cpp create mode 100644 launcher/minecraft/auth/flows/Offline.h create mode 100644 launcher/minecraft/auth/steps/OfflineStep.cpp create mode 100644 launcher/minecraft/auth/steps/OfflineStep.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0052f0e2..df361447 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -221,7 +221,11 @@ set(MINECRAFT_SOURCES minecraft/auth/flows/Mojang.h minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.h + minecraft/auth/flows/Offline.cpp + minecraft/auth/flows/Offline.h + minecraft/auth/steps/OfflineStep.cpp + minecraft/auth/steps/OfflineStep.h minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/GetSkinStep.cpp diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 7750be1a..32fc99cb 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -116,6 +116,12 @@ void LaunchController::login() { m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); + // Launch immediately in true offline mode + if(m_accountToUse->isOffline()) { + launchInstance(); + return; + } + switch(m_accountToUse->accountState()) { case AccountState::Offline: { m_session->wants_online = false; diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 7526c951..9b84fe1a 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -314,6 +314,8 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { type = AccountType::MSA; } else if (typeS == "Mojang") { type = AccountType::Mojang; + } else if (typeS == "Offline") { + type = AccountType::Offline; } else { qWarning() << "Failed to parse account data: type is not recognized."; return false; @@ -363,6 +365,9 @@ QJsonObject AccountData::saveState() const { tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); } + else if (type == AccountType::Offline) { + output["type"] = "Offline"; + } tokenToJSONV3(output, yggdrasilToken, "ygg"); profileToJSONV3(output, minecraftProfile, "profile"); @@ -371,7 +376,7 @@ QJsonObject AccountData::saveState() const { } QString AccountData::userName() const { - if(type != AccountType::Mojang) { + if(type == AccountType::MSA) { return QString(); } return yggdrasilToken.extra["userName"].toString(); @@ -427,6 +432,9 @@ QString AccountData::accountDisplayString() const { case AccountType::Mojang: { return userName(); } + case AccountType::Offline: { + return userName(); + } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { return xboxApiToken.extra["gtg"].toString(); diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index abf84e43..606c1ad1 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -38,7 +38,8 @@ struct MinecraftProfile { enum class AccountType { MSA, - Mojang + Mojang, + Offline }; enum class AccountState { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index ef8b435d..04470e1c 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -302,7 +302,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } case MigrationColumn: { - if(account->isMSA()) { + if(account->isMSA() || account->isOffline()) { return tr("N/A", "Can Migrate?"); } if (account->canMigrate()) { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ed9e945e..6592be0f 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -30,6 +30,7 @@ #include "flows/MSA.h" #include "flows/Mojang.h" +#include "flows/Offline.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); @@ -68,6 +69,23 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() return account; } +MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) +{ + MinecraftAccountPtr account = new MinecraftAccount(); + account->data.type = AccountType::Offline; + account->data.yggdrasilToken.token = "offline"; + account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; + account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); + account->data.yggdrasilToken.extra["userName"] = username; + account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.minecraftEntitlement.ownsMinecraft = true; + account->data.minecraftEntitlement.canPlayMinecraft = true; + account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->data.minecraftProfile.name = username; + account->data.minecraftProfile.validity = Katabasis::Validity::Certain; + return account; +} + QJsonObject MinecraftAccount::saveToJson() const { @@ -111,6 +129,16 @@ shared_qobject_ptr MinecraftAccount::loginMSA() { return m_currentTask; } +shared_qobject_ptr MinecraftAccount::loginOffline() { + Q_ASSERT(m_currentTask.get() == nullptr); + + m_currentTask.reset(new OfflineLogin(&data)); + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + emit activityChanged(true); + return m_currentTask; +} + shared_qobject_ptr MinecraftAccount::refresh() { if(m_currentTask) { return m_currentTask; @@ -119,6 +147,9 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } + if(data.type == AccountType::Offline) { + m_currentTask.reset(new OfflineRefresh(&data)); + } else { m_currentTask.reset(new MojangRefresh(&data)); } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 7ab3c746..6592f9c0 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -73,6 +73,8 @@ public: /* construction */ static MinecraftAccountPtr createBlankMSA(); + static MinecraftAccountPtr createOffline(const QString &username); + static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); @@ -89,6 +91,8 @@ public: /* manipulation */ shared_qobject_ptr loginMSA(); + shared_qobject_ptr loginOffline(); + shared_qobject_ptr refresh(); shared_qobject_ptr currentTask(); @@ -128,6 +132,10 @@ public: /* queries */ return data.type == AccountType::MSA; } + bool isOffline() const { + return data.type == AccountType::Offline; + } + bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; } @@ -149,6 +157,10 @@ public: /* queries */ return "msa"; } break; + case AccountType::Offline: { + return "offline"; + } + break; default: { return "unknown"; } diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp new file mode 100644 index 00000000..fc614a8c --- /dev/null +++ b/launcher/minecraft/auth/flows/Offline.cpp @@ -0,0 +1,17 @@ +#include "Offline.h" + +#include "minecraft/auth/steps/OfflineStep.h" + +OfflineRefresh::OfflineRefresh( + AccountData *data, + QObject *parent +) : AuthFlow(data, parent) { + m_steps.append(new OfflineStep(m_data)); +} + +OfflineLogin::OfflineLogin( + AccountData *data, + QObject *parent +) : AuthFlow(data, parent) { + m_steps.append(new OfflineStep(m_data)); +} diff --git a/launcher/minecraft/auth/flows/Offline.h b/launcher/minecraft/auth/flows/Offline.h new file mode 100644 index 00000000..5d1f83a4 --- /dev/null +++ b/launcher/minecraft/auth/flows/Offline.h @@ -0,0 +1,22 @@ +#pragma once +#include "AuthFlow.h" + +class OfflineRefresh : public AuthFlow +{ + Q_OBJECT +public: + explicit OfflineRefresh( + AccountData *data, + QObject *parent = 0 + ); +}; + +class OfflineLogin : public AuthFlow +{ + Q_OBJECT +public: + explicit OfflineLogin( + AccountData *data, + QObject *parent = 0 + ); +}; diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp new file mode 100644 index 00000000..9f1fc266 --- /dev/null +++ b/launcher/minecraft/auth/steps/OfflineStep.cpp @@ -0,0 +1,18 @@ +#include "OfflineStep.h" + +#include "Application.h" + +OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}; +OfflineStep::~OfflineStep() noexcept = default; + +QString OfflineStep::describe() { + return tr("Creating offline account."); +} + +void OfflineStep::rehydrate() { + // NOOP +} + +void OfflineStep::perform() { + emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); +} diff --git a/launcher/minecraft/auth/steps/OfflineStep.h b/launcher/minecraft/auth/steps/OfflineStep.h new file mode 100644 index 00000000..62addb1f --- /dev/null +++ b/launcher/minecraft/auth/steps/OfflineStep.h @@ -0,0 +1,20 @@ + +#pragma once +#include + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + +#include + +class OfflineStep : public AuthStep { + Q_OBJECT +public: + explicit OfflineStep(AccountData *data); + virtual ~OfflineStep() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; +}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index f6ecc4e9..0cc922f8 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -42,8 +42,8 @@ void OfflineLoginDialog::accept() ui->progressBar->setVisible(true); // Setup the login task and start it - m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text()); - m_loginTask = m_account->login("TODO: create offline mode account flow"); + m_account = MinecraftAccount::createOffline(ui->userTextBox->text()); + m_loginTask = m_account->loginOffline(); 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); From 6ecc8c5496cd1fa121b69f770c0664320fd7dc1d Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 14:57:32 +0100 Subject: [PATCH 03/45] Remove unnecessary license header --- launcher/ui/dialogs/OfflineLoginDialog.cpp | 15 --------------- launcher/ui/dialogs/OfflineLoginDialog.h | 15 --------------- 2 files changed, 30 deletions(-) diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index 0cc922f8..345ed40a 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -1,18 +1,3 @@ -/* 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 "OfflineLoginDialog.h" #include "ui_OfflineLoginDialog.h" diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h index ba0c1823..5e608379 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.h +++ b/launcher/ui/dialogs/OfflineLoginDialog.h @@ -1,18 +1,3 @@ -/* 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 From 46a3b4de6ebb625b958a69aba85316171d3fa168 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Wed, 12 Jan 2022 18:41:33 +0100 Subject: [PATCH 04/45] Remove unnecessary semicolon --- launcher/minecraft/auth/steps/OfflineStep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp index 9f1fc266..dc092bfd 100644 --- a/launcher/minecraft/auth/steps/OfflineStep.cpp +++ b/launcher/minecraft/auth/steps/OfflineStep.cpp @@ -2,7 +2,7 @@ #include "Application.h" -OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {}; +OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} OfflineStep::~OfflineStep() noexcept = default; QString OfflineStep::describe() { From 395e2655648dbb80d089077e6a6b2530f4876c63 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Fri, 14 Jan 2022 00:01:05 +0100 Subject: [PATCH 05/45] Add offline mode disclaimer --- launcher/ui/dialogs/OfflineLoginDialog.ui | 4 ++-- launcher/ui/pages/global/AccountListPage.cpp | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui index d8964a2e..4577d361 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.ui +++ b/launcher/ui/dialogs/OfflineLoginDialog.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 150 + 500 + 250 diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index b9aa7628..1c27d5b7 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -158,7 +158,13 @@ void AccountListPage::on_actionAddOffline_triggered() { MinecraftAccountPtr account = OfflineLoginDialog::newAccount( this, - tr("Please enter your desired username to add your offline account.") + tr("Please enter your desired username to add your offline account.
" + "
" + "It is required by Mojang that you own Minecraft BEFORE you may use offline mode.
" + "The PolyMC organization denounces piracy and takes NO LIABILITY WHATSOEVER
" + "for any illegal activity that may occur in usage of the offline mode feature.
" + "
" + "By continuing you promise that you own a Minecraft account.") ); if (account) From cdaa397dcffb92ae6a9c659047a87d49286dee4f Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Fri, 14 Jan 2022 14:19:31 +0100 Subject: [PATCH 06/45] Reword offline mode disclaimer --- launcher/ui/pages/global/AccountListPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 1c27d5b7..ad88812a 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -161,8 +161,8 @@ void AccountListPage::on_actionAddOffline_triggered() tr("Please enter your desired username to add your offline account.
" "
" "It is required by Mojang that you own Minecraft BEFORE you may use offline mode.
" - "The PolyMC organization denounces piracy and takes NO LIABILITY WHATSOEVER
" - "for any illegal activity that may occur in usage of the offline mode feature.
" + "The PolyMC developers denounce piracy and take NO LIABILITY WHATSOEVER for
" + "any illegal activity that may occur in usage of the offline mode feature.
" "
" "By continuing you promise that you own a Minecraft account.") ); From 41dba376a803825fca2dc6b3b812ff8a15a9588e Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 17:33:34 -0500 Subject: [PATCH 07/45] remove 5 from display name Closes: #58 --- program_info/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index d2f23277..77b971fc 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -3,8 +3,8 @@ set(Launcher_CommonName "PolyMC") set(Launcher_Copyright "PolyMC Contributors" PARENT_SCOPE) set(Launcher_Domain "github.com/PolyMC" PARENT_SCOPE) set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) -set(Launcher_DisplayName "${Launcher_CommonName} 5" PARENT_SCOPE) -set(Launcher_UserAgent "${Launcher_CommonName}/5.0" PARENT_SCOPE) +set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE) +set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_RELEASE_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "polymc.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PolyMC/PolyMC" PARENT_SCOPE) From b19e3156154ba0dd232a3d165b1759c57e2858f2 Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 17:34:50 -0500 Subject: [PATCH 08/45] Set maximum memory allocated to 4GB by default --- launcher/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index a3e2c44f..47c9c20e 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -662,7 +662,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024); + m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096); m_settings->registerSetting("PermGen", 128); // Java Settings From a62155c1c9e561327cc589fe3da7b6d5a107d58d Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 18:20:06 -0500 Subject: [PATCH 09/45] preliminary stuff for paste.ee removal --- CMakeLists.txt | 3 - buildconfig/BuildConfig.cpp.in | 1 - buildconfig/BuildConfig.h | 5 -- launcher/Application.cpp | 4 +- launcher/CMakeLists.txt | 6 +- .../global/{PasteEEPage.cpp => PastePage.cpp} | 22 ++++---- .../global/{PasteEEPage.h => PastePage.h} | 11 ++-- .../global/{PasteEEPage.ui => PastePage.ui} | 55 ++++++------------- 8 files changed, 40 insertions(+), 67 deletions(-) rename launcher/ui/pages/global/{PasteEEPage.cpp => PastePage.cpp} (81%) rename launcher/ui/pages/global/{PasteEEPage.h => PastePage.h} (88%) rename launcher/ui/pages/global/{PasteEEPage.ui => PastePage.ui} (61%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af0aa71..91119b2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,9 +68,6 @@ set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notification # The metadata server set(Launcher_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") -# paste.ee API key -set(Launcher_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account") - # Imgur API Client ID set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application") diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index af8845dc..2595f78b 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -42,7 +42,6 @@ Config::Config() VERSION_STR = "@Launcher_VERSION_STRING@"; NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@"; - PASTE_EE_KEY = "@Launcher_PASTE_EE_API_KEY@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; META_URL = "@Launcher_META_URL@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 009fb2bc..d09d5288 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -67,11 +67,6 @@ public: */ QString NEWS_RSS_URL; - /** - * API key you can get from paste.ee when you register an account - */ - QString PASTE_EE_KEY; - /** * Client ID you can get from Imgur when you register an application */ diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 47c9c20e..98e3e0fc 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -14,7 +14,7 @@ #include "ui/pages/global/ProxyPage.h" #include "ui/pages/global/ExternalToolsPage.h" #include "ui/pages/global/AccountListPage.h" -#include "ui/pages/global/PasteEEPage.h" +#include "ui/pages/global/PastePage.h" #include "ui/pages/global/CustomCommandsPage.h" #include "ui/themes/ITheme.h" @@ -728,7 +728,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); } qDebug() << "<> Settings loaded."; } diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index df361447..21859cad 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -711,8 +711,8 @@ SET(LAUNCHER_SOURCES ui/pages/global/LauncherPage.h ui/pages/global/ProxyPage.cpp ui/pages/global/ProxyPage.h - ui/pages/global/PasteEEPage.cpp - ui/pages/global/PasteEEPage.h + ui/pages/global/PastePage.cpp + ui/pages/global/PastePage.h # GUI - platform pages ui/pages/modplatform/VanillaPage.cpp @@ -848,7 +848,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/global/AccountListPage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui - ui/pages/global/PasteEEPage.ui + ui/pages/global/PastePage.ui ui/pages/global/ProxyPage.ui ui/pages/global/MinecraftPage.ui ui/pages/global/ExternalToolsPage.ui diff --git a/launcher/ui/pages/global/PasteEEPage.cpp b/launcher/ui/pages/global/PastePage.cpp similarity index 81% rename from launcher/ui/pages/global/PasteEEPage.cpp rename to launcher/ui/pages/global/PastePage.cpp index 4b375d9a..3378a6ef 100644 --- a/launcher/ui/pages/global/PasteEEPage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2021 MultiMC & PolyMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ * limitations under the License. */ -#include "PasteEEPage.h" -#include "ui_PasteEEPage.h" +#include "PastePage.h" +#include "ui_PastePage.h" #include #include @@ -25,22 +25,22 @@ #include "tools/BaseProfiler.h" #include "Application.h" -PasteEEPage::PasteEEPage(QWidget *parent) : +PastePage::PastePage(QWidget *parent) : QWidget(parent), - ui(new Ui::PasteEEPage) + ui(new Ui::PastePage) { ui->setupUi(this); ui->tabWidget->tabBar()->hide();\ - connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited); + connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PastePage::textEdited); loadSettings(); } -PasteEEPage::~PasteEEPage() +PastePage::~PastePage() { delete ui; } -void PasteEEPage::loadSettings() +void PastePage::loadSettings() { auto s = APPLICATION->settings(); QString keyToUse = s->get("PasteEEAPIKey").toString(); @@ -55,7 +55,7 @@ void PasteEEPage::loadSettings() } } -void PasteEEPage::applySettings() +void PastePage::applySettings() { auto s = APPLICATION->settings(); @@ -69,13 +69,13 @@ void PasteEEPage::applySettings() s->set("PasteEEAPIKey", pasteKeyToUse); } -bool PasteEEPage::apply() +bool PastePage::apply() { applySettings(); return true; } -void PasteEEPage::textEdited(const QString& text) +void PastePage::textEdited(const QString& text) { ui->customButton->setChecked(true); } diff --git a/launcher/ui/pages/global/PasteEEPage.h b/launcher/ui/pages/global/PastePage.h similarity index 88% rename from launcher/ui/pages/global/PasteEEPage.h rename to launcher/ui/pages/global/PastePage.h index a1c7d434..3930d4ec 100644 --- a/launcher/ui/pages/global/PasteEEPage.h +++ b/launcher/ui/pages/global/PastePage.h @@ -21,16 +21,16 @@ #include namespace Ui { -class PasteEEPage; +class PastePage; } -class PasteEEPage : public QWidget, public BasePage +class PastePage : public QWidget, public BasePage { Q_OBJECT public: - explicit PasteEEPage(QWidget *parent = 0); - ~PasteEEPage(); + explicit PastePage(QWidget *parent = 0); + ~PastePage(); QString displayName() const override { @@ -58,5 +58,6 @@ private slots: void textEdited(const QString &text); private: - Ui::PasteEEPage *ui; + Ui::PastePage *ui; }; + diff --git a/launcher/ui/pages/global/PasteEEPage.ui b/launcher/ui/pages/global/PastePage.ui similarity index 61% rename from launcher/ui/pages/global/PasteEEPage.ui rename to launcher/ui/pages/global/PastePage.ui index 10883781..0bef5a22 100644 --- a/launcher/ui/pages/global/PasteEEPage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -1,7 +1,7 @@ - PasteEEPage - + PastePage + 0 @@ -36,39 +36,9 @@ - paste.ee API key + Pastebin Site - - - - MultiMC key - 12MB &upload limit - - - pasteButtonGroup - - - - - - - &Your own key - 12MB upload limit: - - - pasteButtonGroup - - - - - - - QLineEdit::Password - - - Paste your API key here! - - - @@ -76,10 +46,24 @@ + + + + + 0x0.st + + + + + paste.polymc.org + + + + - <html><head/><body><p><a href="https://paste.ee">paste.ee</a> is used by MultiMC for log uploads. If you have a <a href="https://paste.ee">paste.ee</a> account, you can add your API key here and have your uploaded logs paired with your account.</p></body></html> + <html><head/><body><p>paste.polymc.org is a pastebin managed by PolyMC's lead maintainer. Something something trust</p></body></html> Qt::RichText @@ -116,9 +100,6 @@ tabWidget - multimcButton - customButton - customAPIkeyEdit From a606b47a22443cefc52d865df24c45ff50908f6f Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 18:30:02 -0500 Subject: [PATCH 10/45] pastebin URL app setting --- launcher/Application.cpp | 4 ++-- launcher/ui/pages/global/PastePage.cpp | 13 +++---------- launcher/ui/pages/global/PastePage.ui | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 98e3e0fc..110b2e6b 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -714,8 +714,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); - // paste.ee API key - m_settings->registerSetting("PasteEEAPIKey", "multimc"); + // pastebin URL + m_settings->registerSetting("PastebinURL", "0x0.st"); // Init page provider { diff --git a/launcher/ui/pages/global/PastePage.cpp b/launcher/ui/pages/global/PastePage.cpp index 3378a6ef..495e9937 100644 --- a/launcher/ui/pages/global/PastePage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -43,16 +43,9 @@ PastePage::~PastePage() void PastePage::loadSettings() { auto s = APPLICATION->settings(); - QString keyToUse = s->get("PasteEEAPIKey").toString(); - if(keyToUse == "multimc") - { - ui->multimcButton->setChecked(true); - } - else - { - ui->customButton->setChecked(true); - ui->customAPIkeyEdit->setText(keyToUse); - } + QString pastebin = s->get("PastebinURL"); + int index = ui->urlChoices->findText(pastebin); + ui->urlChoices->setCurrentIndex(index); } void PastePage::applySettings() diff --git a/launcher/ui/pages/global/PastePage.ui b/launcher/ui/pages/global/PastePage.ui index 0bef5a22..784ea3f4 100644 --- a/launcher/ui/pages/global/PastePage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -47,7 +47,7 @@ - + 0x0.st From 6cd4375aff98554c56a0138208d869aca0587656 Mon Sep 17 00:00:00 2001 From: swirl Date: Fri, 14 Jan 2022 18:39:25 -0500 Subject: [PATCH 11/45] add arch linux package to readme closes: #53 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d9d414e7..7b67c588 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Several source build packages are available, along with experimental pre-built g - [Windows (32-bit)](https://packages.polymc.org/latest/win32/win32.zip) ([SHA256](https://packages.polymc.org/latest/win32/win32.zip.sha256)) - this is a portable package, you can extract it anywhere and run it. This package needs testing. - [Debian (AMD64)](https://packages.polymc.org/latest/deb/polymc-amd64.deb) ([SHA256](https://packages.polymc.org/latest/deb/polymc-amd64.deb.sha256)) - this is intended to be installed with `dpkg -i`. Alternatively, you may build the `.deb` yourself, by going to `packages/debian` and running `./makedeb.sh`. - [AppImage (AMD64)](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage) ([SHA256](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage.sha256)) - `chmod +x` must be run on this file before usage. This should work on any distribution. +- [Arch Linux (AMD64)](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst) ([SHA256](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst.sha256) - this is intended to be installed with `pacman -U`. This is an alternative if building the AUR package is not desired. - MacOS currently does not have any packages. We are still working on setting up MacOS packaging. ## Development From ac93c64cd40be038a0f3a71df18686c5e1f955c3 Mon Sep 17 00:00:00 2001 From: Thomas Sirack Date: Fri, 14 Jan 2022 16:59:16 -0700 Subject: [PATCH 12/45] Fix program executable name for shell script --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af0aa71..a19556cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ endif() ####################################### Program Info ####################################### -set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary") +set(Launcher_APP_BINARY_NAME "PolyMC" CACHE STRING "Name of the Launcher binary") add_subdirectory(program_info) ####################################### Install layout ####################################### From 0bbd0ac0b9b0ba7212ed15d4628577bf92005a18 Mon Sep 17 00:00:00 2001 From: Thomas Sirack Date: Fri, 14 Jan 2022 19:28:10 -0700 Subject: [PATCH 13/45] Change method of shell script fix per suggestion The Launcher.in file is now modified rather than CMakeLists.txt --- CMakeLists.txt | 2 +- launcher/Launcher.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a19556cb..2af0aa71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,7 @@ endif() ####################################### Program Info ####################################### -set(Launcher_APP_BINARY_NAME "PolyMC" CACHE STRING "Name of the Launcher binary") +set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary") add_subdirectory(program_info) ####################################### Install layout ####################################### diff --git a/launcher/Launcher.in b/launcher/Launcher.in index b79b276b..5e5e2c2b 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -14,7 +14,7 @@ if [[ $EUID -eq 0 ]]; then fi -LAUNCHER_NAME=@Launcher_Name@ +LAUNCHER_NAME=@Launcher_APP_BINARY_NAME@ LAUNCHER_DIR="$(dirname "$(readlink -f "$0")")" echo "Launcher Dir: ${LAUNCHER_DIR}" From dc129fd8868f1888fcc55136f3cdc2486ae8ab6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 8 Jan 2022 11:14:07 +0100 Subject: [PATCH 14/45] GH-4125 workaround for java printing garbage to stdout on bedrock linux --- launcher/java/JavaChecker.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 80c599cc..c3132af3 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -61,6 +61,10 @@ void JavaChecker::stdoutReady() QByteArray data = process->readAllStandardOutput(); QString added = QString::fromLocal8Bit(data); added.remove('\r'); + // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux + if (added.contains("/bedrock/strata")) { + return; + } m_stdout += added; } From f78bb90ed9f2658eee9259a472efe47a25eddc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 8 Jan 2022 12:26:16 +0100 Subject: [PATCH 15/45] GH-4125 fix it better --- launcher/java/JavaChecker.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index c3132af3..4557784b 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -61,10 +61,6 @@ void JavaChecker::stdoutReady() QByteArray data = process->readAllStandardOutput(); QString added = QString::fromLocal8Bit(data); added.remove('\r'); - // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux - if (added.contains("/bedrock/strata")) { - return; - } m_stdout += added; } @@ -107,6 +103,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) for(QString line : lines) { line = line.trimmed(); + // NOTE: workaround for GH-4125, where garbage is getting printed into stdout on bedrock linux + if (line.contains("/bedrock/strata")) { + continue; + } auto parts = line.split('=', QString::SkipEmptyParts); if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) From b95c27ceef1a1742d62ae0a758657fd0beea18c6 Mon Sep 17 00:00:00 2001 From: swirl Date: Sat, 15 Jan 2022 21:25:49 -0500 Subject: [PATCH 16/45] fix readme formatting --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b67c588..d97ab67c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ PolyMC logo


+ PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. The PolyMC community felt that the maintainer was not acting in the spirit of Free Software so this fork was made. Read "[Why was this fork made?](https://github.com/PolyMC/PolyMC/wiki/FAQ)" on the wiki for more details. @@ -23,7 +24,7 @@ Several source build packages are available, along with experimental pre-built g - [Windows (32-bit)](https://packages.polymc.org/latest/win32/win32.zip) ([SHA256](https://packages.polymc.org/latest/win32/win32.zip.sha256)) - this is a portable package, you can extract it anywhere and run it. This package needs testing. - [Debian (AMD64)](https://packages.polymc.org/latest/deb/polymc-amd64.deb) ([SHA256](https://packages.polymc.org/latest/deb/polymc-amd64.deb.sha256)) - this is intended to be installed with `dpkg -i`. Alternatively, you may build the `.deb` yourself, by going to `packages/debian` and running `./makedeb.sh`. - [AppImage (AMD64)](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage) ([SHA256](https://packages.polymc.org/latest/appimage/PolyMC-latest-x86_64.AppImage.sha256)) - `chmod +x` must be run on this file before usage. This should work on any distribution. -- [Arch Linux (AMD64)](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst) ([SHA256](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst.sha256) - this is intended to be installed with `pacman -U`. This is an alternative if building the AUR package is not desired. +- [Arch Linux (AMD64)](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst) ([SHA256](https://packages.polymc.org/latest/arch/polymc-bin-latest-1-x86_64.pkg.tar.zst.sha256)) - this is intended to be installed with `pacman -U`. This is an alternative if building the AUR package is not desired. - MacOS currently does not have any packages. We are still working on setting up MacOS packaging. ## Development From 8172dcd2d53114df05a23fa58e3832d2011f2e17 Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Sun, 16 Jan 2022 07:55:10 +0100 Subject: [PATCH 17/45] Replace PNG README header with SVG Instead of GitHub specific MarkDown based theming this uses CSS in SVG. --- README.md | 3 +-- program_info/polymc-dark.png | Bin 50722 -> 0 bytes program_info/polymc-header.svg | 38 +++++++++++++++++++++++++++++++++ program_info/polymc-light.png | Bin 48991 -> 0 bytes 4 files changed, 39 insertions(+), 2 deletions(-) delete mode 100644 program_info/polymc-dark.png create mode 100644 program_info/polymc-header.svg delete mode 100644 program_info/polymc-light.png diff --git a/README.md b/README.md index d9d414e7..40d4c635 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

- PolyMC logo - PolyMC logo + PolyMC logo


PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity. diff --git a/program_info/polymc-dark.png b/program_info/polymc-dark.png deleted file mode 100644 index cedf6cef79a83483da55ec682ca6081e860f67e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50722 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 diff --git a/program_info/polymc-header.svg b/program_info/polymc-header.svg new file mode 100644 index 00000000..fb91a54b --- /dev/null +++ b/program_info/polymc-header.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/program_info/polymc-light.png b/program_info/polymc-light.png deleted file mode 100644 index d3899a1f22edaf51a4e7bf478496184cb89a8a3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48991 zcmdqJcT`hb_b!YT1pz&Z^nfBN(xgcZpeP6ksPtX}gkGcs2u%?1AP_`)2_hi9gkB^x zB`8Rwh2EqSAoS3=JH+!l@B4js+%fL==f@a2hPu~Ud#^R;GoSg)x%~UNnj#GqGZh5| z1&z|v$1f=;&P7pBoTj*N9{3y9c_rWv6c?SJ>bX)-Se280ol?*L{Q&q&CbuWLZW>Nj zZZNY~mJ~1;jNiu5&J|+jY{~ER3Yxqk%}hbTL80{cp{8fb(x_L8rIs&opCFl?=WY7( zaU-{N_{COh5%ZgZXOviP3f&zL77?dYnwT~IAnkR!c=*a2$E_D>rkb9wVh4oY(4Kne zO_3$__{ocp4+Wb}^-ctYame?`_vsN48hu9DoyIN3P(m3|X3ML=_?P>(KDSz{Fxh}xqB28zrVnz@Hz&A)D}M})Akc2pbOnJ8d_-= z$9+cNHL}mICkG75223>DO5Li%$CQOC>)5xk~ ziK8L57HQ5uv$6vZ3(q-9tTAb5K+>W&DeF0zCfCLowpnZ|v=Huw z_K+S!_~v-Dgym1Wqvg+!=%`Pg4)6*FHtkoP7}~GGLiU~wDuy0Js9U5)z^A78IaR;U zAIeH^eutX4^*(`zi3^Zdm}NL^@$s~slkTfuNN_53VY&Tml7RHe?!BAR$g<{mNsSLY zSn%W!`~&Sji@c*?Vk~tPLvD6QipKw#*DEWg!x6=Y3+VmK3cdCvFh=fP=$_Ljvzy9r z%AF6H4&#X{KX#RcR|;cFOy_`Tl$Lin5h8+H5NfmiA6!#a8{CcAM-9O*1YQL-F zxzuoNxb~*&QjM)U+70Jk4AzKaze#?)F=F+?-S0_-*#h>b@i18PLju&`50>z0IxT|xVEHxJ3sfcbLOinc_+n`{Z$=+#_WHly9UB~4lr+Q6Ti$Zwtr#ya}v{6IL@5WE!#>! zZAZ!jyRR2L^m1&~bG+{c5xhMj2sZz@mwFY^Fm+g5dnAa(kxzrs3^2b>qW%24Ssug2 zHy4OjMOh&junA2u!R6VwzU)~;t+||^wg{?ak!ya`)EmOLJ zlPLeu1pVK>8s^PYXcT9}@`w+v;!X;8J6#HS*euKG8yul@hA4}!jQzoi*7@)nNW}`A z_eoWebA;Xkb6wqG>xO#m;weHpdBA-kz)GGH#(dT{o?#4cFYLNv6h0GBFvHUB!|q<2 zIX2knb*u>a+3*XeEjSwW6I~w&c)fdn__5$RF+#Cnu|dBFjY((1=^L#!Y9QB(-bL10 zBj&D?$B3|=5}H8G4$+5}x89a@v_Q}i?|{_sVbA$o2(>6*#MQjiLPwj5VZb%E7YUw@ z=T3&n1p#|ZOBnpJi72SR7cTV}F4!{pBJhK?p9G|lYbBYm6xcyUytgAI`EhyRao2)D z?+#t~Tso9#u<oE@5@RMX^j@XOr&%esS*{LcnJ(DT@WP#Y#uWYzWA6zal%i&nwo&i;9`@_ZSJ=L~a>#@?mj zHJwiz7HPfam_NEdSw-*YS-!y}!k{4-BL&H?jPN}|p?4=T{&+DjxyoVqI+2fTa33pk zcb?)G`VZ!rbEEy^MWuOyqQT*MU)h;ga* z>-qF>ZIdSr0Ij2rr zgnw}@mRh(_F$4sz(E=9}&k?wncp=Z?C{cX#N8RrNGuJAV1`iHdz2JdGk%v7;dGt|I z{BRp<g?6XGMq3y7Tb^+L>y(;ws*>LY)BL(P7i{RwAN5Mkb_nDXQ2Q_K@nH#B zzhy5A5RvbF5iL@6t)H49N|Lre${peT*~gnPhRJ76-s7XQCPxMeXAicr_W?XrNz&jY zk4~S1o-YuJXD_H7!Xu5xrzViv9*2>sQscYtvxY%i{3jzR0$yI`$avp5UVf0s=FT7M z=0;Su?{kpf2dQ)w@-PE&b7!P*(+INkgUH^nYlRgekGlJ?Zt)C-S8AQj;9pGe)XcdK zG{~{?xdlEf92JhEf88TKcd45w{sr2hi$-J6lMjLS7V5rw%XNsre;m8bgOHvfo%;+v zpS%YF7QhDx;(YhsoY{WHyQjzrwSqiEL9)$eCS;>bfvzTy=04vXmvXEm6-I6pX2lw;QN6 z2b@&C_zTbWrR;4mtr1FF7Nza9(`YB=wCPCxu7KB9I5Kbru@X-0(ZUu#0c+@BO5K8C znS(2f+6IJk_o{br^e=9Bx0(lQnnEvSYqP_Pm*6~wqO}u^sgwtO02F#iiIHKVQSL(2 z3j@ONdaXRstyR`63sMGsd_MbJC=j21*5T-9LNH_CvN2_dqeJ3%dV=rNL{V9T;0>_iJ@PB2K4f(&c!107t(t=`wG=hZIy7gL*F2whS@ABnu6W)9j3Q zac@=08ya<$zAYzK)BwT6GEQ=Mz#{A>@`k-eh~lma-+d9(g^ft?@P5R~sH=Q-8NQI+ zd$cmtr;I%KGJ=As!lg5g3(Jg8P$}Ge$z5+L4@*H~a)A>WhFKIP<7%7X-ww5{mUoQw z081#YKYG_`K;FX3g+^PaD3rXht(N%)-OyWyr7_*+=4qvSpgMtX+M}(!^y5eA1!&A< zsu5(t7vU>5ThQni7%s6qPmi@}SZvd99IA8}=4gl|Pd5v`7xg07JxyjZp!4f*gs!_< zqA$4K{fU)wl8X%hMvt0on1J7bFUoC13bXd3IX$odT)c*w7T(&Zap}=J9J}hYlTLId zPv?A7PQkLX-f{OuVO;&Q(gvY@Z*NcOL*k=61SK5;-Z>4%b_7=gXS^)M%92%Cv>J?A zqaSz@jJX2u9I@Zj;MoYFORb)f9JeLU*QO8mk9Hsw5Mq}eV-xm^swc`PaP;U^ z@!Y=#YBUW!m)O;vjIKyp&hCu4_uMO~*IBPB8W0;wlQvLdc&1i<<8KX?$o;THSWMm^i@jIOYy+z!SJO6@dH{YnII?E~>rc~M&*8nPX0-ZG^J zNgdY5740-CrR{%){z+0gND(7J7U!V176wiog}DK22XF>GyKkL;Q@^Dg5vV1&bl9%o z=p|MbYm%5jbK|b7+ia798wPAWLr3vC^}joQ?UFzA*OPNX%CJeDamk~NG?@dK_O|`< zVfqt6t*TKMeVv#t;()|h3XXnXJ59=JtGMp=?XkVCsUrBVvRfzP8^0(9gug5tE}!m~ z{Oq0>YdpP3Dd_C1@9a6U(f7s9=!K)_(r8&Zj=mE`$j#7Vm)s2z@QfpSbVC1-7CS|T z-SVtuexYDmW%rfcI<{MyquZmY{td`ts!NFdI}AOw%?zMW{?CNOV)~~`+##%>cK!Xc zOocMu3j$TgoBcZD?1U9xV-&apq(6C$CkE#BS*Uqx8tmZh zxdeW4aT4XkdsDZvbuqB>_umcZi2>C*>0xq+=%7B^#y9|%sr!9jcyJfWS#LY5@8mf` zG!K{%5$1{*Y8rmjB#bJVDF68Hya({kq~*c#`PBij2V%OF-A1kD@y9Jn{r;n_QE$0=so>wTmbY zc>R1#;uO4JHNSCtMXv!zKiJR3l+Qz48rq6O6zv@ZAt7V{3%_;RV%lRYj$X0Seoly| z-g!mWsmb;s-?f;r+MAN%s#J(F7~t9I!m(FCcL+c3!TMk$r%Z5evi+>^xS1{&199Qc8qy{)1>|f20aoxnG z()WJ1A!D9Rc&9Houlpyj7jQ)`;L;sCO8_%GLM+ChS!>k*asNiqWMiewTp_RNtZ(W$ z0;~APN;2mhB(ksp8ylS$O%U?1{T3QOwpr1UOg;+c z0hz1f)=48$xs!kGyO9BHR!_SjP4o8QPRqi96m{*8Ld+v7?&M>%5%O+fLvS zG-gF3GeQ-P)WWy>`+XCH&JJBWd8d!3fRw@5t`doMZdysjhxeIS?-3R5i@R?IT{xG0 z)L7KEDn1n%zFG~qMVv2!bG!T~djfubOrv^S8AvnHsOWp)8JO^;h6uY!1DR^KcC7H9 z*l*2v+Tv++=Wyxowzy@(+ldUFhVSSQ`*T^8*()yx4w`Z~x0l*2sj1Wy(FB(zWSJHG z0iUGwb_9*EZrl>Fup!xwbh@oht*9 zmR^zgK$I3{c%;r}g$ZX$1h|0~LCeI`(E6N*rY9?E?tak3tGwttTpP%=q&sP4W(y&? z9W>-KVGf}10vW(tqJw&w_7e(>WMCaEDYLYQI?fkOHa8~M(E0xv!-zWU*&&$DwR-$# zEmJtrts(X`Td0m1E7Ms;#MY51PV zASGZ}VLM!b4WG|~|532T*c}_670`S-2*;MlLI%s;%fL&QI!@(f4{Oi;_5J9z zU8I;e-wo-N$T}76jo^ex@P|AEXTBpf5V(9imsrP>`?IwGDj4(J*D#;Mb9D=y9%;1J z6?^OgolPh2gY9R)bnmm`D+@de9aJ6EX%QJFGa3pcImp1BrA)H}2D%EuM1CX8rGQO6 zS%+%pS3SLdRTy$Jhse2`Jml$wpNN4Ocj{YLRz990&%AJhw z_Ug^u`aorKKaa<|ll_rix+nG#z|0{BGQ&ISZW~Hj@_*P&Mth~_Q~^A&$6p|GZ$Pk4 zAfE58coxCs*q$IJ`2|OxtM#4!eEYG3!E6k5{HUA8Y!lhe?kNOFf5JH$SLzF~$CitW z^s}VIT?WB0@2C)L@Y`DKirS;sfm8z$@>3shTqz8(h(*$6n`(?_rAnv-f&#)?K{-ct z!q(?ot}ADjTTbv)b6k}?IsHvJkRVAPj7qdMvCgs-+z4vQT~19ryyx*PkqMBumXJ&+ z=u6L(`XB*m+SJ_eRdFx%WDp3h{^F765{uolk`xguv`8QBhrhmePrdISK}z!tT^Gv@ z`~!Y??&qN@26hkzP&Vsf&<3i}D-4&ji|z|^9lgmVF>ydEcuTnUo%EL6>^bp}pVg*= z=yLF2&7nhlJ@2A_1Nl8$MsQw+swM=0BB{R&J%9YFJs$TNDTx{*t$gscYJ#iXs zcFfsqGXD^#37H2ooTpP0h7NQHoGJD$-fNzE#8U#rZu@1>*ciR&$+NJlFs76_BEhV? z;jBhGVTS-R>m|D-r=!|H<#*d`k}M>lWTX_NW2wZgA1pPOV0^P%Hhq{e9xhGx{(BdSY)b68 zm=+o$mbe*;_I|7S{s~1fZzyUW{%g`RNB@axYb5nZY0_mH`JxXah~WJPCP?l>=P~$fxO+hbyCb*~Z^Tf)j05|Il7*?ejHh0m@o~m4s1H=w z%>_rteJ;Af4kBVz0He_8&S3)%^mNM41hR*km>?X&tm)Z4q7UyQJR{Q6)WW+ma>y9yZ?Oaht(T z2dA28SbQA=IR$29rEe3hu)+&Ms69Y_ae|U6FX2%YU+A-e9T;`Nr)WOyb9lMsLSj3B zRADtx;69aB=jubKO}`2)o~Zxr$8$#Ny1|nr7M{Z9>QnUX3i=IY8}m}QV)OTI)nPX4s2a!IsZ}p_h z6`szHw-*H=Vu;+ck!MD4${+4?33P6+gK`V2DHz%(4=GLyIkYs5O zO02==unYQQJ?P&XF^2R7l>Z9 zc_MS2>xvFdm+R-6Cz@<#FD36H9FrlU3x5&$fw{cx;5teRt#kAc_^R{kgL^HIO&lHc=i0i0S}8U}Z?T&H~!U9QI*y zCsF^i>7zY|+UGqsbr>0#c>DP;6zP=sG%OGFvCAH|_hZ?}$J!P|QDY8}Fay3sgtES2 zZxR556Gd{)Nr}-`hClSq;ZtIa-G^bK!1g>cbtpsg&esfYt`5M%&XWv(=v=#N>#Ork zK?Tr4x2Az9I8bUBb{rLkkwrDROQ)y9z8nM6_OnQL8GcBlz^l>cy(;)HAff8`m)=M} zeY@hja-^CHzfIY6w*9c56C;!+cRawSkqQ90%;CH7?@6>Ldoj@xIe4#H(W;WxI6#JtmCt8)f=r&+BS+;8E9CvrNNqyiG-nAOr4TjvVL{NoX-B*;p+LC)F*U6tiDab0Bmq>7W3M zxho{ySrwvzKF_#%*Na{bx(G+n(%kXJYBWhSFarZ zaa$kau8_FznU7GpfT)|oN#sW54QDP+y=w1`OV({j`J*kn@V7G-7ZUacrYA7h{kR z>0o-W(cYLpMk4Am9Y1$R>c_3+^>tQXdkfOHdbOg>RiWQx)BNzT(vTClY&-zGf!>1b z4UPmx1RGRkh;?ogy2zl!33?K|0F1=a&-jaJ^ z{FJw~{dlH%pk;qjF3BRkn(lIy77nY`?QrFfu$+8V!0sEW=~IP3-Dn^%H8jmKyb~97 zdiNwgZjUEJ0aCUiB77~@JKr?*LwCO8evv{|(bJueGRa$(53!?r>M?kx4)pIMiGcGL z5q27}DXs-^^sS;fx|KqB_VKj=DY1h=BZSo`8H;8qPfwrva$Fm$_^d)QUN` zfQv)=FWe9BH@!OEYC{c_hY&u$dF(H>MygnAt` zQCnV@AQ}glUaOUfST{{;LLWoJB#&(89}HBudg>J|Vf4#Vm84L-QcJO9EJ6NfV`+DaK50H=Gm)fZ>bO&+F@z2m5$W}7ei0hI;glbEjajh%aj{8R@xIAl@~q3 zs6rEGWcT@eQ)kvy43z{c%5`D<2ZpsJ+q zSTU3qwV9OHIq{B-l=~~-sjx4g9-Aq5%J-J21z)?{zeTcf>?7UgV+NZ>It2cMO61|R zzM@e%9Mxv@?U8a~ULA));RAvjT6R)AGF!95=;c4Vl~fGmYK!)xpJ)8LZ7ZtsizZz* z^S{7JG8TkM=R@;U8!G4J@Otx9HK1YJOuAUNd}^6mc-Zfu1St~L2m)LqR%`Xa%$coM z=i0aLLOLAsWseN9s~O1RzN+H6S0VoqFb4DPThIYCI(lqgc>>Lp$ZgY0i{?RQ&|XVX z(D7Fw%mW4nRrX$a;P&aui6AGkbII_bGf<-0Zw=7`$dL+7po)o;cLxJ6EOV<4w(LC@ zB3p-l(NC@~|9W<|%{8ULW>+|(i!0i%>WAyupq`h_fbb%9n=3Q{`8OapJ@(|?m9L#= z6SlGl!KCZdpX3N1Q|p!W0RaJO6lf~*+$raw^9pJ#?mAK5PRDpXZWukSPnKsz=-5Ar zwF5MgPf(#>X;wljEsN*2c*I3P*QGx(wv7~Hm1q$1pBmy>f$eJg-8{9^6hlgI>x}^Q z-Vi@i4PtgpaDz^SxunT2n%wSe24I(8xNqwQ-d3njda zZ}X`n-2*?nO7Jg9U{%+%Lp z3;kpah}~BK!LRKSP)a&U${s#)Be)Ws7K?AIUJSe#w_mInnf0a~jY=;GqH0iv<}Z`~ERchJmXc*hThl+~CZg!Gn@ zm>;A?J^Qae^=kU$c>1uRY=sCrLe2GBACeTjN*jjZZ;Hz|SjmAriE4V7#06dFhABIQr0_ z$dc@-V4&%xu_TpLyD});X8TElc&7^}JC7Et;aMuivJ_HX#Wh{01$tC zDy1)}OUkO_U!^DL&HUKh%ArEf>FF2_amMWjCHDr!;_yVvtM|e~U&Pv%o0&gzs^6g* zW`D&;P8k;=0C6VewjFU@S=y4di)9vT=TEYgHK6p0K^|VzJ5n~Cd+7qSjEo4jSQWAy zj{p*FKTWDQ{2YOvls}^e0J&US>9RC7Xr?=8qNx>aCuKEb>C|v&HAkk5AFnGM$#0T! z$05cICM9gq9$xRG#)4%~lt0VfZY5^C&x~>C9}`@B8~*Q zUVMXbqps~`!#(ew6D#Q0KNB_>erTB8r|-L)?09T8LUXdDX@YGZb@g3*k%?c7QOQo_ zUML22HH-@J;Q^g700h4Ib;TjA<{se}5fI{~P|R93_dV&z32HZ^s!aQ<%%qd22R;hd z!M1(=+8!v54Wtq+>ffDIIvj7Z)7(ET&33X1W8iyRGoVWHDI-3W+m1heb2<-u_4?J{ zK$%ftWlopFzpoJXK1a&;ie6C^|Eq>y;Rjf7Nm}YI*X&n`G`Q)neP3Lzd$3(SYrj0C z$Ti%s#PAW2H~W8_K&1)luN_>amD*|3psQJ9VeymHT3SC@lcTS#yu@=nPJ=GB6wV*F zxtNFj>#s{{;H>6_K8$veQhE-_hX6W2op$rn;CrC#$D&Sv(|t%gwagtz%J*~eKbci7 z#%VN93CTPc=q}VKhW{kaBxE^uNhj z6}Qd$w;iUb&btT@g??$YRY5yk1zt4_27No<{wD&RkTXLwrwQ*xU1iUuF^g(InreAB zEHtcRyxA=LPM2K4x%9bz>t=yow*g(sz@-W&Z@BsNe3RCG`79G#7qiBdIAt$F5S+p{Z|R> zIbz^A=Ce{kwmO$nYISb%9O46A>Sun7+56{AWRc+_v-Nx%{sG^vs2!=zq!8Fn9>$cR zvBbe*bDfk#Kwcf$&;s0{2CBK`(jnZIU>55lYLbAb?e6|>Ddh|)4XnW^I~q-{*GHDG zuU9!9o9mGoDMgin-`laFpY7W1DNb{SM^uZfe=tFPxJ)n^-jg{q7Hi_0N z!Ou5OMP)YDz9EGul)(_7!I}kQWO5rCsPu{fjZa6_lUGUoezy%)c4c+@yW-&v(9J+4 z;6@IJpoly<<1&}N*VNDEI9ms_w9>c`K(RvsA675S`oRZf;r4bAX!&tXHGMngYPLEd z;u`YvKxj3Om?t0&y7ibS1JoHwYHCwS)oZQqXp~`tK=N}l>FK=b--#?z4yoQGPn*0f zc9OW58rwY{H&v4K?)sMimGS^P_7wJFXVBtSYXZ7enOlkxRE73>8lQc$rOj&9m+g`7-Lp zYfU!K-ZIrepLw!M#kq)bN~_VNy$W`4U7;$rE90l#%shm*ejSw<*CSz~;BzMZsqJfM zQJ#x=b1518THh1T=iK+8%ZiA#mi=Dfd2}(9?O;=fwPb5J*-u96+l>=XND#0cZT<~T zGjbUi$gCew_SfIidwI5H3aEq;6&^Q@h?&qA6z^4+(1pLFrG}iwFRcRITI7OM-CV&S zQZ)$MNPl+YwfA8^HNZ7bND?nVF(R~p4n&C{vZj;>B zQ?R!Jpi;2>npV}(+JbeT;EjsK%DMcbOHSFYn&l(r%Kx^KBa(c_B&>?f%5#tqg)BBJu@IB%K3Dk_%JQ zD;ghcdHG_rnA7p2KFSjZHr4FCEoXdEojS9U$KDk?QmnE$BkNI&Aym>s2?)99zs%as2O>9FE~CeG35ME>WxL-k(o=O zQ4w1`1wT2UOF8QlUMEF+dsSb}?jL#BmuLSdx&rs4J(dR3qGpQEdhpLknRSFkUZp3= zxrGpH8~qoZ5hqJiOvu?LQJOeBsjd`q{cQG%mbwAv^HOe_4~9)PXOH7#4H3O8a7e#{ z@$N9Sfku^$%o|Ks@%oA$Q0Do6R%#fG#rULY9gN&OrNQ$<{5BmGR+*2{l)}nIKNgdhXc2-g7NA>v{jwXOQ3q)JzlwVCYQ+!rMy z6KIUgy9m+tH2;xFb@1L!_NXTw>Q|npM^+W~ky*R?I6 zQ47ii-Hc=swx5lEeZv09Cj)QjVy-Cq2=uM;ieB)Dn4t}va~hvwgfU4~{En{jUAEI= zKjCBnx0!6I-%%G!&u~NFk|r!!0N(g}vVw-CGRXOI6e(Z+zzhogD%${PO68_Z0Dr^*XW?OYFgb?B4*lvD!G0P)6m zA)70Vzi$1~J)WMWY*)2pFCp6RO#DRX_>xu0B#Ze|>t7vRn}FHn@9U#F8EkIME}?vq zDT_TLnVC}d&>7OOuPgJkK!YX|En-Uj!$u8VGvSZ%^jdV=E`nU@|&v(b$jBDv0vcVulf)> zT#s2fsv*h^L%pj5qH!;uj7V_{UWn9uYOP;pD#0cFncyO@;ksY{PPY6oJr7GJ2R)j9 zaUj&)WnO+mKe?b#UlR&+Xs-?xT}1N)Q94|4ukU(o%4VRmqaNdgO_(#D_A8?dHH}bHO9vcHS*Hzm3KAe)Xkp(UAg+Vs7FDj z{opH*8iLZLxa3YX zMJL0U@zPg25YO+(3aDpfE-H3ol3o&y; zab!PaYP*90v|pu`FQZm?llFX55&Tuy{$$%*%J$c&Opbw!d7`ss0_D~eqJg-=uKka9 z0W0Z6Utcaw%XxLo2yhj9PoS#ud6bPU#p(@E@6aeJap*LfbT7+COhLT+Yl~0`b4cMZ zl8Kf;J_?n$E_3_XX=^tbn3tsd1={Zv>Y$o>Un~=6!Tz9M)0a&1k_x&hypDr4j>R9? zNNORQ?l6MpI*rW(HDKKKM-w9a+s_JXm(vWN>COX%GX~!qwNgIRPip-B=*2JS;y5v{ zg6oU6ZWe{M?%ffS)dexJDb;<(Y5lWXr1^o}s$&4#Ht5YTMeEJIE59uKB>?uw?QP-< z-xTkU<&A}rz6i+w0UJu*n0jSO=cF0!H5HF)Yzh8}Jj!P*l=Q1r@cA-+TL{54q;s;s z{f|G+OaljPjqyVy4jo*XGVQfX73kK#+X14Ul@tVUd6R|V2A~1`2Q27eM;mM&?gwL( z^OcKA2q>qY&4GWsHJ#w%1?mloH+0??%ab7=s;Z6`;I31MWOXgy5XSmJ6+z%yl~<$^ zT3lSR;K!d9BfjZyuhXbWmjhQiwmU2E?+inviV<&7nE2MEGg_;W&&j@}VuO{&4){0Bz))}l-J39|I>_9#_ zpC#x5>xAJ*6*(Yy(bf@MfF!H-4o3HY?#b`KndZD`JD}CP#yxodR>mBrv{v5V=+6-| zo~yVJg#bf(in4vj4ePB#bC)iia0`HzU1!?1I1Bkc>GE}@Vj0frx$>W=VCYScva5d@ zeE2a_p-IRWk-Z$%5iey@W6_gJ)bb@O)(Q{#PFpD1swVOvcO5ct)2l>Ug?ilXO}?)w z;Oiak=7BvogDU5TX5XT26TzLkBPwlk$`@6W*I#?LtiYG3;YRo;ZS^2u1ZB_r&0y|9 zw(a#i1eu`bXaMxICtSj0`rP;o;mz0+cLq`#!y87^ ze(6l;JE^k2$HZ8xpy<_s26wQ&j^+P z9j6LU9DqF&!WnWag^okZ$2n(0+nN%DVI-bht75i6neBpt+V(jdPn$PC0;;ppj}F4X zEfsh;(4k^Yif36mjCHtoCpu}y_onq6LB!M9xO(i-k1)iEVKqJ_k$bzQKs&Bw5pBue zQVI}wo|MWFj(&1gyyU+?gU?JB&-}OlN)RiCLB%_5pJO*gV99+nx>VJ=pp`vmj#(x5 zW@ilZS;|Yx;AfZO6gx9BvFb`6MhP{RHYVi2{N9$HBW*{F-TyJpM=iRQ_!N7frt({43i^Xo8?B=Z~q#xyf017?(C5AF6nnP0RY|1Xz?Ul~mPVemg43a}C^LUo6Fue!v z35aS0nmWO$h>m9tUGrQVm!CGw``Khl$yRK2*5!jQpPXMtmiDRATTDHJ`xpcf{#f#W zvSmK>ixXWP(aDPx^!k=+SN;ilz{QUzg0G>#1wE>zglugepxy8x#&H+(GViS~l>62N zkI@eh-aX`JpO1hwwxAs>S5mk9nE#;7^YWo{Z66@Bwn}wSWA;7Hj3CfGvBxX9k5^Q` zO#Ai0Mel7={Xre6r?%7`Q-Hb|)Sb{MIVXiDwP&2aCm#yj z2Lkeg(GRY?i=@3Yj<^>-r|M(R<*K6ST|kt2P|9*olFUsx3*%0$d#dQDBqjMebkSyH z`}#8;M%CRY{eB@D{tX}%Az-E^-Dk)plO|yanIRg)V8Od4EuVj5Cs3Ui6V(NnKqPIT z5%KZ8B}y*N%e)BYjfhA$lVhS(pKTBN1WE$_>J^gn+Z{LZXn2plPu%<9XTJG0jpokd z2SPo4ZF>7NIkhw=r)U4`x``kwOjHCnjm7}7;Kf}-?(~JbJ|D+Qc*0MRYK&qMWoIuF zjHw-0Ho+eFHNbbFgaMhQDKeh00}I97vWwAE-QFZH@eK(UXfQ%WUuilol4;FWHZ&c$ z4>L`*aFa;=bG1)w4pB{{!7L{V0NtrZ2poOv*$g0Kplpo}G(~(GZ+QXn&|jEc@-Jde z^ruIh^hO7~?0q1I>>Y^xap9~!+rnF?s$S%e>fBp=Ocf$mZ;=upl%1P%(iKp4hk`9>KM9S7LQ;6;#q(Do4zzA)0=30vn7x+ z3kx@!l1a(vWr?6=i{nP}iCrqHJhi?C^fcN`g>!WB|@3#4<{ZQ7M?KzhE zwuy}pAiSg7N{_>~)H)a*W5yD-<(Cs6Mw~#k&$J=Ia71`;e}wY;oR3*CJ}$=L{E0>< zwzPfilsug7YS7Gk*R)vl!meM$_k$jC8RjD+M2o!<^UUK&YZ(WRJyi1WUi4=Hqtp5z z`*cLZLdTebNJ(&R@YbeO8-ux|#T!hJ&TXvWTe^^UgJl6!_EH-v&qh*(P1eH_SQ%6#(--BRdWU5i zX|V&A`jI=tpgGyJM}o)c!{*KYo#|>f;)Y}rEBo(stAzo_aOtP`Fre%^y(<3cuLnRw zO$So2k4KljaNbyd>MW;0H}cas+N0mJEAYEc|cTq&-)4_SL>@% z)4ETt46;-xZTTskH*~aNUrABMp(TVyekqKOg0b>2SBs}8KFEr478XenE zYj2Nm`x>=>1B>hFzL|i(6@q!e^9_5CQ@Znul(01ISf6VjkO3nfp}|wlQ<~faOD?VN z?u+mgz@TdHnDY|Bn_&Kk-8`CLOmv6V8_dZ;$K)TKdR13;Lj-yp8bN#fF!)AkcxzwW zvty=polz@N8n8c|UmL;n?jqit5P7lS%>LkNfe#iev_QM8rvMN80&T14PJt`jJ^9Sk zta2z4N^(!4?!wqx|CMsC!Soh)*bNeN)cbAj_DKX`BvTAXo;MWx^Y{^ zuT8ni!`w<{wdMOA2cBJWjkN<_0VKuWb+2k!dVB>dT&>Ghdqh*;@gG*ef&fBnOq zeVx^3ktF8(0B`P0Z}N79cKp$cBC?XBN)xA%|2Bh^M82omfB5~v*>+CLDhnB>thb-9 zY1di}S2}!{VLxf&3D_;Xa;nq;_UZKWc8uFsSopJVdhZ1JBC3XjTJQ3UN6-xY<_Vc9 z3TaIOE_%(m3QQ880bmv-^IVk zjLQS+-m6iIki<%x?`_uIYOVjR>5MJ`-4E3R4MldmB>I3H-lSTJ!O;Vq@C8rG2ueWf zjh@~)0s!2`z@6qfm8~`Pf{xS|q!glcG3i~EFAVzkos-aT&FOWfJQvt~ufOFM*m4hl zQHh$1FS<9*ZK7cRHWygva0{e_Q1*V>jlBiFk1u9d+B@20y_O?*>Q%O=yJdUjCxFO# z?-9d6<0eBjC3_T~EyG)XR^MKZk@WC*`WFNIv-aq=I{$6n&0tIu=hxx^VZ%B!=5L5Q zzuCUSFC^Y;U}=uY zWPe(#(+%VDRp|y;t4eM0biJbXRz2v|fLrGe_$@Gx$5GL_?kZ$|m!_07Ojoip1MH#P zc^SB`C|+7A0UU^7Y~x9v&{6(idg_TFI*F29P|cH3qX{%3Ri)nJfeQ^qC{hvz)b~yb zIjK!mcrpEp4noWrWh(>4TG@VBU52O?tr0+L5wY&P6mm8Xo_hQCg+8yUwbAJdrJyi< zBdyZiZgsdN-B;$Yg6*Y=c8YcTka_$I{lm&rPyqwbytyUluN|D7Yj@B<6mf6Fo7P`s&^B64>_&JHHaqL6chv!{&~S z3kA!I8EK}Xk>ZD2SNoPO5?H`%r!tJF3qAN^JouwL_!j*fc4JUldA7 zcd|2|&r5Z%?gKhfsMvt~?&YH%<-BM-kd2a<;8nr6eb63=u81>Hv2gRP1^fGsmT1s| z8(ziLuTFc&^0tRP?^J^In{Uy^B8UC<#N2%h7JdH^FP2&3|CN%0LUUe3$zNydM$~k9 z9$ml>CincB@mmWP?!$5srFWFPr#4OKlxiP!2S+>RuRnMHl>=&vE?-EAWslp0J}DII zi}T=*_TaA^0^e9R7C-cU%Z`Q?mS+p+c8(p7ImnGq>51*!T>QoDd#=NB6CB5f-Xj%J zYDvLz$ot$$LkOu)NMDmtte&vusMJu>EpTfiVfn!w&jDpR9@z6Gm!Di>e#>8*wRu|W zKLtM-JuvLvTGXbs*Cv`AQ1qSwN4;(mK?MijI;^TaMt`OAOrFVFtmLq{sjqu!<+;23 zl$i2fY#8<>rOEqEL8arHxoUj$p`TyS11Kw5+g%w(9W|`c?T1j_2M;A@89>URugmS4 zZRc*<^fxCu587fqU(~^vI91CrA~x9~gNdvq-@XE!Pr0hkJeSanR~hE?VTAu`8K?(9 zpE%9>t~PrbDB+L$lmK1Zv-f$zS+0O!x4=U*e?w23Z%`{C1{@WKqnT9psUAz{cCRlq zZ0GE=U)E8neKzV0kb+D7!nY1lwf?NEKOb(b9c;QO5u!J*M<~1t`}D<6PC7u4!Gd=SZ8sQFiH@t5=sA zm+!YeGboJfTIob~+~?MD)*lA`JC(+vkWYrPIhbCX)(FW) zZ9Y|;z2YwWVcU#nxnT1#+(Y%5@8|BTAOCKxAjk{Jd~~#2zr$t3CBu4#BEU>i^=zAJ z>GSSVz}eHi<1&v`x(!749PXW8yQMVubk9zS{e8LGDBT%$uL!#kwA(@mHBZ#t9T`U@sm8J;-skDw&o3$HhBFryejgHi) zLJY#V4hu8yXnJu_P|Uvn(A%-ive9>Oi1-@A=n(pJFDKJUNm3r@LZgk^6pKc4E(r7; zIz_z4RRi!7UQw|R=}`A+oL7?Nv*IHGQ1xy7eQ(R1-(`SU^7U!QB@AvJ*(<>lel8V1Pt_`uf3N*t#UFg%_ypu9FdY8ZNH1r*2Q~)$p`kfX6 zH2>DsyN^Q+#jHws^$WWB23CseUfL+#TL5TZ;gnwscA#HPI!=PA{RV z+*;8EU!kf(J$zLNS;eh0@L;aUc_P*b=yGS+?{|;*1{8@i#FLF*rvi66xTKL4*4yBV zKpP!T{sf<(uG&R}FI1`Uq*SbB9vZtFNc0!cO0Tu20@uhg|6_%-^>3bn&kvk=x;2%L&x*-=d}_e`%N-VmKsDF-#JTWJd&sEFRVlhd6<^%2aYrldG^fL(?lh%Z#cusIMc_Zsx!Xce^7iNp zmHWkQJ27W@JEA&eksn;k~MaSjo7 z5A6)gbkwLve)GhX8SmY+HSv^gbMZlC5`S<=0D(H?f6?`qQBl8N_$WLxbc1x4(hMaa zjUXW1-604_cMq*}NDHWdNSAaCAl)D-$k1Im4Ju6^z6 z-uUGFUL?TX^v)~A2E33barz7<8&zB`8|*XOWe@i&cY*UZg*-#y+DkUN^sIll{TT6W-JN@L+X?a>??1 zlIr)>LyIgRyPVLxVm03+w-dFsb;8&S;0QQ`ONZ;3Edq7^~*GDyTg|r-8(y9}< z;1xCx?m3hyzahzNG%Grye|g*48*%^vX++qSQkQT4s-3r3C1m5g&;MV^uyh9phlpDU&44_B`WP&P_R{s}4hm+yHr#T_Rn zG!;M7))Jad2OL_a-q=a;9+=2V`o@-c;~&_a9Z+f?(qwtE>d_OBptc?*D$-Z&kDK8ZPq*oCD2m?qsa#lG-vTsFAeDfk!YW07ug9tX=T$Qv`v)MIO7<)J zYXOeU_=jV+&cW3{Bk`sezR{3YdltZ$$w4?{Mm~Ao-~ky&!-Uq5y~Z_#Du?o1W{^xArt@eB9p_ zlLKaR+TokOIM#L;=DpMlP4?q`t4%&&JPinFe-wA<)jw>DZ55>#o;@|?y6>d{!E_?h zF5JHtN>$ttQVcW|sw>@`0if)&?5_$!sfNAqjY%ZPZyed}`1G*+hK5mL$?^G{mhR}Y8a9ZJVmvs}u(>hcxQXtlvq+Frw;V?-au0PFo|KYg-bei`uj6^grt zD;!nye`;m&5${>U-{Rm)K?=-tuYYpK$?ER*stL1KR3J(v7))tE(i}1 z`t*?a{P9A*kd2hcUq%9NJNB&q8|t;pu?JYg>J4K{@q5EHIOs<|kHK+`0 zHlLc?_hQLcE0%-Bv0USMaVBFWgF)}FI31+l@)KwTEvZ}lj%fS+G-1#!K@<=!130yggbE1a4-~^e>Q>bzXm!&=f0if@LIh-Oid@VZE)rUS z8FA9I$1_vb$B+e1CY;br&rxM62DqT5xI&y~w16N*BUAP%5KhEplJAFKqXIaz++L;L zsEnERl(yjV7va@&Zf<9usZ4vZ?VrN3JNc=!=c9LPWyE83=Ew1aGySrAKx-?&ftk>j zC#q+!`?orLlb<7QxpjuK`0({jpn|Pkh1%vD8)0B{$}k!G%(U_-Bn}LMRTu3AXOfh< zl_QCDRL{YRF6B3bY)i>0l^Uv26Ye&UjT-N54n^5G8;m-O;z^!mfhBKs`Khm$5N-$V ziHGq&x&4E>>3$CnOS&`5Gt0YUdDZA7BwiF(cybh|Hsqcpq@?_@qQe5WBLWEt0+hMi zS3533+gX0L@^--~mN-=1(4_YeCDaAQ`6o}F$UqDSW{v$zM6Zq$TV+ed>gPnZTgFTF z|M_pqrId;+VMEN>w+VjTuuW;#o*8v4R^{L9`mi6;cfO62!x>-ejnG|v3X zBtaeikR5Pm1FBNepUkvk$;l)RQ%Z9KpM(^}YACT6J^4zH%=9F3dROBKF10iV-D|X9 zl4jMxTwarFsyRS5>N;B9MTgEzc|HFcl5cr`iM+zy8Q9nv-5N{}yrm|$;8}?@U zUCrAV)%$FAMmH(B)p+StJ}zlJds}|mIPFr&b5K_NdqJ<&ZRlV2uB05`o_DzG87e9)bNqG$2*7X@Llmalm)Nz&*jdUvp^ zw&;WjW#p-{zgfz*XNtwOD~ZRF)rrDBEc#r;<4v5Ba&Wh}35@(f!Nr<;H1C=VC6Ere zG8OqFi?a|%!)qVn3`+P2efqQtlDJOAorgXP2Ht0b3cTnFaL7yPC9YTG;Krei>q3oc z{(t;XPb3gByE6DnDicOktsSQe7~GdZz|A^*U#UHP+KkD;fRMI~3zK**-?A`CS`XOv zJIF+&HP-gXZJu^G&Dl}NpwiR$(@R%2+CCz0F8!8@1&ih>r;hka-cC%5RCB=@```6a zh5=l8o2P>BAPE^z8NWj@xQinBmyfLJDEUcH$N-Ao!wC zX}^NtRj$JQlivvqRj?jZ4;&MOouUlZ|=wqNU6t&*z_}x8M%4(Gs%-6w8 zAr#8h5S2B^1|>=qb3^x~wT*i!E~UT6avTP699cis(7kabE!~_y9;TGIv)NPHHZ0sk z0x91S+P<^|{phGf^0O#^CZTyibZ9YxokB`?yTxzO?n$I;BXa(w5hXJ|r(KwQPUPff z&^-3_BpqIp-@vddy$x^)?7Dvx;kTg}ML71vi$R}gioqdVrmu)0CU4ttn<#xCTu^H` zb86>3{uSlMyQOc*+pCfpPkW`nKHz{N97R)@$|RK4u|p2hT1MVs^@11Way(^NT~vV9 zMkOkFjiT#gGMS)5ZT<0#fv?F!{HQm*!jzoJE+3x5wXZ#9dch7;rDq#Qi21%y)j?fm zKQ*=7Iwijk8#em)Q1)LKg5quGZ z2M}N_=O_=Vwb`8Vu4e(A@vXIR43wtpNlw>M4s+CCVgoiqom?`=3Tnqw)7L3^5nduL zgBLl9Cm1`{=DeN>gSIOosYOzAstQ{tXE-ym4MUB3IEiJF-FgA9kOiFCSc%H`M)!QG zOM1JRUKm|o*w3D;4n_HEj!0aV-1^dt`~#CXuFPnQv21nN*LxBT;h;1*FLz=<;a+U{ z7$&Ojc37Q6NabCo$HN{Eq7t@}m)mDsHKVdO8g0Z{secg>qv*CqMJ<~%N07a?_Zp$8*QAI<{K(h|{@ub~Pv0ge|@ zTOTooH9aZjkheySg6EkLsKYgN7IfGt)D>U@XS0q#KlgPP(jva4H^v$3LipDxO9#7! z*VYJw|Jju|3W26Wzv6MFL#1anWe{_PvEQdno3eU3aSli-i_Ci9=Ic}vl|+?AWZ{mL zL}_Cn5o)fC$}gl6Jy8XX$kU45k&T_ z60EbAet7mhue>13!tiGu_}xYMH}otOclEmx13d5phA+wye7h|{XbMzj=2v{c_fzb@rB#8irMnD&9sN0Rc8vG4ep)Vt zNlTMWvZ}9(xny;-Wii<`KJOA{@cML^W15<~T~O~y8x<9v1mX*?7_t|A)Y=R}_32HH zi2@$@Mm>C>Mq90m4sC=yC~{^L-mV)h(Q!Ez;gqc9G>s0(74l1?^Ugl)$T`d$jk*Kx za^UlO+O=jTA9iKR^0^0T8nx4QUc^_v6?!an?Mco!&mI#!x^vkrA3J6`r=))QMcK74 z$q35JwGb2ICco>y#r;M58Xum6?&zHB+Q}mwOBB$N|0_Ko`)ha5T{NfgT^`!;ksiBD z4o%oE@aDNp6y|4Fg-(KXT9UB;yhmVW|3V)SqJAnVJ%;LfcSc!Dr;0ZErgmiHa_VJ* zQHieg%QuQU6fY@eJu#}rBr5%kdu4^)jC&GGh`6E`(%zMM_u^Dr=wTp66~Ar0DNm@< z;J#%)QA0#eMgCe1MA-Zvhq51_j2B+*k~b;+7SbJGeKKQ?xQ|azB7c(H4qt#XPetmS zY)h+Itpc`e2YD?}-7R0#IGb9#j@Wi~sP<9#i?!^IZ54YmXOzw=n_Re*vUrYq(KnKy z>ieq@H_imA#Ds0mx4h@1t%{OYcW&BF*FWVe$Fj(<6BM-LwTlksGL2A*lz=OQhJ1t; zqNChp{a2hn_6KA6HhL1V7k0HhjQw1Rf69F{)ae3P72e+DCc0mQ!@iD+(E1ShPl-j= z7Gb+f)y?Zj@Co^8HIrOm243aOZ8+V^#pZd-PL-b@z)rX@rRlZp-CZ7bV8}x5W zGD+c+P>OuqR8#mn%3)bPYIj-++l>}V9Cut&1=jGUK6(8gzcvlI4=T=+Y|~4;jJ1S* zot5-7MQL0ZA$iHaq|Avbr$^fr( zMLQ90(nkkQT1i!qyos<$vnA=H0hcMP)c%7fnDmbPq;X0Pvjq$3gYYTD$=XtW3j+=! zR4N6^Q73pz@qxLM2@=C%8sA(T? zTrb{zaO~01np4CE_^pEy&>trhyjK{m>O^PnBfZGX8hF32 zo_fyi?AOJ7LJLNjXES|`7F&e#2)o6-q175K?@5|ScG3Y0H`fj7mYX#B>+RCMtuUO( z?J|8EgqXkg==y~*K!E1r3-UE=!C&#B=cTC=`D9J`SvaRyYEBt_`h02KAsb(Qn(n`J zyngJsJUe0ibc-&!O*_w!8t$zc<5nDHrP*qK)Xj}^#CJ&t%|xz z_2@bRFVduSy*N&scbSHef>sC4s<5;sH!{gWxh69)34^JPfmN!K+r5O70^c@e9=!OyK5*tUG~jpgZ?{Cv_eBHbD^%+d36O zXqQR+oLpsL&1^{%zU#)FegmR}qwc(!@)F&WlU09DtSHbok-uLgEOr=^^|I?3o!{@Qa$g?mgxSH3dG_E!h+N7?=HO=zpN5ufj~b zx<->eCMNWBUjM3^in@J2V^8av1~ejpqLq=0z;-9!v!Ch!KkW51w8r%=3b&bXyoBPj z*zdve1WKFufFU^|x`6sGW+;u3PagB`e=Qg)#xNNsgnF;d8U*1jr$kLc_Exue&{& z`xkuiKOJL1*Hg+F;cF+s; zw}(%SxtcE1nqWG*mu?p6fEjlppFJoYZ>6TtREUR31+0FfU5i`k&+21{lb4Dc^UJ||>KP?3F66}x9 zp_2-Tc*J@Vl~x2{_}J&EvrejmgBeU;b<^OrVa>G%p;i-;^U`3Qum4%URQ=;a=KOUQ z(|LXZmG?Oy@`B?d(gs&tFoY1cOZMA9(xjb=6~nqEwXxlUqPLeefrFZ0THTy*rO&Oi zw7-|^if}Tw=qvyq!#Wi(X>8y*Td)@CniT^7ZS6c?{?OXtw&0}ajIO~%+sfW^=7N>E zgZnUVm};rlkBg6LjZ2EJ(`LRGzDXz=M~xk}PiWM{O~ZxpGGf&kJn}CzmR<*CM7_$a zUfynsQ;|BfH8k-xxt5Gr66Y}Z7$o`PdPNw8bya3fUU2G_MJRU@!?itv(L&HY!v#p9 zKj@aQ8uG!#SU`i!OOQKiRHe=^2M!c;mVcKxaii#p$`?=j7M2(gi9F~$Wt9%5J(0Hv z`WX8|6^3guSPqj$*+BPHfxA%)ybIKZ#UsS)_@eUO<$W6D)1Y9fego-ruyjF7j`+rh zIxHDi{M~ybDjY#2^N+5BjL!I-Mp<#Y*A@{D3INwF<@>SNc}zZ-gZ~=4(N6j+hTn>| zkX*4g?7`#jhz!{b)B+OP8W`!IP_c zXED!Wpt~T7t9*gHdKoTIm18-JQEk|>&SDTF+9Bg-`Ap+yzVOj9{1lGU?e%&P#STJ_ zLcSWtU``v0!z+n$M}0zZl(J{+mcd_od9HZ3x3mbRkqgNEcoT5f0x*PyF3%^soToOM zuJZ*@9hx9`N&BawHZsAkIoy)b0WRyx)msJQisZbbE6U_a(!X8xi-TUbBq`bvoxa;@ zxWNdzKVA{BMX2qHro8R)?ip#HOlTR(#1#;;oyhPwRxJfKr@i~WJ?$GK8adU)`QwXO z(Qlxqt08l&pf)7(s%)h`Af}=d-VJp;Hcf4Crb$x?2sM9A&Nr$!g@*Zp5^0BcVVeOC0q{4l|t&1qGm9}5#A zWQSDfJ7eB_tp$nY><{PVH%H=!UG5~beS-HLvrc#tEMb*4j@(@~Fq&pJUMU`{#gcs1 zSMuf|Y34^MP>XC~MRLMawI%u21PI973abP)O+IcQ^<1($t+R8T0|C!kOH!aa0Cf0n z7=m+~nj<}(yA7!@%?wY?CR1rflTgu{gYxBJ$LjZH4)^X)OxR4}&-GE(r{tdgQrd+m zjr8Pxdav>uZ6LBcjvmqj6b{XApsD~#S(f+rHns{Z{Y#aG8+E4I_Z_x`OP|wawAI7k zRP{g7ceIVV{?de~rxE=yKZXhYL^9N-@ghEK0I z-I$?A7{QQ0g9aMX)cr!7%x}4njacBPZI&mPh#fEKxZmkJ>#E0(1-3EtAWYx(iA7FV zf%%tWNo6ADEin5IR#WLPiNxQY3*^aWLBG7gf8~@r#KP*!3in@^-hp2@D2wa&P*~V1 z@YIRgC5g<(A81Frp%~>AlzSu&-_;jUkZ{rqyi-RziBZ=tW{DLPcVAw1{O#3I_IH}< zE}I~`a1t+m2ku4b@^chdQmx_Bd@lHoVlLPJn=3bm(QxR8?5mjZ4?o6>?j_L%T0?&X z&vGpjj;;w!r@ErLCIEG(+Q9X2YRnTaX?VXUKdTu;03Lr$p6n`>4*eW~K44i#S&*UG zXmJG1JaY(SPHW-M9oao#wJZJfHX%T^9sU2_CFWra&&m*zEVr#^X3|>Z&7L#?|5yfz zy_rk2^k%!mfX!4IbqtdCb5umqsEN#;9(2^_Wk<_n91}B8h9swHkR|`C2&XQJWvhX7 zsy&Uc_h;}Wb!WBmij(tehqFe^{y|+b{%Z%=DTd>to6b|Xl~w#f9sF19tGKT*`T#NQ zwQ;B>5O@bWe(S=!Sd#ba^{=~h zjRO&o~gO&{ds}jrR`klZoJzb#_+y=p8^JV$}TR>F>1KX4wx-Zh02uh2Vy6Yza}Sypac^6 z;v?H!xh40}?`eEVeDeVxFcDs#bH+MGB~v6P;>#B*@TWuq9Vk&w@Y?=R?Ny8!t82-` zqGamc2)-+8g0el#=Xv9iSP^LzN4=?f`U(Mkt;=0w5Y(>QvB&`&YN1a7 z9o7nXXet*HxBywN|lxfvRx)63X@`6qtV{@=+oY-Q^$s2l5f#r!YBdH zuwV8Ys5D{<`iXh71;Z4$p&+=WEai_%l*2I@O8O@PG-A}QX zr|-qFO?WghkFfD+Q#}W^lZSnVyuLcZ0-FdPedHHr_H%!o7T6P)4|{MYYKHuav@&l2 zuYp#Fsbn5Xe~fj4WW0%-VF4bgSQ8fGvwK0=X^OBn^uU-w%(myHE1oLG44H6kH@2V~ zE||dHmj)eYKV$i9V|vPqO}Fc_CwTfPx3o}`Ca&<;IyU1Ae1zrFol@F6V2J|&^Ip(q zvYNhn6__2;M&u*nyO@T9R@7$zGJ!TVxD?7O9AXR|dZnCmmVHHN!^EWn+H8A!i)(zV z68|$4g@V?a2-|uU86SoJtm6VTQEQFHtr$49b@KhwP{rgsTY1X+lzkLZm3aN+QtD9kV^r;Kkzd@d*%YRy=$wixMcct;LCb$zPrm=l^8i&I}`-y<3N{-N;QcVSAp z{tnGQ&$m`Vmkm9)556`keZ>TmN~*$7fr>&qh4yRLs|n69n$f4sez=zxT+*b34}ACb z>Ci0T5Rn}~fhr-vR$GzghAN=gVO1Ow5Bg0gbKbSzngaVYJk>fl$yUPi?lR|*X@*c6ZWr`NMYFf0;K2pMG zBZ*yG<)enomjiodh0M=V1rvb|T!$1`NlBRs{fy7LE)+cN*FM=oA&hOA?#WonUKUCY zq{>sGyMT7b89Crw3{ZXyH&rTu{|XLx2`EJsOYWdUNsVp|mvA54f1vo4`mE^}z&wpCWM$;{B7S0`toM)}>kF`) zKR3Aa7ndvQ*J&O_Pyg!aid#KyMK^cyLIvFe<^B! zs1v>@`2zXC^JVeQ5ihA&)DVaqVmL6_C{!UrD%>p)KUGJ~=UkMOh9(&&7o0dPJqzbR zjTL%huwqSloH81JBkI31vN10vDxHkI`Yv1`nbY1c!QFUFq5)Novr*Hm#0{VkL?>8m zgiajL3$-@Sm4O)L$;H{c!*&&GVOLxF6Sa?&q@8CmiFFw9LJ~8C<77=eVR+zo{+K|( zbtupQDUA+OPR2?i@J8Gh1I8xp!J#01{bo}@oB=pMJ}N#?J7vt1p$raA65SBQ*UmPF zoG$UFrV80Mu_rBf=<6?6EcF{w(Ih+u?1m4NNwlKf*j~C8^*?ttUQRq^Fld<7Vk7y7 znaF(-3-46?KG=A;7%;a}q&egx>BT_qM&B2BPG@pbxLOlO4AX^+@5<=N&Z^!2?UgIE ztK-XWNu);=0`h!;!xscWXs#P6ziMkjP0}ZD5bI1;!X(I#BOhXOD5H?;76F!?Pe)lJ2YWplek}h{o1sCv9ca>_7j+3O*H(`Pox-IXMkR%L^ z(@5;^bl7SLTSjFh=mh;s{j(taW@6cyK7APa!1Of`B|G|&y*>LC%3HBoi z{NaQJD7UY@oOC^CoSB8qx0>r)eZY<2H_$SzJfkRcR&5^Of>3cnuawC7N-((|TbX*9 zt|B)Q%_uOG+{idZXZr$b((flo#X#@=G`Ky(AwyM4ci_1+`q=g2Ob!g7yx=a%;9UiP-7q1ElCtq(e(;-%8mNRf$yWPr zFhEO}_T)UCn=F72{X>mm1%)`8+JTrz0)V+(Hbd1@Cf8)b1OG)so>5-XExLpcE9LCa z{vh*xnjkaOL=%!n?dD7Ger#Qd*5|mS&~HZjR~hKj3up&Q*$>Yl#%a9{jV7r4V48=( zsB#6>V)UN!Kzffefx*l;@&Zn(|8=vpGSxJw;9jemKSUF!k0iP)y!fluPxrIGo5K<} z`c*oHB6lrIGsXBeD0jlnD8ifhy>NPfzTDfIrifh+ZB!2Kdfopv|7F4+%P)<7#jjY{eg$E+DPY~u zVfysmyZ~gD4E_Am9Ho=PG@^9x7*+=i-@ObIa`5(JpZYLvpKkj}Mg2bhviyq}8;T-d z04)sjUQnsRL4KTAOoTY@HRYoZMAJM1KJUeI?MN7i>i!=ueEcJB+wuo>-{15aaT4oAYzDhpu7j)|_&nilf0WtnZMv8*KpW1z zuq8BzaT^!KhY?xKcL&E(g3wtX0(XpK>9w1+omS>)0`dVqXl7LfNSuCVEilDm6mtN~ z&3x?fYPjb=^uY?6YQrw?jyF5$dIo>8%arZOa&JNw!dk80f(fpdH-O!?EQP7SuiX$n zZuyX@11k#v%wKqC0?=c?4JZc_AVWb_l#M5|)!m=L?k4^L++z1Ux5L8+~to%9T$0f4a%P1!AoBHIV0lfY2B$0=s{R8!fM9nW&^D zu!#k{qg__}R<43CZp*a)@4RCtSHW_H5BIi5F4sYBe;&%Mhz3P$;E`LswU7XD^8#*_kB^8NbXUk$9Ue0oI>*?9O_MIaJC zAM=~gkK1{@wxTH>D>|!Xa-3@^deJ(xAXFG~3{vZp;7Qq{LHNKPI8EP|?%6JROlBM{ z#D<2XK)=3350ODKBD{B*B7RY0|A`Nvm1OBQ;%E-@#28Qj(GwZnvRL@!Ud@263G?L* z+Sq^u2gOTL{&cH9C+@c`3^m61^wcz#W87dz(qc!FchiO4cJ*2qBW}h+QVLkhuq9fig07y;HcHv$5V5^Gp=487b z$0ElQ%+Y-Qq}M)fpHKrUmAm_tTEHf3wm=@`-0RZ)XWX@?&GL9V5E9y=SMyfvBr&Uz z{bVEKz`vN_&~!HTKejDV)&A)4vkIugqw50+44F80;fR}fUovEn$<;}}tLe05#4#~E z2%h1#KB5M`$7zoLOf`vj2cWLfZ$LB7cKXty!;=Va>XbD2?Z(EwmQ8R2!0?88tuqOB zSaBZNypaeLRpfDk20JabX}5sVOsE*ttt%#ntB5&5KnV*pe@;bfMG`Xk50Voj4eWJGKJ<;g0A$;nPCM&#TB*%3;@`0Q#+ zr5gG@+C1^8KLNxXq=tH*R$|Q?cy5Uh0OSCrdaFP}dr8&=3?~h{XXWkNtL#=!5iHCCl&?`t@V9zvCN!>-))qc_BeX zEabD+;HenW$8ZxR%(0UkDcE)4NQrij-gr9aB^}ZFBfTCEDPiLJFEw+{`Ii(#07#*v zs-R&2UAh764BiWD-(pXqR6WE4RuP+^D6gwMiQKQ%3wU(IY(k386<-sm*{owE;40Jw zT^7%r)9Nhpg`TO1AGH|{x z3IA?i5yf81EoH&57ijgXjHWBkK)UYdh!&+F=9$n>;EwRlV~qU1Q3+E>++ce58Qc(_ zk2vTfcuy{!m*{@KVXzQein6;j(G?~$x0Xyf{EX$Nb>sW2UONBo0(OhtaI zZi225pom2{mTdT(I@Zw%&N$pPPt{PrP-uBCdd=UlM!Hmf?mhb!CXTv9D97l9L>&}S z)Jnuj3Lwxv-txoP#f+5=+>@qbxaVxviFpe%>67iErjtJE`2=*rFKAlXv*jbRt|iIF zG8JZR_KtX+*)S_*-vSK}Ryu2ILTv9_s>%^-vN{C?{ij)xeA6QC(VnzQGu4>L{73FT zXQG9+Y0QlNpTG(GM$4?jHRTD|BHWYl%|>VyP~JIe;ru{b_S7;?j_0T_o*-EIpMb@5 zX_iIxh^xgDD_u=k!Y1`fq@! zt&4LqV&K|@RpEl?bMe{4jL27y#GFIkO2+DR8MiQogUa>r27~#}tS?ic;ig`Pxzy-| z`x>R#D0k$+-b~BTA4gTILhsK;S3n`JJ)|1e&LOTG)p{*S{J|)~58Bs*#e8Js2sM`L zgLg|Gi0F|d_sWA1Q#{EnJ9rC`5Es8)Gt9OMaZ02epd0kq6~=?>aB@ZG7j=|KieM*<-&l8#I9P6Ob^Pp~wMz5H zG0tNYG61S8_UqG3sk~@HL%PjJ{@TU93rd(kN3Up@ykgcCzj7`?No5bCI!=U{8j8GNiW&w9dqmo1_1K!m0Z$$2$rjXChW!tJ6KxIX- znV!#2pu0>QK#GScs;k~>snsnPX|bxVfIcz(@TLeheY?z5>Cr~H7#jFqop#``1M9m( zGWvk-i1EagwMCUDEe8!w1GVi@{QZW9i=D8-sL5*|XZH}S@ohR|9AaW(l32bw@ddms zEWAYR!py-e)tXT5>5P_*DB**F#i&CtxvwJ7YTe;fYdua5VZA{0iFwS9 zfB=;g$^T%`npo^0MAJs;Go{gO^~KA0CX`z7>8i7ZV`}1m07Wzj2dFtV)1c-UpOD>F z;(^12N~Y=^v>mE=$yZ}+?J_6kUsn8Y|$rI{jo@kzfRSB;mtk5N!V<(%uY*)Sh z3g~0MZX)ZbBN9E55;1}eTQHY8_xFopPgLqw5#WuE^o*&c16%uTCgHxKv;;RGW==~` z3{h-mp5P@p@}jqki^=hat|IZGR0zi@$^i>$7CK=jEad9ZZbj>9&Fva&P06Hg*M6X$ zQ|{ug#Z`$z0#*H9!*@IkoF>ZL`d}WM@&J+hkx&}Q{p*(Zm~FUR$w@s;M?n=4QY_R94=R1x`@keo!1?VKw0r#f|Iz~JBkX}({V0^v-O$G9 za9Pdf0oNu}y)fZiMaT}cO9I=)Mzdp=XioNFlVo&NkM;=)?$pCepHVT)x%QwKoQP}{e-JUD$oe?i?Tng7ZgBM+;RV@GKf^v5pOSm^9drlGUY|*Zo6~CSB^jS= z-*DwaHi8kJyG*XEcfiy)BH9QKBO^-l74cZN6IB*TeqZ}sa`lhien(ij4mJ?K7-bpu z#Z!~UxXa9^W`n=yW_oJs5(+tu|9O~juCsspv#=}%=hw{qtP$sTFo~Io0m*Xus#EA0 z82QAL=41}ef)Il+1DxI$jm%S7 zi&ri9q?Ib5ey50cBdM@QB$-#Alyz-?D;CL{_m!qm^AD=fdy!r|mY#JzXt35FY+PaG zxzCG3IWQ@-qu$Z4CJ-8S1NFLlJOiF;XLRIuc+2L2j4eu6lOe(wDAr8|Eg;=wVAXD* ze=eX|4t5lzSqlAqpeLi%XK?rAgzigyPo{PSxK@k35bRD1HTt_uv_8&4&hg5$AWDws z=fgOhqNk0rPNX)3&M@`EyCb(R1~y+cl6FDC-Q7?p3AGZ1s@8xCXSRZ`?ljJGr&1`+ zaIX-F*c|FW(ZQhOS`1{%#Nu&J{#f_J=O8uiN>GC$Ip3~K%B>6Q;eW6;l)##+7JfY? z2U`EEZ>&2p#Nv;^jkiiM>ML+!_NAU0kZ7rc<|15a@RHJ$zI^foD_T~VhVn_am1r^U zuV4UK&zp`YWPaiI0&ufp3w$RW7%Y7fC4M2Ax5_5(xw#gukN&3<9gEYm$8K6Zk_i6~{=v_GcZt8PmTZGNhM0uC z_q>CXLnSNqZTcd|Y|2TUwikn3Z%opD%j?U`V|3jVgxXFBq)WDNR1M+?3bWCMy2w-HX5J|(@!VW#;6szkeIi?9~l(@xz7wq%$lCV~{O zDrozV*g$_@qBEtuu;+Q2AL2rI1=c7^0+ciIve6nx4lof30TJ8FWKSE`@%`s7uI3@c z#AuTznGn|yB5W##;`v3#Bf~m1Ko|7Hh62gt{`&)6e5=hIhQ1;>g9Y&kpx0!{eFo^z z-U?O&G8DxreJ-SHw zKisGw!>QQ78A9gt(TQ+qTh#FR?Q2_4T@ia@sCYyAhf@IkZP9{@`4Um9&3~Yh^2GwI zUFpjZ)ORE%56`>mm1#c*p->Jwk7*r)Zh&^=jUmDxFv%3;iCyupZ_s?o87&IP!UhJb z2yJ))BW;-`U_`|mGIv(Z%5JZyXvXqXTqX9zx3DGHn8_uXNn(*w31v7SW*>?gpiEsI zxWDE)$WP>(zWpj7#wh%kaibGkT|4C*lt>x|MDq^H7BiaTjU{;up7AI3@H9!Ap zZIAK4*hCq-8PaVH4FKe_lSasxFDv2P0yk&vT-iZSO`Cgiq`53=;?z|%*nC~ptgCv( zD|5Draw9MFS6a8&cSS!cy=QeR;4B2X$EuG|=?NS-I&c_(?f|4aDF8PDepigIIk7B< zn>~tM@^A#CJMby+PJJ(i7#lTB1@$p2ZW)dQ*>DY~fE`R)P5>!{PDyJ$Nk-;0G{JtV zXPZ4{f)Q&=KX`BytTMP013)*595^~%M8mqp5rMR~e^g9(z3}Ud&pI*(=#o)61?=|F z`w=PUZ4e~_e;2}X?Jn~&ZCiPJzxQY7={gMpg|EtREFRg~oXyjH09_8Jreu1_QC_j} z6!A~Skq7V80MKcad<25(W%D~%Vyd(vvxXg^838&K8{R(NP8WQIF(VkCZJP!gFCSkh z>F>mn2()0LCho5~dvD?%-2|Vx>EsO{|7^-?to6wHR1q<^@KMJH7`E*m?%amtn?^4I zZ4qf{spxEmic zBtmSG&9Y{qW%>QqTW-`s)euHQLD!2`R&>l3 ze%qrvk@JfS#SBz2?(z$Zh^UwkhN1MPno8HDu-lU>=3N=3HdO$B5-5@*LN(zTsjeO3 zJUV_Ip71_O1|QINUz6E0De0=`6$Ga5nBdm#Oh3^$N) z`=;}z0L~h^ITfk0uB?HK@C4`issOh9XTfkNFrS49tKP@-*uzIMXG^#IcOg%hVqmW9 z{P|p4U~HN>b7H-r$DW=OiTyJdw-}2=755# zwDZ{@-4_G#F3EpXs^}X}D`=Y2umCw4n*`9z zyI4wPTJBpV1N7?KMz?@gq9AlO-7(H4yfeCVXknc7$$IUQ7n1LG4&zaHuzLZ(^z6!> z#L59>m`h)E0s;6hu!uF+UyhUzil!F7Ge7f*UJZnF4Xrh-rf?Lyrdk@vAN1W zo^!&`fbQq}e3ZNeDn&!}iWNGn zYfy@y;2&uz_;N=7K-}CrLVt+-0elBu4e`aF2IzDZBTiG80ycaa-Ws{TgR%tyerTlO zp){_^a4VwE(cT>sRk~S5^jFSJ?|jddzaYFHhcjBB@iRZGlxC5Z1S3DgTM(ih1q?wc zzk~rH&P}@}_avwpZMGUx2CgkN{|1sk+o6>FRZoL@(tRm$kQoF+EH58@3Qoo4-H%lB zr^~T8Mi@+6{?Njpw03U0`V&wuY|3s&e-Z!}1RgtdWk9FNi*QaxY&TGBVC$2h1)t$0 zf~2na-4FcI9WT1A=agF$sprCPvnU#h22cl%;ZD3aHY59Hp(A~^D1-~7QH!DCY3lzG zc3_r8?CmHz|xxrJHM`v*F`?hP{Ndd zyIz;-fI8ptI1MVpZrKW3ft%VM?zgSGZviD;@o{pCl@#O`{C$W8M4zoI>Xs8Hw+h*i zN|eNXM^ef7;VX6$_hh`@_szPW0g;!xO=T~ingF}ay@=aO0Ge(VWLW>|2E%*Jupmdd zq)&w;wtV47u3h5+6J}V?u|~v*Y=O-&ZRjZ#Gh0kjknhKX_MXg|zgLHcLwq6f!p(8_ z9{PCy%~OIz9V>M{VZ(jgO#4@u{ifG}(F%wpGy`aa<9NV-O#o8EG?22wRq<8-zqhaY zoqqBId6Z)aI?d@OCAHQ-_(Hhvf~>zuujB$APDE!8d@XcF_i6U#;M8PLX7-QJineo^#dd#^b9nGi+ zjT4{#n5u2uVDAsv43Tbc|U8-TJSf89WBo; z+r^ttN&8r+kX%8om8aGP+QB0^@#{9h^Yd*sfRrboHFJ47ax$5$w4p0uZDJXy!lZSr zu&Z@wS+G%4y52P*p~Kbb>T_wm_-f=N<8KV$i8lE3lR_iJO&# z@=;KY^ji4F6L_7CocL0?mpThjmyv;Ow!rjMI!uoC$sg2%Sl@W}7MOZ}_1GH4ESuf4 z$>Z(M;J-RRt_YHj_9WtHelCq4S})IYruXJ>NqD4Z-097O>%PdR1x-)tO(**x{1649 zp1TgH?Z(A+G{^Yks6xv{?1UTfYUxcO!9Y53DfG@a(MP%k^#AMbE4-rWzCb7FMx_~~ zQ5pe3S~?^gP`bOjloSM{8$<+YX$GWgXp|C=uAv)-21R)nf1m5Uzu~Plu+}|u=fpm{ z_dfd&UmXjM4(KGA@2~OLQUFJV?o+No*KohLI_QyjD*Avf1G??C+~m6ZB#DH%Rs`9v zj0o&cd#mIVfha!BDIW6#<>PNBK^?p` z%}Kue#}G~3Kk z#D@L&E=&QPXfITHeC$M73estrI*~J$TeFyt{IJ9E6HN+7{8v*WOs6k~iKzbuKWOiK7*+W+`?0plb1HyN zw>>2X>U!-!q&`Wxakl<`V*KqnV`=oz*6w@dYDP3Y+7qQUPqVHLkp#(O>_#T>oV@!2 zZ4?72HScd4z|&WdHPZQavJ)*08}r;TwjZ}86nvQUl|SNTl61K4l4>i|4@+h3yfAqN z`1}+U0nx4kU5mfYbr~BOHT0XCltuJweyBUSu3NweP%Hba;;bF(``Z^N24riaP2TG; z)lUq_{}M3I>f^bqM=n@SCk>iG?Y)@X64Ce(A9$8O5kD=C^Ak>39&neP7zyViXM?cS z`$XpjdB4?A;c&tZ3$<5lYLMHY068Q9zBI(& zSXS}_=G*fwyce29#O{$jmp zsa(4d4dQC3AY9l|d^mAng!vNCY^Pjt(e+$nMz8#OB-S8b1>U(o>wsHi2~n(8*v(Rm z9Z6uFhI^FBN0S0EJ(1287l7CSx%oE=9ucO-K0j4oVNSto+(b@RK6(ThX=zCM-L*}3 zI6a<{$Dd|;h^Aca!5G=lILn|MA#(3+%C8XDDQ7xyC+hfW==%f9!Ka{`wdnTQ8iyDzNi%MVu7y5#Dmo;!jMR(jMCd z15phWHYsM!=Y&V-rt)IqIO1=X{DX(Pg_4!4cjl1d1KJY%b7~#R#l2U&jNt2;!4Vn&8oac1?C1PMW&?jp8BEhhOxCDuUY%hyeQeYH_-$s;3*(7Vt2I zs4e=@D0lv`9}XbAV?Xy2tSZ!e>rs_B=+r2+nI}TgEmMy%9z3ac4f|A}23Esc7S^Wc ze;q)B&ZC?f$fMGH!lOu`kxN{!ln*By51ePvcac3@pv~C*3^#MJhz_50UN12GRiHvT z0U$tC`v<@9gVrSJ9*1xzj1n#7%R5AN6mVY&jNLXd1+7)TY0!a(iBm}2S9-~SRqO`e z>k?1b_O?neU7YVw2siOEWj63P)DY>DeIjw~cQVQksFWu_X7FuZ3~lvacYDJVRd&p> zx8=>}C(~h$w3HL2;^0TUyxxCxm{egDZILU}B zJwaV#-9Mp2Mc~-kcAs_b7Aeic`st_MUi<0|sH%Y%@4>lZsu~D!RsmO`um1a{gfCxs zn|5B%jDem&dbcc5w!F`AbeerdN1W$Wzgykq*cqMsE87~&+M@G=+ngSzu(xF%{QzCL zv>7axeu!Y1!l*K6!lBL%xgJ$4`MgAq0s{b-T{-(LVqw%AizzYRxk?ePrI7a$x@&Hv zxza6tM+Z1z6;KLf47~UAfqoD%d^?E@qHamghn@|O}7ThHr=cATmux4f${P8 zN!)U^z2t7^Kyq+0Jcp_)jzkw&Ga$XF0FQTVcX$kHI0vy=b9%jM*+8Q3Rwj4cgV>E9 zNg;IAAm(Bl;VEENjLc}ejy@S32NGg{C^j&%fK)7!N+mUxxn%@;+ zET{WR@qEoiQPHD0ujPGkg~1<|=Kgun@6 z&&3PjHPJ=Cbl68$cphHGJ-rKasldB&&6Mw_ew)q@9yS0R=Nph6xD-hvm#-yrs&tnW zD7~?+2`I8ZV`3?MHhN%t_a{q5K8yl{UCK0&wfwvnU*v>I>DHVo{z7x*o7aa^dB|kk z#!5}k_Mrze*OOt4OW!=WUh0sF`WO}R8;yaOF_`@d&jZr}rt?K={im5HwTYqNKhMBE zl7Py!EsMec+*tceZ_t$2zQ={bw+FpMvP*I8dcxCv1JdA(Z+(&2LhAaT#@U7)3pr-2Qid13s zlU7geST57ii1GH`8biki;B7d(m?O>dX`s**(Yp)P5~rUc1zLLh@waneqVXfWu$)TS z1AKLFJHv-gxr<$YHTHX!Lc}0qJPmDlORrFykX>rjG1kFSzkk=MxJQ?wu{dx#0tf?l zt!oCgaFsyqgJ^RMtd}24Rjyt{|4)@Lym`cAF5v*rHli|57iAhL%iUp@ zS6BVg>EV zucoitiug-qhF*ty+1v$i<+s)~z|clOBA~V3=D5!6_rJSoTEDvCpSs4#y!< zw;?F(H@l@<*C0xAoeQ5Gny-L{>8W*W&e-B-SUXq*qFS@^C>o^!Ocv6a7edtZ3WIExo(Yo*>Uy)xV5kN0#Vz!%#GrAy$28bvEf@HF|uOC@Jsp_!#IP| zqC)ud+WXH$Wni~%5%(GWpf?_{{XxCy7JnXfr}^Q_&;Sl*0PG8MJ(-K#(W6clwlnm3 zK;UJ&7D!PBYGiUFnVg}1!ck4SO(3$8Lng^bmGGeJQJqrZOycFH$YB8flEHJd)rnC- zPxvO_c9JeN$flA2aD~b|g*6z+*3UN-N$Q^VUj7Jz}rhDV6_lk=<6L&K9~4>INA{yoUKG6k3unH{s5->L~rYxfon zW}(-L4Po`G^19EwP3PYxr-PjC85qkQ8d+@ozJRprt$P(8kxk`jr!jYY1i0jug3vgA z*DYf3$%dLc0PHfj)+}x63vh`DmhV<$8YLxN3b$NJ!oaOMyFIe2q)y7}9^!!uj4%Gs z5Xo%(M$@*<4>k#?qSBi(Mf;~ir;9`#$id5dgxC5KD*X)|{j zUQWz-6?=dmqMrg;yJ9k-BRquh=HK5VgdlR;BC~P`O7viqsIS)mcVw`7<&d2SkXq(_ zYucB^eu+pS^!S5-J0jCSx_vsrz`#IcPwMRfxxrLI{-TT@P+8^$0 z##6mG6+l-3uy>sUWmiCcGmG@*z9>?r&{&~(PG*OM>&#*uL5VyQEV--lIok2bU4z*27-||3Z zG0OE0L8;hx9X9pGq5sncwoZ85P5h*rXeUA){^7+ltZ=!@foY1?@8d-TK&;R z*_=6kpP=w5NAr8~^j;sW1&RiZ;7n9$*#L_NHEudQ(M90{ zTku^BMF4EUJjEG=n!d09_5@5SQtz?z(m)bTJ-@{Bcy0wbBxU-{!q-eH-)N^Qib9$+ zPSP0%*@rBvMSwzQw|SfJ`g~08+41_-r(A{w-?n^~JZ&S~c~nX94n<$((S0Ter@t5TAEs-*^3?iomn9XC&SPq+;=?mPkCngUw+ zFIOLvC7sqIIXliODGc<#`jYs(G+78Ri1?yo;uvJ_!XN+QSd72{sFl$TLDlC+DW#T*dJm9a5l6h)@s=-NJ<^Z=4fr! z;)S2?o_Pd@c`;1w#vuP5xw)BZK6%hMd_26mq~EGyH&e|rgOQ$YVot+`UBg0EXS$?% z2gizXGTuUmHI%?2F(^19o|nKr-GqOc8TG=*q9p`@2#H2?)J0niRKHZIJ~y;G%YDh? zxfxgQ&ncwWr2pc=-+AJ~eONpudiz&%LE+Cy_v{PbG|=3R{~i2(%mM!9n?kdEz4g&P z?<&78HThC6m{1g#&MRu*^If=ZQI^T#P1$3*vv+bZ7?0^$|Q|-&%4gt8$K#Qgb10>yv1Bm+@12B1U7>8QRdhW`sp#r zqP4u2VqqqkcY|(VqOvOzL>|3Ft`VJdwEBSd%}3t%SU~LCn5VvOtFMr6);DXT0;^XF z9PZX$qq8geGZ^z#CLPsU8=sWfC$UgDz?@scHfEk3%7hZ8!d=_}(Ejin^R>}RF12a1 zpR8=AnkEyK1y^97_Y&n~T=JoH_DEL!Kt8s6B=1B>FKvLQdv!m1OH?pYTFk8(}C z(8zD9dy9eeUiVU@^i_K254f#7>Uv@m>jsa=;7QHgUO{|R=&NZ4yzffW(X3>}*S||C zu;3?U!>Q^71!Q zqbMrwN5iDyTD!r`0NN&jjSF1Er$-CH5Y3O_4oiaDC0ZZhzZy^qcA_%tfp;)>x&W zbMHXS%B!v~HZhv>;D$ObhG)Q2;sWIr@`{g;z_^y% zBMCpMl**|w=Au_fv|Q88GQKaW!1)WCsM=zK3s zta|RgTCoZ9!z`mQYHJtv<&FC19?&%C!{1{MJ#6z5-I;8~1xm2{w6|QmphA5_&#_}> zb<0sSNPupMRs}jEQ*{&EyV2BRb894>;=gY=e&z!^{f$EaxPVwJTvhb7PRlENY@*oi z*gRH^Vx$tk$P-sRg)X8wu?%G++olUDC51Dtv5b$GM5qz7@XrbO!Pb2MSy~E=;6PzU zk~tCcpG%Mv2$=`EF)tnVS<0BMElrIHeg)=GYIv58$-Rh?-$PiN+5Wm&^VE zag)7>$@Jx^67-7AB_NDDM$eX)Ew+5HJLtzFv%e*2E494x0^h}RH{o<|f@@wcA|)kV zxsH=DRum&@9U((oqz*oef|+Fo_I`XfF?|voNaO$@3gEGgodiCl@Cb#-t*2q zmTSkJS8|W8@qwXbI&tEDoI;nPT~{`W^O z1<~pn4N<@H^%We^t??C#D_a^yE9RWE7Ib}kB%D+FF{#j7;2P`o6JMG3cEyb9x5!q2 zX`Be4ViA>=$95)Q+ad9=DvZgZa|k8tb@by5QiI`2bNggI@>0dai@KdnWz@kmL+pwN ziUR422>CJXfr3j1*9p3%GR-J-7h>Y~C!A%8e~JIjGUkGX74GXYVfFZ6uj3t=va0rL z4)Yor%8cAK} z1}ERlZa2AS)4(`VC*I1bSNpyVDuc*VRQLfec5wstL3GYsp*TF7yTMU+IpmBbrB`}v z-ekWqw6V~-o5+n|^RCtEPy}p^w~gSmhbSk19x4X#ff&JK2kVK+9$>~r&r#LW3|b8= zq2i42R8qVJ;=8@RDWm*BDmcU}n)e3~)R$g^AoBRT-xbX{|+}Qw5P1%SLUb5J7$2c%}1R7xGU!J=1n{F3)e9r7; zZy*|D{?-4PwSsjpg{yQ9_i*1%A{%QGU_D@KkkDPaGD%6d)OR8&ym)40=m2S5de1

XOqjkF*0i4o{s2@m>!VV`?0Q%u7_Z5dm` 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 Date: Sun, 16 Jan 2022 12:02:53 -0500 Subject: [PATCH 18/45] Default to colored icons, update copyright Closes: #74 --- COPYING.md | 4 ++-- launcher/Application.cpp | 2 +- launcher/ui/dialogs/AboutDialog.cpp | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/COPYING.md b/COPYING.md index 4205d441..1ac6d5cb 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,7 +1,7 @@ # PolyMC Copyright (C) 2012-2021 MultiMC Contributors - Copyright (C) 2021 PolyMC Contributors + Copyright (C) 2021-2022 PolyMC Contributors 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 @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . - + # Launcher (https://github.com/MultiMC/Launcher) Copyright 2012-2021 MultiMC Contributors Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 47c9c20e..8d1c4d62 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -595,7 +595,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("AutoUpdate", true); // Theming - m_settings->registerSetting("IconTheme", QString("multimc")); + m_settings->registerSetting("IconTheme", QString("pe_colored")); m_settings->registerSetting("ApplicationTheme", QString("system")); // Notifications diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 2ba34f1a..46d2f429 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -99,7 +99,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia QString urlText("

%1

"); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); - QString copyText("© 2012-2021 %1"); + QString copyText("© 2021-2022 %1"); ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT)); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 4d4d4e89..81ecd58f 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -256,7 +256,7 @@ void LauncherPage::applySettings() s->set("IconTheme", "pe_blue"); break; case 4: - s->set("IconTheme", "pe_colored"); + s->set("IconTheme", "multimc"); break; case 5: s->set("IconTheme", "OSX"); @@ -272,7 +272,7 @@ void LauncherPage::applySettings() break; case 0: default: - s->set("IconTheme", "multimc"); + s->set("IconTheme", "pe_colored"); break; } From 5f9270ed4b97dd647bb49840f76132ddcdc13a1b Mon Sep 17 00:00:00 2001 From: bexnoss <82064510+bexnoss@users.noreply.github.com> Date: Sun, 16 Jan 2022 23:30:17 +0100 Subject: [PATCH 19/45] Fix MSA account refresh --- launcher/minecraft/auth/MinecraftAccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 6592be0f..ffc81ed8 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -147,7 +147,7 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } - if(data.type == AccountType::Offline) { + else if(data.type == AccountType::Offline) { m_currentTask.reset(new OfflineRefresh(&data)); } else { From f55297eca94ed3e1912e6d7a13d457f9e4b9010b Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Mon, 17 Jan 2022 03:45:33 +0000 Subject: [PATCH 20/45] Revert "Merge pull request #81 from bexnoss/fix-msa-account-refresh" This reverts commit 0bc8baf1172d6967cdb2993826e3469ecd9aab66, reversing changes made to 81fe41a038ee12ff2ee0200525b39e4ad650bec2. --- launcher/minecraft/auth/MinecraftAccount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index ffc81ed8..6592be0f 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -147,7 +147,7 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } - else if(data.type == AccountType::Offline) { + if(data.type == AccountType::Offline) { m_currentTask.reset(new OfflineRefresh(&data)); } else { From 55597b458ced4d7ad8082ab226617ba48e177ee6 Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Mon, 17 Jan 2022 03:45:47 +0000 Subject: [PATCH 21/45] Revert "Merge pull request #50 from bexnoss/offline-mode" This reverts commit b4f750e7db40352111417ea89a9f375ae8c746ab, reversing changes made to b19e3156154ba0dd232a3d165b1759c57e2858f2. --- launcher/CMakeLists.txt | 7 -- launcher/LaunchController.cpp | 6 -- launcher/minecraft/auth/AccountData.cpp | 10 +- launcher/minecraft/auth/AccountData.h | 3 +- launcher/minecraft/auth/AccountList.cpp | 2 +- launcher/minecraft/auth/MinecraftAccount.cpp | 31 ------ launcher/minecraft/auth/MinecraftAccount.h | 12 --- launcher/minecraft/auth/flows/Offline.cpp | 17 ---- launcher/minecraft/auth/flows/Offline.h | 22 ----- launcher/minecraft/auth/steps/OfflineStep.cpp | 18 ---- launcher/minecraft/auth/steps/OfflineStep.h | 20 ---- launcher/ui/dialogs/OfflineLoginDialog.cpp | 98 ------------------- launcher/ui/dialogs/OfflineLoginDialog.h | 43 -------- launcher/ui/dialogs/OfflineLoginDialog.ui | 67 ------------- launcher/ui/pages/global/AccountListPage.cpp | 23 ----- launcher/ui/pages/global/AccountListPage.h | 1 - launcher/ui/pages/global/AccountListPage.ui | 6 -- 17 files changed, 3 insertions(+), 383 deletions(-) delete mode 100644 launcher/minecraft/auth/flows/Offline.cpp delete mode 100644 launcher/minecraft/auth/flows/Offline.h delete mode 100644 launcher/minecraft/auth/steps/OfflineStep.cpp delete mode 100644 launcher/minecraft/auth/steps/OfflineStep.h delete mode 100644 launcher/ui/dialogs/OfflineLoginDialog.cpp delete mode 100644 launcher/ui/dialogs/OfflineLoginDialog.h delete mode 100644 launcher/ui/dialogs/OfflineLoginDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index df361447..b5c52afa 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -221,11 +221,7 @@ set(MINECRAFT_SOURCES minecraft/auth/flows/Mojang.h minecraft/auth/flows/MSA.cpp minecraft/auth/flows/MSA.h - minecraft/auth/flows/Offline.cpp - minecraft/auth/flows/Offline.h - minecraft/auth/steps/OfflineStep.cpp - minecraft/auth/steps/OfflineStep.h minecraft/auth/steps/EntitlementsStep.cpp minecraft/auth/steps/EntitlementsStep.h minecraft/auth/steps/GetSkinStep.cpp @@ -773,8 +769,6 @@ SET(LAUNCHER_SOURCES ui/dialogs/LoginDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h - ui/dialogs/OfflineLoginDialog.cpp - ui/dialogs/OfflineLoginDialog.h ui/dialogs/NewComponentDialog.cpp ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp @@ -886,7 +880,6 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/ExportInstanceDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/MSALoginDialog.ui - ui/dialogs/OfflineLoginDialog.ui ui/dialogs/AboutDialog.ui ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 32fc99cb..7750be1a 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -116,12 +116,6 @@ void LaunchController::login() { m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); - // Launch immediately in true offline mode - if(m_accountToUse->isOffline()) { - launchInstance(); - return; - } - switch(m_accountToUse->accountState()) { case AccountState::Offline: { m_session->wants_online = false; diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp index 9b84fe1a..7526c951 100644 --- a/launcher/minecraft/auth/AccountData.cpp +++ b/launcher/minecraft/auth/AccountData.cpp @@ -314,8 +314,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data) { type = AccountType::MSA; } else if (typeS == "Mojang") { type = AccountType::Mojang; - } else if (typeS == "Offline") { - type = AccountType::Offline; } else { qWarning() << "Failed to parse account data: type is not recognized."; return false; @@ -365,9 +363,6 @@ QJsonObject AccountData::saveState() const { tokenToJSONV3(output, xboxApiToken, "xrp-main"); tokenToJSONV3(output, mojangservicesToken, "xrp-mc"); } - else if (type == AccountType::Offline) { - output["type"] = "Offline"; - } tokenToJSONV3(output, yggdrasilToken, "ygg"); profileToJSONV3(output, minecraftProfile, "profile"); @@ -376,7 +371,7 @@ QJsonObject AccountData::saveState() const { } QString AccountData::userName() const { - if(type == AccountType::MSA) { + if(type != AccountType::Mojang) { return QString(); } return yggdrasilToken.extra["userName"].toString(); @@ -432,9 +427,6 @@ QString AccountData::accountDisplayString() const { case AccountType::Mojang: { return userName(); } - case AccountType::Offline: { - return userName(); - } case AccountType::MSA: { if(xboxApiToken.extra.contains("gtg")) { return xboxApiToken.extra["gtg"].toString(); diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 606c1ad1..abf84e43 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -38,8 +38,7 @@ struct MinecraftProfile { enum class AccountType { MSA, - Mojang, - Offline + Mojang }; enum class AccountState { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 04470e1c..ef8b435d 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -302,7 +302,7 @@ QVariant AccountList::data(const QModelIndex &index, int role) const } case MigrationColumn: { - if(account->isMSA() || account->isOffline()) { + if(account->isMSA()) { return tr("N/A", "Can Migrate?"); } if (account->canMigrate()) { diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index 6592be0f..ed9e945e 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -30,7 +30,6 @@ #include "flows/MSA.h" #include "flows/Mojang.h" -#include "flows/Offline.h" MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent) { data.internalId = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); @@ -69,23 +68,6 @@ MinecraftAccountPtr MinecraftAccount::createBlankMSA() return account; } -MinecraftAccountPtr MinecraftAccount::createOffline(const QString &username) -{ - MinecraftAccountPtr account = new MinecraftAccount(); - account->data.type = AccountType::Offline; - account->data.yggdrasilToken.token = "offline"; - account->data.yggdrasilToken.validity = Katabasis::Validity::Certain; - account->data.yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); - account->data.yggdrasilToken.extra["userName"] = username; - account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->data.minecraftEntitlement.ownsMinecraft = true; - account->data.minecraftEntitlement.canPlayMinecraft = true; - account->data.minecraftProfile.id = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->data.minecraftProfile.name = username; - account->data.minecraftProfile.validity = Katabasis::Validity::Certain; - return account; -} - QJsonObject MinecraftAccount::saveToJson() const { @@ -129,16 +111,6 @@ shared_qobject_ptr MinecraftAccount::loginMSA() { return m_currentTask; } -shared_qobject_ptr MinecraftAccount::loginOffline() { - Q_ASSERT(m_currentTask.get() == nullptr); - - m_currentTask.reset(new OfflineLogin(&data)); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - emit activityChanged(true); - return m_currentTask; -} - shared_qobject_ptr MinecraftAccount::refresh() { if(m_currentTask) { return m_currentTask; @@ -147,9 +119,6 @@ shared_qobject_ptr MinecraftAccount::refresh() { if(data.type == AccountType::MSA) { m_currentTask.reset(new MSASilent(&data)); } - if(data.type == AccountType::Offline) { - m_currentTask.reset(new OfflineRefresh(&data)); - } else { m_currentTask.reset(new MojangRefresh(&data)); } diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h index 6592f9c0..7ab3c746 100644 --- a/launcher/minecraft/auth/MinecraftAccount.h +++ b/launcher/minecraft/auth/MinecraftAccount.h @@ -73,8 +73,6 @@ public: /* construction */ static MinecraftAccountPtr createBlankMSA(); - static MinecraftAccountPtr createOffline(const QString &username); - static MinecraftAccountPtr loadFromJsonV2(const QJsonObject &json); static MinecraftAccountPtr loadFromJsonV3(const QJsonObject &json); @@ -91,8 +89,6 @@ public: /* manipulation */ shared_qobject_ptr loginMSA(); - shared_qobject_ptr loginOffline(); - shared_qobject_ptr refresh(); shared_qobject_ptr currentTask(); @@ -132,10 +128,6 @@ public: /* queries */ return data.type == AccountType::MSA; } - bool isOffline() const { - return data.type == AccountType::Offline; - } - bool ownsMinecraft() const { return data.minecraftEntitlement.ownsMinecraft; } @@ -157,10 +149,6 @@ public: /* queries */ return "msa"; } break; - case AccountType::Offline: { - return "offline"; - } - break; default: { return "unknown"; } diff --git a/launcher/minecraft/auth/flows/Offline.cpp b/launcher/minecraft/auth/flows/Offline.cpp deleted file mode 100644 index fc614a8c..00000000 --- a/launcher/minecraft/auth/flows/Offline.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "Offline.h" - -#include "minecraft/auth/steps/OfflineStep.h" - -OfflineRefresh::OfflineRefresh( - AccountData *data, - QObject *parent -) : AuthFlow(data, parent) { - m_steps.append(new OfflineStep(m_data)); -} - -OfflineLogin::OfflineLogin( - AccountData *data, - QObject *parent -) : AuthFlow(data, parent) { - m_steps.append(new OfflineStep(m_data)); -} diff --git a/launcher/minecraft/auth/flows/Offline.h b/launcher/minecraft/auth/flows/Offline.h deleted file mode 100644 index 5d1f83a4..00000000 --- a/launcher/minecraft/auth/flows/Offline.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "AuthFlow.h" - -class OfflineRefresh : public AuthFlow -{ - Q_OBJECT -public: - explicit OfflineRefresh( - AccountData *data, - QObject *parent = 0 - ); -}; - -class OfflineLogin : public AuthFlow -{ - Q_OBJECT -public: - explicit OfflineLogin( - AccountData *data, - QObject *parent = 0 - ); -}; diff --git a/launcher/minecraft/auth/steps/OfflineStep.cpp b/launcher/minecraft/auth/steps/OfflineStep.cpp deleted file mode 100644 index dc092bfd..00000000 --- a/launcher/minecraft/auth/steps/OfflineStep.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "OfflineStep.h" - -#include "Application.h" - -OfflineStep::OfflineStep(AccountData* data) : AuthStep(data) {} -OfflineStep::~OfflineStep() noexcept = default; - -QString OfflineStep::describe() { - return tr("Creating offline account."); -} - -void OfflineStep::rehydrate() { - // NOOP -} - -void OfflineStep::perform() { - emit finished(AccountTaskState::STATE_WORKING, tr("Created offline account.")); -} diff --git a/launcher/minecraft/auth/steps/OfflineStep.h b/launcher/minecraft/auth/steps/OfflineStep.h deleted file mode 100644 index 62addb1f..00000000 --- a/launcher/minecraft/auth/steps/OfflineStep.h +++ /dev/null @@ -1,20 +0,0 @@ - -#pragma once -#include - -#include "QObjectPtr.h" -#include "minecraft/auth/AuthStep.h" - -#include - -class OfflineStep : public AuthStep { - Q_OBJECT -public: - explicit OfflineStep(AccountData *data); - virtual ~OfflineStep() noexcept; - - void perform() override; - void rehydrate() override; - - QString describe() override; -}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp deleted file mode 100644 index 345ed40a..00000000 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "OfflineLoginDialog.h" -#include "ui_OfflineLoginDialog.h" - -#include "minecraft/auth/AccountTask.h" - -#include - -OfflineLoginDialog::OfflineLoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog) -{ - 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); -} - -OfflineLoginDialog::~OfflineLoginDialog() -{ - delete ui; -} - -// Stage 1: User interaction -void OfflineLoginDialog::accept() -{ - setUserInputsEnabled(false); - ui->progressBar->setVisible(true); - - // Setup the login task and start it - m_account = MinecraftAccount::createOffline(ui->userTextBox->text()); - m_loginTask = m_account->loginOffline(); - 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); - connect(m_loginTask.get(), &Task::progress, this, &OfflineLoginDialog::onTaskProgress); - m_loginTask->start(); -} - -void OfflineLoginDialog::setUserInputsEnabled(bool enable) -{ - ui->userTextBox->setEnabled(enable); - ui->buttonBox->setEnabled(enable); -} - -// Enable the OK button only when the textbox contains something. -void OfflineLoginDialog::on_userTextBox_textEdited(const QString &newText) -{ - ui->buttonBox->button(QDialogButtonBox::Ok) - ->setEnabled(!newText.isEmpty()); -} - -void OfflineLoginDialog::onTaskFailed(const QString &reason) -{ - // Set message - auto lines = reason.split('\n'); - QString processed; - for(auto line: lines) { - if(line.size()) { - processed += "" + line + "
"; - } - else { - processed += "
"; - } - } - ui->label->setText(processed); - - // Re-enable user-interaction - setUserInputsEnabled(true); - ui->progressBar->setVisible(false); -} - -void OfflineLoginDialog::onTaskSucceeded() -{ - QDialog::accept(); -} - -void OfflineLoginDialog::onTaskStatus(const QString &status) -{ - ui->label->setText(status); -} - -void OfflineLoginDialog::onTaskProgress(qint64 current, qint64 total) -{ - ui->progressBar->setMaximum(total); - ui->progressBar->setValue(current); -} - -// Public interface -MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg) -{ - OfflineLoginDialog dlg(parent); - dlg.ui->label->setText(msg); - if (dlg.exec() == QDialog::Accepted) - { - return dlg.m_account; - } - return 0; -} diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h deleted file mode 100644 index 5e608379..00000000 --- a/launcher/ui/dialogs/OfflineLoginDialog.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include - -#include "minecraft/auth/MinecraftAccount.h" -#include "tasks/Task.h" - -namespace Ui -{ -class OfflineLoginDialog; -} - -class OfflineLoginDialog : public QDialog -{ - Q_OBJECT - -public: - ~OfflineLoginDialog(); - - static MinecraftAccountPtr newAccount(QWidget *parent, QString message); - -private: - explicit OfflineLoginDialog(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); - -private: - Ui::OfflineLoginDialog *ui; - MinecraftAccountPtr m_account; - Task::Ptr m_loginTask; -}; diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui deleted file mode 100644 index 4577d361..00000000 --- a/launcher/ui/dialogs/OfflineLoginDialog.ui +++ /dev/null @@ -1,67 +0,0 @@ - - - OfflineLoginDialog - - - - 0 - 0 - 500 - 250 - - - - - 0 - 0 - - - - Add Account - - - - - - Message label placeholder. - - - Qt::RichText - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Username - - - - - - - 69 - - - false - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index ad88812a..b8da6c75 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -24,7 +24,6 @@ #include "net/NetJob.h" #include "ui/dialogs/ProgressDialog.h" -#include "ui/dialogs/OfflineLoginDialog.h" #include "ui/dialogs/LoginDialog.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/CustomMessageBox.h" @@ -154,28 +153,6 @@ void AccountListPage::on_actionAddMicrosoft_triggered() } } -void AccountListPage::on_actionAddOffline_triggered() -{ - MinecraftAccountPtr account = OfflineLoginDialog::newAccount( - this, - tr("Please enter your desired username to add your offline account.
" - "
" - "It is required by Mojang that you own Minecraft BEFORE you may use offline mode.
" - "The PolyMC developers denounce piracy and take NO LIABILITY WHATSOEVER for
" - "any illegal activity that may occur in usage of the offline mode feature.
" - "
" - "By continuing you promise that you own a Minecraft account.") - ); - - if (account) - { - m_accounts->addAccount(account); - if (m_accounts->count() == 1) { - m_accounts->setDefaultAccount(account); - } - } -} - void AccountListPage::on_actionRemove_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 841c3fd2..1c65e708 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -62,7 +62,6 @@ public: public slots: void on_actionAddMojang_triggered(); void on_actionAddMicrosoft_triggered(); - void on_actionAddOffline_triggered(); void on_actionRemove_triggered(); void on_actionRefresh_triggered(); void on_actionSetDefault_triggered(); diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui index d21a92e2..29738c02 100644 --- a/launcher/ui/pages/global/AccountListPage.ui +++ b/launcher/ui/pages/global/AccountListPage.ui @@ -54,7 +54,6 @@ - @@ -104,11 +103,6 @@ Add Microsoft
- - - Add Offline - - Refresh From 236c0166f6b0f7fc2d726d509edd49b017b98b12 Mon Sep 17 00:00:00 2001 From: Philipp David Date: Wed, 12 Jan 2022 17:44:58 +0100 Subject: [PATCH 22/45] Default to system locale language --- launcher/translations/TranslationsModel.cpp | 29 ++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 2e744007..5533c082 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -143,6 +143,8 @@ struct TranslationsModel::Private std::unique_ptr m_po_translator; QFileSystemWatcher *watcher; + + bool no_language_set = false; }; TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent) @@ -165,7 +167,26 @@ void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; reloadLocalFiles(); - selectLanguage(selectedLanguage()); + + if (d->no_language_set) + { + auto bcp47Name = QLocale::system().name(); + if (!findLanguage(bcp47Name)) + { + bcp47Name = bcp47Name.split('_').front(); + } + selectLanguage(bcp47Name); + if (selectedLanguage() != defaultLangCode) + { + updateLanguage(selectedLanguage()); + APPLICATION->settings()->set("Language", selectedLanguage()); + } + d->no_language_set = false; + } + else + { + selectLanguage(selectedLanguage()); + } } void TranslationsModel::indexReceived() @@ -439,6 +460,12 @@ bool TranslationsModel::selectLanguage(QString key) { QString &langCode = key; auto langPtr = findLanguage(key); + + if (langCode.length() == 0) + { + d->no_language_set = true; + } + if(!langPtr) { qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; From 126e6d13aa59f5c09b4bb3956cbf85ed8ae72fa2 Mon Sep 17 00:00:00 2001 From: Philipp David Date: Thu, 13 Jan 2022 08:29:50 +0100 Subject: [PATCH 23/45] Use isEmpty instead of 0 length check --- launcher/translations/TranslationsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 5533c082..bfbe8f58 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -461,7 +461,7 @@ bool TranslationsModel::selectLanguage(QString key) QString &langCode = key; auto langPtr = findLanguage(key); - if (langCode.length() == 0) + if (langCode.isEmpty()) { d->no_language_set = true; } From 2dd2555a63a3098359e5a9873b748619b20b98ef Mon Sep 17 00:00:00 2001 From: Philipp David Date: Thu, 13 Jan 2022 09:41:51 +0100 Subject: [PATCH 24/45] Update selected language automatically --- launcher/translations/TranslationsModel.cpp | 2 +- launcher/ui/widgets/LanguageSelectionWidget.cpp | 11 +++++++++++ launcher/ui/widgets/LanguageSelectionWidget.h | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index bfbe8f58..aa76faa2 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -179,8 +179,8 @@ void TranslationsModel::translationDirChanged(const QString& path) if (selectedLanguage() != defaultLangCode) { updateLanguage(selectedLanguage()); - APPLICATION->settings()->set("Language", selectedLanguage()); } + APPLICATION->settings()->set("Language", selectedLanguage()); d->no_language_set = false; } else diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp index cf70c7b4..964d2b7c 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.cpp +++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp @@ -6,6 +6,7 @@ #include #include "Application.h" #include "translations/TranslationsModel.h" +#include "settings/Setting.h" LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : QWidget(parent) @@ -37,6 +38,9 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch); connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged); verticalLayout->setContentsMargins(0,0,0,0); + + auto language_setting = APPLICATION->settings()->getSetting("Language"); + connect(language_setting.get(), &Setting::SettingChanged, this, &LanguageSelectionWidget::languageSettingChanged); } QString LanguageSelectionWidget::getSelectedLanguageKey() const @@ -64,3 +68,10 @@ void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, con translations->selectLanguage(key); translations->updateLanguage(key); } + +void LanguageSelectionWidget::languageSettingChanged(const Setting &, const QVariant) +{ + auto translations = APPLICATION->translations(); + auto index = translations->selectedIndex(); + languageView->setCurrentIndex(index); +} diff --git a/launcher/ui/widgets/LanguageSelectionWidget.h b/launcher/ui/widgets/LanguageSelectionWidget.h index e65936db..4a88924c 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.h +++ b/launcher/ui/widgets/LanguageSelectionWidget.h @@ -20,6 +20,7 @@ class QVBoxLayout; class QTreeView; class QLabel; +class Setting; class LanguageSelectionWidget: public QWidget { @@ -33,6 +34,7 @@ public: protected slots: void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void languageSettingChanged(const Setting &, const QVariant); private: QVBoxLayout *verticalLayout = nullptr; From b9beb3c7d27f41b601c37caf94044015b0a80cc7 Mon Sep 17 00:00:00 2001 From: Philipp David Date: Thu, 13 Jan 2022 10:20:53 +0100 Subject: [PATCH 25/45] Sort system locale to front of list --- launcher/translations/TranslationsModel.cpp | 26 ++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index aa76faa2..0cf4d548 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -144,6 +144,9 @@ struct TranslationsModel::Private std::unique_ptr m_po_translator; QFileSystemWatcher *watcher; + const QString m_system_locale = QLocale::system().name(); + const QString m_system_language = m_system_locale.split('_').front(); + bool no_language_set = false; }; @@ -170,12 +173,12 @@ void TranslationsModel::translationDirChanged(const QString& path) if (d->no_language_set) { - auto bcp47Name = QLocale::system().name(); - if (!findLanguage(bcp47Name)) + auto language = d->m_system_locale; + if (!findLanguage(language)) { - bcp47Name = bcp47Name.split('_').front(); + language = d->m_system_language; } - selectLanguage(bcp47Name); + selectLanguage(language); if (selectedLanguage() != defaultLangCode) { updateLanguage(selectedLanguage()); @@ -340,8 +343,19 @@ void TranslationsModel::reloadLocalFiles() { d->m_languages.append(language); } - std::sort(d->m_languages.begin(), d->m_languages.end(), [](const Language& a, const Language& b) { - return a.key.compare(b.key) < 0; + std::sort(d->m_languages.begin(), d->m_languages.end(), [this](const Language& a, const Language& b) { + if (a.key != b.key) + { + if (a.key == d->m_system_locale || a.key == d->m_system_language) + { + return true; + } + if (b.key == d->m_system_locale || b.key == d->m_system_language) + { + return false; + } + } + return a.key < b.key; }); endInsertRows(); } From 83e1dd285af04a1edcc9344bc9316d30f036cadd Mon Sep 17 00:00:00 2001 From: Philipp David Date: Mon, 17 Jan 2022 09:49:47 +0100 Subject: [PATCH 26/45] Set default lang only if index received --- launcher/translations/TranslationsModel.cpp | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 0cf4d548..0fa82e35 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -169,10 +169,22 @@ TranslationsModel::~TranslationsModel() void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; - reloadLocalFiles(); + if (!d->no_language_set) + { + reloadLocalFiles(); + } + selectLanguage(selectedLanguage()); +} + +void TranslationsModel::indexReceived() +{ + qDebug() << "Got translations index!"; + d->m_index_job.reset(); if (d->no_language_set) { + reloadLocalFiles(); + auto language = d->m_system_locale; if (!findLanguage(language)) { @@ -186,17 +198,8 @@ void TranslationsModel::translationDirChanged(const QString& path) APPLICATION->settings()->set("Language", selectedLanguage()); d->no_language_set = false; } - else - { - selectLanguage(selectedLanguage()); - } -} -void TranslationsModel::indexReceived() -{ - qDebug() << "Got translations index!"; - d->m_index_job.reset(); - if(d->m_selectedLanguage != defaultLangCode) + else if(d->m_selectedLanguage != defaultLangCode) { downloadTranslation(d->m_selectedLanguage); } From b50e58436975761fcb7b8886eb137330e5b6e29a Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 19 Jan 2022 07:44:29 +0000 Subject: [PATCH 27/45] PasteUpload task changed to use 0x0.st's protocol - Modified PasteUpload task to upload the log file to 0x0.st and other services with the same protocol. - Modified Paste settings UI to allow the user to select a custom paste URL, simplified the settings page code. --- launcher/Application.cpp | 2 +- launcher/net/PasteUpload.cpp | 88 +++++++-------------- launcher/net/PasteUpload.h | 22 +----- launcher/ui/GuiUtil.cpp | 17 +--- launcher/ui/pages/global/PastePage.cpp | 23 ++---- launcher/ui/pages/global/PastePage.h | 3 - launcher/ui/pages/global/PastePage.ui | 17 ++-- launcher/ui/pages/instance/LogPage.ui | 2 +- launcher/ui/pages/instance/OtherLogsPage.ui | 2 +- 9 files changed, 51 insertions(+), 125 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 110b2e6b..a5b861d0 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -715,7 +715,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); // pastebin URL - m_settings->registerSetting("PastebinURL", "0x0.st"); + m_settings->registerSetting("PastebinURL", "https://0x0.st"); // Init page provider { diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 4b69b68a..6c5aa221 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -8,44 +8,34 @@ #include #include -PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window) +PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8()) { - m_key = key; - QByteArray temp; - QJsonObject topLevelObj; - QJsonObject sectionObject; - sectionObject.insert("contents", text); - QJsonArray sectionArray; - sectionArray.append(sectionObject); - topLevelObj.insert("description", "Log Upload"); - topLevelObj.insert("sections", sectionArray); - QJsonDocument docOut; - docOut.setObject(topLevelObj); - m_jsonContent = docOut.toJson(); } PasteUpload::~PasteUpload() { } -bool PasteUpload::validateText() -{ - return m_jsonContent.size() <= maxSize(); -} - void PasteUpload::executeTask() { - QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); + QNetworkRequest request{QUrl(m_uploadUrl)}; request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); - request.setRawHeader("Content-Type", "application/json"); - request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); - request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); + QHttpMultiPart *multiPart = new QHttpMultiPart{QHttpMultiPart::FormDataType}; - QNetworkReply *rep = APPLICATION->network()->post(request, m_jsonContent); + QHttpPart filePart; + filePart.setBody(m_text); + filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\""); + + multiPart->append(filePart); + + QNetworkReply *rep = APPLICATION->network()->post(request, multiPart); + multiPart->setParent(rep); m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading to paste.ee")); + setStatus(tr("Uploading to %1").arg(m_uploadUrl)); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); @@ -61,45 +51,23 @@ void PasteUpload::downloadError(QNetworkReply::NetworkError error) void PasteUpload::downloadFinished() { QByteArray data = m_reply->readAll(); - // if the download succeeded - if (m_reply->error() == QNetworkReply::NetworkError::NoError) + int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (m_reply->error() != QNetworkReply::NetworkError::NoError) { - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed(jsonError.errorString()); - return; - } - if (!parseResult(doc)) - { - emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); - return; - } - } - // else the download failed - else - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + emitFailed(tr("Network error: %1").arg(m_reply->errorString())); m_reply.reset(); return; } + else if (statusCode != 200) + { + QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); + qCritical() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data; + m_reply.reset(); + return; + } + + m_pasteLink = QString::fromUtf8(data); emitSucceeded(); } - -bool PasteUpload::parseResult(QJsonDocument doc) -{ - auto object = doc.object(); - auto status = object.value("success").toBool(); - if (!status) - { - qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); - return false; - } - m_pasteLink = object.value("link").toString(); - m_pasteID = object.value("id").toString(); - qDebug() << m_pasteLink; - return true; -} - diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h index 5514e058..62b2dc36 100644 --- a/launcher/net/PasteUpload.h +++ b/launcher/net/PasteUpload.h @@ -8,37 +8,21 @@ class PasteUpload : public Task { Q_OBJECT public: - PasteUpload(QWidget *window, QString text, QString key = "public"); + PasteUpload(QWidget *window, QString text, QString url); virtual ~PasteUpload(); QString pasteLink() { return m_pasteLink; } - QString pasteID() - { - return m_pasteID; - } - int maxSize() - { - // 2MB for paste.ee - public - if(m_key == "public") - return 1024*1024*2; - // 12MB for paste.ee - with actual key - return 1024*1024*12; - } - bool validateText(); protected: virtual void executeTask(); private: - bool parseResult(QJsonDocument doc); - QString m_error; QWidget *m_window; - QString m_pasteID; QString m_pasteLink; - QString m_key; - QByteArray m_jsonContent; + QString m_uploadUrl; + QByteArray m_text; std::shared_ptr m_reply; public slots: diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index efb1a4df..9eb658e2 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -16,21 +16,8 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget) { ProgressDialog dialog(parentWidget); - auto APIKeySetting = APPLICATION->settings()->get("PasteEEAPIKey").toString(); - if(APIKeySetting == "multimc") - { - APIKeySetting = BuildConfig.PASTE_EE_KEY; - } - std::unique_ptr paste(new PasteUpload(parentWidget, text, APIKeySetting)); - - if (!paste->validateText()) - { - CustomMessageBox::selectable( - parentWidget, QObject::tr("Upload failed"), - QObject::tr("The log file is too big. You'll have to upload it manually."), - QMessageBox::Warning)->exec(); - return QString(); - } + auto pasteUrlSetting = APPLICATION->settings()->get("PastebinURL").toString(); + std::unique_ptr paste(new PasteUpload(parentWidget, text, pasteUrlSetting)); dialog.execWithTask(paste.get()); if (!paste->wasSuccessful()) diff --git a/launcher/ui/pages/global/PastePage.cpp b/launcher/ui/pages/global/PastePage.cpp index 495e9937..0965da77 100644 --- a/launcher/ui/pages/global/PastePage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" #include "tools/BaseProfiler.h" @@ -31,7 +32,6 @@ PastePage::PastePage(QWidget *parent) : { ui->setupUi(this); ui->tabWidget->tabBar()->hide();\ - connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PastePage::textEdited); loadSettings(); } @@ -43,23 +43,15 @@ PastePage::~PastePage() void PastePage::loadSettings() { auto s = APPLICATION->settings(); - QString pastebin = s->get("PastebinURL"); - int index = ui->urlChoices->findText(pastebin); - ui->urlChoices->setCurrentIndex(index); + QString pastebinURL = s->get("PastebinURL").toString(); + ui->urlChoices->setCurrentText(pastebinURL); } void PastePage::applySettings() { auto s = APPLICATION->settings(); - - QString pasteKeyToUse; - if (ui->customButton->isChecked()) - pasteKeyToUse = ui->customAPIkeyEdit->text(); - else - { - pasteKeyToUse = "multimc"; - } - s->set("PasteEEAPIKey", pasteKeyToUse); + QString pastebinURL = ui->urlChoices->currentText(); + s->set("PastebinURL", pastebinURL); } bool PastePage::apply() @@ -67,8 +59,3 @@ bool PastePage::apply() applySettings(); return true; } - -void PastePage::textEdited(const QString& text) -{ - ui->customButton->setChecked(true); -} diff --git a/launcher/ui/pages/global/PastePage.h b/launcher/ui/pages/global/PastePage.h index 3930d4ec..d475dfd9 100644 --- a/launcher/ui/pages/global/PastePage.h +++ b/launcher/ui/pages/global/PastePage.h @@ -54,9 +54,6 @@ private: void loadSettings(); void applySettings(); -private slots: - void textEdited(const QString &text); - private: Ui::PastePage *ui; }; diff --git a/launcher/ui/pages/global/PastePage.ui b/launcher/ui/pages/global/PastePage.ui index 784ea3f4..fe372540 100644 --- a/launcher/ui/pages/global/PastePage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -36,7 +36,7 @@ - Pastebin Site + Pastebin URL @@ -48,14 +48,20 @@ + + true + + + QComboBox::NoInsert + - 0x0.st + https://0x0.st - paste.polymc.org + https://paste.polymc.org @@ -63,7 +69,7 @@ - <html><head/><body><p>paste.polymc.org is a pastebin managed by PolyMC's lead maintainer. Something something trust</p></body></html> + <html><head/><body><p>Here you can choose from a predefined list, or input the URL of a different paste service, provided that it supports the same protocol as 0x0.st, that is POST a file to the URL and return a link in the response body.</p></body></html> Qt::RichText @@ -103,7 +109,4 @@ - - - diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui index ccfc1551..31bb368c 100644 --- a/launcher/ui/pages/instance/LogPage.ui +++ b/launcher/ui/pages/instance/LogPage.ui @@ -100,7 +100,7 @@ - Upload the log to paste.ee - it will stay online for a month + Upload the log to the paste service configured in preferences Upload diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 56ff3b62..77f3e647 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -84,7 +84,7 @@ - Upload the log to paste.ee - it will stay online for a month + Upload the log to the paste service configured in preferences. Upload From 7022d3d40129f5e97a70dbdda63e30d341d9e32a Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 19 Jan 2022 18:59:24 +0100 Subject: [PATCH 28/45] fix(readme): header color should follow GH theme As browser color scheme preference and GitHub theme can be different from each other, let's follow GitHub theme instead of browser preference. --- README.md | 3 ++- program_info/polymc-header-black.svg | 31 ++++++++++++++++++++++++++++ program_info/polymc-header.svg | 9 +------- 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 program_info/polymc-header-black.svg diff --git a/README.md b/README.md index 4b5d85f9..778a2d96 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@

- PolyMC logo + PolyMC logo + PolyMC logo


diff --git a/program_info/polymc-header-black.svg b/program_info/polymc-header-black.svg new file mode 100644 index 00000000..34cda4ab --- /dev/null +++ b/program_info/polymc-header-black.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/program_info/polymc-header.svg b/program_info/polymc-header.svg index fb91a54b..a896787f 100644 --- a/program_info/polymc-header.svg +++ b/program_info/polymc-header.svg @@ -1,13 +1,6 @@ - @@ -27,7 +20,7 @@ - + From caeab926bcc8563996f7826a060966b55580376d Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 19 Jan 2022 14:59:09 +0000 Subject: [PATCH 29/45] PasteUpload: Trim whitespace from response body --- launcher/net/PasteUpload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 6c5aa221..4efcefdd 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -68,6 +68,6 @@ void PasteUpload::downloadFinished() return; } - m_pasteLink = QString::fromUtf8(data); + m_pasteLink = QString::fromUtf8(data).trimmed(); emitSucceeded(); } From dd76fb0ec7ee997f85c2b00d1c3f550bdebbcce6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 19 Jan 2022 19:49:43 +0100 Subject: [PATCH 30/45] feat(MSALogin): add open page & copy code button Closes #55 --- launcher/ui/dialogs/MSALoginDialog.cpp | 12 ++++++++++++ launcher/ui/dialogs/MSALoginDialog.ui | 27 ++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index f46aa3b9..174ad46c 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -16,15 +16,19 @@ #include "MSALoginDialog.h" #include "ui_MSALoginDialog.h" +#include "DesktopServices.h" #include "minecraft/auth/AccountTask.h" #include #include +#include +#include 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); @@ -81,10 +85,17 @@ void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& QString urlString = uri.toString(); QString linkString = QString("%2").arg(urlString, urlString); ui->label->setText(tr("

Please open up %1 in a browser and put in the code %2 to proceed with login.

").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) @@ -110,6 +121,7 @@ void MSALoginDialog::onTaskFailed(const QString &reason) // Re-enable user-interaction setUserInputsEnabled(true); ui->progressBar->setVisible(false); + ui->actionButton->setVisible(false); } void MSALoginDialog::onTaskSucceeded() diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index 78cbfb26..c18d01a1 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -49,14 +49,25 @@ aaaaa
- - - Qt::Horizontal - - - QDialogButtonBox::Cancel - - + + + + + Open page and copy code + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + +
From 3da69d8e539dea088f18fb575f7b6ef0d0c75b07 Mon Sep 17 00:00:00 2001 From: Daniel Huang <24739403+danielhuang@users.noreply.github.com> Date: Sat, 22 Jan 2022 14:13:21 -0500 Subject: [PATCH 31/45] Update org.polymc.PolyMC.desktop.in --- program_info/org.polymc.PolyMC.desktop.in | 1 + 1 file changed, 1 insertion(+) diff --git a/program_info/org.polymc.PolyMC.desktop.in b/program_info/org.polymc.PolyMC.desktop.in index 8bbdc505..5d982b38 100644 --- a/program_info/org.polymc.PolyMC.desktop.in +++ b/program_info/org.polymc.PolyMC.desktop.in @@ -10,3 +10,4 @@ Icon=org.polymc.PolyMC PrefersNonDefaultGPU=true Categories=Game; Keywords=game;minecraft;launcher; +StartupWMClass=PolyMC From af20b5ee0e09d5c9ec36d54c80ca688c7c67012d Mon Sep 17 00:00:00 2001 From: swirl Date: Sun, 23 Jan 2022 12:54:58 -0500 Subject: [PATCH 32/45] support paste.polymc.org --- launcher/net/PasteUpload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp index 4efcefdd..52b82a0e 100644 --- a/launcher/net/PasteUpload.cpp +++ b/launcher/net/PasteUpload.cpp @@ -59,7 +59,7 @@ void PasteUpload::downloadFinished() m_reply.reset(); return; } - else if (statusCode != 200) + else if (statusCode != 200 && statusCode != 201) { QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase)); From 70c04745ee49a127f43a0947e0a0e0d9b2f8eb5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 11:43:19 +0100 Subject: [PATCH 33/45] NOISSUE add some logging to profile fetching failures --- launcher/minecraft/auth/steps/MinecraftProfileStep.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 9fef99b0..dc3df44f 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -56,6 +56,11 @@ void MinecraftProfileStep::onRequestDone( return; } if (error != QNetworkReply::NoError) { + qWarning() << "Error getting profile:"; + qWarning() << " HTTP Status: " << requestor->httpStatus_; + qWarning() << " Internal error no.: " << error; + qWarning() << " Error string: " << requestor->errorString_; + emit finished( AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed.") From ddfed7bb878a8e5caa491b67a85d39f2917a0056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 12:05:40 +0100 Subject: [PATCH 34/45] NOISSUE in java checker, ignore invalid lines altogether Declaring them as errors is just causing problems because Java randomly prints garbage to STDOUT now. --- launcher/java/JavaChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 4557784b..35ddc35c 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -111,7 +111,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto parts = line.split('=', QString::SkipEmptyParts); if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) { - success = false; + continue; } else { From 54e3438e372c1e21d9fc39e8b08cf5c50219dd6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 12:46:20 +0100 Subject: [PATCH 35/45] NOISSUE correctly set http status code in auth reply --- launcher/minecraft/auth/AuthRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AuthRequest.cpp b/launcher/minecraft/auth/AuthRequest.cpp index 459d2354..feface80 100644 --- a/launcher/minecraft/auth/AuthRequest.cpp +++ b/launcher/minecraft/auth/AuthRequest.cpp @@ -44,7 +44,7 @@ void AuthRequest::onRequestFinished() { if (reply_ != qobject_cast(sender())) { return; } - httpStatus_ = 200; + httpStatus_ = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); finish(); } From 8804b035b2d6daa1a1a4e04696315d428e782661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 16 Jan 2022 12:51:42 +0100 Subject: [PATCH 36/45] NOISSUE log server response when failing to fetch profile --- launcher/minecraft/auth/steps/MinecraftProfileStep.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index dc3df44f..add91659 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -61,6 +61,9 @@ void MinecraftProfileStep::onRequestDone( qWarning() << " Internal error no.: " << error; qWarning() << " Error string: " << requestor->errorString_; + qWarning() << " Response:"; + qWarning() << QString::fromUtf8(data); + emit finished( AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed.") From 0235eb5c286413332dcc2f7af8171bd87b61c3c5 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sat, 22 Jan 2022 21:58:32 +0100 Subject: [PATCH 37/45] Fix error message The code is trying to get a string from a json object, and if that fails it should log "is not a string", not "is not a timestamp". --- launcher/minecraft/auth/Parsers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index ed31e934..2dd36562 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -94,7 +94,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na return false; } if(!getString(obj.value("Token"), output.token)) { - qWarning() << "User Token is not a timestamp"; + qWarning() << "User Token is not a string"; return false; } auto arrayVal = obj.value("DisplayClaims").toObject().value("xui"); From 4edd2cff9f0fbb0069cea009d1136275b4beaf11 Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Mon, 24 Jan 2022 23:15:54 +0100 Subject: [PATCH 38/45] remove explicit dependencies, correct dependencies to work on OpenSuse --- packages/rpm/polymc.spec | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/rpm/polymc.spec b/packages/rpm/polymc.spec index f5a6fe07..259dc526 100644 --- a/packages/rpm/polymc.spec +++ b/packages/rpm/polymc.spec @@ -6,7 +6,7 @@ Name: polymc Version: 1.0.5 -Release: 1%{?dist} +Release: 2%{?dist} Summary: Minecraft launcher with ability to manage multiple instances # @@ -61,23 +61,18 @@ Source0: https://github.com/PolyMC/PolyMC/archive/%{version}/%{name}-%{ve Source1: https://github.com/MultiMC/libnbtplusplus/archive/%{libnbtplusplus_commit}/libnbtplusplus-%{libnbtplusplus_shortcommit}.tar.gz Source2: https://github.com/PolyMC/quazip/archive/%{quazip_commit}/quazip-%{quazip_shortcommit}.tar.gz -BuildRequires: cmake3 +BuildRequires: cmake BuildRequires: desktop-file-utils BuildRequires: gcc-c++ -# Fix warning: Could not complete Guile gdb module initialization from: -# /usr/share/gdb/guile/gdb/boot.scm -BuildRequires: gdb-headless - BuildRequires: java-devel -BuildRequires: pkgconfig(gl) -BuildRequires: pkgconfig(Qt5) -BuildRequires: pkgconfig(zlib) +BuildRequires: %{?suse_version:lib}qt5-qtbase-devel +BuildRequires: zlib-devel -Requires: java-headless -Requires: pkgconfig(gl) -Requires: pkgconfig(Qt5) -Requires: pkgconfig(zlib) +# Minecraft < 1.17 +Recommends: java-1.8.0-openjdk-headless +# Minecraft >= 1.17 +Recommends: java-17-openjdk-headless %description PolyMC is a free, open source launcher for Minecraft. It allows you to have @@ -105,7 +100,6 @@ mv -f libraries/libnbtplusplus-%{libnbtplusplus_commit} libraries/libnbtplusplus %cmake_build - %install %cmake_install @@ -115,8 +109,11 @@ echo "%{_libdir}/%{name}" > "%{buildroot}%{_sysconfdir}/ld.so.conf.d/%{name}-%{_ %check +# skip tests on systems that aren't officially supported +%if ! 0%{?suse_version} %ctest desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.desktop +%endif %files @@ -132,6 +129,9 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.des %changelog +* Mon Jan 24 2022 Jan Drögehoff - 1.0.5-2 +- remove explicit dependencies, correct dependencies to work on OpenSuse + * Sun Jan 09 2022 Jan Drögehoff - 1.0.5-1 - Update to 1.0.5 From 0eff21a4f1ee97e4b0a484c608eac9617897446d Mon Sep 17 00:00:00 2001 From: Lenny McLennington Date: Wed, 26 Jan 2022 00:31:08 +0000 Subject: [PATCH 39/45] Validate Pastebin URL with regex --- launcher/ui/pages/global/PastePage.cpp | 2 ++ launcher/ui/pages/global/PastePage.ui | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/global/PastePage.cpp b/launcher/ui/pages/global/PastePage.cpp index 0965da77..7c69e1a4 100644 --- a/launcher/ui/pages/global/PastePage.cpp +++ b/launcher/ui/pages/global/PastePage.cpp @@ -30,7 +30,9 @@ PastePage::PastePage(QWidget *parent) : QWidget(parent), ui(new Ui::PastePage) { + static QRegularExpression validUrlRegExp("https?://.+"); ui->setupUi(this); + ui->urlChoices->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->urlChoices)); ui->tabWidget->tabBar()->hide();\ loadSettings(); } diff --git a/launcher/ui/pages/global/PastePage.ui b/launcher/ui/pages/global/PastePage.ui index fe372540..2d13a765 100644 --- a/launcher/ui/pages/global/PastePage.ui +++ b/launcher/ui/pages/global/PastePage.ui @@ -38,7 +38,7 @@ Pastebin URL - + @@ -46,6 +46,21 @@ + + + + + 10 + + + + <html><head/><body><p>Note: only input that starts with <span style=" font-weight:600;">http://</span> or <span style=" font-weight:600;">https://</span> will be accepted.</p></body></html> + + + false + + + @@ -69,7 +84,7 @@ - <html><head/><body><p>Here you can choose from a predefined list, or input the URL of a different paste service, provided that it supports the same protocol as 0x0.st, that is POST a file to the URL and return a link in the response body.</p></body></html> + <html><head/><body><p>Here you can choose from a predefined list of paste services, or input the URL of a different paste service of your choice, provided it supports the same protocol as 0x0.st, that is POST a file parameter to the URL and return a link in the response body.</p></body></html> Qt::RichText From ec1e27031a0b1fb9760f650ad8415d9e14e3f9f3 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 26 Jan 2022 23:51:29 -0500 Subject: [PATCH 40/45] Fix Freedesktop icons This fixes #51. The desktop file is now exactly the same as the window class, which is also now corrected to org.polymc.polymc. The file capitalization is also consistent with other Freedesktop files as well. --- packages/nix/polymc/default.nix | 2 +- packages/rpm/polymc.spec | 4 ++-- program_info/CMakeLists.txt | 6 +++--- ...olymc.PolyMC.desktop.in => org.polymc.polymc.desktop.in} | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) rename program_info/{org.polymc.PolyMC.desktop.in => org.polymc.polymc.desktop.in} (93%) diff --git a/packages/nix/polymc/default.nix b/packages/nix/polymc/default.nix index 5da00ff8..b6bf6c5e 100644 --- a/packages/nix/polymc/default.nix +++ b/packages/nix/polymc/default.nix @@ -88,7 +88,7 @@ mkDerivation rec { postInstall = '' install -Dm644 ../launcher/resources/multimc/scalable/launcher.svg $out/share/pixmaps/polymc.svg - install -Dm644 ${desktopItem}/share/applications/polymc.desktop $out/share/applications/org.polymc.PolyMC.desktop + install -Dm644 ${desktopItem}/share/applications/polymc.desktop $out/share/applications/org.polymc.polymc.desktop # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128 wrapProgram $out/bin/polymc \ diff --git a/packages/rpm/polymc.spec b/packages/rpm/polymc.spec index 259dc526..f52b6261 100644 --- a/packages/rpm/polymc.spec +++ b/packages/rpm/polymc.spec @@ -112,7 +112,7 @@ echo "%{_libdir}/%{name}" > "%{buildroot}%{_sysconfdir}/ld.so.conf.d/%{name}-%{_ # skip tests on systems that aren't officially supported %if ! 0%{?suse_version} %ctest -desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.desktop +desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.polymc.desktop %endif @@ -123,7 +123,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.PolyMC.des %{_libdir}/%{name}/* %{_datadir}/%{name}/* %{_datadir}/metainfo/org.polymc.PolyMC.metainfo.xml -%{_datadir}/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg +%{_datadir}/icons/hicolor/scalable/apps/org.polymc.polymC.svg %{_datadir}/applications/org.polymc.PolyMC.desktop %config %{_sysconfdir}/ld.so.conf.d/* diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 77b971fc..26369fe5 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -1,19 +1,19 @@ set(Launcher_CommonName "PolyMC") set(Launcher_Copyright "PolyMC Contributors" PARENT_SCOPE) -set(Launcher_Domain "github.com/PolyMC" PARENT_SCOPE) +set(Launcher_Domain "polymc.org" PARENT_SCOPE) set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_RELEASE_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "polymc.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PolyMC/PolyMC" PARENT_SCOPE) -set(Launcher_Desktop "program_info/org.polymc.PolyMC.desktop" PARENT_SCOPE) +set(Launcher_Desktop "program_info/org.polymc.polymc.desktop" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/org.polymc.PolyMC.metainfo.xml" PARENT_SCOPE) set(Launcher_SVG "program_info/org.polymc.PolyMC.svg" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) -configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop) +configure_file(org.polymc.polymc.desktop.in org.polymc.polymc.desktop) configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml) diff --git a/program_info/org.polymc.PolyMC.desktop.in b/program_info/org.polymc.polymc.desktop.in similarity index 93% rename from program_info/org.polymc.PolyMC.desktop.in rename to program_info/org.polymc.polymc.desktop.in index 5d982b38..8bbdc505 100644 --- a/program_info/org.polymc.PolyMC.desktop.in +++ b/program_info/org.polymc.polymc.desktop.in @@ -10,4 +10,3 @@ Icon=org.polymc.PolyMC PrefersNonDefaultGPU=true Categories=Game; Keywords=game;minecraft;launcher; -StartupWMClass=PolyMC From cd5faee7d746a132bd690f9e3a2b35dcc5af91c6 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Wed, 26 Jan 2022 23:51:29 -0500 Subject: [PATCH 41/45] Fix RPM spec referencing old desktop file --- packages/rpm/polymc.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rpm/polymc.spec b/packages/rpm/polymc.spec index f52b6261..0b659ed5 100644 --- a/packages/rpm/polymc.spec +++ b/packages/rpm/polymc.spec @@ -123,8 +123,8 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.polymc.polymc.des %{_libdir}/%{name}/* %{_datadir}/%{name}/* %{_datadir}/metainfo/org.polymc.PolyMC.metainfo.xml -%{_datadir}/icons/hicolor/scalable/apps/org.polymc.polymC.svg -%{_datadir}/applications/org.polymc.PolyMC.desktop +%{_datadir}/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg +%{_datadir}/applications/org.polymc.polymc.desktop %config %{_sysconfdir}/ld.so.conf.d/* From 1c982b01824d1b3892cd91208fe295d6816f352e Mon Sep 17 00:00:00 2001 From: redstrate <54911369+redstrate@users.noreply.github.com> Date: Thu, 27 Jan 2022 13:17:26 +0000 Subject: [PATCH 42/45] Fix RPM build instructions --- BUILD.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index 1c06febb..d14f2941 100644 --- a/BUILD.md +++ b/BUILD.md @@ -47,16 +47,25 @@ If everything works correctly, the .deb will be next to the build script, in `Po ### Building a .rpm -You don't need to install the build dependencies, as the script will use `dnf` to install them for you. +Build dependencies are automatically installed using `dnf`, but you do need the `rpmdevtools` package (on Fedora) +in order to fetch sources and setup your tree. ``` +cd ~ +# setup your ~/rpmbuild directory, required for rpmbuild to work. +rpmdev-setuptree +# note: submodules are not needed here, as the spec file will download the tarball instead git clone https://github.com/PolyMC/PolyMC.git -cd packages/rpm -sudo dnf builddep ./polymec.spec -rpmbuild -bb ./polymec.spec +cd PolyMC/packages/rpm +# install build dependencies +sudo dnf builddep polymc.spec +# download build sources +spectool -g -R polymc.spec +# now build! +rpmbuild -bb polymc.spec ``` -The path to the rpm packages will be printed at the end of building +The path to the rpm packages will be printed when the build is complete. ### Building from command line You need a source folder, a build folder and an install folder. From ad6e3a08683c388a1949e4fb362f85065b77c75b Mon Sep 17 00:00:00 2001 From: swirl Date: Thu, 27 Jan 2022 16:58:28 -0500 Subject: [PATCH 43/45] Fix meta --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91119b2f..c5006b4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.") set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.") # The metadata server -set(Launcher_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") +set(Launcher_META_URL "https://meta.polymc.org/v1/" CACHE STRING "URL to fetch Launcher's meta files from.") # Imgur API Client ID set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application") From 361ce7818ec8891e9a35bdfac4cdea77a0b6a949 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 27 Jan 2022 22:58:38 +0100 Subject: [PATCH 44/45] refactor: remove news feed Closes #63 --- CMakeLists.txt | 3 - launcher/CMakeLists.txt | 9 --- launcher/news/NewsChecker.cpp | 132 ---------------------------------- launcher/news/NewsChecker.h | 105 --------------------------- launcher/news/NewsEntry.cpp | 77 -------------------- launcher/news/NewsEntry.h | 65 ----------------- launcher/ui/MainWindow.cpp | 89 ----------------------- launcher/ui/MainWindow.h | 9 --- 8 files changed, 489 deletions(-) delete mode 100644 launcher/news/NewsChecker.cpp delete mode 100644 launcher/news/NewsChecker.h delete mode 100644 launcher/news/NewsEntry.cpp delete mode 100644 launcher/news/NewsEntry.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2af0aa71..b7fb557a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") ##################################### Set Application options ##################################### -######## Set URLs ######## -set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch Launcher's news RSS feed from.") - ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MINOR 0) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b5c52afa..c5a2b835 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -177,15 +177,6 @@ set(NOTIFICATIONS_SOURCES notifications/NotificationChecker.cpp ) -# Backend for the news bar... there's usually no news. -set(NEWS_SOURCES - # News System - news/NewsChecker.h - news/NewsChecker.cpp - news/NewsEntry.h - news/NewsEntry.cpp -) - # Icon interface set(ICONS_SOURCES # Icons System and related code diff --git a/launcher/news/NewsChecker.cpp b/launcher/news/NewsChecker.cpp deleted file mode 100644 index 4f4359b8..00000000 --- a/launcher/news/NewsChecker.cpp +++ /dev/null @@ -1,132 +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 "NewsChecker.h" - -#include -#include - -#include - -NewsChecker::NewsChecker(shared_qobject_ptr network, const QString& feedUrl) -{ - m_network = network; - m_feedUrl = feedUrl; -} - -void NewsChecker::reloadNews() -{ - // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. - if (isLoadingNews()) - { - qDebug() << "Ignored request to reload news. Currently reloading already."; - return; - } - - qDebug() << "Reloading news."; - - NetJob* job = new NetJob("News RSS Feed", m_network); - job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); - QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); - m_newsNetJob.reset(job); - job->start(); -} - -void NewsChecker::rssDownloadFinished() -{ - // Parse the XML file and process the RSS feed entries. - qDebug() << "Finished loading RSS feed."; - - m_newsNetJob.reset(); - QDomDocument doc; - { - // Stuff to store error info in. - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - // Parse the XML. - if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) - { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); - fail(fullErrorMsg); - newsData.clear(); - return; - } - newsData.clear(); - } - - // If the parsing succeeded, read it. - QDomNodeList items = doc.elementsByTagName("item"); - m_newsEntries.clear(); - for (int i = 0; i < items.length(); i++) - { - QDomElement element = items.at(i).toElement(); - NewsEntryPtr entry; - entry.reset(new NewsEntry()); - QString errorMsg = "An unknown error occurred."; - if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) - { - qDebug() << "Loaded news entry" << entry->title; - m_newsEntries.append(entry); - } - else - { - qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; - } - } - - succeed(); -} - -void NewsChecker::rssDownloadFailed(QString reason) -{ - // Set an error message and fail. - fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); -} - - -QList NewsChecker::getNewsEntries() const -{ - return m_newsEntries; -} - -bool NewsChecker::isLoadingNews() const -{ - return m_newsNetJob.get() != nullptr; -} - -QString NewsChecker::getLastLoadErrorMsg() const -{ - return m_lastLoadError; -} - -void NewsChecker::succeed() -{ - m_lastLoadError = ""; - qDebug() << "News loading succeeded."; - m_newsNetJob.reset(); - emit newsLoaded(); -} - -void NewsChecker::fail(const QString& errorMsg) -{ - m_lastLoadError = errorMsg; - qDebug() << "Failed to load news:" << errorMsg; - m_newsNetJob.reset(); - emit newsLoadingFailed(errorMsg); -} - diff --git a/launcher/news/NewsChecker.h b/launcher/news/NewsChecker.h deleted file mode 100644 index 8467a541..00000000 --- a/launcher/news/NewsChecker.h +++ /dev/null @@ -1,105 +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 -#include -#include - -#include - -#include "NewsEntry.h" - -class NewsChecker : public QObject -{ - Q_OBJECT -public: - /*! - * Constructs a news reader to read from the given RSS feed URL. - */ - NewsChecker(shared_qobject_ptr network, const QString& feedUrl); - - /*! - * Returns the error message for the last time the news was loaded. - * Empty string if the last load was successful. - */ - QString getLastLoadErrorMsg() const; - - /*! - * Returns true if the news has been loaded successfully. - */ - bool isNewsLoaded() const; - - //! True if the news is currently loading. If true, reloadNews() will do nothing. - bool isLoadingNews() const; - - /*! - * Returns a list of news entries. - */ - QList getNewsEntries() const; - - /*! - * Reloads the news from the website's RSS feed. - * If the news is already loading, this does nothing. - */ - void Q_SLOT reloadNews(); - -signals: - /*! - * Signal fired after the news has finished loading. - */ - void newsLoaded(); - - /*! - * Signal fired after the news fails to load. - */ - void newsLoadingFailed(QString errorMsg); - -protected slots: - void rssDownloadFinished(); - void rssDownloadFailed(QString reason); - -protected: /* data */ - //! The URL for the RSS feed to fetch. - QString m_feedUrl; - - //! List of news entries. - QList m_newsEntries; - - //! The network job to use to load the news. - NetJob::Ptr m_newsNetJob; - - //! True if news has been loaded. - bool m_loadedNews; - - QByteArray newsData; - - /*! - * Gets the error message that was given last time the news was loaded. - * If the last news load succeeded, this will be an empty string. - */ - QString m_lastLoadError; - - shared_qobject_ptr m_network; - -protected slots: - /// Emits newsLoaded() and sets m_lastLoadError to empty string. - void succeed(); - - /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. - void fail(const QString& errorMsg); -}; - diff --git a/launcher/news/NewsEntry.cpp b/launcher/news/NewsEntry.cpp deleted file mode 100644 index 7eff657b..00000000 --- a/launcher/news/NewsEntry.cpp +++ /dev/null @@ -1,77 +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 "NewsEntry.h" - -#include -#include - -NewsEntry::NewsEntry(QObject* parent) : - QObject(parent) -{ - this->title = tr("Untitled"); - this->content = tr("No content."); - this->link = ""; - this->author = tr("Unknown Author"); - this->pubDate = QDateTime::currentDateTime(); -} - -NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent) : - QObject(parent) -{ - this->title = title; - this->content = content; - this->link = link; - this->author = author; - this->pubDate = pubDate; -} - -/*! - * Gets the text content of the given child element as a QVariant. - */ -inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="") -{ - QDomNodeList nodes = element.elementsByTagName(childName); - if (nodes.count() > 0) - { - QDomElement element = nodes.at(0).toElement(); - return element.text(); - } - else - { - return defaultVal; - } -} - -bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg) -{ - QString title = childValue(element, "title", tr("Untitled")); - QString content = childValue(element, "description", tr("No content.")); - QString link = childValue(element, "link"); - QString author = childValue(element, "dc:creator", tr("Unknown Author")); - QString pubDateStr = childValue(element, "pubDate"); - - // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. - QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); - QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); - - entry->title = title; - entry->content = content; - entry->link = link; - entry->author = author; - entry->pubDate = pubDate; - return true; -} - diff --git a/launcher/news/NewsEntry.h b/launcher/news/NewsEntry.h deleted file mode 100644 index 0dbc70a5..00000000 --- a/launcher/news/NewsEntry.h +++ /dev/null @@ -1,65 +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 -#include -#include -#include - -#include - -class NewsEntry : public QObject -{ - Q_OBJECT - -public: - /*! - * Constructs an empty news entry. - */ - explicit NewsEntry(QObject* parent=0); - - /*! - * Constructs a new news entry. - * Note that content may contain HTML. - */ - NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); - - /*! - * Attempts to load information from the given XML element into the given news entry pointer. - * If this fails, the function will return false and store an error message in the errorMsg pointer. - */ - static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); - - - //! The post title. - QString title; - - //! The post's content. May contain HTML. - QString content; - - //! URL to the post. - QString link; - - //! The post's author. - QString author; - - //! The date and time that this post was published. - QDateTime pubDate; -}; - -typedef std::shared_ptr NewsEntryPtr; - diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 3dcc8ee9..202924ff 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -58,7 +58,6 @@ #include #include #include -#include #include #include #include @@ -201,7 +200,6 @@ public: //TranslatedAction actionRefresh; TranslatedAction actionCheckUpdate; TranslatedAction actionSettings; - TranslatedAction actionMoreNews; TranslatedAction actionManageAccounts; TranslatedAction actionLaunchInstance; TranslatedAction actionRenameInstance; @@ -246,7 +244,6 @@ public: TranslatedToolbar mainToolBar; TranslatedToolbar instanceToolBar; - TranslatedToolbar newsToolBar; QVector all_toolbars; bool m_kill = false; @@ -429,29 +426,6 @@ public: MainWindow->setStatusBar(statusBar); } - void createNewsToolbar(QMainWindow *MainWindow) - { - newsToolBar = TranslatedToolbar(MainWindow); - newsToolBar->setObjectName(QStringLiteral("newsToolBar")); - newsToolBar->setMovable(false); - newsToolBar->setAllowedAreas(Qt::BottomToolBarArea); - newsToolBar->setIconSize(QSize(16, 16)); - newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - newsToolBar->setFloatable(false); - newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar")); - - actionMoreNews = TranslatedAction(MainWindow); - actionMoreNews->setObjectName(QStringLiteral("actionMoreNews")); - actionMoreNews->setIcon(APPLICATION->getThemedIcon("news")); - actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news...")); - actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the development blog to read more news about %1.")); - all_actions.append(&actionMoreNews); - newsToolBar->addAction(actionMoreNews); - - all_toolbars.append(&newsToolBar); - MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); - } - void createInstanceToolbar(QMainWindow *MainWindow) { instanceToolBar = TranslatedToolbar(MainWindow); @@ -636,7 +610,6 @@ public: MainWindow->setCentralWidget(centralWidget); createStatusBar(MainWindow); - createNewsToolbar(MainWindow); createInstanceToolbar(MainWindow); retranslateUi(MainWindow); @@ -691,20 +664,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow connect(secretEventFilter, &KonamiCode::triggered, this, &MainWindow::konamiTriggered); } - // Add the news label to the news toolbar. - { - m_newsChecker.reset(new NewsChecker(APPLICATION->network(), BuildConfig.NEWS_RSS_URL)); - newsLabel = new QToolButton(); - newsLabel->setIcon(APPLICATION->getThemedIcon("news")); - newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - newsLabel->setFocusPolicy(Qt::NoFocus); - ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); - QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); - QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); - updateNewsLabel(); - } - // Create the instance list widget { view = new InstanceView(ui->centralWidget); @@ -809,13 +768,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow // TODO: refresh accounts here? // auto accounts = APPLICATION->accounts(); - // load the news - { - m_newsChecker->reloadNews(); - updateNewsLabel(); - } - - if(BuildConfig.UPDATER_ENABLED) { bool updatesAllowed = APPLICATION->updatesAreAllowed(); @@ -1189,29 +1141,6 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev) return QMainWindow::eventFilter(obj, ev); } -void MainWindow::updateNewsLabel() -{ - if (m_newsChecker->isLoadingNews()) - { - newsLabel->setText(tr("Loading news...")); - newsLabel->setEnabled(false); - } - else - { - QList entries = m_newsChecker->getNewsEntries(); - if (entries.length() > 0) - { - newsLabel->setText(entries[0]->title); - newsLabel->setEnabled(true); - } - else - { - newsLabel->setText(tr("No news available.")); - newsLabel->setEnabled(false); - } - } -} - void MainWindow::updateAvailable(GoUpdate::Status status) { if(!APPLICATION->updatesAreAllowed()) @@ -1685,24 +1614,6 @@ void MainWindow::on_actionReportBug_triggered() DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL)); } -void MainWindow::on_actionMoreNews_triggered() -{ - DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); -} - -void MainWindow::newsButtonClicked() -{ - QList entries = m_newsChecker->getNewsEntries(); - if (entries.count() > 0) - { - DesktopServices::openUrl(QUrl(entries[0]->link)); - } - else - { - DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); - } -} - void MainWindow::on_actionAbout_triggered() { AboutDialog dialog(this); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index f6940ab0..38d925a9 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -27,7 +27,6 @@ #include "updater/GoUpdate.h" class LaunchController; -class NewsChecker; class NotificationChecker; class QToolButton; class InstanceProxyModel; @@ -109,10 +108,6 @@ private slots: void on_actionReportBug_triggered(); - void on_actionMoreNews_triggered(); - - void newsButtonClicked(); - void on_actionLaunchInstance_triggered(); void on_actionLaunchInstanceOffline_triggered(); @@ -174,8 +169,6 @@ private slots: void repopulateAccountsMenu(); - void updateNewsLabel(); - /*! * Runs the DownloadTask and installs updates. */ @@ -205,14 +198,12 @@ private: // these are managed by Qt's memory management model! InstanceView *view = nullptr; InstanceProxyModel *proxymodel = nullptr; - QToolButton *newsLabel = nullptr; QLabel *m_statusLeft = nullptr; QLabel *m_statusCenter = nullptr; QMenu *accountMenu = nullptr; QToolButton *accountMenuButton = nullptr; KonamiCode * secretEventFilter = nullptr; - unique_qobject_ptr m_newsChecker; unique_qobject_ptr m_notificationChecker; InstancePtr m_selectedInstance; From 5ac528f141f2c55fb09c6d59c5fd71d5f83c46ce Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 27 Jan 2022 18:37:57 -0500 Subject: [PATCH 45/45] Fix icons changing when exiting the settings window --- launcher/ui/pages/global/LauncherPage.cpp | 43 +++++++++++------------ launcher/ui/pages/global/LauncherPage.ui | 10 +++--- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 81ecd58f..0ffe8050 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -246,33 +246,32 @@ void LauncherPage::applySettings() //FIXME: make generic switch (ui->themeComboBox->currentIndex()) { - case 1: + case 0: s->set("IconTheme", "pe_dark"); break; - case 2: + case 1: s->set("IconTheme", "pe_light"); break; - case 3: + case 2: s->set("IconTheme", "pe_blue"); break; - case 4: - s->set("IconTheme", "multimc"); + case 3: + s->set("IconTheme", "pe_colored"); break; - case 5: + case 4: s->set("IconTheme", "OSX"); break; - case 6: + case 5: s->set("IconTheme", "iOS"); break; - case 7: + case 6: s->set("IconTheme", "flat"); break; - case 8: + case 7: s->set("IconTheme", "custom"); break; - case 0: - default: - s->set("IconTheme", "pe_colored"); + case 8: + s->set("IconTheme", "multimc"); break; } @@ -327,29 +326,33 @@ void LauncherPage::loadSettings() auto theme = s->get("IconTheme").toString(); if (theme == "pe_dark") { - ui->themeComboBox->setCurrentIndex(1); + ui->themeComboBox->setCurrentIndex(0); } else if (theme == "pe_light") { - ui->themeComboBox->setCurrentIndex(2); + ui->themeComboBox->setCurrentIndex(1); } else if (theme == "pe_blue") { - ui->themeComboBox->setCurrentIndex(3); + ui->themeComboBox->setCurrentIndex(2); } else if (theme == "pe_colored") { - ui->themeComboBox->setCurrentIndex(4); + ui->themeComboBox->setCurrentIndex(3); } else if (theme == "OSX") { - ui->themeComboBox->setCurrentIndex(5); + ui->themeComboBox->setCurrentIndex(4); } else if (theme == "iOS") { - ui->themeComboBox->setCurrentIndex(6); + ui->themeComboBox->setCurrentIndex(5); } else if (theme == "flat") + { + ui->themeComboBox->setCurrentIndex(6); + } + else if (theme == "multimc") { ui->themeComboBox->setCurrentIndex(7); } @@ -357,10 +360,6 @@ void LauncherPage::loadSettings() { ui->themeComboBox->setCurrentIndex(8); } - else - { - ui->themeComboBox->setCurrentIndex(0); - } { auto currentTheme = s->get("ApplicationTheme").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 2b3729bc..47fed873 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -262,11 +262,6 @@ Qt::StrongFocus - - - Default - - Simple (Dark Icons) @@ -307,6 +302,11 @@ Custom + + + MultiMC + +