From 8d100ba97c9d48ac1aa0d474a112d6c1201b8db4 Mon Sep 17 00:00:00 2001 From: Alfio Date: Sun, 11 Sep 2022 16:59:00 +0200 Subject: [PATCH 01/54] Update README.md fixed a couple of typos and reformatted some stuff to be a little more legible. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b158625c..9a609872 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ If you want to contribute, talk to us on [Discord](https://discord.gg/multimc) f While blindly submitting PRs is definitely possible, they're not necessarily going to get accepted. -We aren't looking for flashy features, but expanding upon the existing feature set without distruption or endangering future viability of the project is OK. +We aren't looking for flashy features, but expanding upon the existing feature set without disruption or endangering the future viability of the project is OK. ### Building If you want to build the launcher yourself, check [BUILD.md](BUILD.md) for build instructions. @@ -40,9 +40,9 @@ Unless required by applicable law or agreed to in writing, software distributed ## Forking/Redistributing/Custom builds policy We keep Launcher open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license. -The license gives you access to the source MultiMC is build from, but: -- Not the name, logo and other branding. -- Not the API tokens required to talk to services the launcher depends on. +The license gives you access to the source MultiMC is built from, but not: +- The name, logo and other branding. +- The API tokens required to talk to services that the launcher depends on. Because of the nature of the agreements required to interact with the Microsoft identity platform, it's impossible for us to continue allowing everyone to build the code as 'MultiMC'. The source code has been debranded and now builds as `DevLauncher` by default. From c37b7f771e711b7c33ed6b9567cc95a62b238c91 Mon Sep 17 00:00:00 2001 From: ryanbrown535 Date: Sun, 2 Oct 2022 20:45:06 -0400 Subject: [PATCH 02/54] Confirm screenshot upload Adds a message box on upload asking if the user is sure they want to upload to imgur --- launcher/ui/pages/instance/ScreenshotsPage.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 4011d88c..e70117eb 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -314,6 +314,20 @@ void ScreenshotsPage::on_actionUpload_triggered() if (selection.isEmpty()) return; + auto uploadText = tr("Upload screenshot to imgur.com?"); + if (selection.size() > 1) + uploadText = tr("Upload %1 screenshots to imgur.com?").arg(selection.size()); + + auto response = CustomMessageBox::selectable( + this, + tr("Upload?"), + uploadText, + QMessageBox::Question, + QMessageBox::Yes | QMessageBox::No + )->exec(); + if (response == QMessageBox::No) + return; + QList uploaded; auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network())); if(selection.size() < 2) From af36e5c43fcc06c4d7b4e2432ba44e3bcf0a0d6a Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Thu, 5 May 2022 20:14:19 +0100 Subject: [PATCH 03/54] NOISSUE Display ATLauncher install messages --- .../modplatform/atlauncher/ATLPackInstallTask.cpp | 4 ++++ launcher/modplatform/atlauncher/ATLPackInstallTask.h | 7 ++++++- launcher/modplatform/atlauncher/ATLPackManifest.cpp | 11 +++++++++++ launcher/modplatform/atlauncher/ATLPackManifest.h | 8 ++++++++ launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp | 9 ++++++++- launcher/ui/pages/modplatform/atlauncher/AtlPage.h | 3 ++- 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index c4e197b4..a34db774 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -96,6 +96,10 @@ void PackInstallTask::onDownloadSucceeded() } m_version = version; + // Display install message if one exists + if (!m_version.messages.install.isEmpty()) + m_support->displayMessage(m_version.messages.install); + auto vlist = APPLICATION->metadataIndex()->get("net.minecraft"); if(!vlist) { diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index 783ec19b..814a754a 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Jamie Mansfield + * Copyright 2020-2022 Jamie Mansfield * Copyright 2021 Petr Mrazek * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,6 +45,11 @@ public: */ virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0; + /** + * Requests a user interaction to display a message. + */ + virtual void displayMessage(QString message) = 0; + }; class PackInstallTask : public InstanceTask diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index b214f738..fc6b8e3f 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -197,6 +197,12 @@ static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments & a, a.depends = Json::ensureString(obj, "depends", ""); } +static void loadVersionMessages(ATLauncher::VersionMessages & m, QJsonObject & obj) +{ + m.install = Json::ensureString(obj, "install", ""); + m.update = Json::ensureString(obj, "update", ""); +} + void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) { v.version = Json::requireString(obj, "version"); @@ -244,4 +250,9 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) auto configsObj = Json::requireObject(obj, "configs"); loadVersionConfigs(v.configs, configsObj); } + + if(obj.contains("messages")) { + auto messages = Json::requireObject(obj, "messages"); + loadVersionMessages(v.messages, messages); + } } diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 6be82da4..9f109a54 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -134,6 +134,12 @@ struct PackVersionExtraArguments QString depends; }; +struct VersionMessages +{ + QString install; + QString update; +}; + struct PackVersion { QString version; @@ -146,6 +152,8 @@ struct PackVersion QVector libraries; QVector mods; VersionConfigs configs; + + VersionMessages messages; }; void loadVersion(PackVersion & v, QJsonObject & obj); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index af0cc8d6..b1fa0605 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Jamie Mansfield + * Copyright 2020-2022 Jamie Mansfield * Copyright 2021 Philip T * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,8 @@ #include +#include + AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent) : QWidget(parent), ui(new Ui::AtlPage), dialog(dialog) { @@ -186,3 +188,8 @@ QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVers vselect.exec(); return vselect.selectedVersion()->descriptor(); } + +void AtlPage::displayMessage(QString message) +{ + QMessageBox::information(this, tr("Installing"), message); +} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index 5b3f2228..d8917bbd 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Jamie Mansfield + * Copyright 2020-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,6 +65,7 @@ private: QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; QVector chooseOptionalMods(QVector mods) override; + void displayMessage(QString message) override; private slots: void triggerSearch(); From 41f728b22ffe3f532d7019d637e669be8319ba22 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Thu, 5 May 2022 20:30:51 +0100 Subject: [PATCH 04/54] NOISSUE Pass the optional mod dialog the full version We will need more information, let's just pass the whole thing. --- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 2 +- launcher/modplatform/atlauncher/ATLPackInstallTask.h | 2 +- .../modplatform/atlauncher/AtlOptionalModDialog.cpp | 10 +++++----- .../modplatform/atlauncher/AtlOptionalModDialog.h | 8 +++++--- launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp | 4 ++-- launcher/ui/pages/modplatform/atlauncher/AtlPage.h | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a34db774..09d0e415 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -530,7 +530,7 @@ void PackInstallTask::downloadMods() QVector selectedMods; if (!optionalMods.isEmpty()) { setStatus(tr("Selecting optional mods...")); - selectedMods = m_support->chooseOptionalMods(optionalMods); + selectedMods = m_support->chooseOptionalMods(m_version, optionalMods); } setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index 814a754a..bb1e5860 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -37,7 +37,7 @@ public: /** * Requests a user interaction to select which optional mods should be installed. */ - virtual QVector chooseOptionalMods(QVector mods) = 0; + virtual QVector chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index ac3869dc..b7cd0938 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 Jamie Mansfield + * Copyright 2021-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ #include "AtlOptionalModDialog.h" #include "ui_AtlOptionalModDialog.h" -AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector mods) - : QAbstractListModel(parent), m_mods(mods) { +AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, ATLauncher::PackVersion version, QVector mods) + : QAbstractListModel(parent), m_version(version), m_mods(mods) { // fill mod index for (int i = 0; i < m_mods.size(); i++) { @@ -199,11 +199,11 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool } -AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector mods) +AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, ATLauncher::PackVersion version, QVector mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { ui->setupUi(this); - listModel = new AtlOptionalModListModel(this, mods); + listModel = new AtlOptionalModListModel(this, version, mods); ui->treeView->setModel(listModel); ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 9832014c..26a2fdfa 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -1,5 +1,5 @@ /* - * Copyright 2021 Jamie Mansfield + * Copyright 2021-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ public: DescriptionColumn, }; - AtlOptionalModListModel(QWidget *parent, QVector mods); + AtlOptionalModListModel(QWidget *parent, ATLauncher::PackVersion version, QVector mods); QVector getResult(); @@ -58,7 +58,9 @@ private: void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true); private: + ATLauncher::PackVersion m_version; QVector m_mods; + QMap m_selection; QMap m_index; QMap> m_dependants; @@ -68,7 +70,7 @@ class AtlOptionalModDialog : public QDialog { Q_OBJECT public: - AtlOptionalModDialog(QWidget *parent, QVector mods); + AtlOptionalModDialog(QWidget *parent, ATLauncher::PackVersion version, QVector mods); ~AtlOptionalModDialog() override; QVector getResult() { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index b1fa0605..d4e2cc3c 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -147,8 +147,8 @@ void AtlPage::onVersionSelectionChanged(QString data) suggestCurrent(); } -QVector AtlPage::chooseOptionalMods(QVector mods) { - AtlOptionalModDialog optionalModDialog(this, mods); +QVector AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) { + AtlOptionalModDialog optionalModDialog(this, version, mods); optionalModDialog.exec(); return optionalModDialog.getResult(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index d8917bbd..a7c56eb1 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -64,7 +64,7 @@ private: void suggestCurrent(); QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; - QVector chooseOptionalMods(QVector mods) override; + QVector chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; void displayMessage(QString message) override; private slots: From c24a89f3af8db09ac7d9e7e7479ccbc4499e4163 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Thu, 5 May 2022 20:58:12 +0100 Subject: [PATCH 05/54] NOISSUE Display warnings when selecting optional mods --- .../modplatform/atlauncher/ATLPackManifest.cpp | 9 +++++++++ .../modplatform/atlauncher/ATLPackManifest.h | 2 ++ .../atlauncher/AtlOptionalModDialog.cpp | 18 +++++++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index fc6b8e3f..fdd06087 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -178,6 +178,7 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { p.depends.append(Json::requireValueString(depends)); } } + p.warning = Json::ensureString(obj, QString("warning"), ""); p.client = Json::ensureBoolean(obj, QString("client"), false); @@ -251,6 +252,14 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) loadVersionConfigs(v.configs, configsObj); } + if(obj.contains("warnings")) { + auto warningsObj = Json::requireObject(obj, "warnings"); + + for (const auto &key : warningsObj.keys()) { + v.warnings[key] = Json::requireValueString(warningsObj.value(key), "warning"); + } + } + if(obj.contains("messages")) { auto messages = Json::requireObject(obj, "messages"); loadVersionMessages(v.messages, messages); diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 9f109a54..3a5d4240 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -109,6 +109,7 @@ struct VersionMod bool library; QString group; QVector depends; + QString warning; bool client; @@ -153,6 +154,7 @@ struct PackVersion QVector mods; VersionConfigs configs; + QMap warnings; VersionMessages messages; }; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index b7cd0938..153097eb 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -17,6 +17,8 @@ #include "AtlOptionalModDialog.h" #include "ui_AtlOptionalModDialog.h" +#include + AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, ATLauncher::PackVersion version, QVector mods) : QAbstractListModel(parent), m_version(version), m_mods(mods) { @@ -134,7 +136,21 @@ void AtlOptionalModListModel::clearAll() { } void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) { - setMod(mod, index, !m_selection[mod.name]); + auto enable = !m_selection[mod.name]; + + // If there is a warning for the mod, display that first (if we would be enabling the mod) + if (enable && !mod.warning.isEmpty() && m_version.warnings.contains(mod.warning)) { + auto message = QString("%1

%2") + .arg(m_version.warnings[mod.warning], tr("Are you sure that you want to enable this mod?")); + + // fixme: avoid casting here + auto result = QMessageBox::warning((QWidget*) this->parent(), tr("Warning"), message, QMessageBox::Yes | QMessageBox::No); + if (result != QMessageBox::Yes) { + return; + } + } + + setMod(mod, index, enable); } void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) { From b433882ac1eb91091e90b51ed298eb9d3d7fb406 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Wed, 19 Oct 2022 12:49:24 +0100 Subject: [PATCH 06/54] NOISSUE Add missing QMap include This should fix the build. --- launcher/modplatform/atlauncher/ATLPackManifest.h | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 3a5d4240..b6536908 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include From dbe7d9ea2e7197946bab1faf1e15554b16a085c3 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 2 May 2022 20:27:20 +0100 Subject: [PATCH 07/54] NOISSUE Display mod colours in optional mod dialog --- launcher/modplatform/atlauncher/ATLPackManifest.cpp | 9 +++++++++ launcher/modplatform/atlauncher/ATLPackManifest.h | 2 ++ .../modplatform/atlauncher/AtlOptionalModDialog.cpp | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index fdd06087..f8f7c2f4 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -178,6 +178,7 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) { p.depends.append(Json::requireValueString(depends)); } } + p.colour = Json::ensureString(obj, QString("colour"), ""); p.warning = Json::ensureString(obj, QString("warning"), ""); p.client = Json::ensureBoolean(obj, QString("client"), false); @@ -252,6 +253,14 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) loadVersionConfigs(v.configs, configsObj); } + if(obj.contains("colours")) { + auto colourObj = Json::requireObject(obj, "colours"); + + for (const auto &key : colourObj.keys()) { + v.colours[key] = Json::requireValueString(colourObj.value(key), "colour"); + } + } + if(obj.contains("warnings")) { auto warningsObj = Json::requireObject(obj, "warnings"); diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index b6536908..4efa4d38 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -110,6 +110,7 @@ struct VersionMod bool library; QString group; QVector depends; + QString colour; QString warning; bool client; @@ -155,6 +156,7 @@ struct PackVersion QVector mods; VersionConfigs configs; + QMap colours; QMap warnings; VersionMessages messages; }; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 153097eb..c0aaf450 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -73,6 +73,11 @@ QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const return mod.description; } } + else if (role == Qt::ForegroundRole) { + if (!mod.colour.isEmpty() && m_version.colours.contains(mod.colour)) { + return QColor(QString("#%1").arg(m_version.colours[mod.colour])); + } + } else if (role == Qt::CheckStateRole) { if (index.column() == EnabledColumn) { return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked; From 79cd37be9459a7cdfb441b72bff9f998064bff38 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 23:26:47 +0100 Subject: [PATCH 08/54] NOISSUE Add settings to support managed packs Managed packs means an installation of a modpack through a modpack provider. Managed packs track their origins (pack platform, name, id), so that in future features can exist around this - such as updating, and reinstalling. --- launcher/BaseInstance.cpp | 49 +++++++++++++++++++++++++++++++++++++++ launcher/BaseInstance.h | 9 +++++++ 2 files changed, 58 insertions(+) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 374d4a29..5602c728 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -1,4 +1,5 @@ /* Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +56,14 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); + + // Managed Packs + m_settings->registerSetting("ManagedPack", false); + m_settings->registerSetting("ManagedPackType", ""); + m_settings->registerSetting("ManagedPackID", ""); + m_settings->registerSetting("ManagedPackName", ""); + m_settings->registerSetting("ManagedPackVersionID", ""); + m_settings->registerSetting("ManagedPackVersionName", ""); } QString BaseInstance::getPreLaunchCommand() @@ -72,6 +81,46 @@ QString BaseInstance::getPostExitCommand() return settings()->get("PostExitCommand").toString(); } +bool BaseInstance::isManagedPack() +{ + return settings()->get("ManagedPack").toBool(); +} + +QString BaseInstance::getManagedPackType() +{ + return settings()->get("ManagedPackType").toString(); +} + +QString BaseInstance::getManagedPackID() +{ + return settings()->get("ManagedPackID").toString(); +} + +QString BaseInstance::getManagedPackName() +{ + return settings()->get("ManagedPackName").toString(); +} + +QString BaseInstance::getManagedPackVersionID() +{ + return settings()->get("ManagedPackVersionID").toString(); +} + +QString BaseInstance::getManagedPackVersionName() +{ + return settings()->get("ManagedPackVersionName").toString(); +} + +void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version) +{ + settings()->set("ManagedPack", true); + settings()->set("ManagedPackType", type); + settings()->set("ManagedPackID", id); + settings()->set("ManagedPackName", name); + settings()->set("ManagedPackVersionID", versionId); + settings()->set("ManagedPackVersionName", version); +} + int BaseInstance::getConsoleMaxLines() const { auto lineSetting = settings()->getSetting("ConsoleMaxLines"); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 488f2781..9f8e58d3 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -1,4 +1,5 @@ /* Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,6 +120,14 @@ public: QString getPostExitCommand(); QString getWrapperCommand(); + bool isManagedPack(); + QString getManagedPackType(); + QString getManagedPackID(); + QString getManagedPackName(); + QString getManagedPackVersionID(); + QString getManagedPackVersionName(); + void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); + /// guess log level from a line of game log virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) { From 79204e5df0580a84835d32ff9fb468302689a6b7 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 22 May 2022 14:34:11 +0100 Subject: [PATCH 09/54] NOISSUE Pass the full pack name through to the install task --- .../modplatform/atlauncher/ATLPackInstallTask.cpp | 15 ++++++++------- .../modplatform/atlauncher/ATLPackInstallTask.h | 5 +++-- .../ui/pages/modplatform/atlauncher/AtlPage.cpp | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 09d0e415..9ad63f6c 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -39,10 +39,11 @@ namespace ATLauncher { -PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version) +PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version) { m_support = support; - m_pack = pack; + m_pack_name = packName; + m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), ""); m_version_name = version; } @@ -60,7 +61,7 @@ void PackInstallTask::executeTask() qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") - .arg(m_pack).arg(m_version_name); + .arg(m_pack_safe_name).arg(m_version_name); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -307,7 +308,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); auto f = std::make_shared(); - f->name = m_pack + " " + m_version_name + " (libraries)"; + f->name = m_pack_name + " " + m_version_name + " (libraries)"; for(const auto & lib : m_version.libraries) { auto libName = detectLibrary(lib); @@ -412,7 +413,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< } auto f = std::make_shared(); - f->name = m_pack + " " + m_version_name; + f->name = m_pack_name + " " + m_version_name; if(!mainClass.isEmpty() && !mainClasses.contains(mainClass)) { f->mainClass = mainClass; } @@ -454,9 +455,9 @@ void PackInstallTask::installConfigs() setStatus(tr("Downloading configs...")); jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); - auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name); + auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") - .arg(m_pack).arg(m_version_name); + .arg(m_pack_safe_name).arg(m_version_name); auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path); entry->setStale(true); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index bb1e5860..44bf01e7 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -57,7 +57,7 @@ class PackInstallTask : public InstanceTask Q_OBJECT public: - explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version); + explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version); virtual ~PackInstallTask(){} bool canAbort() const override { return true; } @@ -99,7 +99,8 @@ private: NetJob::Ptr jobPtr; QByteArray response; - QString m_pack; + QString m_pack_name; + QString m_pack_safe_name; QString m_version_name; PackVersion m_version; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index d4e2cc3c..8b5a3187 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -91,7 +91,7 @@ void AtlPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion)); + dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion)); auto editedLogoName = selected.safeName; auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) From 064c0febd3e245840ebe232752fd3f15c3b61cc3 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 23:38:21 +0100 Subject: [PATCH 10/54] NOISSUE Make ATLauncher packs managed when installing --- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 9ad63f6c..79fc5d86 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -815,6 +815,7 @@ void PackInstallTask::install() instance.setName(m_instName); instance.setIconKey(m_instIcon); + instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name); instanceSettings->resumeSave(); jarmods.clear(); From e35f2b6c2c4b1c7675a4d7b72c01adbff1a43205 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Fri, 24 Dec 2021 15:00:36 +0000 Subject: [PATCH 11/54] NOISSUE Allow Technic pack API urls to be used in search This mimics the behaviour that the Technic launcher has, and their website displays API URLs for. The big benefit of this, is to be able to install private packs now :) --- .../modplatform/technic/TechnicModel.cpp | 80 ++++++++++++++----- .../pages/modplatform/technic/TechnicModel.h | 5 ++ 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 0bc0d498..c62d36a7 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -1,4 +1,5 @@ /* Copyright 2020-2021 MultiMC Contributors + * Copyright 2021 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,12 +96,21 @@ void Technic::ListModel::performSearch() QString searchUrl = ""; if (currentSearchTerm.isEmpty()) { searchUrl = "https://api.technicpack.net/trending?build=multimc"; + searchMode = List; } - else - { + else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) { + searchUrl = QString("https://%1?build=multimc").arg(currentSearchTerm.mid(7)); + searchMode = Single; + } + else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { + searchUrl = QString("%1?build=multimc").arg(currentSearchTerm); + searchMode = Single; + } + else { searchUrl = QString( "https://api.technicpack.net/search?build=multimc&q=%1" ).arg(currentSearchTerm); + searchMode = List; } netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; @@ -125,26 +135,58 @@ void Technic::ListModel::searchRequestFinished() QList newList; try { auto root = Json::requireObject(doc); - auto objs = Json::requireArray(root, "modpacks"); - for (auto technicPack: objs) { - Modpack pack; - auto technicPackObject = Json::requireValueObject(technicPack); - pack.name = Json::requireString(technicPackObject, "name"); - pack.slug = Json::requireString(technicPackObject, "slug"); - if (pack.slug == "vanilla") - continue; - auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); - if(rawURL == "null") { - pack.logoUrl = "null"; - pack.logoName = "null"; + switch (searchMode) { + case List: { + auto objs = Json::requireArray(root, "modpacks"); + for (auto technicPack: objs) { + Modpack pack; + auto technicPackObject = Json::requireValueObject(technicPack); + pack.name = Json::requireString(technicPackObject, "name"); + pack.slug = Json::requireString(technicPackObject, "slug"); + if (pack.slug == "vanilla") + continue; + + auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); + if(rawURL == "null") { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + else { + pack.logoUrl = rawURL; + pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + pack.broken = false; + newList.append(pack); + } + break; } - else { - pack.logoUrl = rawURL; - pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + case Single: { + if (root.contains("error")) { + // Invalid API url + break; + } + + Modpack pack; + pack.name = Json::requireString(root, "displayName"); + pack.slug = Json::requireString(root, "name"); + + if (root.contains("icon")) { + auto iconObj = Json::requireObject(root, "icon"); + auto iconUrl = Json::requireString(iconObj, "url"); + + pack.logoUrl = iconUrl; + pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + else { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + + pack.broken = false; + newList.append(pack); + break; } - pack.broken = false; - newList.append(pack); } } catch (const JSONValidationError &err) diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h index e80e6e7c..d2748811 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.h +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h @@ -1,4 +1,5 @@ /* Copyright 2020-2021 MultiMC Contributors + * Copyright 2021 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +64,10 @@ private: ResetRequested, Finished } searchState = None; + enum SearchMode { + List, + Single, + } searchMode = List; NetJob::Ptr jobPtr; QByteArray response; }; From ddc094b76ba60ff2f1f158317f0bf31bbd7a420d Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Fri, 24 Dec 2021 15:20:34 +0000 Subject: [PATCH 12/54] NOISUE Prevent potential HTML injection --- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 64054168..09535017 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -178,14 +178,12 @@ void TechnicPage::metadataLoaded() QString name = current.name; if (current.websiteUrl.isEmpty()) - // This allows injecting HTML here. - text = name; + text = name.toHtmlEscaped(); else - // URL not properly escaped for inclusion in HTML. The name allows for injecting HTML. - text = "" + name + ""; + text = "" + name.toHtmlEscaped() + ""; + if (!current.author.isEmpty()) { - // This allows injecting HTML here - text += tr(" by ") + current.author; + text += tr(" by ") + current.author.toHtmlEscaped(); } text += "

"; From 65a4f8919ab0f5e3d97db264a9a455cdf0df55fe Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Fri, 24 Dec 2021 15:10:42 +0000 Subject: [PATCH 13/54] NOISSUE Include the modpack version in instance title --- launcher/ui/pages/modplatform/technic/TechnicData.h | 1 + launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/technic/TechnicData.h b/launcher/ui/pages/modplatform/technic/TechnicData.h index 50fd75e8..4a374a6b 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicData.h +++ b/launcher/ui/pages/modplatform/technic/TechnicData.h @@ -36,6 +36,7 @@ struct Modpack { QString websiteUrl; QString author; QString description; + QString currentVersion; }; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 09535017..34d18052 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -165,6 +165,7 @@ void TechnicPage::suggestCurrent() current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__"); current.author = Json::ensureString(obj, "user", QString(), "__placeholder__"); current.description = Json::ensureString(obj, "description", QString(), "__placeholder__"); + current.currentVersion = Json::ensureString(obj, "version", QString(), "__placeholder__"); current.metadataLoaded = true; metadataLoaded(); }); @@ -191,11 +192,11 @@ void TechnicPage::metadataLoaded() ui->packDescription->setHtml(text + current.description); if (!current.isSolder) { - dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + current.currentVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); } else { while (current.url.endsWith('/')) current.url.chop(1); - dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + current.currentVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion)); } } From 2334a44221ac19bd90acd9c23c909b37899a3fd5 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 27 Dec 2021 03:55:08 +0000 Subject: [PATCH 14/54] NOISSUE Match CurseForge pack description format in Technic UI --- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 34d18052..59d78bfc 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -184,7 +184,7 @@ void TechnicPage::metadataLoaded() text = "" + name.toHtmlEscaped() + ""; if (!current.author.isEmpty()) { - text += tr(" by ") + current.author.toHtmlEscaped(); + text += "
" + tr(" by ") + current.author.toHtmlEscaped(); } text += "

"; From 18f790953aa9e2beb6aa6f1127c409ab039c3829 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Fri, 1 Apr 2022 23:40:20 +0100 Subject: [PATCH 15/54] NOISSUE Add API models for Solder packs This will be able to replace the ugly parsing/usage code that currently exists in the Solder install task :) --- launcher/CMakeLists.txt | 2 + .../technic/SolderPackManifest.cpp | 56 +++++++++++++++++++ .../modplatform/technic/SolderPackManifest.h | 47 ++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 launcher/modplatform/technic/SolderPackManifest.cpp create mode 100644 launcher/modplatform/technic/SolderPackManifest.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 25c28063..85f669eb 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -509,6 +509,8 @@ set(TECHNIC_SOURCES modplatform/technic/SingleZipPackInstallTask.cpp modplatform/technic/SolderPackInstallTask.h modplatform/technic/SolderPackInstallTask.cpp + modplatform/technic/SolderPackManifest.h + modplatform/technic/SolderPackManifest.cpp modplatform/technic/TechnicPackProcessor.h modplatform/technic/TechnicPackProcessor.cpp ) diff --git a/launcher/modplatform/technic/SolderPackManifest.cpp b/launcher/modplatform/technic/SolderPackManifest.cpp new file mode 100644 index 00000000..ad195d3a --- /dev/null +++ b/launcher/modplatform/technic/SolderPackManifest.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2022 Jamie Mansfield + * + * 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 "SolderPackManifest.h" + +#include "Json.h" + +namespace TechnicSolder { + +void loadPack(Pack& v, QJsonObject& obj) +{ + v.recommended = Json::requireString(obj, "recommended"); + v.latest = Json::requireString(obj, "latest"); + + auto builds = Json::requireArray(obj, "builds"); + for (const auto buildRaw : builds) { + auto build = Json::requireValueString(buildRaw); + v.builds.append(build); + } +} + +static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj) +{ + b.name = Json::requireString(obj, "name"); + b.version = Json::requireString(obj, "version"); + b.md5 = Json::requireString(obj, "md5"); + b.url = Json::requireString(obj, "url"); +} + +void loadPackBuild(PackBuild& v, QJsonObject& obj) +{ + v.minecraft = Json::requireString(obj, "minecraft"); + + auto mods = Json::requireArray(obj, "mods"); + for (const auto modRaw : mods) { + auto modObj = Json::requireValueObject(modRaw); + PackBuildMod mod; + loadPackBuildMod(mod, modObj); + v.mods.append(mod); + } +} + +} diff --git a/launcher/modplatform/technic/SolderPackManifest.h b/launcher/modplatform/technic/SolderPackManifest.h new file mode 100644 index 00000000..b4d8cf53 --- /dev/null +++ b/launcher/modplatform/technic/SolderPackManifest.h @@ -0,0 +1,47 @@ +/* + * Copyright 2022 Jamie Mansfield + * + * 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 + +namespace TechnicSolder { + +struct Pack { + QString recommended; + QString latest; + QVector builds; +}; + +void loadPack(Pack& v, QJsonObject& obj); + +struct PackBuildMod { + QString name; + QString version; + QString md5; + QString url; +}; + +struct PackBuild { + QString minecraft; + QVector mods; +}; + +void loadPackBuild(PackBuild& v, QJsonObject& obj); + +} From b8a736c67316040518dd9e2c93028c4c1303cc25 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 2 Apr 2022 13:26:03 +0100 Subject: [PATCH 16/54] NOISSUE Replace inline parsing code with Solder API models --- .../technic/SolderPackInstallTask.cpp | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 4e9c7ad3..673f93e0 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -1,4 +1,5 @@ /* Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ #include #include #include "TechnicPackProcessor.h" +#include "SolderPackManifest.h" Technic::SolderPackInstallTask::SolderPackInstallTask( shared_qobject_ptr network, @@ -52,21 +54,29 @@ void Technic::SolderPackInstallTask::executeTask() void Technic::SolderPackInstallTask::versionSucceeded() { - try - { - QJsonDocument doc = Json::requireDocument(m_response); - QJsonObject obj = Json::requireObject(doc); - QString version = Json::requireString(obj, "recommended", "__placeholder__"); - m_sourceUrl = m_sourceUrl.toString() + '/' + version; + setStatus(tr("Resolving modpack files")); + + QJsonParseError parse_error {}; + QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << m_response; + return; } - catch (const JSONValidationError &e) - { - emitFailed(e.cause()); + auto obj = doc.object(); + + TechnicSolder::Pack pack; + try { + TechnicSolder::loadPack(pack, obj); + } + catch (const JSONValidationError& e) { + emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); m_filesNetJob.reset(); return; } - setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString())); + m_sourceUrl = m_sourceUrl.toString() + '/' + pack.recommended; + m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network); m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); auto job = m_filesNetJob.get(); @@ -77,38 +87,40 @@ void Technic::SolderPackInstallTask::versionSucceeded() void Technic::SolderPackInstallTask::fileListSucceeded() { - setStatus(tr("Downloading modpack:")); - QStringList modUrls; - try - { - QJsonDocument doc = Json::requireDocument(m_response); - QJsonObject obj = Json::requireObject(doc); - QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__"); - if (!minecraftVersion.isEmpty()) - m_minecraftVersion = minecraftVersion; - QJsonArray mods = Json::requireArray(obj, "mods", "'mods'"); - for (auto mod: mods) - { - QJsonObject modObject = Json::requireValueObject(mod); - modUrls.append(Json::requireString(modObject, "url", "'url'")); - } + setStatus(tr("Downloading modpack")); + + QJsonParseError parse_error {}; + QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << m_response; + return; } - catch (const JSONValidationError &e) - { - emitFailed(e.cause()); + auto obj = doc.object(); + + TechnicSolder::PackBuild build; + try { + TechnicSolder::loadPackBuild(build, obj); + } + catch (const JSONValidationError& e) { + emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); m_filesNetJob.reset(); return; } + + if (!build.minecraft.isEmpty()) + m_minecraftVersion = build.minecraft; + m_filesNetJob = new NetJob(tr("Downloading modpack"), m_network); int i = 0; - for (auto &modUrl: modUrls) + for (const auto &mod : build.mods) { auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); - m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, path)); + m_filesNetJob->addNetAction(Net::Download::makeFile(mod.url, path)); i++; } - m_modCount = modUrls.size(); + m_modCount = build.mods.size(); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); @@ -206,6 +218,4 @@ void Technic::SolderPackInstallTask::extractFinished() void Technic::SolderPackInstallTask::extractAborted() { emitFailed(tr("Instance import has been aborted.")); - return; } - From b6290ac254f5d5e5cac704aedbdf2e00f1c28f43 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 27 Dec 2021 03:52:54 +0000 Subject: [PATCH 17/54] GH-3516 Display available versions for Solder packs --- .../technic/SolderPackInstallTask.cpp | 40 +----- .../technic/SolderPackInstallTask.h | 5 +- .../pages/modplatform/technic/TechnicData.h | 6 + .../pages/modplatform/technic/TechnicPage.cpp | 125 ++++++++++++++++-- .../pages/modplatform/technic/TechnicPage.h | 10 ++ .../pages/modplatform/technic/TechnicPage.ui | 97 +++++++------- 6 files changed, 191 insertions(+), 92 deletions(-) diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 673f93e0..d2babf84 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -1,5 +1,5 @@ /* Copyright 2013-2021 MultiMC Contributors - * Copyright 2022 Jamie Mansfield + * Copyright 2021-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,12 @@ Technic::SolderPackInstallTask::SolderPackInstallTask( shared_qobject_ptr network, const QUrl &sourceUrl, + const QString &version, const QString &minecraftVersion ) { m_sourceUrl = sourceUrl; m_minecraftVersion = minecraftVersion; + m_version = version; m_network = network; } @@ -42,43 +44,13 @@ bool Technic::SolderPackInstallTask::abort() { } void Technic::SolderPackInstallTask::executeTask() -{ - setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString())); - m_filesNetJob = new NetJob(tr("Finding recommended version"), m_network); - m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded); - connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); - m_filesNetJob->start(); -} - -void Technic::SolderPackInstallTask::versionSucceeded() { setStatus(tr("Resolving modpack files")); - QJsonParseError parse_error {}; - QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << m_response; - return; - } - auto obj = doc.object(); - - TechnicSolder::Pack pack; - try { - TechnicSolder::loadPack(pack, obj); - } - catch (const JSONValidationError& e) { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - m_filesNetJob.reset(); - return; - } - - m_sourceUrl = m_sourceUrl.toString() + '/' + pack.recommended; - m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network); - m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response)); + auto sourceUrl = QString("%1/%2").arg(m_sourceUrl.toString(), m_version); + m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response)); + auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); diff --git a/launcher/modplatform/technic/SolderPackInstallTask.h b/launcher/modplatform/technic/SolderPackInstallTask.h index 9b2058d8..21596783 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.h +++ b/launcher/modplatform/technic/SolderPackInstallTask.h @@ -1,4 +1,5 @@ /* Copyright 2013-2021 MultiMC Contributors + * Copyright 2021 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +28,7 @@ namespace Technic { Q_OBJECT public: - explicit SolderPackInstallTask(shared_qobject_ptr network, const QUrl &sourceUrl, const QString &minecraftVersion); + explicit SolderPackInstallTask(shared_qobject_ptr network, const QUrl &sourceUrl, const QString& version, const QString &minecraftVersion); bool canAbort() const override { return true; } bool abort() override; @@ -37,7 +38,6 @@ namespace Technic virtual void executeTask() override; private slots: - void versionSucceeded(); void fileListSucceeded(); void downloadSucceeded(); void downloadFailed(QString reason); @@ -52,6 +52,7 @@ namespace Technic NetJob::Ptr m_filesNetJob; QUrl m_sourceUrl; + QString m_version; QString m_minecraftVersion; QByteArray m_response; QTemporaryDir m_outputDir; diff --git a/launcher/ui/pages/modplatform/technic/TechnicData.h b/launcher/ui/pages/modplatform/technic/TechnicData.h index 4a374a6b..e92cda17 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicData.h +++ b/launcher/ui/pages/modplatform/technic/TechnicData.h @@ -1,4 +1,5 @@ /* Copyright 2020-2021 MultiMC Contributors + * Copyright 2021-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ #include #include +#include namespace Technic { struct Modpack { @@ -37,6 +39,10 @@ struct Modpack { QString author; QString description; QString currentVersion; + + bool versionsLoaded = false; + QString recommended; + QVector versions; }; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 59d78bfc..bfeb3690 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -1,4 +1,5 @@ /* Copyright 2013-2022 MultiMC Contributors + * Copyright 2021-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" +#include "modplatform/technic/SolderPackManifest.h" #include "Json.h" #include "Application.h" @@ -36,7 +38,9 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) ui->searchEdit->installEventFilter(this); model = new Technic::ListModel(this); ui->packView->setModel(model); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); } bool TechnicPage::eventFilter(QObject* watched, QEvent* event) @@ -74,13 +78,14 @@ void TechnicPage::triggerSearch() { void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second) { + ui->versionSelectionBox->clear(); + if(!first.isValid()) { if(isOpened) { dialog->setSuggestedPack(); } - //ui->frame->clear(); return; } @@ -113,17 +118,19 @@ void TechnicPage::suggestCurrent() } NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); - std::shared_ptr response = std::make_shared(); QString slug = current.slug; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get())); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug] + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), &response)); + QObject::connect(netJob, &NetJob::succeeded, this, [this, slug] { + jobPtr.reset(); + if (current.slug != slug) { return; } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + + QJsonParseError parse_error {}; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonObject obj = doc.object(); if(parse_error.error != QJsonParseError::NoError) { @@ -167,9 +174,12 @@ void TechnicPage::suggestCurrent() current.description = Json::ensureString(obj, "description", QString(), "__placeholder__"); current.currentVersion = Json::ensureString(obj, "version", QString(), "__placeholder__"); current.metadataLoaded = true; + metadataLoaded(); }); - netJob->start(); + + jobPtr = netJob; + jobPtr->start(); } // expects current.metadataLoaded to be true @@ -190,13 +200,108 @@ void TechnicPage::metadataLoaded() text += "

"; ui->packDescription->setHtml(text + current.description); + + // Strip trailing forward-slashes from Solder URL's + if (current.isSolder) { + while (current.url.endsWith('/')) current.url.chop(1); + } + + // Display versions from Solder + if (!current.isSolder) { + // If the pack isn't a Solder pack, it only has the single version + ui->versionSelectionBox->addItem(current.currentVersion); + } + else if (current.versionsLoaded) { + // reverse foreach, so that the newest versions are first + for (auto i = current.versions.size(); i--;) { + ui->versionSelectionBox->addItem(current.versions.at(i)); + } + ui->versionSelectionBox->setCurrentText(current.recommended); + } + else { + // For now, until the versions are pulled from the Solder instance, display the current + // version so we can display something quicker + ui->versionSelectionBox->addItem(current.currentVersion); + + auto* netJob = new NetJob(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); + auto url = QString("%1/modpack/%2").arg(current.url, current.slug); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); + + QObject::connect(netJob, &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); + + jobPtr = netJob; + jobPtr->start(); + } + + selectVersion(); +} + +void TechnicPage::selectVersion() { + if (!isOpened) { + return; + } + if (current.broken) { + dialog->setSuggestedPack(); + return; + } + if (!current.isSolder) { - dialog->setSuggestedPack(current.name + " " + current.currentVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); } else { - while (current.url.endsWith('/')) current.url.chop(1); - dialog->setSuggestedPack(current.name + " " + current.currentVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, selectedVersion, current.minecraftVersion)); } } + +void TechnicPage::onSolderLoaded() { + jobPtr.reset(); + + auto fallback = [this]() { + current.versionsLoaded = true; + + current.versions.clear(); + current.versions.append(current.currentVersion); + }; + + current.versions.clear(); + + QJsonParseError parse_error {}; + auto doc = QJsonDocument::fromJson(response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + fallback(); + return; + } + auto obj = doc.object(); + + TechnicSolder::Pack pack; + try { + TechnicSolder::loadPack(pack, obj); + } + catch (const JSONValidationError &err) { + qCritical() << "Couldn't parse Solder pack metadata:" << err.cause(); + fallback(); + return; + } + + current.versionsLoaded = true; + current.recommended = pack.recommended; + current.versions.append(pack.builds); + + // Finally, let's reload :) + ui->versionSelectionBox->clear(); + metadataLoaded(); +} + +void TechnicPage::onVersionSelectionChanged(QString data) { + if (data.isNull() || data.isEmpty()) { + selectedVersion = ""; + return; + } + + selectedVersion = data; + selectVersion(); +} diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h index 21695dd0..920ca009 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.h +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h @@ -1,4 +1,5 @@ /* Copyright 2013-2021 MultiMC Contributors + * Copyright 2021-2022 Jamie Mansfield * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +20,7 @@ #include "ui/pages/BasePage.h" #include +#include "net/NetJob.h" #include "tasks/Task.h" #include "TechnicData.h" @@ -65,14 +67,22 @@ public: private: void suggestCurrent(); void metadataLoaded(); + void selectVersion(); private slots: void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); + void onSolderLoaded(); + void onVersionSelectionChanged(QString data); private: Ui::TechnicPage *ui = nullptr; NewInstanceDialog* dialog = nullptr; Technic::ListModel* model = nullptr; + Technic::Modpack current; + QString selectedVersion; + + NetJob::Ptr jobPtr; + QByteArray response; }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index ab6b4255..aa2df370 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -10,52 +10,44 @@ 405 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Search and filter ... - - - - - - - Search - - - - - - - - - - 0 - + + + + + + - + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + - - - Qt::ScrollBarAlwaysOff + + + Qt::Horizontal + + QSizePolicy::Preferred + + + + 1 + 1 + + + + + + + + + + true @@ -67,14 +59,27 @@ + + + + + + + Search and filter ... + + + + + + + Search + + + - - searchEdit - searchButton - From fb0970b4963b8d0aacaa92f5c372f4c3ac3be0d6 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 2 Apr 2022 00:11:33 +0100 Subject: [PATCH 18/54] NOISSUE Verify checksums for pack build mods --- launcher/modplatform/technic/SolderPackInstallTask.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index d2babf84..ccd0b921 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -22,6 +22,7 @@ #include #include "TechnicPackProcessor.h" #include "SolderPackManifest.h" +#include "net/ChecksumValidator.h" Technic::SolderPackInstallTask::SolderPackInstallTask( shared_qobject_ptr network, @@ -88,7 +89,14 @@ void Technic::SolderPackInstallTask::fileListSucceeded() for (const auto &mod : build.mods) { auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); - m_filesNetJob->addNetAction(Net::Download::makeFile(mod.url, path)); + + auto dl = Net::Download::makeFile(mod.url, path); + if (!mod.md5.isEmpty()) { + auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); + } + m_filesNetJob->addNetAction(dl); + i++; } From 9bdfa5c8de31879676578a137659650f27156126 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 9 Jan 2022 23:02:32 +0000 Subject: [PATCH 19/54] NOISSUE Make Technic API base URL and build constants --- buildconfig/BuildConfig.h | 6 ++++++ .../ui/pages/modplatform/technic/TechnicModel.cpp | 13 ++++++++----- .../ui/pages/modplatform/technic/TechnicPage.cpp | 3 ++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 903827ea..7ab5b100 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -100,6 +100,12 @@ public: QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/"; + QString TECHNIC_API_BASE_URL = "https://api.technicpack.net/"; + /** + * The build that is reported to the Technic API. + */ + QString TECHNIC_API_BUILD = "multimc"; + /** * \brief Converts the Version to a string. * \return The version number in string format (major.minor.revision.build). diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index c62d36a7..b0128b98 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -16,6 +16,7 @@ #include "TechnicModel.h" #include "Application.h" +#include "BuildConfig.h" #include "Json.h" #include @@ -95,21 +96,23 @@ void Technic::ListModel::performSearch() NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network()); QString searchUrl = ""; if (currentSearchTerm.isEmpty()) { - searchUrl = "https://api.technicpack.net/trending?build=multimc"; + searchUrl = QString("%1trending?build=%2") + .arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD); searchMode = List; } else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) { - searchUrl = QString("https://%1?build=multimc").arg(currentSearchTerm.mid(7)); + searchUrl = QString("https://%1?build=%2") + .arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD); searchMode = Single; } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { - searchUrl = QString("%1?build=multimc").arg(currentSearchTerm); + searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); searchMode = Single; } else { searchUrl = QString( - "https://api.technicpack.net/search?build=multimc&q=%1" - ).arg(currentSearchTerm); + "%1search?build=%2&q=%3" + ).arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); searchMode = List; } netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index bfeb3690..bfbe1639 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -22,6 +22,7 @@ #include "ui/dialogs/NewInstanceDialog.h" +#include "BuildConfig.h" #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" @@ -119,7 +120,7 @@ void TechnicPage::suggestCurrent() NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); QString slug = current.slug; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), &response)); + netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response)); QObject::connect(netJob, &NetJob::succeeded, this, [this, slug] { jobPtr.reset(); From b82d6678594a105b1d33bf50ccfa9607f460ee9a Mon Sep 17 00:00:00 2001 From: arthomnix Date: Wed, 2 Nov 2022 16:11:56 +0000 Subject: [PATCH 20/54] NOISSUE Add setting to display playtime in hours only --- launcher/Application.cpp | 1 + launcher/minecraft/MinecraftInstance.cpp | 12 ++++++++++-- launcher/ui/MainWindow.cpp | 6 +++++- launcher/ui/pages/global/MinecraftPage.cpp | 3 ++- launcher/ui/pages/global/MinecraftPage.ui | 7 +++++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index dc3b9a75..475b05a1 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -718,6 +718,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("ShowGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true); m_settings->registerSetting("RecordGameTime", true); + m_settings->registerSetting("ShowGameTimeHours", false); // Minecraft launch method m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ae0fbd31..3769f826 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -785,11 +785,19 @@ QString MinecraftInstance::getStatusbarDescription() if(m_settings->get("ShowGameTime").toBool()) { if (lastTimePlayed() > 0) { - description.append(tr(", last played for %1").arg(Time::prettifyDuration(lastTimePlayed()))); + if (APPLICATION->settings()->get("ShowGameTimeHours").toBool()) { + description.append(tr(", last played for %1 hours").arg(lastTimePlayed() / 3600.0, 0, 'f', 2)); + } else { + description.append(tr(", last played for %1").arg(Time::prettifyDuration(lastTimePlayed()))); + } } if (totalTimePlayed() > 0) { - description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed()))); + if (APPLICATION->settings()->get("ShowGameTimeHours").toBool()) { + description.append(tr(", total played for %1 hours").arg(totalTimePlayed() / 3600.0, 0, 'f', 1)); + } else { + description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed()))); + } } } if(hasCrashed()) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 139ca780..0a1eba4e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2006,6 +2006,10 @@ void MainWindow::updateStatusCenter() int timePlayed = APPLICATION->instances()->getTotalPlayTime(); if (timePlayed > 0) { - m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); + if (APPLICATION->settings()->get("ShowGameTimeHours").toBool()) { + m_statusCenter->setText(tr("Total playtime: %1 hours").arg(timePlayed / 3600.0, 0, 'f', 1)); + } else { + m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); + } } } diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index c763f8ac..3815d912 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2022 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,7 @@ void MinecraftPage::applySettings() s->set("ShowGameTime", ui->showGameTime->isChecked()); s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); s->set("RecordGameTime", ui->recordGameTime->isChecked()); + s->set("ShowGameTimeHours", ui->showGameTimeHours->isChecked()); } void MinecraftPage::loadSettings() diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 857b8cfb..7a5137d8 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -161,6 +161,13 @@ + + + + Show time spent playing in hours only + + + From 1849db93ec9717accde790fef97997d9dde73258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 19 Nov 2022 23:23:11 +0100 Subject: [PATCH 21/54] Fix a build problem --- launcher/modplatform/atlauncher/ATLPackManifest.h | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 3a5d4240..147d2fbe 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -18,6 +18,7 @@ #include #include +#include #include namespace ATLauncher From 7354c578fd5f80502cb1dbc48aa05f65d0bb1c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 20 Nov 2022 00:00:44 +0100 Subject: [PATCH 22/54] NOISSUE Move hour formatting for play time to a function --- launcher/MMCTime.cpp | 4 ++++ launcher/MMCTime.h | 1 + launcher/minecraft/MinecraftInstance.cpp | 4 ++-- launcher/ui/MainWindow.cpp | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/launcher/MMCTime.cpp b/launcher/MMCTime.cpp index 4d7f424d..387ecf6d 100644 --- a/launcher/MMCTime.cpp +++ b/launcher/MMCTime.cpp @@ -36,3 +36,7 @@ QString Time::prettifyDuration(int64_t duration) { } return QObject::tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); } + +QString Time::prettifyDurationHours(int64_t duration) { + return QString("%1").arg(duration / 3600.0, 0, 'f', 0); +} diff --git a/launcher/MMCTime.h b/launcher/MMCTime.h index 10ff2ffe..ae1fa9a8 100644 --- a/launcher/MMCTime.h +++ b/launcher/MMCTime.h @@ -21,5 +21,6 @@ namespace Time { QString prettifyDuration(int64_t duration); +QString prettifyDurationHours(int64_t duration); } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 3769f826..dbb9c98f 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -786,7 +786,7 @@ QString MinecraftInstance::getStatusbarDescription() { if (lastTimePlayed() > 0) { if (APPLICATION->settings()->get("ShowGameTimeHours").toBool()) { - description.append(tr(", last played for %1 hours").arg(lastTimePlayed() / 3600.0, 0, 'f', 2)); + description.append(tr(", last played for %1 hours").arg(Time::prettifyDurationHours(lastTimePlayed()))); } else { description.append(tr(", last played for %1").arg(Time::prettifyDuration(lastTimePlayed()))); } @@ -794,7 +794,7 @@ QString MinecraftInstance::getStatusbarDescription() if (totalTimePlayed() > 0) { if (APPLICATION->settings()->get("ShowGameTimeHours").toBool()) { - description.append(tr(", total played for %1 hours").arg(totalTimePlayed() / 3600.0, 0, 'f', 1)); + description.append(tr(", total played for %1 hours").arg(Time::prettifyDurationHours(totalTimePlayed()))); } else { description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed()))); } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 0a1eba4e..a3f31f5f 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -2007,7 +2007,7 @@ void MainWindow::updateStatusCenter() int timePlayed = APPLICATION->instances()->getTotalPlayTime(); if (timePlayed > 0) { if (APPLICATION->settings()->get("ShowGameTimeHours").toBool()) { - m_statusCenter->setText(tr("Total playtime: %1 hours").arg(timePlayed / 3600.0, 0, 'f', 1)); + m_statusCenter->setText(tr("Total playtime: %1 hours").arg(Time::prettifyDurationHours(timePlayed))); } else { m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed))); } From 86f68389c9056279c198be3ab0854f1a607dc24f Mon Sep 17 00:00:00 2001 From: arthomnix Date: Wed, 20 Jul 2022 16:55:31 +0100 Subject: [PATCH 23/54] NOISSUE Add button to copy MSA code --- launcher/ui/dialogs/MSALoginDialog.cpp | 14 ++++++++++++- launcher/ui/dialogs/MSALoginDialog.h | 2 ++ launcher/ui/dialogs/MSALoginDialog.ui | 27 ++++++++++++++++++-------- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index f46aa3b9..41aac720 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2022 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ #include #include +#include MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MSALoginDialog) { @@ -34,6 +35,7 @@ MSALoginDialog::MSALoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MS int MSALoginDialog::exec() { setUserInputsEnabled(false); ui->progressBar->setVisible(true); + ui->copyCodeButton->setVisible(false); // Setup the login task and start it m_account = MinecraftAccount::createBlankMSA(); @@ -68,6 +70,8 @@ void MSALoginDialog::externalLoginTick() { void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn) { + ui->copyCodeButton->setVisible(true); + m_externalLoginElapsed = 0; m_externalLoginTimeout = expiresIn; @@ -81,9 +85,12 @@ 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)); + + m_code = code; } void MSALoginDialog::hideVerificationUriAndCode() { + ui->copyCodeButton->setVisible(false); m_externalLoginTimer.stop(); } @@ -139,3 +146,8 @@ MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg) } return 0; } + +void MSALoginDialog::on_copyCodeButton_clicked() +{ + QApplication::clipboard()->setText(m_code); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index 4cf146ab..963f550b 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -49,6 +49,7 @@ slots: void onTaskProgress(qint64 current, qint64 total); void showVerificationUriAndCode(const QUrl &uri, const QString &code, int expiresIn); void hideVerificationUriAndCode(); + void on_copyCodeButton_clicked(); void externalLoginTick(); @@ -57,6 +58,7 @@ private: MinecraftAccountPtr m_account; shared_qobject_ptr m_loginTask; QTimer m_externalLoginTimer; + QString m_code; int m_externalLoginElapsed = 0; int m_externalLoginTimeout = 0; }; diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index 78cbfb26..0921e38a 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -49,14 +49,25 @@ aaaaa - - - Qt::Horizontal - - - QDialogButtonBox::Cancel - - + + + + + Copy Code + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + From 6307689cf1851a398149c64fdb3364b2549235a3 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Wed, 20 Jul 2022 17:03:20 +0100 Subject: [PATCH 24/54] NOISSUE Use .command extension for shortcut scripts on macOS This means that the script will run when clicked, instead of being opened in a text editor --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 85397fea..2f8c49cf 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -60,9 +60,12 @@ CreateShortcutDialog::~CreateShortcutDialog() void CreateShortcutDialog::on_shortcutPathBrowse_clicked() { QString linkExtension; -#ifdef Q_OS_UNIX +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) linkExtension = ui->createScriptCheckBox->isChecked() ? "sh" : "desktop"; #endif +#ifdef Q_OS_MAC + linkExtension = "command"; +#endif #ifdef Q_OS_WIN linkExtension = ui->createScriptCheckBox->isChecked() ? "bat" : "lnk"; #endif From 08dd08afc1918a1ac7e6482fb977bde091056f94 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sat, 30 Jul 2022 16:43:30 +0100 Subject: [PATCH 25/54] NOISSUE Enclose all arguments in quotes, fix batch scripts --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index 2f8c49cf..a5366835 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -116,11 +116,11 @@ QString CreateShortcutDialog::getLaunchCommand() QString CreateShortcutDialog::getLaunchArgs() { return " -d \"" + QDir::toNativeSeparators(QDir::currentPath()) + "\"" - + " -l " + m_instance->id() - + (ui->joinServerCheckBox->isChecked() ? " -s " + ui->joinServer->text() : "") - + (ui->useProfileCheckBox->isChecked() ? " -a " + ui->profileComboBox->currentText() : "") + + " -l \"" + m_instance->id() + "\"" + + (ui->joinServerCheckBox->isChecked() ? " -s \"" + ui->joinServer->text() + "\"" : "") + + (ui->useProfileCheckBox->isChecked() ? " -a \"" + ui->profileComboBox->currentText() + "\"" : "") + (ui->launchOfflineCheckBox->isChecked() ? " -o" : "") - + (ui->offlineUsernameCheckBox->isChecked() ? " -n " + ui->offlineUsername->text() : ""); + + (ui->offlineUsernameCheckBox->isChecked() ? " -n \"" + ui->offlineUsername->text() + "\"" : ""); } void CreateShortcutDialog::createShortcut() @@ -162,7 +162,7 @@ void CreateShortcutDialog::createShortcut() // Windows batch script implementation shortcutText = "@ECHO OFF\r\n" "CD \"" + QDir::toNativeSeparators(QDir::currentPath()) + "\"\r\n" - "START /B " + getLaunchCommand() + "\r\n"; + "START /B \"\" " + getLaunchCommand() + "\r\n"; #endif QFile shortcutFile(ui->shortcutPath->text()); if (shortcutFile.open(QIODevice::WriteOnly)) From 858487521e5bce60b6dc8fd6436cf052779ac31f Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 31 Jul 2022 11:38:00 +0100 Subject: [PATCH 26/54] NOISSUE Escape quotes in paths Just in case the user decides to place MMC in a path containing quotes. .desktop files appear to require two backslashes to escape quotes, testing on other desktop environments would be appreciated to make sure this isn't just a KDE-specific bug --- launcher/ui/dialogs/CreateShortcutDialog.cpp | 14 +++++++------- launcher/ui/dialogs/CreateShortcutDialog.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/ui/dialogs/CreateShortcutDialog.cpp b/launcher/ui/dialogs/CreateShortcutDialog.cpp index a5366835..6eb5e78e 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.cpp +++ b/launcher/ui/dialogs/CreateShortcutDialog.cpp @@ -107,15 +107,15 @@ void CreateShortcutDialog::updateDialogState() } } -QString CreateShortcutDialog::getLaunchCommand() +QString CreateShortcutDialog::getLaunchCommand(bool escapeQuotesTwice) { - return "\"" + QDir::toNativeSeparators(QCoreApplication::applicationFilePath()) + "\"" - + getLaunchArgs(); + return "\"" + QDir::toNativeSeparators(QCoreApplication::applicationFilePath()).replace('"', escapeQuotesTwice ? "\\\\\"" : "\\\"") + "\"" + + getLaunchArgs(escapeQuotesTwice); } -QString CreateShortcutDialog::getLaunchArgs() +QString CreateShortcutDialog::getLaunchArgs(bool escapeQuotesTwice) { - return " -d \"" + QDir::toNativeSeparators(QDir::currentPath()) + "\"" + return " -d \"" + QDir::toNativeSeparators(QDir::currentPath()).replace('"', escapeQuotesTwice ? "\\\\\"" : "\\\"") + "\"" + " -l \"" + m_instance->id() + "\"" + (ui->joinServerCheckBox->isChecked() ? " -s \"" + ui->joinServer->text() + "\"" : "") + (ui->useProfileCheckBox->isChecked() ? " -a \"" + ui->profileComboBox->currentText() + "\"" : "") @@ -137,7 +137,7 @@ void CreateShortcutDialog::createShortcut() { shortcutText = "#!/bin/sh\n" // FIXME: is there a way to use the launcher script instead of the raw binary here? - "cd \"" + QDir::currentPath() + "\"\n" + "cd \"" + QDir::currentPath().replace('"', "\\\"") + "\"\n" + getLaunchCommand() + " &\n"; } else // freedesktop.org desktop entry @@ -152,7 +152,7 @@ void CreateShortcutDialog::createShortcut() shortcutText = "[Desktop Entry]\n" "Type=Application\n" "Name=" + m_instance->name() + " - " + BuildConfig.LAUNCHER_DISPLAYNAME + "\n" - + "Exec=" + getLaunchCommand() + "\n" + + "Exec=" + getLaunchCommand(true) + "\n" + "Path=" + QDir::currentPath() + "\n" + "Icon=" + QDir::currentPath() + "/icons/shortcut-icon.png\n"; diff --git a/launcher/ui/dialogs/CreateShortcutDialog.h b/launcher/ui/dialogs/CreateShortcutDialog.h index a2497dd6..4714253b 100644 --- a/launcher/ui/dialogs/CreateShortcutDialog.h +++ b/launcher/ui/dialogs/CreateShortcutDialog.h @@ -39,8 +39,8 @@ private: Ui::CreateShortcutDialog *ui; InstancePtr m_instance; - QString getLaunchCommand(); - QString getLaunchArgs(); + QString getLaunchCommand(bool escapeQuotesTwice = false); + QString getLaunchArgs(bool escapeQuotesTwice = false); void createShortcut(); From 30312ea701cee76865bcfdb67c493df6c3a80eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 20 Nov 2022 00:12:46 +0100 Subject: [PATCH 27/54] And fix a build issue --- launcher/ui/pages/modplatform/technic/TechnicPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index bfbe1639..d05eadfa 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -290,7 +290,7 @@ void TechnicPage::onSolderLoaded() { current.versionsLoaded = true; current.recommended = pack.recommended; - current.versions.append(pack.builds); + current.versions << pack.builds; // Finally, let's reload :) ui->versionSelectionBox->clear(); From 149adbd1d6cc3e4611fa00c3061d3ba9b83abf31 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 20 Nov 2022 11:45:34 +0000 Subject: [PATCH 28/54] NOISSUE Load ShowGameTimeHours setting correctly on settings page Bugfix for #4964 --- launcher/ui/pages/global/MinecraftPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index 3815d912..8491e988 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -89,4 +89,5 @@ void MinecraftPage::loadSettings() ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); + ui->showGameTimeHours->setChecked(s->get("ShowGameTimeHours").toBool()); } From e044744fafe4d0c088446b7cd57ae788d5e3a645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 20 Nov 2022 15:59:05 +0100 Subject: [PATCH 29/54] NOISSUE add a way to require object from Json value ref --- launcher/Json.cpp | 8 ++++++++ launcher/Json.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/launcher/Json.cpp b/launcher/Json.cpp index 37ada1aa..8f6908d3 100644 --- a/launcher/Json.cpp +++ b/launcher/Json.cpp @@ -78,6 +78,14 @@ QJsonObject requireObject(const QJsonDocument &doc, const QString &what) } return doc.object(); } +QJsonObject requireObject(const QJsonValueRef &node, const QString &what) +{ + if (!node.isObject()) + { + throw JsonException(what + " is not an object"); + } + return node.toObject(); +} QJsonArray requireArray(const QJsonDocument &doc, const QString &what) { if (!doc.isArray()) diff --git a/launcher/Json.h b/launcher/Json.h index dd70bf56..359f4757 100644 --- a/launcher/Json.h +++ b/launcher/Json.h @@ -41,6 +41,8 @@ QJsonDocument requireDocument(const QString &filename, const QString &what = "Do /// @throw JsonException QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document"); /// @throw JsonException +QJsonObject requireObject(const QJsonValueRef &node, const QString &what = "Node"); +/// @throw JsonException QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document"); /////////////////// WRITING //////////////////// From 0fd546a11c9c299bf214a59d74a629314b2f5747 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Thu, 1 Dec 2022 20:21:37 +0000 Subject: [PATCH 30/54] GH-4978 Add button to log back in when account expired --- launcher/LaunchController.cpp | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 17f6400b..47faf9e0 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -9,6 +9,8 @@ #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ProfileSetupDialog.h" +#include "ui/dialogs/LoginDialog.h" +#include "ui/dialogs/MSALoginDialog.h" #include #include @@ -119,7 +121,8 @@ void LaunchController::login() { m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); - switch(m_accountToUse->accountState()) { + //switch(m_accountToUse->accountState()) { + switch (AccountState::Expired) { case AccountState::Offline: { m_session->wants_online = false; // NOTE: fallthrough is intentional @@ -223,16 +226,57 @@ void LaunchController::login() { } */ case AccountState::Expired: { - auto errorString = tr("The account has expired and needs to be logged into manually again."); - QMessageBox::warning( + auto errorString = tr("The account has expired and needs to be logged into manually. Press OK to log in again."); + auto button = QMessageBox::warning( m_parentWidget, tr("Account refresh failed"), errorString, - QMessageBox::StandardButton::Ok, + QMessageBox::StandardButton::Ok | QMessageBox::StandardButton::Cancel, QMessageBox::StandardButton::Ok ); - emitFailed(errorString); - return; + if (button == QMessageBox::StandardButton::Ok) { + auto accounts = APPLICATION->accounts(); + bool isDefault = accounts->defaultAccount() == m_accountToUse; + bool msa = m_accountToUse->isMSA(); + accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId()))); + MinecraftAccountPtr newAccount = nullptr; + if (msa) { + if(BuildConfig.BUILD_PLATFORM == "osx64") { + CustomMessageBox::selectable( + m_parentWidget, + tr("Microsoft Accounts not available"), + tr( + "Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated MultiMC.\n\n" + "Please update both your operating system and MultiMC." + ), + QMessageBox::Warning + )->exec(); + return; + } + newAccount = MSALoginDialog::newAccount( + m_parentWidget, + tr("Please enter your Mojang account email and password to add your account.") + ); + } else { + newAccount = LoginDialog::newAccount( + m_parentWidget, + tr("Please enter your Mojang account email and password to add your account.") + ); + } + if (newAccount) { + accounts->addAccount(newAccount); + if (isDefault) { + accounts->setDefaultAccount(newAccount); + } + m_accountToUse = newAccount; + } else { + emitFailed(tr("Account expired and re-login attempt failed")); + return; + } + } else { + emitFailed(errorString); + return; + } } case AccountState::Gone: { auto errorString = tr("The account no longer exists on the servers. It may have been migrated, in which case please add the new account you migrated this one to."); From e5b7517d2fe89c3c59d4bc47f2f22c4ac4a0f4e1 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Thu, 1 Dec 2022 20:50:36 +0000 Subject: [PATCH 31/54] GH-4978 Remove testing code --- launcher/LaunchController.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 47faf9e0..3852b5fe 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -121,8 +121,7 @@ void LaunchController::login() { m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); - //switch(m_accountToUse->accountState()) { - switch (AccountState::Expired) { + switch(m_accountToUse->accountState()) { case AccountState::Offline: { m_session->wants_online = false; // NOTE: fallthrough is intentional @@ -268,7 +267,8 @@ void LaunchController::login() { if (isDefault) { accounts->setDefaultAccount(newAccount); } - m_accountToUse = newAccount; + decideAccount(); + continue; } else { emitFailed(tr("Account expired and re-login attempt failed")); return; From 73e1d15ab9ea79bbb91f2e7acc36d88ca2c1f204 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Thu, 1 Dec 2022 20:54:04 +0000 Subject: [PATCH 32/54] GH-4978 Correctly emit failed when attempting to relogin to msa on osx64 --- launcher/LaunchController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 3852b5fe..ee4ead01 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -250,6 +250,7 @@ void LaunchController::login() { ), QMessageBox::Warning )->exec(); + emitFailed(tr("Attempted to re-login to a Microsoft account on an unsupported platform")); return; } newAccount = MSALoginDialog::newAccount( From cb81cb9835addcbb41b973bb7a29c614ef698744 Mon Sep 17 00:00:00 2001 From: graywolf <57069542+graytoowolf@users.noreply.github.com> Date: Fri, 6 Jan 2023 12:59:45 +0800 Subject: [PATCH 33/54] Update LaunchController.cpp --- launcher/LaunchController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index ee4ead01..32c5f49b 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -268,6 +268,7 @@ void LaunchController::login() { if (isDefault) { accounts->setDefaultAccount(newAccount); } + m_accountToUse = nullptr; decideAccount(); continue; } else { From f624754f0cb51fc8a46e5508d2afb42de2396ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 2 Feb 2023 20:51:23 +0100 Subject: [PATCH 34/54] NOISSUE Improve path parsing for modrinth packs --- launcher/InstanceImportTask.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 1cdcb4a6..e1553f06 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -311,7 +311,13 @@ void InstanceImportTask::processModrinth() { auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); for(auto & obj: jsonFiles) { Modrinth::File file; - file.path = Json::requireString(obj, "path"); + auto dirtyPath = Json::requireString(obj, "path"); + dirtyPath.replace('\\', '/'); + auto simplifiedPath = QDir::cleanPath(dirtyPath); + QFileInfo fileInfo (simplifiedPath); + if(simplifiedPath.startsWith("../") || simplifiedPath.contains("/../") || fileInfo.isAbsolute()) { + throw JSONValidationError("Invalid path found in modpack files:\n\n" + simplifiedPath); + } // env doesn't have to be present, in that case mod is required auto env = Json::ensureObject(obj, "env"); From 75568ed04b3491e582692278b5bea8419e7b995d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 2 Feb 2023 21:43:01 +0100 Subject: [PATCH 35/54] NOISSUE and don't break the thing in the process... --- launcher/InstanceImportTask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index e1553f06..a90166fa 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -318,6 +318,7 @@ void InstanceImportTask::processModrinth() { if(simplifiedPath.startsWith("../") || simplifiedPath.contains("/../") || fileInfo.isAbsolute()) { throw JSONValidationError("Invalid path found in modpack files:\n\n" + simplifiedPath); } + file.path = simplifiedPath; // env doesn't have to be present, in that case mod is required auto env = Json::ensureObject(obj, "env"); From 458944ad91c766c79a2e221e18d6524d7602fe7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 3 Feb 2023 23:05:27 +0100 Subject: [PATCH 36/54] NOISSUE Remove concept of switching update channels It is all develop from now on, we no longer make stable releases. This means no maintenance of version numbers and removal of all the overhead associated with making stable releases. MultiMC 6 might have a better system, but with how infrequent and stable MultiMC releases are getting, there's no need to have a distinction between `stable` and `develop` anymore. --- buildconfig/BuildConfig.cpp.in | 2 +- launcher/Application.cpp | 3 +- .../notifications/NotificationChecker.cpp | 7 +- launcher/notifications/NotificationChecker.h | 1 - launcher/ui/MainWindow.cpp | 5 +- launcher/ui/dialogs/UpdateDialog.cpp | 21 +---- launcher/ui/pages/global/LauncherPage.cpp | 91 +------------------ launcher/ui/pages/global/LauncherPage.h | 17 ---- launcher/ui/pages/global/LauncherPage.ui | 28 ------ launcher/updater/UpdateChecker.cpp | 20 ++-- launcher/updater/UpdateChecker.h | 9 +- launcher/updater/UpdateChecker_test.cpp | 15 +-- 12 files changed, 28 insertions(+), 191 deletions(-) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 9e449aec..8c04dd4d 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -57,7 +57,7 @@ QString Config::printableVersionString() const QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX)); // If the build is not a main release, append the channel - if(VERSION_CHANNEL != "stable") + if(VERSION_CHANNEL != "develop") { vstr += "-" + VERSION_CHANNEL; } diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 475b05a1..b471f469 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -627,7 +627,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this)); // Updates - m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL); m_settings->registerSetting("AutoUpdate", true); // Theming @@ -812,7 +811,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM); auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json"; qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl; - m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); + m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_BUILD)); qDebug() << "<> Updater started."; } diff --git a/launcher/notifications/NotificationChecker.cpp b/launcher/notifications/NotificationChecker.cpp index c08bcdcb..d91465c6 100644 --- a/launcher/notifications/NotificationChecker.cpp +++ b/launcher/notifications/NotificationChecker.cpp @@ -10,7 +10,7 @@ #include "Application.h" NotificationChecker::NotificationChecker(QObject *parent) - : QObject(parent) + : QObject(parent), m_appVersionChannel("develop") { } @@ -19,11 +19,6 @@ void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) m_notificationsUrl = notificationsUrl; } -void NotificationChecker::setApplicationChannel(QString channel) -{ - m_appVersionChannel = channel; -} - void NotificationChecker::setApplicationFullVersion(QString version) { m_appFullVersion = version; diff --git a/launcher/notifications/NotificationChecker.h b/launcher/notifications/NotificationChecker.h index 0f305f33..4049e55b 100644 --- a/launcher/notifications/NotificationChecker.h +++ b/launcher/notifications/NotificationChecker.h @@ -14,7 +14,6 @@ public: void setNotificationsUrl(const QUrl ¬ificationsUrl); void setApplicationPlatform(QString platform); - void setApplicationChannel(QString channel); void setApplicationFullVersion(QString version); struct NotificationEntry diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a3f31f5f..44cb504b 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -849,14 +849,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow // if automatic update checks are allowed, start one. if (APPLICATION->settings()->get("AutoUpdate").toBool() && updatesAllowed) { - updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false); + updater->checkForUpdate(false); } } { auto checker = new NotificationChecker(); checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL)); - checker->setApplicationChannel(BuildConfig.VERSION_CHANNEL); checker->setApplicationPlatform(BuildConfig.BUILD_PLATFORM); checker->setApplicationFullVersion(BuildConfig.FULL_VERSION_STR); m_notificationChecker.reset(checker); @@ -1639,7 +1638,7 @@ void MainWindow::checkForUpdates() if(BuildConfig.UPDATER_ENABLED) { auto updater = APPLICATION->updateChecker(); - updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), true); + updater->checkForUpdate(true); } else { diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index c2189c2b..f949ebe6 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -11,14 +11,13 @@ UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog) { ui->setupUi(this); - auto channel = APPLICATION->settings()->get("UpdateChannel").toString(); if(hasUpdate) { - ui->label->setText(tr("A new %1 update is available!").arg(channel)); + ui->label->setText(tr("A new update is available!")); } else { - ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel)); + ui->label->setText(tr("No updates found. You are running the latest version.")); ui->btnUpdateNow->setHidden(true); ui->btnUpdateLater->setText(tr("Close")); } @@ -33,19 +32,10 @@ UpdateDialog::~UpdateDialog() void UpdateDialog::loadChangelog() { - auto channel = APPLICATION->settings()->get("UpdateChannel").toString(); dljob = new NetJob("Changelog", APPLICATION->network()); QString url; - if(channel == "stable") - { - url = QString("https://raw.githubusercontent.com/MultiMC/Launcher/%1/changelog.md").arg(channel); - m_changelogType = CHANGELOG_MARKDOWN; - } - else - { - url = QString("https://api.github.com/repos/MultiMC/Launcher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); - m_changelogType = CHANGELOG_COMMITS; - } + url = QString("https://api.github.com/repos/MultiMC/Launcher/compare/%1...develop").arg(BuildConfig.GIT_COMMIT); + m_changelogType = CHANGELOG_COMMITS; dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded); connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed); @@ -65,7 +55,6 @@ QString reprocessMarkdown(QByteArray markdown) QString reprocessCommits(QByteArray json) { - auto channel = APPLICATION->settings()->get("UpdateChannel").toString(); try { QString result; @@ -119,7 +108,7 @@ QString reprocessCommits(QByteArray json) if(status == "identical") { - return QObject::tr("

There are no code changes between your current version and latest %1.

").arg(channel); + return QObject::tr("

There are no code changes between your current version and the latest.

"); } else if(status == "ahead") { diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 2eb73e44..1f986c19 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -56,23 +56,12 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch m_languageModel = APPLICATION->translations(); loadSettings(); - if(BuildConfig.UPDATER_ENABLED) - { - QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList); - - if (APPLICATION->updateChecker()->hasChannels()) - { - refreshUpdateChannelList(); - } - else - { - APPLICATION->updateChecker()->updateChanList(false); - } - } - else + // Updater + if(!BuildConfig.UPDATER_ENABLED) { ui->updateSettingsBox->setHidden(true); } + // Analytics if(BuildConfig.ANALYTICS_ID.isEmpty()) { @@ -163,78 +152,6 @@ void LauncherPage::on_migrateDataFolderMacBtn_clicked() qApp->quit(); } -void LauncherPage::refreshUpdateChannelList() -{ - // Stop listening for selection changes. It's going to change a lot while we update it and - // we don't need to update the - // description label constantly. - QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(updateChannelSelectionChanged(int))); - - QList channelList = APPLICATION->updateChecker()->getChannelList(); - ui->updateChannelComboBox->clear(); - int selection = -1; - for (int i = 0; i < channelList.count(); i++) - { - UpdateChecker::ChannelListEntry entry = channelList.at(i); - - // When it comes to selection, we'll rely on the indexes of a channel entry being the - // same in the - // combo box as it is in the update checker's channel list. - // This probably isn't very safe, but the channel list doesn't change often enough (or - // at all) for - // this to be a big deal. Hope it doesn't break... - ui->updateChannelComboBox->addItem(entry.name); - - // If the update channel we just added was the selected one, set the current index in - // the combo box to it. - if (entry.id == m_currentUpdateChannel) - { - qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel; - selection = i; - } - } - - ui->updateChannelComboBox->setCurrentIndex(selection); - - // Start listening for selection changes again and update the description label. - QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this, - SLOT(updateChannelSelectionChanged(int))); - refreshUpdateChannelDesc(); - - // Now that we've updated the channel list, we can enable the combo box. - // It starts off disabled so that if the channel list hasn't been loaded, it will be - // disabled. - ui->updateChannelComboBox->setEnabled(true); -} - -void LauncherPage::updateChannelSelectionChanged(int index) -{ - refreshUpdateChannelDesc(); -} - -void LauncherPage::refreshUpdateChannelDesc() -{ - // Get the channel list. - QList channelList = APPLICATION->updateChecker()->getChannelList(); - int selectedIndex = ui->updateChannelComboBox->currentIndex(); - if (selectedIndex < 0) - { - return; - } - if (selectedIndex < channelList.count()) - { - // Find the channel list entry with the given index. - UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex); - - // Set the description text. - ui->updateChannelDescLabel->setText(selected.description); - - // Set the currently selected channel ID. - m_currentUpdateChannel = selected.id; - } -} - void LauncherPage::applySettings() { auto s = APPLICATION->settings(); @@ -246,7 +163,6 @@ void LauncherPage::applySettings() // Updates s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); - s->set("UpdateChannel", m_currentUpdateChannel); auto original = s->get("IconTheme").toString(); //FIXME: make generic switch (ui->themeComboBox->currentIndex()) @@ -333,7 +249,6 @@ void LauncherPage::loadSettings() auto s = APPLICATION->settings(); // Updates ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); - m_currentUpdateChannel = s->get("UpdateChannel").toString(); //FIXME: make generic auto theme = s->get("IconTheme").toString(); if (theme == "pe_dark") diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index 4d0cf3c9..d5ea2353 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -69,31 +69,14 @@ slots: void on_iconsDirBrowseBtn_clicked(); void on_migrateDataFolderMacBtn_clicked(); - /*! - * Updates the list of update channels in the combo box. - */ - void refreshUpdateChannelList(); - - /*! - * Updates the channel description label. - */ - void refreshUpdateChannelDesc(); - /*! * Updates the font preview */ void refreshFontPreview(); - void updateChannelSelectionChanged(int index); - private: Ui::LauncherPage *ui; - /*! - * Stores the currently selected update channel. - */ - QString m_currentUpdateChannel; - // default format for the font preview... QTextCharFormat *defaultFormat; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 62a66d73..d1728fed 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -58,33 +58,6 @@ - - - - Up&date Channel: - - - updateChannelComboBox - - - - - - - false - - - - - - - No channel selected. - - - true - - - @@ -555,7 +528,6 @@ tabWidget autoUpdateCheckBox - updateChannelComboBox instDirTextBox instDirBrowseBtn modsDirTextBox diff --git a/launcher/updater/UpdateChecker.cpp b/launcher/updater/UpdateChecker.cpp index efdb6093..c0505908 100644 --- a/launcher/updater/UpdateChecker.cpp +++ b/launcher/updater/UpdateChecker.cpp @@ -26,11 +26,11 @@ #include "BuildConfig.h" #include "sys.h" -UpdateChecker::UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel, int currentBuild) +UpdateChecker::UpdateChecker(shared_qobject_ptr nam, QString channelUrl, int currentBuild) { m_network = nam; m_channelUrl = channelUrl; - m_currentChannel = currentChannel; + m_currentChannel = "develop"; m_currentBuild = currentBuild; } @@ -44,9 +44,10 @@ bool UpdateChecker::hasChannels() const return !m_channels.isEmpty(); } -void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) +void UpdateChecker::checkForUpdate(bool notifyNoUpdate) { qDebug() << "Checking for updates."; + QString updateChannel = "develop"; // If the channel list hasn't loaded yet, load it and defer checking for updates until // later. @@ -54,7 +55,6 @@ void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) { qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring update check."; m_checkUpdateWaiting = true; - m_deferredUpdateChannel = updateChannel; updateChanList(notifyNoUpdate); return; } @@ -67,13 +67,13 @@ void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) // Find the desired channel within the channel list and get its repo URL. If if cannot be // found, error. - QString stableUrl; + QString developUrl; m_newRepoUrl = ""; for (ChannelListEntry entry : m_channels) { qDebug() << "channelEntry = " << entry.id; - if(entry.id == "stable") { - stableUrl = entry.url; + if(entry.id == "develop") { + developUrl = entry.url; } if (entry.id == updateChannel) { m_newRepoUrl = entry.url; @@ -88,8 +88,8 @@ void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) qDebug() << "m_repoUrl = " << m_newRepoUrl; if (m_newRepoUrl.isEmpty()) { - qWarning() << "m_repoUrl was empty. defaulting to 'stable': " << stableUrl; - m_newRepoUrl = stableUrl; + qWarning() << "m_repoUrl was empty. defaulting to 'develop': " << developUrl; + m_newRepoUrl = developUrl; } // If nothing applies, error @@ -255,7 +255,7 @@ void UpdateChecker::chanListDownloadFinished(bool notifyNoUpdate) // If we're waiting to check for updates, do that now. if (m_checkUpdateWaiting) { - checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate); + checkForUpdate(notifyNoUpdate); } emit channelListLoaded(); diff --git a/launcher/updater/UpdateChecker.h b/launcher/updater/UpdateChecker.h index 13ee4efd..6fe41807 100644 --- a/launcher/updater/UpdateChecker.h +++ b/launcher/updater/UpdateChecker.h @@ -23,8 +23,8 @@ class UpdateChecker : public QObject Q_OBJECT public: - UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel, int currentBuild); - void checkForUpdate(QString updateChannel, bool notifyNoUpdate); + UpdateChecker(shared_qobject_ptr nam, QString channelUrl, int currentBuild); + void checkForUpdate(bool notifyNoUpdate); /*! * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). @@ -107,11 +107,6 @@ private: */ bool m_checkUpdateWaiting = false; - /*! - * if m_checkUpdateWaiting, this is the last used update channel - */ - QString m_deferredUpdateChannel; - int m_currentBuild = -1; QString m_currentChannel; QString m_currentRepoUrl; diff --git a/launcher/updater/UpdateChecker_test.cpp b/launcher/updater/UpdateChecker_test.cpp index ec55a40e..845ed993 100644 --- a/launcher/updater/UpdateChecker_test.cpp +++ b/launcher/updater/UpdateChecker_test.cpp @@ -42,38 +42,32 @@ slots: void tst_ChannelListParsing_data() { - QTest::addColumn("channel"); QTest::addColumn("channelUrl"); QTest::addColumn("hasChannels"); QTest::addColumn("valid"); QTest::addColumn >("result"); QTest::newRow("garbage") - << QString() << findTestDataUrl("data/garbageChannels.json") << false << false << QList(); QTest::newRow("errors") - << QString() << findTestDataUrl("data/errorChannels.json") << false << true << QList(); QTest::newRow("no channels") - << QString() << findTestDataUrl("data/noChannels.json") << false << true << QList(); QTest::newRow("one channel") - << QString("develop") << findTestDataUrl("data/oneChannel.json") << true << true << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); QTest::newRow("several channels") - << QString("develop") << findTestDataUrl("data/channels.json") << true << true @@ -84,15 +78,13 @@ slots: } void tst_ChannelListParsing() { - - QFETCH(QString, channel); QFETCH(QString, channelUrl); QFETCH(bool, hasChannels); QFETCH(bool, valid); QFETCH(QList, result); shared_qobject_ptr nam = new QNetworkAccessManager(); - UpdateChecker checker(nam, channelUrl, channel, 0); + UpdateChecker checker(nam, channelUrl, 0); QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); QVERIFY(channelListLoadedSpy.isValid()); @@ -116,12 +108,11 @@ slots: void tst_UpdateChecking() { - QString channel = "develop"; QString channelUrl = findTestDataUrl("data/channels.json"); int currentBuild = 2; shared_qobject_ptr nam = new QNetworkAccessManager(); - UpdateChecker checker(nam, channelUrl, channel, currentBuild); + UpdateChecker checker(nam, channelUrl, currentBuild); QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); QVERIFY(updateAvailableSpy.isValid()); @@ -133,7 +124,7 @@ slots: qDebug() << "CWD:" << QDir::current().absolutePath(); checker.m_channels[0].url = findTestDataUrl("data/"); - checker.checkForUpdate(channel, false); + checker.checkForUpdate(false); QVERIFY(updateAvailableSpy.wait()); From 867d240a2fe6cef3aa05d47ac9144f97ec975c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 3 Feb 2023 23:30:59 +0100 Subject: [PATCH 37/54] NOISSUE update copyright year a bit --- CMakeLists.txt | 2 +- launcher/Application.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d005081c..fc1e1eb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,7 +195,7 @@ if(Launcher_LAYOUT_REAL STREQUAL "mac-bundle") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}.${Launcher_VERSION_BUILD}") set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) - set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2021 ${Launcher_Copyright}") + set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2023 ${Launcher_Copyright}") # directories to look for dependencies set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index b471f469..6dd01519 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -575,7 +575,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) FS::updateTimestamp(m_rootPath); #endif - qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; + qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2023 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; From 16cf56b7a49e1affbad3aa2fc2d60ff690823a79 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sat, 4 Feb 2023 21:41:24 +0000 Subject: [PATCH 38/54] GH-4699 Modrinth pack exporter (WIP) --- launcher/CMakeLists.txt | 8 + launcher/ModrinthInstanceExportTask.cpp | 221 ++++++++++++++ launcher/ModrinthInstanceExportTask.h | 69 +++++ launcher/ui/MainWindow.cpp | 4 +- launcher/ui/dialogs/ModrinthExportDialog.cpp | 114 +++++++ launcher/ui/dialogs/ModrinthExportDialog.h | 37 +++ launcher/ui/dialogs/ModrinthExportDialog.ui | 277 ++++++++++++++++++ .../SelectInstanceExportFormatDialog.cpp | 37 +++ .../SelectInstanceExportFormatDialog.h | 36 +++ .../SelectInstanceExportFormatDialog.ui | 95 ++++++ 10 files changed, 896 insertions(+), 2 deletions(-) create mode 100644 launcher/ModrinthInstanceExportTask.cpp create mode 100644 launcher/ModrinthInstanceExportTask.h create mode 100644 launcher/ui/dialogs/ModrinthExportDialog.cpp create mode 100644 launcher/ui/dialogs/ModrinthExportDialog.h create mode 100644 launcher/ui/dialogs/ModrinthExportDialog.ui create mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp create mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.h create mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 85f669eb..be12d08c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -36,6 +36,8 @@ set(CORE_SOURCES InstanceCopyTask.cpp InstanceImportTask.h InstanceImportTask.cpp + ModrinthInstanceExportTask.h + ModrinthInstanceExportTask.cpp # Use tracking separate from memory management Usable.h @@ -784,6 +786,10 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/CreateShortcutDialog.cpp ui/dialogs/CreateShortcutDialog.h + ui/dialogs/SelectInstanceExportFormatDialog.cpp + ui/dialogs/SelectInstanceExportFormatDialog.h + ui/dialogs/ModrinthExportDialog.cpp + ui/dialogs/ModrinthExportDialog.h # GUI - widgets ui/widgets/Common.cpp @@ -882,6 +888,8 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/CreateShortcutDialog.ui + ui/dialogs/SelectInstanceExportFormatDialog.ui + ui/dialogs/ModrinthExportDialog.ui ) qt5_add_resources(LAUNCHER_RESOURCES diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp new file mode 100644 index 00000000..943294c6 --- /dev/null +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -0,0 +1,221 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include +#include +#include +#include +#include "Json.h" +#include "ModrinthInstanceExportTask.h" +#include "net/NetJob.h" +#include "Application.h" +#include "ui/dialogs/ModrinthExportDialog.h" +#include "JlCompress.h" +#include "FileSystem.h" + +ModrinthInstanceExportTask::ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings) : m_instance(instance), m_settings(settings) {} + +void ModrinthInstanceExportTask::executeTask() +{ + QDir modsDir(m_instance->gameRoot() + "/mods"); + modsDir.setFilter(QDir::Files); + modsDir.setNameFilters(QStringList() << "*.jar"); + + QDir resourcePacksDir(m_instance->gameRoot() + "/resourcepacks"); + resourcePacksDir.setFilter(QDir::Files); + resourcePacksDir.setNameFilters(QStringList() << "*.zip"); + + QDir shaderPacksDir(m_instance->gameRoot() + "/shaderpacks"); + shaderPacksDir.setFilter(QDir::Files); + shaderPacksDir.setNameFilters(QStringList() << "*.zip"); + + QStringList filesToResolve; + + if (modsDir.exists()) { + QDirIterator modsIterator(modsDir); + while (modsIterator.hasNext()) { + filesToResolve << modsIterator.next(); + } + } + + if (m_settings.includeResourcePacks && resourcePacksDir.exists()) { + QDirIterator resourcePacksIterator(resourcePacksDir); + while (resourcePacksIterator.hasNext()) { + filesToResolve << resourcePacksIterator.next(); + } + } + + if (m_settings.includeShaderPacks && shaderPacksDir.exists()) { + QDirIterator shaderPacksIterator(shaderPacksDir); + while (shaderPacksIterator.hasNext()) { + filesToResolve << shaderPacksIterator.next(); + } + } + + m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); + + for (QString filePath: filesToResolve) { + QFile file(filePath); + + if (file.open(QFile::ReadOnly)) { + QByteArray contents = file.readAll(); + QCryptographicHash hasher(QCryptographicHash::Sha512); + hasher.addData(contents); + QString hash = hasher.result().toHex(); + + m_responses.append(ModrinthLookupData { + QFileInfo(file), + QByteArray() + }); + + m_netJob->addNetAction(Net::Download::makeByteArray( + QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha512").arg(hash), + &m_responses.last().response + )); + } + } + + connect(m_netJob.get(), &NetJob::succeeded, this, &ModrinthInstanceExportTask::lookupSucceeded); + connect(m_netJob.get(), &NetJob::failed, this, &ModrinthInstanceExportTask::lookupFailed); + connect(m_netJob.get(), &NetJob::progress, this, &ModrinthInstanceExportTask::lookupProgress); + + m_netJob->start(); +} + +void ModrinthInstanceExportTask::lookupSucceeded() +{ + QList resolvedFiles; + QFileInfoList failedFiles; + + for (const auto &data : m_responses) { + try { + auto document = Json::requireDocument(data.response); + auto object = Json::requireObject(document); + auto file = Json::requireIsArrayOf(object, "files").first(); + auto url = Json::requireString(file, "url"); + auto hashes = Json::requireObject(file, "hashes"); + + QString sha512Hash = Json::requireString(hashes, "sha512"); + QString sha1Hash = Json::requireString(hashes, "sha1"); + + ModrinthFile fileData; + + QDir gameDir(m_instance->gameRoot()); + + fileData.path = gameDir.relativeFilePath(data.fileInfo.absoluteFilePath()); + fileData.download = url; + fileData.sha512 = sha512Hash; + fileData.sha1 = sha1Hash; + fileData.fileSize = data.fileInfo.size(); + + resolvedFiles << fileData; + } catch (const Json::JsonException &e) { + qDebug() << "File " << data.fileInfo.path() << " failed to process for reason " << e.cause() << ", adding to overrides"; + failedFiles << data.fileInfo; + } + } + + qDebug() << "Failed files: " << failedFiles; + + QJsonObject indexJson; + indexJson.insert("formatVersion", QJsonValue(1)); + indexJson.insert("game", QJsonValue("minecraft")); + indexJson.insert("versionId", QJsonValue(m_settings.version)); + indexJson.insert("name", QJsonValue(m_settings.name)); + + if (!m_settings.description.isEmpty()) { + indexJson.insert("summary", QJsonValue(m_settings.description)); + } + + QJsonArray files; + + for (const auto &file : resolvedFiles) { + QJsonObject fileObj; + fileObj.insert("path", file.path); + + QJsonObject hashes; + hashes.insert("sha512", file.sha512); + hashes.insert("sha1", file.sha1); + fileObj.insert("hashes", hashes); + + QJsonArray downloads; + downloads.append(file.download); + fileObj.insert("downloads", downloads); + + fileObj.insert("fileSize", QJsonValue(file.fileSize)); + + files.append(fileObj); + } + + indexJson.insert("files", files); + + QJsonObject dependencies; + dependencies.insert("minecraft", m_settings.gameVersion); + if (!m_settings.forgeVersion.isEmpty()) { + dependencies.insert("forge", m_settings.forgeVersion); + } + if (!m_settings.fabricVersion.isEmpty()) { + dependencies.insert("fabric-loader", m_settings.fabricVersion); + } + if (!m_settings.quiltVersion.isEmpty()) { + dependencies.insert("quilt-loader", m_settings.quiltVersion); + } + + indexJson.insert("dependencies", dependencies); + + QTemporaryDir tmp; + if (tmp.isValid()) { + Json::write(indexJson, tmp.filePath("modrinth.index.json")); + + if (!failedFiles.isEmpty()) { + QDir tmpDir(tmp.path()); + QDir gameDir(m_instance->gameRoot()); + for (const auto &file : failedFiles) { + QString src = file.absoluteFilePath(); + tmpDir.mkpath("overrides/" + gameDir.relativeFilePath(file.absolutePath())); + QString dest = tmpDir.path() + "/overrides/" + gameDir.relativeFilePath(src); + qDebug() << dest; + if (!QFile::copy(file.absoluteFilePath(), dest)) { + emitFailed(tr("Failed to copy file %1 to overrides").arg(src)); + return; + } + } + + if (m_settings.includeGameConfig) { + tmpDir.mkdir("overrides"); + QFile::copy(gameDir.absoluteFilePath("options.txt"), tmpDir.absoluteFilePath("overrides/options.txt")); + } + + if (m_settings.includeModConfigs) { + tmpDir.mkdir("overrides"); + FS::copy copy(m_instance->gameRoot() + "/config", tmpDir.absoluteFilePath("overrides/config")); + copy(); + } + } + + if (!JlCompress::compressDir(m_settings.exportPath, tmp.path())) { + emitFailed(tr("Failed to create zip file")); + return; + } + } else { + emitFailed(tr("Failed to create temporary directory")); + return; + } + + emitSucceeded(); +} + +void ModrinthInstanceExportTask::lookupFailed(const QString &) +{ + lookupSucceeded(); // the NetJob will fail if some files were not found on Modrinth, we still want to continue in that case + // FIXME: the NetJob will retry each download 3 times if it fails, we should probably stop it from doing that +} + +void ModrinthInstanceExportTask::lookupProgress(qint64 current, qint64 total) +{ + setProgress(current, total); +} \ No newline at end of file diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/ModrinthInstanceExportTask.h new file mode 100644 index 00000000..8749ded4 --- /dev/null +++ b/launcher/ModrinthInstanceExportTask.h @@ -0,0 +1,69 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include "tasks/Task.h" +#include "BaseInstance.h" +#include "net/NetJob.h" +#include "ui/dialogs/ModrinthExportDialog.h" + +struct ModrinthExportSettings { + QString version; + QString name; + QString description; + + bool includeGameConfig; + bool includeModConfigs; + bool includeResourcePacks; + bool includeShaderPacks; + + QString gameVersion; + QString forgeVersion; + QString fabricVersion; + QString quiltVersion; + + QString exportPath; +}; + +struct ModrinthLookupData { + QFileInfo fileInfo; + QByteArray response; +}; + +// Using the existing Modrinth::File struct from the importer doesn't actually make much sense here (doesn't support multiple hashes, hash is a byte array rather than a string, no file size, etc) +struct ModrinthFile +{ + QString path; + QString sha512; + QString sha1; + QString download; + qint64 fileSize; +}; + +class ModrinthInstanceExportTask : public Task +{ +Q_OBJECT + +public: + explicit ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings); + +protected: + //! Entry point for tasks. + virtual void executeTask() override; + +private slots: + void lookupSucceeded(); + void lookupFailed(const QString &); + void lookupProgress(qint64 current, qint64 total); + +private: + InstancePtr m_instance; + ModrinthExportSettings m_settings; + QList m_responses; + NetJob::Ptr m_netJob; +}; \ No newline at end of file diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 44cb504b..9dcbabea 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -84,7 +84,7 @@ #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/NotificationDialog.h" #include "ui/dialogs/CreateShortcutDialog.h" -#include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/SelectInstanceExportFormatDialog.h" #include "UpdateController.h" #include "KonamiCode.h" @@ -1756,7 +1756,7 @@ void MainWindow::on_actionExportInstance_triggered() { if (m_selectedInstance) { - ExportInstanceDialog dlg(m_selectedInstance, this); + SelectInstanceExportFormatDialog dlg(m_selectedInstance, this); dlg.exec(); } } diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp new file mode 100644 index 00000000..decde9ec --- /dev/null +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include +#include +#include +#include +#include "ModrinthExportDialog.h" +#include "ui_ModrinthExportDialog.h" +#include "BaseInstance.h" +#include "ModrinthInstanceExportTask.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "ProgressDialog.h" +#include "CustomMessageBox.h" + + +ModrinthExportDialog::ModrinthExportDialog(InstancePtr instance, QWidget *parent) : + QDialog(parent), ui(new Ui::ModrinthExportDialog), m_instance(instance) +{ + ui->setupUi(this); + ui->name->setText(m_instance->name()); + ui->version->setText("1.0"); +} + +void ModrinthExportDialog::updateDialogState() +{ + ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled( + !ui->name->text().isEmpty() + && !ui->version->text().isEmpty() + && !ui->file->text().isEmpty() + ); +} + +void ModrinthExportDialog::on_fileBrowseButton_clicked() +{ + QFileDialog dialog(this, tr("Select modpack file"), QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); + dialog.setDefaultSuffix("mrpack"); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.selectFile(ui->name->text() + ".mrpack"); + + if (dialog.exec()) { + ui->file->setText(dialog.selectedFiles().at(0)); + } + + updateDialogState(); +} + +void ModrinthExportDialog::accept() +{ + ModrinthExportSettings settings; + + settings.name = ui->name->text(); + settings.version = ui->version->text(); + settings.description = ui->description->text(); + + settings.includeGameConfig = ui->includeGameConfig->isChecked(); + settings.includeModConfigs = ui->includeModConfigs->isChecked(); + settings.includeResourcePacks = ui->includeResourcePacks->isChecked(); + settings.includeShaderPacks = ui->includeShaderPacks->isChecked(); + + MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast(m_instance); + minecraftInstance->getPackProfile()->reload(Net::Mode::Offline); + + auto minecraftComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraft"); + auto forgeComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraftforge"); + auto fabricComponent = minecraftInstance->getPackProfile()->getComponent("net.fabricmc.fabric-loader"); + auto quiltComponent = minecraftInstance->getPackProfile()->getComponent("org.quiltmc.quilt-loader"); + + if (minecraftComponent) { + settings.gameVersion = minecraftComponent->getVersion(); + } + if (forgeComponent) { + settings.forgeVersion = forgeComponent->getVersion(); + } + if (fabricComponent) { + settings.fabricVersion = fabricComponent->getVersion(); + } + if (quiltComponent) { + settings.quiltVersion = quiltComponent->getVersion(); + } + + settings.exportPath = ui->file->text(); + + auto *task = new ModrinthInstanceExportTask(m_instance, settings); + + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(parentWidget(), tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(task, &Task::succeeded, [this, task]() + { + QStringList warnings = task->warnings(); + if(warnings.count()) + { + CustomMessageBox::selectable(parentWidget(), tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(task); + + QDialog::accept(); +} + +ModrinthExportDialog::~ModrinthExportDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/ModrinthExportDialog.h b/launcher/ui/dialogs/ModrinthExportDialog.h new file mode 100644 index 00000000..3ebc9436 --- /dev/null +++ b/launcher/ui/dialogs/ModrinthExportDialog.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include "ExportInstanceDialog.h" + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class ModrinthExportDialog; +} +QT_END_NAMESPACE + +class ModrinthExportDialog : public QDialog +{ +Q_OBJECT + +public: + explicit ModrinthExportDialog(InstancePtr instance, QWidget *parent = nullptr); + + ~ModrinthExportDialog() override; + +private slots: + void on_fileBrowseButton_clicked(); + void accept() override; + void updateDialogState(); + +private: + Ui::ModrinthExportDialog *ui; + InstancePtr m_instance; +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/ModrinthExportDialog.ui b/launcher/ui/dialogs/ModrinthExportDialog.ui new file mode 100644 index 00000000..7aa18bfc --- /dev/null +++ b/launcher/ui/dialogs/ModrinthExportDialog.ui @@ -0,0 +1,277 @@ + + + ModrinthExportDialog + + + + 0 + 0 + 679 + 559 + + + + + 0 + 0 + + + + ModrinthExportDialog + + + + + 10 + 10 + 661 + 541 + + + + + + + + 16777215 + 25 + + + + Export Modrinth modpack + + + + + + + + 16777215 + 200 + + + + Metadata + + + + + 10 + 30 + 641 + 151 + + + + + + + + + Name + + + + + + + + + + Version + + + + + + + Description + + + + + + + + + + + + + + + + + + + Export Options + + + + + 9 + 29 + 641 + 221 + + + + + + + QLayout::SetFixedSize + + + + + File + + + + + + + + + + Browse... + + + + + + + + + Include Minecraft config + + + true + + + + + + + Include mod configs + + + true + + + + + + + Include resource packs + + + + + + + Include shader packs + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + buttonBox + accepted() + ModrinthExportDialog + accept() + + + 340 + 532 + + + 338 + 279 + + + + + buttonBox + rejected() + ModrinthExportDialog + reject() + + + 340 + 532 + + + 338 + 279 + + + + + name + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 395 + 90 + + + 339 + 279 + + + + + version + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 395 + 129 + + + 339 + 279 + + + + + file + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 309 + 329 + + + 339 + 279 + + + + + + updateDialogState() + + diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp new file mode 100644 index 00000000..2a3c6f4b --- /dev/null +++ b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include "SelectInstanceExportFormatDialog.h" +#include "ui_SelectInstanceExportFormatDialog.h" +#include "BuildConfig.h" +#include "ModrinthExportDialog.h" + + +SelectInstanceExportFormatDialog::SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent) : + QDialog(parent), ui(new Ui::SelectInstanceExportFormatDialog), m_instance(instance) +{ + ui->setupUi(this); + ui->mmcFormat->setText(BuildConfig.LAUNCHER_NAME); +} + +void SelectInstanceExportFormatDialog::accept() +{ + if (ui->mmcFormat->isChecked()) { + ExportInstanceDialog dlg(m_instance, parentWidget()); + QDialog::accept(); + dlg.exec(); + } else if (ui->modrinthFormat->isChecked()) { + ModrinthExportDialog dlg(m_instance, parentWidget()); + QDialog::accept(); + dlg.exec(); + } +} + +SelectInstanceExportFormatDialog::~SelectInstanceExportFormatDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h new file mode 100644 index 00000000..2c286fad --- /dev/null +++ b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h @@ -0,0 +1,36 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include "ExportInstanceDialog.h" + + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class SelectInstanceExportFormatDialog; +} +QT_END_NAMESPACE + +class SelectInstanceExportFormatDialog : public QDialog +{ +Q_OBJECT + +public: + explicit SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent = nullptr); + + ~SelectInstanceExportFormatDialog() override; + +private slots: + void accept() override; + +private: + Ui::SelectInstanceExportFormatDialog *ui; + InstancePtr m_instance; +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui new file mode 100644 index 00000000..5b779a4e --- /dev/null +++ b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui @@ -0,0 +1,95 @@ + + + SelectInstanceExportFormatDialog + + + + 0 + 0 + 446 + 181 + + + + Select Instance Export Format + + + + + 10 + 10 + 421 + 161 + + + + + + + Select export format + + + + + + + Launcher + + + true + + + + + + + Modrinth (WIP) + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + buttonBox + accepted() + SelectInstanceExportFormatDialog + accept() + + + 220 + 152 + + + 222 + 90 + + + + + buttonBox + rejected() + SelectInstanceExportFormatDialog + reject() + + + 220 + 152 + + + 222 + 90 + + + + + From 74addfb78ba66b9ddf0ef916b950cfc88872b43e Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 10:13:13 +0000 Subject: [PATCH 39/54] GH-4699 Clean some things up Add a menu to select between MMC/Modrinth format packs instead of the custom dialog Treat 404s on requests to the Modrinth API as success, as the API returns a 404 if a hash was not found, and we don't want to retry the download in this case Improve logging --- launcher/CMakeLists.txt | 5 +- launcher/ModrinthInstanceExportTask.cpp | 15 +-- launcher/net/Download.cpp | 7 ++ launcher/net/Download.h | 3 +- launcher/ui/MainWindow.cpp | 30 +++++- .../SelectInstanceExportFormatDialog.cpp | 37 -------- .../SelectInstanceExportFormatDialog.h | 36 ------- .../SelectInstanceExportFormatDialog.ui | 95 ------------------- 8 files changed, 46 insertions(+), 182 deletions(-) delete mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp delete mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.h delete mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index be12d08c..c8e363eb 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -786,9 +786,7 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/CreateShortcutDialog.cpp ui/dialogs/CreateShortcutDialog.h - ui/dialogs/SelectInstanceExportFormatDialog.cpp - ui/dialogs/SelectInstanceExportFormatDialog.h - ui/dialogs/ModrinthExportDialog.cpp + ui/dialogs/ModrinthExportDialog.cpp ui/dialogs/ModrinthExportDialog.h # GUI - widgets @@ -888,7 +886,6 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/CreateShortcutDialog.ui - ui/dialogs/SelectInstanceExportFormatDialog.ui ui/dialogs/ModrinthExportDialog.ui ) diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp index 943294c6..50841d28 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -58,7 +58,8 @@ void ModrinthInstanceExportTask::executeTask() m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); - for (QString filePath: filesToResolve) { + for (const QString &filePath: filesToResolve) { + qDebug() << "Attempting to resolve file hash from Modrinth API: " << filePath; QFile file(filePath); if (file.open(QFile::ReadOnly)) { @@ -74,7 +75,8 @@ void ModrinthInstanceExportTask::executeTask() m_netJob->addNetAction(Net::Download::makeByteArray( QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha512").arg(hash), - &m_responses.last().response + &m_responses.last().response, + Net::Download::Options(Net::Download::Option::AllowNotFound) )); } } @@ -114,7 +116,7 @@ void ModrinthInstanceExportTask::lookupSucceeded() resolvedFiles << fileData; } catch (const Json::JsonException &e) { - qDebug() << "File " << data.fileInfo.path() << " failed to process for reason " << e.cause() << ", adding to overrides"; + qDebug() << "File " << data.fileInfo.absoluteFilePath() << " failed to process for reason " << e.cause() << ", adding to overrides"; failedFiles << data.fileInfo; } } @@ -178,7 +180,6 @@ void ModrinthInstanceExportTask::lookupSucceeded() QString src = file.absoluteFilePath(); tmpDir.mkpath("overrides/" + gameDir.relativeFilePath(file.absolutePath())); QString dest = tmpDir.path() + "/overrides/" + gameDir.relativeFilePath(src); - qDebug() << dest; if (!QFile::copy(file.absoluteFilePath(), dest)) { emitFailed(tr("Failed to copy file %1 to overrides").arg(src)); return; @@ -206,13 +207,13 @@ void ModrinthInstanceExportTask::lookupSucceeded() return; } + qDebug() << "Successfully exported Modrinth pack to " << m_settings.exportPath; emitSucceeded(); } -void ModrinthInstanceExportTask::lookupFailed(const QString &) +void ModrinthInstanceExportTask::lookupFailed(const QString &reason) { - lookupSucceeded(); // the NetJob will fail if some files were not found on Modrinth, we still want to continue in that case - // FIXME: the NetJob will retry each download 3 times if it fails, we should probably stop it from doing that + emitFailed(reason); } void ModrinthInstanceExportTask::lookupProgress(qint64 current, qint64 total) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index b314573f..2150fb14 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -122,6 +122,13 @@ void Download::downloadError(QNetworkReply::NetworkError error) qCritical() << "Aborted " << m_url.toString(); m_status = Job_Aborted; } + else if(error == QNetworkReply::ContentNotFoundError && (m_options & Option::AllowNotFound)) + { + // The Modrinth API returns a 404 when a hash was not found when performing reverse hash lookup, we don't want to treat this as a failure + qDebug() << "Received 404 from " << m_url.toString() << ", continuing..."; + m_status = Job_Finished; + return; + } else { if(m_options & Option::AcceptLocalFiles) diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 0f9bfe7f..08523e34 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -32,7 +32,8 @@ public: /* types */ enum class Option { NoOptions = 0, - AcceptLocalFiles = 1 + AcceptLocalFiles = 1, + AllowNotFound =2 }; Q_DECLARE_FLAGS(Options, Option) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 9dcbabea..607c1045 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -84,7 +84,8 @@ #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/NotificationDialog.h" #include "ui/dialogs/CreateShortcutDialog.h" -#include "ui/dialogs/SelectInstanceExportFormatDialog.h" +#include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/ModrinthExportDialog.h" #include "UpdateController.h" #include "KonamiCode.h" @@ -974,6 +975,31 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) void MainWindow::updateToolsMenu() { + QToolButton *exportButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionExportInstance)); + exportButton->setPopupMode(QToolButton::MenuButtonPopup); + + QMenu *exportMenu = ui->actionExportInstance->menu(); + + if (exportMenu) { + exportMenu->clear(); + } else { + exportMenu = new QMenu(); + } + + QAction *mmcExport = exportMenu->addAction(BuildConfig.LAUNCHER_NAME); + QAction *modrinthExport = exportMenu->addAction(tr("Modrinth")); + + connect(mmcExport, &QAction::triggered, this, &MainWindow::on_actionExportInstance_triggered); + connect(modrinthExport, &QAction::triggered, [this]() + { + if (m_selectedInstance) { + ModrinthExportDialog dlg(m_selectedInstance, this); + dlg.exec(); + } + }); + + ui->actionExportInstance->setMenu(exportMenu); + QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); @@ -1756,7 +1782,7 @@ void MainWindow::on_actionExportInstance_triggered() { if (m_selectedInstance) { - SelectInstanceExportFormatDialog dlg(m_selectedInstance, this); + ExportInstanceDialog dlg(m_selectedInstance, this); dlg.exec(); } } diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp deleted file mode 100644 index 2a3c6f4b..00000000 --- a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 arthomnix - * - * This source is subject to the Microsoft Public License (MS-PL). - * Please see the COPYING.md file for more information. - */ - -#include "SelectInstanceExportFormatDialog.h" -#include "ui_SelectInstanceExportFormatDialog.h" -#include "BuildConfig.h" -#include "ModrinthExportDialog.h" - - -SelectInstanceExportFormatDialog::SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent) : - QDialog(parent), ui(new Ui::SelectInstanceExportFormatDialog), m_instance(instance) -{ - ui->setupUi(this); - ui->mmcFormat->setText(BuildConfig.LAUNCHER_NAME); -} - -void SelectInstanceExportFormatDialog::accept() -{ - if (ui->mmcFormat->isChecked()) { - ExportInstanceDialog dlg(m_instance, parentWidget()); - QDialog::accept(); - dlg.exec(); - } else if (ui->modrinthFormat->isChecked()) { - ModrinthExportDialog dlg(m_instance, parentWidget()); - QDialog::accept(); - dlg.exec(); - } -} - -SelectInstanceExportFormatDialog::~SelectInstanceExportFormatDialog() -{ - delete ui; -} diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h deleted file mode 100644 index 2c286fad..00000000 --- a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 arthomnix - * - * This source is subject to the Microsoft Public License (MS-PL). - * Please see the COPYING.md file for more information. - */ - -#pragma once - -#include -#include "ExportInstanceDialog.h" - - -QT_BEGIN_NAMESPACE -namespace Ui -{ - class SelectInstanceExportFormatDialog; -} -QT_END_NAMESPACE - -class SelectInstanceExportFormatDialog : public QDialog -{ -Q_OBJECT - -public: - explicit SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent = nullptr); - - ~SelectInstanceExportFormatDialog() override; - -private slots: - void accept() override; - -private: - Ui::SelectInstanceExportFormatDialog *ui; - InstancePtr m_instance; -}; \ No newline at end of file diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui deleted file mode 100644 index 5b779a4e..00000000 --- a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui +++ /dev/null @@ -1,95 +0,0 @@ - - - SelectInstanceExportFormatDialog - - - - 0 - 0 - 446 - 181 - - - - Select Instance Export Format - - - - - 10 - 10 - 421 - 161 - - - - - - - Select export format - - - - - - - Launcher - - - true - - - - - - - Modrinth (WIP) - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - buttonBox - accepted() - SelectInstanceExportFormatDialog - accept() - - - 220 - 152 - - - 222 - 90 - - - - - buttonBox - rejected() - SelectInstanceExportFormatDialog - reject() - - - 220 - 152 - - - 222 - 90 - - - - - From a6dff61ff7b03f979f367a4399a0b78488193b7d Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 10:15:29 +0000 Subject: [PATCH 40/54] GH-4699 Formatting --- launcher/CMakeLists.txt | 2 +- launcher/ModrinthInstanceExportTask.h | 2 +- launcher/net/Download.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c8e363eb..47e37df5 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -786,7 +786,7 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/CreateShortcutDialog.cpp ui/dialogs/CreateShortcutDialog.h - ui/dialogs/ModrinthExportDialog.cpp + ui/dialogs/ModrinthExportDialog.cpp ui/dialogs/ModrinthExportDialog.h # GUI - widgets diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/ModrinthInstanceExportTask.h index 8749ded4..fbcdb0f5 100644 --- a/launcher/ModrinthInstanceExportTask.h +++ b/launcher/ModrinthInstanceExportTask.h @@ -58,7 +58,7 @@ protected: private slots: void lookupSucceeded(); - void lookupFailed(const QString &); + void lookupFailed(const QString &reason); void lookupProgress(qint64 current, qint64 total); private: diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 08523e34..c5102421 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -33,7 +33,7 @@ public: /* types */ { NoOptions = 0, AcceptLocalFiles = 1, - AllowNotFound =2 + AllowNotFound = 2 }; Q_DECLARE_FLAGS(Options, Option) From a1f256a7451223546e6dcda3f8e427a38e01273a Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 12:03:00 +0000 Subject: [PATCH 41/54] GH-4699 Add global datapacks support to Modrinth exporter --- launcher/ModrinthInstanceExportTask.cpp | 13 ++++ launcher/ModrinthInstanceExportTask.h | 1 + launcher/ui/dialogs/ModrinthExportDialog.cpp | 24 +++++++- launcher/ui/dialogs/ModrinthExportDialog.h | 1 + launcher/ui/dialogs/ModrinthExportDialog.ui | 64 ++++++++++++++++++-- 5 files changed, 98 insertions(+), 5 deletions(-) diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp index 50841d28..b0d46b37 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -56,6 +56,19 @@ void ModrinthInstanceExportTask::executeTask() } } + if (!m_settings.datapacksPath.isEmpty()) { + QDir datapacksDir(m_instance->gameRoot() + "/" + m_settings.datapacksPath); + datapacksDir.setFilter(QDir::Files); + datapacksDir.setNameFilters(QStringList() << "*.zip"); + + if (datapacksDir.exists()) { + QDirIterator datapacksIterator(datapacksDir); + while (datapacksIterator.hasNext()) { + filesToResolve << datapacksIterator.next(); + } + } + } + m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); for (const QString &filePath: filesToResolve) { diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/ModrinthInstanceExportTask.h index fbcdb0f5..d5e103c2 100644 --- a/launcher/ModrinthInstanceExportTask.h +++ b/launcher/ModrinthInstanceExportTask.h @@ -21,6 +21,7 @@ struct ModrinthExportSettings { bool includeModConfigs; bool includeResourcePacks; bool includeShaderPacks; + QString datapacksPath; QString gameVersion; QString forgeVersion; diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index decde9ec..ac01616e 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -32,7 +32,11 @@ void ModrinthExportDialog::updateDialogState() ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled( !ui->name->text().isEmpty() && !ui->version->text().isEmpty() - && !ui->file->text().isEmpty() + && ui->file->text().endsWith(".mrpack") + && ( + !ui->includeDatapacks->isChecked() + || (!ui->datapacksPath->text().isEmpty() && QDir(m_instance->gameRoot() + "/" + ui->datapacksPath->text()).exists()) + ) ); } @@ -40,6 +44,7 @@ void ModrinthExportDialog::on_fileBrowseButton_clicked() { QFileDialog dialog(this, tr("Select modpack file"), QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setDefaultSuffix("mrpack"); + dialog.setNameFilter("Modrinth modpacks (*.mrpack)"); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.selectFile(ui->name->text() + ".mrpack"); @@ -51,6 +56,19 @@ void ModrinthExportDialog::on_fileBrowseButton_clicked() updateDialogState(); } +void ModrinthExportDialog::on_datapackPathBrowse_clicked() +{ + QFileDialog dialog(this, tr("Select global datapacks folder"), m_instance->gameRoot()); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::DirectoryOnly); + + if (dialog.exec()) { + ui->datapacksPath->setText(QDir(m_instance->gameRoot()).relativeFilePath(dialog.selectedFiles().at(0))); + } + + updateDialogState(); +} + void ModrinthExportDialog::accept() { ModrinthExportSettings settings; @@ -64,6 +82,10 @@ void ModrinthExportDialog::accept() settings.includeResourcePacks = ui->includeResourcePacks->isChecked(); settings.includeShaderPacks = ui->includeShaderPacks->isChecked(); + if (ui->includeDatapacks->isChecked()) { + settings.datapacksPath = ui->datapacksPath->text(); + } + MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast(m_instance); minecraftInstance->getPackProfile()->reload(Net::Mode::Offline); diff --git a/launcher/ui/dialogs/ModrinthExportDialog.h b/launcher/ui/dialogs/ModrinthExportDialog.h index 3ebc9436..08a244c9 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.h +++ b/launcher/ui/dialogs/ModrinthExportDialog.h @@ -28,6 +28,7 @@ public: private slots: void on_fileBrowseButton_clicked(); + void on_datapackPathBrowse_clicked(); void accept() override; void updateDialogState(); diff --git a/launcher/ui/dialogs/ModrinthExportDialog.ui b/launcher/ui/dialogs/ModrinthExportDialog.ui index 7aa18bfc..c386f881 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.ui +++ b/launcher/ui/dialogs/ModrinthExportDialog.ui @@ -6,7 +6,7 @@ 0 0 - 679 + 835 559 @@ -24,7 +24,7 @@ 10 10 - 661 + 821 541 @@ -58,7 +58,7 @@ 10 30 - 641 + 801 151 @@ -111,7 +111,7 @@ 9 29 - 641 + 801 221 @@ -174,6 +174,30 @@ + + + + + + Use this if your modpack contains a mod which adds global datapacks. + + + Include global datapacks folder: + + + + + + + + + + Browse... + + + + + @@ -270,6 +294,38 @@ + + datapacksPath + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 532 + 472 + + + 417 + 279 + + + + + includeDatapacks + stateChanged(int) + ModrinthExportDialog + updateDialogState() + + + 183 + 472 + + + 417 + 279 + + + updateDialogState() From 01f3511e888a23de1707ba5b5dcd96db8117172f Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 12:11:27 +0000 Subject: [PATCH 42/54] GH-4699 Improve export instance menu --- launcher/ui/MainWindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 607c1045..8a6688d2 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -986,8 +986,10 @@ void MainWindow::updateToolsMenu() exportMenu = new QMenu(); } + exportMenu->addSeparator()->setText(tr("Format")); + QAction *mmcExport = exportMenu->addAction(BuildConfig.LAUNCHER_NAME); - QAction *modrinthExport = exportMenu->addAction(tr("Modrinth")); + QAction *modrinthExport = exportMenu->addAction(tr("Modrinth (WIP)")); connect(mmcExport, &QAction::triggered, this, &MainWindow::on_actionExportInstance_triggered); connect(modrinthExport, &QAction::triggered, [this]() From a452b7ee962268704acd0a9b81a3b068d32af3f7 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 12:33:56 +0000 Subject: [PATCH 43/54] GH-4699 Set Modrinth exporter task status --- launcher/ModrinthInstanceExportTask.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp index b0d46b37..b1d62085 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -21,6 +21,8 @@ ModrinthInstanceExportTask::ModrinthInstanceExportTask(InstancePtr instance, Mod void ModrinthInstanceExportTask::executeTask() { + setStatus(tr("Finding files to look up on Modrinth...")); + QDir modsDir(m_instance->gameRoot() + "/mods"); modsDir.setFilter(QDir::Files); modsDir.setNameFilters(QStringList() << "*.jar"); @@ -99,10 +101,12 @@ void ModrinthInstanceExportTask::executeTask() connect(m_netJob.get(), &NetJob::progress, this, &ModrinthInstanceExportTask::lookupProgress); m_netJob->start(); + setStatus(tr("Looking up files on Modrinth...")); } void ModrinthInstanceExportTask::lookupSucceeded() { + setStatus(tr("Creating modpack metadata...")); QList resolvedFiles; QFileInfoList failedFiles; @@ -182,6 +186,8 @@ void ModrinthInstanceExportTask::lookupSucceeded() indexJson.insert("dependencies", dependencies); + setStatus(tr("Copying files to modpack...")); + QTemporaryDir tmp; if (tmp.isValid()) { Json::write(indexJson, tmp.filePath("modrinth.index.json")); @@ -211,6 +217,7 @@ void ModrinthInstanceExportTask::lookupSucceeded() } } + setStatus(tr("Zipping modpack...")); if (!JlCompress::compressDir(m_settings.exportPath, tmp.path())) { emitFailed(tr("Failed to create zip file")); return; From aae2f23eb6f974f4986fd019b7e4d17713eb44ae Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 14:28:18 +0000 Subject: [PATCH 44/54] GH-4699 Move ModrinthInstanceExportTask to modplatform/modrinth; truncate error messages in dialog --- launcher/CMakeLists.txt | 4 +-- .../modrinth}/ModrinthInstanceExportTask.cpp | 31 +++++++++++-------- .../modrinth}/ModrinthInstanceExportTask.h | 23 +++++++++----- launcher/ui/dialogs/ModrinthExportDialog.cpp | 14 ++++++--- 4 files changed, 45 insertions(+), 27 deletions(-) rename launcher/{ => modplatform/modrinth}/ModrinthInstanceExportTask.cpp (89%) rename launcher/{ => modplatform/modrinth}/ModrinthInstanceExportTask.h (80%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 47e37df5..e813835d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -36,8 +36,6 @@ set(CORE_SOURCES InstanceCopyTask.cpp InstanceImportTask.h InstanceImportTask.cpp - ModrinthInstanceExportTask.h - ModrinthInstanceExportTask.cpp # Use tracking separate from memory management Usable.h @@ -529,6 +527,8 @@ set(ATLAUNCHER_SOURCES set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackManifest.cpp modplatform/modrinth/ModrinthPackManifest.h + modplatform/modrinth/ModrinthInstanceExportTask.h + modplatform/modrinth/ModrinthInstanceExportTask.cpp ) add_unit_test(Index diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp similarity index 89% rename from launcher/ModrinthInstanceExportTask.cpp rename to launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp index b1d62085..1def5152 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp @@ -17,9 +17,12 @@ #include "JlCompress.h" #include "FileSystem.h" -ModrinthInstanceExportTask::ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings) : m_instance(instance), m_settings(settings) {} +namespace Modrinth +{ -void ModrinthInstanceExportTask::executeTask() +InstanceExportTask::InstanceExportTask(InstancePtr instance, ExportSettings settings) : m_instance(instance), m_settings(settings) {} + +void InstanceExportTask::executeTask() { setStatus(tr("Finding files to look up on Modrinth...")); @@ -83,9 +86,9 @@ void ModrinthInstanceExportTask::executeTask() hasher.addData(contents); QString hash = hasher.result().toHex(); - m_responses.append(ModrinthLookupData { - QFileInfo(file), - QByteArray() + m_responses.append(HashLookupData{ + QFileInfo(file), + QByteArray() }); m_netJob->addNetAction(Net::Download::makeByteArray( @@ -96,18 +99,18 @@ void ModrinthInstanceExportTask::executeTask() } } - connect(m_netJob.get(), &NetJob::succeeded, this, &ModrinthInstanceExportTask::lookupSucceeded); - connect(m_netJob.get(), &NetJob::failed, this, &ModrinthInstanceExportTask::lookupFailed); - connect(m_netJob.get(), &NetJob::progress, this, &ModrinthInstanceExportTask::lookupProgress); + connect(m_netJob.get(), &NetJob::succeeded, this, &InstanceExportTask::lookupSucceeded); + connect(m_netJob.get(), &NetJob::failed, this, &InstanceExportTask::lookupFailed); + connect(m_netJob.get(), &NetJob::progress, this, &InstanceExportTask::lookupProgress); m_netJob->start(); setStatus(tr("Looking up files on Modrinth...")); } -void ModrinthInstanceExportTask::lookupSucceeded() +void InstanceExportTask::lookupSucceeded() { setStatus(tr("Creating modpack metadata...")); - QList resolvedFiles; + QList resolvedFiles; QFileInfoList failedFiles; for (const auto &data : m_responses) { @@ -121,7 +124,7 @@ void ModrinthInstanceExportTask::lookupSucceeded() QString sha512Hash = Json::requireString(hashes, "sha512"); QString sha1Hash = Json::requireString(hashes, "sha1"); - ModrinthFile fileData; + ExportFile fileData; QDir gameDir(m_instance->gameRoot()); @@ -231,12 +234,14 @@ void ModrinthInstanceExportTask::lookupSucceeded() emitSucceeded(); } -void ModrinthInstanceExportTask::lookupFailed(const QString &reason) +void InstanceExportTask::lookupFailed(const QString &reason) { emitFailed(reason); } -void ModrinthInstanceExportTask::lookupProgress(qint64 current, qint64 total) +void InstanceExportTask::lookupProgress(qint64 current, qint64 total) { setProgress(current, total); +} + } \ No newline at end of file diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h similarity index 80% rename from launcher/ModrinthInstanceExportTask.h rename to launcher/modplatform/modrinth/ModrinthInstanceExportTask.h index d5e103c2..ff59f45b 100644 --- a/launcher/ModrinthInstanceExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h @@ -12,7 +12,11 @@ #include "net/NetJob.h" #include "ui/dialogs/ModrinthExportDialog.h" -struct ModrinthExportSettings { +namespace Modrinth +{ + +struct ExportSettings +{ QString version; QString name; QString description; @@ -31,13 +35,14 @@ struct ModrinthExportSettings { QString exportPath; }; -struct ModrinthLookupData { +struct HashLookupData +{ QFileInfo fileInfo; QByteArray response; }; // Using the existing Modrinth::File struct from the importer doesn't actually make much sense here (doesn't support multiple hashes, hash is a byte array rather than a string, no file size, etc) -struct ModrinthFile +struct ExportFile { QString path; QString sha512; @@ -46,12 +51,12 @@ struct ModrinthFile qint64 fileSize; }; -class ModrinthInstanceExportTask : public Task +class InstanceExportTask : public Task { Q_OBJECT public: - explicit ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings); + explicit InstanceExportTask(InstancePtr instance, ExportSettings settings); protected: //! Entry point for tasks. @@ -64,7 +69,9 @@ private slots: private: InstancePtr m_instance; - ModrinthExportSettings m_settings; - QList m_responses; + ExportSettings m_settings; + QList m_responses; NetJob::Ptr m_netJob; -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index ac01616e..4b0d3682 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -12,7 +12,7 @@ #include "ModrinthExportDialog.h" #include "ui_ModrinthExportDialog.h" #include "BaseInstance.h" -#include "ModrinthInstanceExportTask.h" +#include "modplatform/modrinth/ModrinthInstanceExportTask.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ProgressDialog.h" @@ -71,7 +71,7 @@ void ModrinthExportDialog::on_datapackPathBrowse_clicked() void ModrinthExportDialog::accept() { - ModrinthExportSettings settings; + Modrinth::ExportSettings settings; settings.name = ui->name->text(); settings.version = ui->version->text(); @@ -109,11 +109,17 @@ void ModrinthExportDialog::accept() settings.exportPath = ui->file->text(); - auto *task = new ModrinthInstanceExportTask(m_instance, settings); + auto *task = new Modrinth::InstanceExportTask(m_instance, settings); connect(task, &Task::failed, [this](QString reason) { - CustomMessageBox::selectable(parentWidget(), tr("Error"), reason, QMessageBox::Critical)->show(); + QString text; + if (reason.length() > 1000) { + text = reason.left(1000) + "..."; + } else { + text = reason; + } + CustomMessageBox::selectable(parentWidget(), tr("Error"), text, QMessageBox::Critical)->show(); }); connect(task, &Task::succeeded, [this, task]() { From 82dde9f426c9e6377fa25083593dd35c149e88f3 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 14:48:28 +0000 Subject: [PATCH 45/54] Update copyright years --- launcher/net/Download.cpp | 2 +- launcher/net/Download.h | 2 +- launcher/ui/MainWindow.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 2150fb14..f92d4de7 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2023 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/launcher/net/Download.h b/launcher/net/Download.h index c5102421..343ce7cd 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2023 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8a6688d2..0a747734 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2023 MultiMC Contributors * * Authors: Andrew Okin * Peterix From e463edb185b0a65e0e170e96a8ea6842073aebc7 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 15:55:49 +0000 Subject: [PATCH 46/54] GH-4699 Warn when exporting instance with custom components --- launcher/ui/dialogs/ModrinthExportDialog.cpp | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index 4b0d3682..58db2322 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -89,23 +89,23 @@ void ModrinthExportDialog::accept() MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast(m_instance); minecraftInstance->getPackProfile()->reload(Net::Mode::Offline); - auto minecraftComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraft"); - auto forgeComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraftforge"); - auto fabricComponent = minecraftInstance->getPackProfile()->getComponent("net.fabricmc.fabric-loader"); - auto quiltComponent = minecraftInstance->getPackProfile()->getComponent("org.quiltmc.quilt-loader"); + for (int i = 0; i < minecraftInstance->getPackProfile()->rowCount(); i++) { + auto component = minecraftInstance->getPackProfile()->getComponent(i); + if (component->isCustom()) { + CustomMessageBox::selectable( + this, + tr("Warning"), + tr("Instance contains a custom component: %1\nThis cannot be exported to a Modrinth pack; the exported pack may not work correctly!") + .arg(component->getName()), + QMessageBox::Warning + )->exec(); + } + } - if (minecraftComponent) { - settings.gameVersion = minecraftComponent->getVersion(); - } - if (forgeComponent) { - settings.forgeVersion = forgeComponent->getVersion(); - } - if (fabricComponent) { - settings.fabricVersion = fabricComponent->getVersion(); - } - if (quiltComponent) { - settings.quiltVersion = quiltComponent->getVersion(); - } + settings.gameVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.minecraft"); + settings.forgeVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.minecraftforge"); + settings.fabricVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader"); + settings.forgeVersion = minecraftInstance->getPackProfile()->getComponentVersion("org.quiltmc.quilt-loader"); settings.exportPath = ui->file->text(); From c7b0d1539371a52fd4e159dc3fa33478d67f4fef Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 16:33:20 +0000 Subject: [PATCH 47/54] NOISSUE Fix build on Qt 5.4 (hopefully) --- launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp index 1def5152..4b2a6268 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp @@ -141,8 +141,6 @@ void InstanceExportTask::lookupSucceeded() } } - qDebug() << "Failed files: " << failedFiles; - QJsonObject indexJson; indexJson.insert("formatVersion", QJsonValue(1)); indexJson.insert("game", QJsonValue("minecraft")); @@ -193,7 +191,7 @@ void InstanceExportTask::lookupSucceeded() QTemporaryDir tmp; if (tmp.isValid()) { - Json::write(indexJson, tmp.filePath("modrinth.index.json")); + Json::write(indexJson, tmp.path() + "/modrinth.index.json"); if (!failedFiles.isEmpty()) { QDir tmpDir(tmp.path()); From bea3251d9c0e9fccee163eb22b6d9b78591815ba Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 16:44:44 +0000 Subject: [PATCH 48/54] NOISSUE Fix typo in Modrinth exporter --- launcher/ui/dialogs/ModrinthExportDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index 58db2322..1762f9a6 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -105,7 +105,7 @@ void ModrinthExportDialog::accept() settings.gameVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.minecraft"); settings.forgeVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.minecraftforge"); settings.fabricVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader"); - settings.forgeVersion = minecraftInstance->getPackProfile()->getComponentVersion("org.quiltmc.quilt-loader"); + settings.quiltVersion = minecraftInstance->getPackProfile()->getComponentVersion("org.quiltmc.quilt-loader"); settings.exportPath = ui->file->text(); From 496440cbc81dde34f8ee3851425dffef9b9fec9e Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 17:36:44 +0000 Subject: [PATCH 49/54] NOISSUE Handle Modrinth files with multiple downloads --- .../modrinth/ModrinthInstanceExportTask.cpp | 15 ++++++++++++++- .../modrinth/ModrinthInstanceExportTask.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp index 4b2a6268..4112c5f7 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp @@ -88,6 +88,7 @@ void InstanceExportTask::executeTask() m_responses.append(HashLookupData{ QFileInfo(file), + hash, QByteArray() }); @@ -117,7 +118,19 @@ void InstanceExportTask::lookupSucceeded() try { auto document = Json::requireDocument(data.response); auto object = Json::requireObject(document); - auto file = Json::requireIsArrayOf(object, "files").first(); + auto files = Json::requireIsArrayOf(object, "files"); + + QJsonObject file; + + for (const auto &fileJson : files) { + auto hashes = Json::requireObject(fileJson, "hashes"); + QString sha512 = Json::requireString(hashes, "sha512"); + + if (sha512 == data.sha512) { + file = fileJson; + } + } + auto url = Json::requireString(file, "url"); auto hashes = Json::requireObject(file, "hashes"); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h index ff59f45b..c1592bc2 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h @@ -38,6 +38,7 @@ struct ExportSettings struct HashLookupData { QFileInfo fileInfo; + QString sha512; QByteArray response; }; From 4f95e4618fb61710e76ba68c32bff4e9e6bc04ce Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 21:09:16 +0000 Subject: [PATCH 50/54] NOISSUE Modrinth exporter: lookup all hashes in one request --- launcher/CMakeLists.txt | 2 + .../modrinth/ModrinthHashLookupRequest.cpp | 124 ++++++++++++++++++ .../modrinth/ModrinthHashLookupRequest.h | 55 ++++++++ .../modrinth/ModrinthInstanceExportTask.cpp | 78 +++++------ .../modrinth/ModrinthInstanceExportTask.h | 10 +- 5 files changed, 219 insertions(+), 50 deletions(-) create mode 100644 launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp create mode 100644 launcher/modplatform/modrinth/ModrinthHashLookupRequest.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e813835d..95db0882 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -529,6 +529,8 @@ set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackManifest.h modplatform/modrinth/ModrinthInstanceExportTask.h modplatform/modrinth/ModrinthInstanceExportTask.cpp + modplatform/modrinth/ModrinthHashLookupRequest.h + modplatform/modrinth/ModrinthHashLookupRequest.cpp ) add_unit_test(Index diff --git a/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp b/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp new file mode 100644 index 00000000..de7e3a79 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp @@ -0,0 +1,124 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include +#include +#include "ModrinthHashLookupRequest.h" +#include "BuildConfig.h" +#include "Json.h" + +namespace Modrinth +{ + +HashLookupRequest::HashLookupRequest(QList hashes, QList *output) : NetAction(), m_hashes(hashes), m_output(output) +{ + m_url = "https://api.modrinth.com/v2/version_files"; + m_status = Job_NotStarted; +} + +void HashLookupRequest::startImpl() +{ + finished = false; + m_status = Job_InProgress; + + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QJsonObject requestObject; + QJsonArray hashes; + + for (const auto &data : m_hashes) { + hashes.append(data.hash); + } + + requestObject.insert("hashes", hashes); + requestObject.insert("algorithm", QJsonValue("sha512")); + + QNetworkReply *rep = m_network->post(request, QJsonDocument(requestObject).toJson()); + m_reply.reset(rep); + connect(rep, &QNetworkReply::uploadProgress, this, &HashLookupRequest::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &HashLookupRequest::downloadFinished); + connect(rep, &QNetworkReply::errorOccurred, this, &HashLookupRequest::downloadError); +} + +void HashLookupRequest::downloadError(QNetworkReply::NetworkError error) +{ + qCritical() << "Modrinth hash lookup request failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); + if (finished) { + qCritical() << "Double finished ModrinthHashLookupRequest!"; + return; + } + m_status = Job_Failed; + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); +} + +void HashLookupRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); +} + +void HashLookupRequest::downloadFinished() +{ + if (finished) { + qCritical() << "Double finished ModrinthHashLookupRequest!"; + return; + } + + QByteArray data = m_reply->readAll(); + m_reply.reset(); + + try { + auto document = Json::requireDocument(data); + auto rootObject = Json::requireObject(document); + + for (const auto &hashData : m_hashes) { + if (rootObject.contains(hashData.hash)) { + auto versionObject = Json::requireObject(rootObject, hashData.hash); + + auto files = Json::requireIsArrayOf(versionObject, "files"); + + QJsonObject file; + + for (const auto &fileJson : files) { + auto hashes = Json::requireObject(fileJson, "hashes"); + QString sha512 = Json::requireString(hashes, "sha512"); + + if (sha512 == hashData.hash) { + file = fileJson; + } + } + + m_output->append(HashLookupResponseData { + hashData.fileInfo, + true, + file + }); + } else { + m_output->append(HashLookupResponseData { + hashData.fileInfo, + false, + QJsonObject() + }); + } + } + + m_status = Job_Finished; + finished = true; + emit succeeded(m_index_within_job); + } catch (const Json::JsonException &e) { + qCritical() << "Failed to parse Modrinth hash lookup response: " << e.cause(); + m_status = Job_Failed; + finished = true; + emit failed(m_index_within_job); + } +} +} \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthHashLookupRequest.h b/launcher/modplatform/modrinth/ModrinthHashLookupRequest.h new file mode 100644 index 00000000..a3a803f1 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthHashLookupRequest.h @@ -0,0 +1,55 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include +#include "net/NetAction.h" + +namespace Modrinth +{ + +struct HashLookupData +{ + QFileInfo fileInfo; + QString hash; +}; + +struct HashLookupResponseData +{ + QFileInfo fileInfo; + bool found; + QJsonObject fileJson; +}; + +class HashLookupRequest : public NetAction +{ +public: + using Ptr = shared_qobject_ptr; + + explicit HashLookupRequest(QList hashes, QList *output); + static Ptr make(QList hashes, QList *output) { + return Ptr(new HashLookupRequest(hashes, output)); + } + +protected slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void downloadFinished() override; + void downloadReadyRead() override {} + +public slots: + void startImpl() override; + +private: + QList m_hashes; + std::shared_ptr> m_output; + bool finished = true; +}; + +} \ No newline at end of file diff --git a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp index 4112c5f7..7f33c4de 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp @@ -16,6 +16,7 @@ #include "ui/dialogs/ModrinthExportDialog.h" #include "JlCompress.h" #include "FileSystem.h" +#include "ModrinthHashLookupRequest.h" namespace Modrinth { @@ -76,6 +77,10 @@ void InstanceExportTask::executeTask() m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); + QList hashes; + + qint64 progress = 0; + setProgress(progress, filesToResolve.length()); for (const QString &filePath: filesToResolve) { qDebug() << "Attempting to resolve file hash from Modrinth API: " << filePath; QFile file(filePath); @@ -86,20 +91,20 @@ void InstanceExportTask::executeTask() hasher.addData(contents); QString hash = hasher.result().toHex(); - m_responses.append(HashLookupData{ - QFileInfo(file), - hash, - QByteArray() + hashes.append(HashLookupData { + QFileInfo(file), + hash }); - m_netJob->addNetAction(Net::Download::makeByteArray( - QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha512").arg(hash), - &m_responses.last().response, - Net::Download::Options(Net::Download::Option::AllowNotFound) - )); + progress++; + setProgress(progress, filesToResolve.length()); } } + m_response.reset(new QList); + + m_netJob->addNetAction(HashLookupRequest::make(hashes, m_response.get())); + connect(m_netJob.get(), &NetJob::succeeded, this, &InstanceExportTask::lookupSucceeded); connect(m_netJob.get(), &NetJob::failed, this, &InstanceExportTask::lookupFailed); connect(m_netJob.get(), &NetJob::progress, this, &InstanceExportTask::lookupProgress); @@ -114,43 +119,32 @@ void InstanceExportTask::lookupSucceeded() QList resolvedFiles; QFileInfoList failedFiles; - for (const auto &data : m_responses) { - try { - auto document = Json::requireDocument(data.response); - auto object = Json::requireObject(document); - auto files = Json::requireIsArrayOf(object, "files"); + for (const auto &file : *m_response) { + if (file.found) { + try { + auto url = Json::requireString(file.fileJson, "url"); + auto hashes = Json::requireObject(file.fileJson, "hashes"); - QJsonObject file; + QString sha512Hash = Json::requireString(hashes, "sha512"); + QString sha1Hash = Json::requireString(hashes, "sha1"); - for (const auto &fileJson : files) { - auto hashes = Json::requireObject(fileJson, "hashes"); - QString sha512 = Json::requireString(hashes, "sha512"); + ExportFile fileData; - if (sha512 == data.sha512) { - file = fileJson; - } + QDir gameDir(m_instance->gameRoot()); + + fileData.path = gameDir.relativeFilePath(file.fileInfo.absoluteFilePath()); + fileData.download = url; + fileData.sha512 = sha512Hash; + fileData.sha1 = sha1Hash; + fileData.fileSize = file.fileInfo.size(); + + resolvedFiles << fileData; + } catch (const Json::JsonException &e) { + qDebug() << "File " << file.fileInfo.absoluteFilePath() << " failed to process for reason " << e.cause() << ", adding to overrides"; + failedFiles << file.fileInfo; } - - auto url = Json::requireString(file, "url"); - auto hashes = Json::requireObject(file, "hashes"); - - QString sha512Hash = Json::requireString(hashes, "sha512"); - QString sha1Hash = Json::requireString(hashes, "sha1"); - - ExportFile fileData; - - QDir gameDir(m_instance->gameRoot()); - - fileData.path = gameDir.relativeFilePath(data.fileInfo.absoluteFilePath()); - fileData.download = url; - fileData.sha512 = sha512Hash; - fileData.sha1 = sha1Hash; - fileData.fileSize = data.fileInfo.size(); - - resolvedFiles << fileData; - } catch (const Json::JsonException &e) { - qDebug() << "File " << data.fileInfo.absoluteFilePath() << " failed to process for reason " << e.cause() << ", adding to overrides"; - failedFiles << data.fileInfo; + } else { + failedFiles << file.fileInfo; } } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h index c1592bc2..4a6010c8 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h @@ -11,6 +11,7 @@ #include "BaseInstance.h" #include "net/NetJob.h" #include "ui/dialogs/ModrinthExportDialog.h" +#include "ModrinthHashLookupRequest.h" namespace Modrinth { @@ -35,13 +36,6 @@ struct ExportSettings QString exportPath; }; -struct HashLookupData -{ - QFileInfo fileInfo; - QString sha512; - QByteArray response; -}; - // Using the existing Modrinth::File struct from the importer doesn't actually make much sense here (doesn't support multiple hashes, hash is a byte array rather than a string, no file size, etc) struct ExportFile { @@ -71,7 +65,7 @@ private slots: private: InstancePtr m_instance; ExportSettings m_settings; - QList m_responses; + std::shared_ptr> m_response; NetJob::Ptr m_netJob; }; From 413397c3c1af5d0b1ef29dfca03d1a0342820907 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Mon, 6 Feb 2023 07:36:34 +0000 Subject: [PATCH 51/54] NOISSUE Fix build on Qt 5.4 again --- launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp b/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp index de7e3a79..4edb9128 100644 --- a/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp +++ b/launcher/modplatform/modrinth/ModrinthHashLookupRequest.cpp @@ -43,7 +43,7 @@ void HashLookupRequest::startImpl() m_reply.reset(rep); connect(rep, &QNetworkReply::uploadProgress, this, &HashLookupRequest::downloadProgress); connect(rep, &QNetworkReply::finished, this, &HashLookupRequest::downloadFinished); - connect(rep, &QNetworkReply::errorOccurred, this, &HashLookupRequest::downloadError); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); } void HashLookupRequest::downloadError(QNetworkReply::NetworkError error) From c1ed09e74765e7e362c644685b49b77529b748af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 10 Mar 2023 19:56:17 +0100 Subject: [PATCH 52/54] NOISSUE add an interface to pass information to CraftPresence Specifically, the icon key and instance title. --- launcher/BaseInstance.cpp | 5 +++++ launcher/BaseInstance.h | 2 ++ launcher/minecraft/MinecraftInstance.cpp | 2 ++ launcher/translations/TranslationsModel.cpp | 2 -- .../launcher/org/multimc/onesix/OneSixLauncher.java | 10 ++++++++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 5602c728..928a70a5 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -313,6 +313,11 @@ QString BaseInstance::windowTitle() const return BuildConfig.LAUNCHER_NAME + ": " + name().replace(QRegExp("[ \n\r\t]+"), " "); } +QString BaseInstance::instanceTitle() const +{ + return name().replace(QRegExp("[ \n\r\t]+"), " "); +} + // FIXME: why is this here? move it to MinecraftInstance!!! QStringList BaseInstance::extraArguments() const { diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9f8e58d3..c148f37e 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -110,6 +110,8 @@ public: /// Value used for instance window titles QString windowTitle() const; + QString instanceTitle() const; + QString iconKey() const; void setIconKey(QString val); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index dbb9c98f..24650235 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -513,6 +513,8 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS .arg(settings()->get("MinecraftWinHeight").toInt()); launchScript += "windowTitle " + windowTitle() + "\n"; launchScript += "windowParams " + windowParams + "\n"; + launchScript += "instanceTitle " + instanceTitle() + "\n"; + launchScript += "instanceIconId " + iconKey() + "\n"; } // legacy auth diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 5064eebd..3ce89391 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -192,7 +192,6 @@ void readIndex(const QString & path, QMap& languages) return; } - int index = 1; try { auto toplevel_doc = Json::requireDocument(data); @@ -225,7 +224,6 @@ void readIndex(const QString & path, QMap& languages) lang.file_size = Json::requireInteger(langObj, "size"); languages.insert(lang.key, lang); - index++; } } catch (Json::JsonException & e) diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java index d341cad7..7c4bab3d 100644 --- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java +++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java @@ -42,6 +42,9 @@ public class OneSixLauncher implements Launcher private String windowTitle; private String windowParams; + private String instanceTitle; + private String instanceIconId; + // secondary parameters private int winSizeW; private int winSizeH; @@ -68,6 +71,13 @@ public class OneSixLauncher implements Launcher windowTitle = params.firstSafe("windowTitle", "Minecraft"); windowParams = params.firstSafe("windowParams", "854x480"); + instanceTitle = params.firstSafe("instanceTitle", "Minecraft"); + instanceIconId = params.firstSafe("instanceIconId", "default"); + + // NOTE: this is included for the CraftPresence mod + System.setProperty("multimc.instance.title", instanceTitle); + System.setProperty("multimc.instance.icon", instanceIconId); + serverAddress = params.firstSafe("serverAddress", null); serverPort = params.firstSafe("serverPort", null); From 22f82c34bf830e5823e57d724cdf3af957323696 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sat, 8 Apr 2023 10:29:12 +0100 Subject: [PATCH 53/54] NOISSUE Add support for joining servers via Quick Play --- launcher/minecraft/MinecraftInstance.cpp | 15 +++++++++++--- launcher/minecraft/VersionFilterData.cpp | 7 ++++--- launcher/minecraft/VersionFilterData.h | 2 ++ .../org/multimc/onesix/OneSixLauncher.java | 20 ++++++++++++++----- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 24650235..19256e30 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -48,6 +48,7 @@ #include "MinecraftLoadAndCheck.h" #include "minecraft/gameoptions/GameOptions.h" #include "minecraft/update/FoldersTask.h" +#include "minecraft/VersionFilterData.h" #define IBUS "@im=ibus" @@ -425,10 +426,17 @@ QStringList MinecraftInstance::processMinecraftArgs( if (serverToJoin && !serverToJoin->address.isEmpty()) { - args_pattern += " --server " + serverToJoin->address; - args_pattern += " --port " + QString::number(serverToJoin->port); + if (m_components->getComponent("net.minecraft")->getReleaseDateTime() >= g_VersionFilterData.quickPlayBeginsDate) + { + args_pattern += " --quickPlayMultiplayer " + serverToJoin->address + ":" + QString::number(serverToJoin->port); + } + else + { + args_pattern += " --server " + serverToJoin->address; + args_pattern += " --port " + QString::number(serverToJoin->port); + } } - + QMap token_mapping; // yggdrasil! if(session) { @@ -489,6 +497,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS if (serverToJoin && !serverToJoin->address.isEmpty()) { + launchScript += "useQuickPlay " + QString::number(m_components->getComponent("net.minecraft")->getReleaseDateTime() >= g_VersionFilterData.quickPlayBeginsDate) + "\n"; launchScript += "serverAddress " + serverToJoin->address + "\n"; launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n"; } diff --git a/launcher/minecraft/VersionFilterData.cpp b/launcher/minecraft/VersionFilterData.cpp index c286d266..64ae488c 100644 --- a/launcher/minecraft/VersionFilterData.cpp +++ b/launcher/minecraft/VersionFilterData.cpp @@ -66,7 +66,8 @@ VersionFilterData::VersionFilterData() "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; - java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00"); - java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00"); - java17BeginsDate = timeFromS3Time("2021-11-16T17:04:48+00:00"); + java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00"); + java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00"); + java17BeginsDate = timeFromS3Time("2021-11-16T17:04:48+00:00"); + quickPlayBeginsDate = timeFromS3Time("2023-04-05T12:05:17+00:00"); } diff --git a/launcher/minecraft/VersionFilterData.h b/launcher/minecraft/VersionFilterData.h index 13445a51..8ff0a00c 100644 --- a/launcher/minecraft/VersionFilterData.h +++ b/launcher/minecraft/VersionFilterData.h @@ -27,5 +27,7 @@ struct VersionFilterData QDateTime java16BeginsDate; // release data of first version to require Java 17 (1.18 Pre Release 2) QDateTime java17BeginsDate; + // release date of first version to use --quickPlayMultiplayer instead of --server/--port for directly joining servers + QDateTime quickPlayBeginsDate; }; extern VersionFilterData g_VersionFilterData; diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java index 7c4bab3d..f57ad636 100644 --- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java +++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java @@ -1,4 +1,4 @@ -/* Copyright 2012-2021 MultiMC Contributors +/* Copyright 2012-2023 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,7 @@ public class OneSixLauncher implements Launcher private String serverAddress; private String serverPort; + private boolean useQuickPlay; // the much abused system classloader, for convenience (for further abuse) private ClassLoader cl; @@ -80,6 +81,7 @@ public class OneSixLauncher implements Launcher serverAddress = params.firstSafe("serverAddress", null); serverPort = params.firstSafe("serverPort", null); + useQuickPlay = params.firstSafe("useQuickPlay").startsWith("1"); cwd = System.getProperty("user.dir"); @@ -185,10 +187,18 @@ public class OneSixLauncher implements Launcher if (serverAddress != null) { - mcparams.add("--server"); - mcparams.add(serverAddress); - mcparams.add("--port"); - mcparams.add(serverPort); + if (useQuickPlay) + { + mcparams.add("--quickPlayMultiplayer"); + mcparams.add(serverAddress + ":" + serverPort); + } + else + { + mcparams.add("--server"); + mcparams.add(serverAddress); + mcparams.add("--port"); + mcparams.add(serverPort); + } } // Get the Minecraft Class. From b0844db1dff8c2a2a57cda11055ddc227189ee9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 14 May 2023 23:17:06 +0200 Subject: [PATCH 54/54] Create dispatch.yml --- .github/workflows/dispatch.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/dispatch.yml diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml new file mode 100644 index 00000000..097524cf --- /dev/null +++ b/.github/workflows/dispatch.yml @@ -0,0 +1,20 @@ +name: Dispatcher +on: + push: + branches: ['6'] +jobs: + dispatch: + name: Dispatch + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Extract branch name + shell: bash + run: echo "branch=$(echo ${GITHUB_REF#refs/heads/})" >>$GITHUB_OUTPUT + id: extract_branch + - name: Dispatch to workflows + run: | + curl -H "Accept: application/vnd.github.everest-preview+json" \ + -H "Authorization: token ${{ secrets.DISPATCH_TOKEN }}" \ + --request POST \ + --data '{"event_type": "push_to_main_repo", "client_payload": { "branch": "${{ steps.extract_branch.outputs.branch }}" }}' https://api.github.com/repos/MultiMC/Build/dispatches