From 580fc3251810009dc38c590253648b8f5784ce76 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 16 Sep 2024 16:41:16 +0300 Subject: [PATCH 01/37] split the auth loading screen Signed-off-by: Trial97 --- launcher/ui/dialogs/MSALoginDialog.cpp | 14 +- launcher/ui/dialogs/MSALoginDialog.h | 3 +- launcher/ui/dialogs/MSALoginDialog.ui | 262 ++++++++++++++++--------- 3 files changed, 180 insertions(+), 99 deletions(-) diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index 799b5b332..4e38b4187 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -78,7 +78,7 @@ int MSALoginDialog::exec() connect(m_authflow_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); connect(m_authflow_task.get(), &Task::succeeded, this, &QDialog::accept); connect(m_authflow_task.get(), &Task::aborted, this, &MSALoginDialog::reject); - connect(m_authflow_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); + connect(m_authflow_task.get(), &Task::status, this, &MSALoginDialog::onAuthFlowStatus); connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra); connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_authflow_task.get(), &Task::abort); @@ -87,7 +87,7 @@ int MSALoginDialog::exec() connect(m_devicecode_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed); connect(m_devicecode_task.get(), &Task::succeeded, this, &QDialog::accept); connect(m_devicecode_task.get(), &Task::aborted, this, &MSALoginDialog::reject); - connect(m_devicecode_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus); + connect(m_devicecode_task.get(), &Task::status, this, &MSALoginDialog::onDeviceFlowStatus); connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser); connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra); connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_devicecode_task.get(), &Task::abort); @@ -132,7 +132,7 @@ void MSALoginDialog::onTaskFailed(QString reason) void MSALoginDialog::authorizeWithBrowser(const QUrl& url) { - ui->stackedWidget->setCurrentIndex(1); + ui->stackedWidget2->setCurrentIndex(1); ui->loginButton->setToolTip(QString("
%1
").arg(url.toString())); m_url = url; } @@ -152,12 +152,18 @@ void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, in } } -void MSALoginDialog::onTaskStatus(QString status) +void MSALoginDialog::onDeviceFlowStatus(QString status) { ui->stackedWidget->setCurrentIndex(0); ui->status->setText(status); } +void MSALoginDialog::onAuthFlowStatus(QString status) +{ + ui->stackedWidget2->setCurrentIndex(0); + ui->status2->setText(status); +} + // Public interface MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent) { diff --git a/launcher/ui/dialogs/MSALoginDialog.h b/launcher/ui/dialogs/MSALoginDialog.h index 70f480ca9..375ccc57a 100644 --- a/launcher/ui/dialogs/MSALoginDialog.h +++ b/launcher/ui/dialogs/MSALoginDialog.h @@ -40,7 +40,8 @@ class MSALoginDialog : public QDialog { protected slots: void onTaskFailed(QString reason); - void onTaskStatus(QString status); + void onDeviceFlowStatus(QString status); + void onAuthFlowStatus(QString status); void authorizeWithBrowser(const QUrl& url); void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn); diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui index c6821782f..69cd2e1ab 100644 --- a/launcher/ui/dialogs/MSALoginDialog.ui +++ b/launcher/ui/dialogs/MSALoginDialog.ui @@ -7,7 +7,7 @@ 0 0 440 - 430 + 447 @@ -20,6 +20,171 @@ Add Microsoft Account + + + + 1 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 16 + 75 + true + + + + Please wait... + + + Qt::AlignCenter + + + true + + + + + + + Status + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 250 + 40 + + + + Sign in with Microsoft + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + + 16 + + + + Or + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + @@ -28,7 +193,7 @@ - + Qt::Vertical @@ -89,98 +254,7 @@ - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 250 - 40 - - - - Sign in with Microsoft - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - - 16 - - - - Or - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - + From dfe3cd849def92682c45610e65bde3f6f5344a39 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 2 Oct 2024 01:06:45 +0300 Subject: [PATCH 02/37] add curseforge modpack filter Signed-off-by: Trial97 --- launcher/modplatform/ModIndex.h | 2 +- launcher/modplatform/flame/FlameAPI.cpp | 10 ++- launcher/modplatform/flame/FlameAPI.h | 17 +++-- launcher/modplatform/flame/FlamePackIndex.cpp | 22 +++++- launcher/modplatform/flame/FlamePackIndex.h | 1 + launcher/modplatform/modrinth/ModrinthAPI.cpp | 11 ++- launcher/modplatform/modrinth/ModrinthAPI.h | 1 + .../ui/pages/modplatform/flame/FlameModel.cpp | 27 +++---- .../ui/pages/modplatform/flame/FlameModel.h | 4 +- .../ui/pages/modplatform/flame/FlamePage.cpp | 65 +++++++++++++++- .../ui/pages/modplatform/flame/FlamePage.h | 5 ++ .../ui/pages/modplatform/flame/FlamePage.ui | 69 ++++++++++------- .../modplatform/flame/FlameResourcePages.cpp | 6 +- .../modplatform/flame/FlameResourcePages.h | 3 + launcher/ui/widgets/ModFilterWidget.cpp | 74 +++++++++++++++---- 15 files changed, 242 insertions(+), 75 deletions(-) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index e3fe69e0c..d5ee12473 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -35,7 +35,7 @@ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) enum class ResourceProvider { MODRINTH, FLAME }; -enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; +enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, MODPACK }; enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN }; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 72437976d..ddd48c2b1 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -221,14 +221,20 @@ QList FlameAPI::getSortingMethods() const { 8, "GameVersion", QObject::tr("Sort by Game Version") } }; } -Task::Ptr FlameAPI::getModCategories(std::shared_ptr response) +Task::Ptr FlameAPI::getCategories(std::shared_ptr response, ModPlatform::ResourceType type) { auto netJob = makeShared(QString("Flame::GetCategories"), APPLICATION->network()); - netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl("https://api.curseforge.com/v1/categories?gameId=432&classId=6"), response)); + netJob->addNetAction(Net::ApiDownload::makeByteArray( + QUrl(QString("https://api.curseforge.com/v1/categories?gameId=432&classId=%1").arg(getClassId(type))), response)); QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; }); return netJob; } +Task::Ptr FlameAPI::getModCategories(std::shared_ptr response) +{ + return getCategories(response, ModPlatform::ResourceType::MOD); +} + QList FlameAPI::loadModCategories(std::shared_ptr response) { QList categories; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 1160151c5..c66ece8a3 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -25,6 +25,7 @@ class FlameAPI : public NetworkResourceAPI { Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr response) const; Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr response) const; + static Task::Ptr getCategories(std::shared_ptr response, ModPlatform::ResourceType type); static Task::Ptr getModCategories(std::shared_ptr response); static QList loadModCategories(std::shared_ptr response); @@ -46,6 +47,8 @@ class FlameAPI : public NetworkResourceAPI { return 12; case ModPlatform::ResourceType::SHADER_PACK: return 6552; + case ModPlatform::ResourceType::MODPACK: + return 4471; } } @@ -82,8 +85,8 @@ class FlameAPI : public NetworkResourceAPI { static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; } - private: - [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override + public: + static std::optional getStaticSearchURL(SearchArgs const& args) { auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); @@ -105,12 +108,14 @@ class FlameAPI : public NetworkResourceAPI { get_arguments.append(gameVersionStr); return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); - }; + } + [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override { return getStaticSearchURL(args); } + private: [[nodiscard]] std::optional getInfoURL(QString const& id) const override { return QString("https://api.curseforge.com/v1/mods/%1").arg(id); - }; + } [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override { @@ -125,7 +130,7 @@ class FlameAPI : public NetworkResourceAPI { url += QString("&modLoaderType=%1").arg(mappedModLoader); } return url; - }; + } [[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override { @@ -137,5 +142,5 @@ class FlameAPI : public NetworkResourceAPI { url += QString("&modLoaderType=%1").arg(mappedModLoader); } return url; - }; + } }; diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index ca8e0a853..8c25b0482 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -3,6 +3,7 @@ #include #include "Json.h" +#include "modplatform/ModIndex.h" void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) { @@ -88,8 +89,27 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) continue; } + for (auto mcVer : versionArray) { + auto str = mcVer.toString(); + + if (str.contains('.')) + file.mcVersion.append(str); + + if (auto loader = str.toLower(); loader == "neoforge") + file.loaders |= ModPlatform::NeoForge; + else if (loader == "forge") + file.loaders |= ModPlatform::Forge; + else if (loader == "cauldron") + file.loaders |= ModPlatform::Cauldron; + else if (loader == "liteloader") + file.loaders |= ModPlatform::LiteLoader; + else if (loader == "fabric") + file.loaders |= ModPlatform::Fabric; + else if (loader == "quilt") + file.loaders |= ModPlatform::Quilt; + } + // pick the latest version supported - file.mcVersion = versionArray[0].toString(); file.version = Json::requireString(version, "displayName"); ModPlatform::IndexedVersionType::VersionType ver_type; diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index b2a12a67f..11633deee 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -18,6 +18,7 @@ struct IndexedVersion { int fileId; QString version; ModPlatform::IndexedVersionType version_type; + ModPlatform::ModLoaderTypes loaders = {}; QString mcVersion; QString downloadUrl; }; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 4798ace84..323711e02 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -129,7 +129,7 @@ Task::Ptr ModrinthAPI::getModCategories(std::shared_ptr response) return netJob; } -QList ModrinthAPI::loadModCategories(std::shared_ptr response) +QList ModrinthAPI::loadCategories(std::shared_ptr response, QString projectType) { QList categories; QJsonParseError parse_error{}; @@ -147,7 +147,7 @@ QList ModrinthAPI::loadModCategories(std::shared_ptr ModrinthAPI::loadModCategories(std::shared_ptr ModrinthAPI::loadModCategories(std::shared_ptr response) +{ + return loadCategories(response, "mod"); +}; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index d1f8f712a..07be06756 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -31,6 +31,7 @@ class ModrinthAPI : public NetworkResourceAPI { Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const override; static Task::Ptr getModCategories(std::shared_ptr response); + static QList loadCategories(std::shared_ptr response, QString projectType); static QList loadModCategories(std::shared_ptr response); public: diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index a92d5b579..8383b756f 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -1,6 +1,7 @@ #include "FlameModel.h" #include #include "Application.h" +#include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" #include "modplatform/flame/FlameAPI.h" #include "ui/widgets/ProjectItem.h" @@ -183,34 +184,28 @@ void ListModel::performPaginatedSearch() return; } } - auto netJob = makeShared("Flame::Search", APPLICATION->network()); - auto searchUrl = QString( - "https://api.curseforge.com/v1/mods/search?" - "gameId=432&" - "classId=4471&" - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sortField=%3&" - "sortOrder=desc") - .arg(nextSearchOffset) - .arg(currentSearchTerm) - .arg(currentSort + 1); + ResourceAPI::SortingMethod sort{}; + sort.index = currentSort + 1; - netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response)); + auto netJob = makeShared("Flame::Search", APPLICATION->network()); + auto searchUrl = FlameAPI::getStaticSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, + m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); + + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), response)); jobPtr = netJob; jobPtr->start(); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed); } -void ListModel::searchWithTerm(const QString& term, int sort) +void ListModel::searchWithTerm(const QString& term, int sort, std::shared_ptr filter, bool filterChanged) { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filterChanged) { return; } currentSearchTerm = term; currentSort = sort; + m_filter = filter; if (hasActiveSearchJob()) { jobPtr->abort(); searchState = ResetRequested; diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h index 9b6d70fec..026f6d1ee 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModel.h @@ -14,6 +14,7 @@ #include #include +#include "ui/widgets/ModFilterWidget.h" #include @@ -38,7 +39,7 @@ class ListModel : public QAbstractListModel { void fetchMore(const QModelIndex& parent) override; void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - void searchWithTerm(const QString& term, int sort); + void searchWithTerm(const QString& term, int sort, std::shared_ptr filter, bool filterChanged); [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } @@ -65,6 +66,7 @@ class ListModel : public QAbstractListModel { QString currentSearchTerm; int currentSort = 0; + std::shared_ptr m_filter; int nextSearchOffset = 0; enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; Task::Ptr jobPtr; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 4195683e7..62798f4bc 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -34,10 +34,13 @@ */ #include "FlamePage.h" +#include "modplatform/flame/FlamePackIndex.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/widgets/ModFilterWidget.h" #include "ui_FlamePage.h" #include +#include #include "Application.h" #include "FlameModel.h" @@ -88,6 +91,7 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packDescription->setMetaEntry("FlamePacks"); + createFilterWidget(); } FlamePage::~FlamePage() @@ -131,10 +135,35 @@ void FlamePage::openedImpl() void FlamePage::triggerSearch() { - listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); + ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); + ui->packView->clearSelection(); + ui->packDescription->clear(); + ui->versionSelectionBox->clear(); + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), + m_filterWidget->changed()); m_fetch_progress.watch(listModel->activeSearchJob().get()); } +bool checkMcVersions(std::list filter, QString value) +{ + for (auto mcVersion : filter) { + if (value == mcVersion.toString()) { + return true; + } + } + return filter.empty(); +} + +bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptr filter) +{ + if (!filter) + return true; + return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders + (filter->releases.empty() || // releases + std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && + checkMcVersions(filter->versions, v.mcVersion)); // mcVersions} +} + void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) { ui->versionSelectionBox->clear(); @@ -148,7 +177,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde current = listModel->data(curr, Qt::UserRole).value(); - if (current.versionsLoaded == false) { + if (!current.versionsLoaded || m_filterWidget->changed()) { qDebug() << "Loading flame modpack versions"; auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto response = std::make_shared(); @@ -176,6 +205,16 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde qWarning() << "Error while reading flame modpack version: " << e.cause(); } + auto pred = [this](const Flame::IndexedVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); }; +#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) + current.versions.removeIf(pred); +#else + for (auto it = current.versions.begin(); it != current.versions.end();) + if (pred(*it)) + it = current.versions.erase(it); + else + ++it; +#endif for (auto version : current.versions) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion) @@ -308,3 +347,25 @@ void FlamePage::setSearchTerm(QString term) { ui->searchEdit->setText(term); } + +void FlamePage::createFilterWidget() +{ + auto widget = ModFilterWidget::create(nullptr, false, this); + m_filterWidget.swap(widget); + auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); + // because we replaced the widget we also need to delete it + if (old) { + delete old; + } + + connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); + + connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch); + auto response = std::make_shared(); + m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::MODPACK); + QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { + auto categories = FlameAPI::loadModCategories(response); + m_filterWidget->setCategories(categories); + }); + m_categoriesTask->start(); +} diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index 45a3c6b22..27c96d2f1 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -41,6 +41,7 @@ #include #include #include "ui/pages/modplatform/ModpackProviderBasePage.h" +#include "ui/widgets/ModFilterWidget.h" #include "ui/widgets/ProgressWidget.h" namespace Ui { @@ -84,6 +85,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); + void createFilterWidget(); private: Ui::FlamePage* ui = nullptr; @@ -97,4 +99,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; + + unique_qobject_ptr m_filterWidget; + Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index d4ddb37a4..b028e7569 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -30,42 +30,59 @@ - - - Search and filter... - - - - - + - - - Qt::ScrollBarAlwaysOff - - - true - - - - 48 - 48 - + + + Search and filter... - - - true - - - true + + + Filter + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + true + + + true + + + + diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 62c22902e..ce8c03fa1 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -215,11 +215,11 @@ unique_qobject_ptr FlameModPage::createFilterWidget() void FlameModPage::prepareProviderCategories() { auto response = std::make_shared(); - auto task = FlameAPI::getModCategories(response); - QObject::connect(task.get(), &Task::succeeded, [this, response]() { + m_categoriesTask = FlameAPI::getModCategories(response); + QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { auto categories = FlameAPI::loadModCategories(response); m_filter_widget->setCategories(categories); }); - task->start(); + m_categoriesTask->start(); }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index 6eef3e435..052706549 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -100,6 +100,9 @@ class FlameModPage : public ModPage { protected: virtual void prepareProviderCategories() override; + + private: + Task::Ptr m_categoriesTask; }; class FlameResourcePackPage : public ResourcePackResourcePage { diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index bbb91eac2..a048dbe10 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -68,6 +68,40 @@ class VersionBasicModel : public QIdentityProxyModel { } }; +class AllVersionProxyModel : public QSortFilterProxyModel { + Q_OBJECT + + public: + AllVersionProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} + + int rowCount(const QModelIndex& parent = QModelIndex()) const override { return QSortFilterProxyModel::rowCount(parent) + 1; } + + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override + { + if (!index.isValid()) { + return {}; + } + + if (index.row() == 0) { + if (role == Qt::DisplayRole) { + return tr("All Versions"); + } + return {}; + } + + QModelIndex newIndex = QSortFilterProxyModel::index(index.row() - 1, index.column()); + return QSortFilterProxyModel::data(newIndex, role); + } + + Qt::ItemFlags flags(const QModelIndex& index) const override + { + if (index.row() == 0) { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } + return QSortFilterProxyModel::flags(index); + } +}; + ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent) : QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter()) { @@ -76,9 +110,15 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi m_versions_proxy = new VersionProxyModel(this); m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release")); - auto proxy = new VersionBasicModel(this); + QAbstractProxyModel* proxy = new VersionBasicModel(this); proxy->setSourceModel(m_versions_proxy); + if (!m_instance && !extended) { + auto allVersions = new AllVersionProxyModel(this); + allVersions->setSourceModel(proxy); + proxy = allVersions; + } + if (extended) { ui->versions->setSourceModel(proxy); ui->versions->setSeparator(", "); @@ -162,18 +202,22 @@ void ModFilterWidget::loadVersionList() void ModFilterWidget::prepareBasicFilter() { - m_filter->hideInstalled = false; - m_filter->side = ""; // or "both" - auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value(); - ui->neoForge->setChecked(loaders & ModPlatform::NeoForge); - ui->forge->setChecked(loaders & ModPlatform::Forge); - ui->fabric->setChecked(loaders & ModPlatform::Fabric); - ui->quilt->setChecked(loaders & ModPlatform::Quilt); - m_filter->loaders = loaders; - auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); - m_filter->versions.emplace_front(def); - ui->versions->setCheckedItems({ def }); - ui->version->setCurrentIndex(ui->version->findText(def)); + if (m_instance) { + m_filter->hideInstalled = false; + m_filter->side = ""; // or "both" + auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value(); + ui->neoForge->setChecked(loaders & ModPlatform::NeoForge); + ui->forge->setChecked(loaders & ModPlatform::Forge); + ui->fabric->setChecked(loaders & ModPlatform::Fabric); + ui->quilt->setChecked(loaders & ModPlatform::Quilt); + m_filter->loaders = loaders; + auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); + m_filter->versions.emplace_front(def); + ui->versions->setCheckedItems({ def }); + ui->version->setCurrentIndex(ui->version->findText(def)); + } else { + ui->hideInstalled->hide(); + } } void ModFilterWidget::onShowAllVersionsChanged() @@ -249,7 +293,9 @@ void ModFilterWidget::onHideInstalledFilterChanged() void ModFilterWidget::onVersionFilterTextChanged(const QString& version) { m_filter->versions.clear(); - m_filter->versions.emplace_back(version); + if (version != tr("All Versions")) { + m_filter->versions.emplace_back(version); + } m_filter_changed = true; emit filterChanged(); } From 010678da53c864f08b0f900265aacdca256a4dd8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:46:28 +0000 Subject: [PATCH 03/37] chore(deps): update cachix/install-nix-action action to v30 --- .github/workflows/build.yml | 2 +- .github/workflows/update-flake.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad77d282b..f578839fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -658,7 +658,7 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: cachix/install-nix-action@v29 + uses: cachix/install-nix-action@v30 # For PRs - name: Setup Nix Magic Cache diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 0c8cfde67..5e978f356 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@9f70348d77d0422624097c4b7a75563948901306 # v29 + - uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30 - uses: DeterminateSystems/update-flake-lock@v24 with: From 859fac604bf698755e73083625c75969b59c5e8a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 4 Oct 2024 17:06:47 +0300 Subject: [PATCH 04/37] add modrinth modpack filter Signed-off-by: Trial97 --- launcher/Version.cpp | 15 +++- launcher/Version.h | 2 + launcher/modplatform/flame/FlameAPI.h | 8 +-- launcher/modplatform/modrinth/ModrinthAPI.h | 19 +++-- .../modrinth/ModrinthPackManifest.cpp | 15 ++++ .../modrinth/ModrinthPackManifest.h | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 12 ---- .../ui/pages/modplatform/flame/FlamePage.cpp | 13 +--- .../modplatform/modrinth/ModrinthModel.cpp | 41 +++++------ .../modplatform/modrinth/ModrinthModel.h | 5 +- .../modplatform/modrinth/ModrinthPage.cpp | 53 +++++++++++++- .../pages/modplatform/modrinth/ModrinthPage.h | 5 ++ .../modplatform/modrinth/ModrinthPage.ui | 69 ++++++++++++------- 13 files changed, 170 insertions(+), 88 deletions(-) diff --git a/launcher/Version.cpp b/launcher/Version.cpp index 511aa9c35..e43d9d3f7 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -123,8 +123,19 @@ QDebug operator<<(QDebug debug, const Version& v) first = false; } - debug.nospace() << " ]" - << " }"; + debug.nospace() << " ]" << " }"; return debug; } + +bool checkMcVersions(std::list filter, QStringList value) +{ + bool valid = false; + for (auto mcVersion : filter) { + if (value.contains(mcVersion.toString())) { + valid = true; + break; + } + } + return filter.empty() || valid; +} diff --git a/launcher/Version.h b/launcher/Version.h index b06e256aa..f596bfeba 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -161,3 +161,5 @@ class Version { void parse(); }; + +bool checkMcVersions(std::list filter, QStringList value); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index c66ece8a3..4abaff4ea 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -88,9 +88,6 @@ class FlameAPI : public NetworkResourceAPI { public: static std::optional getStaticSearchURL(SearchArgs const& args) { - auto gameVersionStr = - args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); - QStringList get_arguments; get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); get_arguments.append(QString("index=%1").arg(args.offset)); @@ -100,12 +97,13 @@ class FlameAPI : public NetworkResourceAPI { if (args.sorting.has_value()) get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); get_arguments.append("sortOrder=desc"); - if (args.loaders.has_value()) + if (args.loaders.has_value() && args.loaders.value() != 0) get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value()))); if (args.categoryIds.has_value() && !args.categoryIds->empty()) get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(","))); - get_arguments.append(gameVersionStr); + if (args.versions.has_value() && !args.versions.value().empty()) + get_arguments.append(QString("gameVersion=%1").arg(args.versions.value().front().toString())); return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); } diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 07be06756..b84d281aa 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -91,6 +91,8 @@ class ModrinthAPI : public NetworkResourceAPI { return "resourcepack"; case ModPlatform::ResourceType::SHADER_PACK: return "shader"; + case ModPlatform::ResourceType::MODPACK: + return "modpack"; default: qWarning() << "Invalid resource type for Modrinth API!"; break; @@ -99,13 +101,13 @@ class ModrinthAPI : public NetworkResourceAPI { return ""; } - [[nodiscard]] QString createFacets(SearchArgs const& args) const + [[nodiscard]] static QString createFacets(SearchArgs const& args) { QStringList facets_list; - if (args.loaders.has_value()) + if (args.loaders.has_value() && args.loaders.value() != 0) facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); - if (args.versions.has_value()) + if (args.versions.has_value() && !args.versions.value().empty()) facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); if (args.side.has_value()) { auto side = getSideFilters(args.side.value()); @@ -121,9 +123,9 @@ class ModrinthAPI : public NetworkResourceAPI { } public: - [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override + static std::optional getStaticSearchURL(SearchArgs const& args) { - if (args.loaders.has_value()) { + if (args.loaders.has_value() && args.loaders.value() != 0) { if (!validateModLoaders(args.loaders.value())) { qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!"; return {}; @@ -142,6 +144,11 @@ class ModrinthAPI : public NetworkResourceAPI { return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; + [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override + { + return getStaticSearchURL(args); + } + inline auto getInfoURL(QString const& id) const -> std::optional override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; @@ -164,7 +171,7 @@ class ModrinthAPI : public NetworkResourceAPI { .arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; - auto getGameVersionsArray(std::list mcVersions) const -> QString + static QString getGameVersionsArray(std::list mcVersions) { QString s; for (auto& ver : mcVersions) { diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index f360df43a..c52a1743b 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -135,6 +135,21 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion if (!gameVersions.isEmpty()) { file.gameVersion = Json::ensureString(gameVersions[0]); } + auto loaders = Json::requireArray(obj, "loaders"); + for (auto loader : loaders) { + if (loader == "neoforge") + file.loaders |= ModPlatform::NeoForge; + else if (loader == "forge") + file.loaders |= ModPlatform::Forge; + else if (loader == "cauldron") + file.loaders |= ModPlatform::Cauldron; + else if (loader == "liteloader") + file.loaders |= ModPlatform::LiteLoader; + else if (loader == "fabric") + file.loaders |= ModPlatform::Fabric; + else if (loader == "quilt") + file.loaders |= ModPlatform::Quilt; + } file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type")); file.changelog = Json::ensureString(obj, "changelog"); diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h index 2bd61c5d9..2e5e2da84 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.h +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -87,6 +87,7 @@ struct ModpackVersion { QString gameVersion; ModPlatform::IndexedVersionType version_type; QString changelog; + ModPlatform::ModLoaderTypes loaders = {}; QString id; QString project_id; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index e87a423fa..dfc308566 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -104,18 +104,6 @@ bool checkSide(QString filter, QString value) return filter.isEmpty() || value.isEmpty() || filter == "both" || value == "both" || filter == value; } -bool checkMcVersions(std::list filter, QStringList value) -{ - bool valid = false; - for (auto mcVersion : filter) { - if (value.contains(mcVersion.toString())) { - valid = true; - break; - } - } - return filter.empty() || valid; -} - bool ModModel::checkFilters(ModPlatform::IndexedPack::Ptr pack) { if (!m_filter) diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 62798f4bc..b92401988 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -34,6 +34,7 @@ */ #include "FlamePage.h" +#include "Version.h" #include "modplatform/flame/FlamePackIndex.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/widgets/ModFilterWidget.h" @@ -144,16 +145,6 @@ void FlamePage::triggerSearch() m_fetch_progress.watch(listModel->activeSearchJob().get()); } -bool checkMcVersions(std::list filter, QString value) -{ - for (auto mcVersion : filter) { - if (value == mcVersion.toString()) { - return true; - } - } - return filter.empty(); -} - bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptr filter) { if (!filter) @@ -161,7 +152,7 @@ bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptrloaders || !v.loaders || filter->loaders & v.loaders) && // loaders (filter->releases.empty() || // releases std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && - checkMcVersions(filter->versions, v.mcVersion)); // mcVersions} + checkMcVersions(filter->versions, { v.mcVersion })); // mcVersions} } void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index b53eea4ef..132150bd2 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -152,33 +152,26 @@ void ModpackListModel::performPaginatedSearch() return; } } // TODO: Move to standalone API - auto netJob = makeShared("Modrinth::SearchModpack", APPLICATION->network()); - auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + - "/search?" - "offset=%1&" - "limit=%2&" - "query=%3&" - "index=%4&" - "facets=[[\"project_type:modpack\"]]") - .arg(nextSearchOffset) - .arg(m_modpacks_per_page) - .arg(currentSearchTerm) - .arg(currentSort); + ResourceAPI::SortingMethod sort{}; + sort.name = currentSort; + auto searchUrl = ModrinthAPI::getStaticSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, + m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); - netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response)); + auto netJob = makeShared("Modrinth::SearchModpack", APPLICATION->network()); + netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), m_allResponse)); QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] { - QJsonParseError parse_error_all{}; + QJsonParseError parseError{}; - QJsonDocument doc_all = QJsonDocument::fromJson(*m_all_response, &parse_error_all); - if (parse_error_all.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset - << " reason: " << parse_error_all.errorString(); - qWarning() << *m_all_response; + QJsonDocument doc = QJsonDocument::fromJson(*m_allResponse, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parseError.offset + << " reason: " << parseError.errorString(); + qWarning() << *m_allResponse; return; } - searchRequestFinished(doc_all); + searchRequestFinished(doc); }); QObject::connect(netJob.get(), &NetJob::failed, this, &ModpackListModel::searchRequestFailed); @@ -220,19 +213,23 @@ static auto sortFromIndex(int index) -> QString } } -void ModpackListModel::searchWithTerm(const QString& term, const int sort) +void ModpackListModel::searchWithTerm(const QString& term, + const int sort, + std::shared_ptr filter, + bool filterChanged) { if (sort > 5 || sort < 0) return; auto sort_str = sortFromIndex(sort); - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str) { + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str && !filterChanged) { return; } currentSearchTerm = term; currentSort = sort_str; + m_filter = filter; refresh(); } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 514ee4484..640ddf688 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -71,7 +71,7 @@ class ModpackListModel : public QAbstractListModel { /* Ask the API for more information */ void fetchMore(const QModelIndex& parent) override; void refresh(); - void searchWithTerm(const QString& term, int sort); + void searchWithTerm(const QString& term, int sort, std::shared_ptr filter, bool filterChanged); [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } @@ -112,12 +112,13 @@ class ModpackListModel : public QAbstractListModel { QString currentSearchTerm; QString currentSort; + std::shared_ptr m_filter; int nextSearchOffset = 0; enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; Task::Ptr jobPtr; - std::shared_ptr m_all_response = std::make_shared(); + std::shared_ptr m_allResponse = std::make_shared(); QByteArray m_specific_response; int m_modpacks_per_page = 20; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index a000044fa..ff21e8c37 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -35,6 +35,8 @@ */ #include "ModrinthPage.h" +#include "Version.h" +#include "modplatform/modrinth/ModrinthAPI.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ModrinthPage.h" @@ -58,6 +60,7 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false) { ui->setupUi(this); + createFilterWidget(); ui->searchEdit->installEventFilter(this); m_model = new Modrinth::ModpackListModel(this); @@ -126,6 +129,16 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) return QObject::eventFilter(watched, event); } +bool checkVersionFilters(const Modrinth::ModpackVersion& v, std::shared_ptr filter) +{ + if (!filter) + return true; + return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders + (filter->releases.empty() || // releases + std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && + checkMcVersions(filter->versions, { v.gameVersion })); // gameVersion} +} + void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) { ui->versionSelectionBox->clear(); @@ -190,7 +203,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI } else updateUI(); - if (!current.versionsLoaded) { + if (!current.versionsLoaded || m_filterWidget->changed()) { qDebug() << "Loading modrinth modpack versions"; auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network()); @@ -221,6 +234,16 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI qDebug() << *response; qWarning() << "Error while reading modrinth modpack version: " << e.cause(); } + auto pred = [this](const Modrinth::ModpackVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); }; +#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) + current.versions.removeIf(pred); +#else + for (auto it = current.versions.begin(); it != current.versions.end();) + if (pred(*it)) + it = current.versions.erase(it); + else + ++it; +#endif for (auto version : current.versions) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion) @@ -338,7 +361,11 @@ void ModrinthPage::suggestCurrent() void ModrinthPage::triggerSearch() { - m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); + ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect); + ui->packView->clearSelection(); + ui->packDescription->clear(); + ui->versionSelectionBox->clear(); + m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), m_filterWidget->changed()); m_fetch_progress.watch(m_model->activeSearchJob().get()); } @@ -361,3 +388,25 @@ QString ModrinthPage::getSerachTerm() const { return ui->searchEdit->text(); } + +void ModrinthPage::createFilterWidget() +{ + auto widget = ModFilterWidget::create(nullptr, false, this); + m_filterWidget.swap(widget); + auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); + // because we replaced the widget we also need to delete it + if (old) { + delete old; + } + + connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); }); + + connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch); + auto response = std::make_shared(); + m_categoriesTask = ModrinthAPI::getModCategories(response); + QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() { + auto categories = ModrinthAPI::loadCategories(response, "modpack"); + m_filterWidget->setCategories(categories); + }); + m_categoriesTask->start(); +} \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index dd99e0d29..7f504cdbd 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -41,6 +41,7 @@ #include "modplatform/modrinth/ModrinthPackManifest.h" #include "ui/pages/modplatform/ModpackProviderBasePage.h" +#include "ui/widgets/ModFilterWidget.h" #include "ui/widgets/ProgressWidget.h" #include @@ -87,6 +88,7 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(int index); void triggerSearch(); + void createFilterWidget(); private: Ui::ModrinthPage* ui; @@ -100,4 +102,7 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; + + unique_qobject_ptr m_filterWidget; + Task::Ptr m_categoriesTask; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index 7f4f903f6..ef44abb52 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -12,42 +12,59 @@ - - - Search and filter ... - - - - - + - - - Qt::ScrollBarAlwaysOff - - - true - - - - 48 - 48 - + + + Search and filter... - - - true - - - true + + + Filter + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + true + + + true + + + + From cc0c9d208dc4b686a254138f71465199a895ee3a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:22:10 +0000 Subject: [PATCH 05/37] chore(deps): update actions/cache action to v4.1.0 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad77d282b..a659e162c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -169,7 +169,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v4.0.2 + uses: actions/cache@v4.1.0 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From f7bd76c7d4ce0d834f259e4d085b726e8117bd29 Mon Sep 17 00:00:00 2001 From: arnfaldur Date: Sat, 5 Oct 2024 17:58:53 +0000 Subject: [PATCH 06/37] flatpak: don't cleanup libGLU The mod AAA Particles (found in the modpack Prominence II RPG) fails to start due to a missing `libGLU.so.1` file. Removing this cleanup command fixes the issue and makes the modpack run without issue. I reproduced the issue at the head of the develop branch, with and without the change. No other issue was detected by adding the library, and I could not find the initial justification for removing it. Signed-off-by: arnfaldur --- flatpak/org.prismlauncher.PrismLauncher.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index bd09f7fd8..71e6dd11e 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -22,9 +22,6 @@ finish-args: # FTBApp import - --filesystem=~/.ftba:ro -cleanup: - - /lib/libGLU* - modules: # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) - shared-modules/libusb/libusb.json From 6e5f616ced96acee9a9aad76193f82e6a0c2cd27 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 6 Oct 2024 23:33:24 +0300 Subject: [PATCH 07/37] move checkMcVersions to Filter struct Signed-off-by: Trial97 --- launcher/Version.cpp | 12 ------------ launcher/Version.h | 2 -- launcher/ui/pages/modplatform/ModModel.cpp | 2 +- launcher/ui/pages/modplatform/flame/FlamePage.cpp | 2 +- .../ui/pages/modplatform/modrinth/ModrinthPage.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.h | 9 +++++++++ 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/launcher/Version.cpp b/launcher/Version.cpp index e43d9d3f7..2edb17e72 100644 --- a/launcher/Version.cpp +++ b/launcher/Version.cpp @@ -127,15 +127,3 @@ QDebug operator<<(QDebug debug, const Version& v) return debug; } - -bool checkMcVersions(std::list filter, QStringList value) -{ - bool valid = false; - for (auto mcVersion : filter) { - if (value.contains(mcVersion.toString())) { - valid = true; - break; - } - } - return filter.empty() || valid; -} diff --git a/launcher/Version.h b/launcher/Version.h index f596bfeba..b06e256aa 100644 --- a/launcher/Version.h +++ b/launcher/Version.h @@ -161,5 +161,3 @@ class Version { void parse(); }; - -bool checkMcVersions(std::list filter, QStringList value); diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index dfc308566..1f0329321 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -123,7 +123,7 @@ bool ModModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) checkSide(m_filter->side, v.side) && // side (m_filter->releases.empty() || // releases std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.version_type) != m_filter->releases.cend()) && - checkMcVersions(m_filter->versions, v.mcVersion)); // mcVersions + m_filter->checkMcVersions(v.mcVersion)); // mcVersions } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index b92401988..9abf4a9c6 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -152,7 +152,7 @@ bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptrloaders || !v.loaders || filter->loaders & v.loaders) && // loaders (filter->releases.empty() || // releases std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && - checkMcVersions(filter->versions, { v.mcVersion })); // mcVersions} + filter->checkMcVersions({ v.mcVersion })); // mcVersions} } void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index ff21e8c37..2c19d54ad 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -136,7 +136,7 @@ bool checkVersionFilters(const Modrinth::ModpackVersion& v, std::shared_ptrloaders || !v.loaders || filter->loaders & v.loaders) && // loaders (filter->releases.empty() || // releases std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) && - checkMcVersions(filter->versions, { v.gameVersion })); // gameVersion} + filter->checkMcVersions({ v.gameVersion })); // gameVersion} } void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index fdfd2c8bb..50f0e06c9 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -71,6 +71,15 @@ class ModFilterWidget : public QTabWidget { releases == other.releases && categoryIds == other.categoryIds; } bool operator!=(const Filter& other) const { return !(*this == other); } + + bool checkMcVersions(QStringList value) + { + for (auto mcVersion : versions) + if (value.contains(mcVersion.toString())) + return true; + + return versions.empty(); + } }; static unique_qobject_ptr create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr); From 5bd5627a9646361ccee2d96bb555de58072bc7ab Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Oct 2024 15:06:50 +0300 Subject: [PATCH 08/37] extended the modrinth search and applied the suggested change Signed-off-by: Trial97 --- .../modplatform/modrinth/ModrinthPage.cpp | 2 +- launcher/ui/widgets/ModFilterWidget.cpp | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 2c19d54ad..8803c6dd9 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -391,7 +391,7 @@ QString ModrinthPage::getSerachTerm() const void ModrinthPage::createFilterWidget() { - auto widget = ModFilterWidget::create(nullptr, false, this); + auto widget = ModFilterWidget::create(nullptr, true, this); m_filterWidget.swap(widget); auto old = ui->splitter->replaceWidget(0, m_filterWidget.get()); // because we replaced the widget we also need to delete it diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index a048dbe10..68adcdb71 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -64,6 +64,8 @@ class VersionBasicModel : public QIdentityProxyModel { { if (role == Qt::DisplayRole) return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole); + if (role == Qt::UserRole) + return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole); return {}; } }; @@ -86,6 +88,9 @@ class AllVersionProxyModel : public QSortFilterProxyModel { if (role == Qt::DisplayRole) { return tr("All Versions"); } + if (role == Qt::UserRole) { + return "all"; + } return {}; } @@ -113,17 +118,18 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi QAbstractProxyModel* proxy = new VersionBasicModel(this); proxy->setSourceModel(m_versions_proxy); - if (!m_instance && !extended) { + if (extended) { + if (!m_instance) { + ui->environmentGroup->hide(); + } + ui->versions->setSourceModel(proxy); + ui->versions->setSeparator(", "); + ui->versions->setDefaultText(tr("All Versions")); + ui->version->hide(); + } else { auto allVersions = new AllVersionProxyModel(this); allVersions->setSourceModel(proxy); proxy = allVersions; - } - - if (extended) { - ui->versions->setSourceModel(proxy); - ui->versions->setSeparator(", "); - ui->version->hide(); - } else { ui->version->setModel(proxy); ui->versions->hide(); ui->showAllVersions->hide(); @@ -293,7 +299,7 @@ void ModFilterWidget::onHideInstalledFilterChanged() void ModFilterWidget::onVersionFilterTextChanged(const QString& version) { m_filter->versions.clear(); - if (version != tr("All Versions")) { + if (ui->version->currentData(Qt::UserRole) != "all") { m_filter->versions.emplace_back(version); } m_filter_changed = true; From df9f54454ac8cfe40fad525e64e6cc3578f26a77 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Oct 2024 19:19:03 +0300 Subject: [PATCH 09/37] removed static functions Signed-off-by: Trial97 --- launcher/modplatform/flame/FlameAPI.h | 3 +-- launcher/modplatform/modrinth/ModrinthAPI.h | 11 +++-------- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 4 ++-- .../ui/pages/modplatform/modrinth/ModrinthModel.cpp | 4 ++-- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4abaff4ea..3ca0d5448 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -86,7 +86,7 @@ class FlameAPI : public NetworkResourceAPI { static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; } public: - static std::optional getStaticSearchURL(SearchArgs const& args) + [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override { QStringList get_arguments; get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); @@ -107,7 +107,6 @@ class FlameAPI : public NetworkResourceAPI { return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); } - [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override { return getStaticSearchURL(args); } private: [[nodiscard]] std::optional getInfoURL(QString const& id) const override diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index b84d281aa..070f59dad 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -101,7 +101,7 @@ class ModrinthAPI : public NetworkResourceAPI { return ""; } - [[nodiscard]] static QString createFacets(SearchArgs const& args) + [[nodiscard]] QString createFacets(SearchArgs const& args) const { QStringList facets_list; @@ -123,7 +123,7 @@ class ModrinthAPI : public NetworkResourceAPI { } public: - static std::optional getStaticSearchURL(SearchArgs const& args) + [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override { if (args.loaders.has_value() && args.loaders.value() != 0) { if (!validateModLoaders(args.loaders.value())) { @@ -144,11 +144,6 @@ class ModrinthAPI : public NetworkResourceAPI { return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; - [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override - { - return getStaticSearchURL(args); - } - inline auto getInfoURL(QString const& id) const -> std::optional override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; @@ -171,7 +166,7 @@ class ModrinthAPI : public NetworkResourceAPI { .arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; - static QString getGameVersionsArray(std::list mcVersions) + QString getGameVersionsArray(std::list mcVersions) const { QString s; for (auto& ver : mcVersions) { diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 8383b756f..cfdb185ff 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -188,8 +188,8 @@ void ListModel::performPaginatedSearch() sort.index = currentSort + 1; auto netJob = makeShared("Flame::Search", APPLICATION->network()); - auto searchUrl = FlameAPI::getStaticSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, - m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); + auto searchUrl = FlameAPI().getSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, + m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), response)); jobPtr = netJob; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 132150bd2..417ff4080 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -154,8 +154,8 @@ void ModpackListModel::performPaginatedSearch() } // TODO: Move to standalone API ResourceAPI::SortingMethod sort{}; sort.name = currentSort; - auto searchUrl = ModrinthAPI::getStaticSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, - m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); + auto searchUrl = ModrinthAPI().getSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort, + m_filter->loaders, m_filter->versions, "", m_filter->categoryIds }); auto netJob = makeShared("Modrinth::SearchModpack", APPLICATION->network()); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), m_allResponse)); From d31c947b36c907f59e82150f4fc60781d70185ab Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 8 Oct 2024 22:02:38 +0300 Subject: [PATCH 10/37] fix crash when no resorce version is available Signed-off-by: Trial97 --- launcher/ui/pages/modplatform/ResourcePage.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index bed118465..dea28d6d5 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -254,7 +254,10 @@ void ResourcePage::updateSelectionButton() m_ui->resourceSelectionButton->setEnabled(true); if (auto current_pack = getCurrentPack(); current_pack) { - if (!current_pack->isVersionSelected(m_selected_version_index)) + if (current_pack->versionsLoaded && current_pack->versions.empty()) { + m_ui->resourceSelectionButton->setEnabled(false); + qWarning() << tr("No version available for the selected pack"); + } else if (!current_pack->isVersionSelected(m_selected_version_index)) m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); else m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); From 093313e5914b3759bdb1147cd0fd1ff6627bdb31 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 07:14:47 +0000 Subject: [PATCH 11/37] chore(deps): update actions/cache action to v4.1.1 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5dc02edf3..dc45252fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -173,7 +173,7 @@ jobs: - name: Retrieve ccache cache (Windows MinGW-w64) if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug' - uses: actions/cache@v4.1.0 + uses: actions/cache@v4.1.1 with: path: '${{ github.workspace }}\.ccache' key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }} From 909114bf2ae1090e7c4d2d757645b413c71aaba1 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 9 Oct 2024 15:07:09 +0100 Subject: [PATCH 12/37] Change LaunchTask to only accept MinecraftInstance Signed-off-by: TheKodeToad --- launcher/launch/LaunchTask.cpp | 4 +-- launcher/launch/LaunchTask.h | 9 +++--- launcher/minecraft/launch/AutoInstallJava.cpp | 2 +- .../minecraft/launch/CreateGameFolders.cpp | 5 ++-- launcher/minecraft/launch/ExtractNatives.cpp | 9 +++--- .../minecraft/launch/LauncherPartLaunch.cpp | 29 +++++++++---------- launcher/minecraft/launch/ModMinecraftJar.cpp | 4 +-- .../minecraft/launch/ReconstructAssets.cpp | 5 ++-- launcher/minecraft/launch/ScanModFolders.cpp | 2 +- .../minecraft/launch/VerifyJavaInstall.cpp | 2 +- 10 files changed, 34 insertions(+), 37 deletions(-) diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 4e4f5ead4..0251b302d 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -51,14 +51,14 @@ void LaunchTask::init() m_instance->setRunning(true); } -shared_qobject_ptr LaunchTask::create(InstancePtr inst) +shared_qobject_ptr LaunchTask::create(MinecraftInstancePtr inst) { shared_qobject_ptr proc(new LaunchTask(inst)); proc->init(); return proc; } -LaunchTask::LaunchTask(InstancePtr instance) : m_instance(instance) {} +LaunchTask::LaunchTask(MinecraftInstancePtr instance) : m_instance(instance) {} void LaunchTask::appendStep(shared_qobject_ptr step) { diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index 2fd8c78c7..56065af5b 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -37,6 +37,7 @@ #pragma once #include +#include #include #include "BaseInstance.h" #include "LaunchStep.h" @@ -46,21 +47,21 @@ class LaunchTask : public Task { Q_OBJECT protected: - explicit LaunchTask(InstancePtr instance); + explicit LaunchTask(MinecraftInstancePtr instance); void init(); public: enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished }; public: /* methods */ - static shared_qobject_ptr create(InstancePtr inst); + static shared_qobject_ptr create(MinecraftInstancePtr inst); virtual ~LaunchTask() = default; void appendStep(shared_qobject_ptr step); void prependStep(shared_qobject_ptr step); void setCensorFilter(QMap filter); - InstancePtr instance() { return m_instance; } + MinecraftInstancePtr instance() { return m_instance; } void setPid(qint64 pid) { m_pid = pid; } @@ -115,7 +116,7 @@ class LaunchTask : public Task { void finalizeSteps(bool successful, const QString& error); protected: /* data */ - InstancePtr m_instance; + MinecraftInstancePtr m_instance; shared_qobject_ptr m_logModel; QList> m_steps; QMap m_censorFilter; diff --git a/launcher/minecraft/launch/AutoInstallJava.cpp b/launcher/minecraft/launch/AutoInstallJava.cpp index 4fad6f15f..b23b23ed2 100644 --- a/launcher/minecraft/launch/AutoInstallJava.cpp +++ b/launcher/minecraft/launch/AutoInstallJava.cpp @@ -58,7 +58,7 @@ AutoInstallJava::AutoInstallJava(LaunchTask* parent) : LaunchStep(parent) - , m_instance(std::dynamic_pointer_cast(m_parent->instance())) + , m_instance(m_parent->instance()) , m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {}; void AutoInstallJava::executeTask() diff --git a/launcher/minecraft/launch/CreateGameFolders.cpp b/launcher/minecraft/launch/CreateGameFolders.cpp index 36f5e6407..07bdbb600 100644 --- a/launcher/minecraft/launch/CreateGameFolders.cpp +++ b/launcher/minecraft/launch/CreateGameFolders.cpp @@ -8,16 +8,15 @@ CreateGameFolders::CreateGameFolders(LaunchTask* parent) : LaunchStep(parent) {} void CreateGameFolders::executeTask() { auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - if (!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) { + if (!FS::ensureFolderPathExists(instance->gameRoot())) { emit logLine("Couldn't create the main game folder", MessageLevel::Error); emitFailed(tr("Couldn't create the main game folder")); return; } // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. - if (!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) { + if (!FS::ensureFolderPathExists(FS::PathCombine(instance->gameRoot(), "server-resource-packs"))) { emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error); } emitSucceeded(); diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp index 405008f40..afe091758 100644 --- a/launcher/minecraft/launch/ExtractNatives.cpp +++ b/launcher/minecraft/launch/ExtractNatives.cpp @@ -70,17 +70,16 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH void ExtractNatives::executeTask() { auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - auto toExtract = minecraftInstance->getNativeJars(); + auto toExtract = instance->getNativeJars(); if (toExtract.isEmpty()) { emitSucceeded(); return; } - auto settings = minecraftInstance->settings(); + auto settings = instance->settings(); - auto outputPath = minecraftInstance->getNativePath(); + auto outputPath = instance->getNativePath(); FS::ensureFolderPathExists(outputPath); - auto javaVersion = minecraftInstance->getJavaVersion(); + auto javaVersion = instance->getJavaVersion(); bool jniHackEnabled = javaVersion.major() >= 8; for (const auto& source : toExtract) { if (!unzipNatives(source, outputPath, jniHackEnabled)) { diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 2b932ae47..10c14a95f 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -50,16 +50,16 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent) { - auto instance = parent->instance(); - if (instance->settings()->get("CloseAfterLaunch").toBool()) { + if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { std::shared_ptr connection{ new QMetaObject::Connection }; - *connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, [[maybe_unused]] MessageLevel::Enum level) { - qDebug() << lines; - if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) { - APPLICATION->closeAllWindows(); - disconnect(*connection); - } - }); + *connection = + connect(&m_process, &LoggedProcess::log, this, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) { + qDebug() << lines; + if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) { + APPLICATION->closeAllWindows(); + disconnect(*connection); + } + }); } connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); @@ -77,10 +77,9 @@ void LauncherPartLaunch::executeTask() } auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); QString legacyJarPath; - if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) { + if (instance->getLauncher() == "legacy" || instance->shouldApplyOnlineFixes()) { legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar"); if (legacyJarPath.isEmpty()) { const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation."); @@ -90,8 +89,8 @@ void LauncherPartLaunch::executeTask() } } - m_launchScript = minecraftInstance->createLaunchScript(m_session, m_targetToJoin); - QStringList args = minecraftInstance->javaArguments(); + m_launchScript = instance->createLaunchScript(m_session, m_targetToJoin); + QStringList args = instance->javaArguments(); QString allArgs = args.join(", "); emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher); @@ -102,13 +101,13 @@ void LauncherPartLaunch::executeTask() // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); - auto classPath = minecraftInstance->getClassPath(); + auto classPath = instance->getClassPath(); classPath.prepend(jarPath); if (!legacyJarPath.isEmpty()) classPath.prepend(legacyJarPath); - auto natPath = minecraftInstance->getNativePath(); + auto natPath = instance->getNativePath(); #ifdef Q_OS_WIN natPath = FS::getPathNameInLocal8bit(natPath); #endif diff --git a/launcher/minecraft/launch/ModMinecraftJar.cpp b/launcher/minecraft/launch/ModMinecraftJar.cpp index 6e73333b1..e06080ba7 100644 --- a/launcher/minecraft/launch/ModMinecraftJar.cpp +++ b/launcher/minecraft/launch/ModMinecraftJar.cpp @@ -42,7 +42,7 @@ void ModMinecraftJar::executeTask() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto m_inst = m_parent->instance(); if (!m_inst->getJarMods().size()) { emitSucceeded(); @@ -82,7 +82,7 @@ void ModMinecraftJar::finalize() bool ModMinecraftJar::removeJar() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto m_inst = m_parent->instance(); auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); QFile finalJar(finalJarPath); if (finalJar.exists()) { diff --git a/launcher/minecraft/launch/ReconstructAssets.cpp b/launcher/minecraft/launch/ReconstructAssets.cpp index 843ccc554..21ae395f0 100644 --- a/launcher/minecraft/launch/ReconstructAssets.cpp +++ b/launcher/minecraft/launch/ReconstructAssets.cpp @@ -22,12 +22,11 @@ void ReconstructAssets::executeTask() { auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - auto components = minecraftInstance->getPackProfile(); + auto components = instance->getPackProfile(); auto profile = components->getProfile(); auto assets = profile->getMinecraftAssets(); - if (!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) { + if (!AssetsUtils::reconstructAssets(assets->id, instance->resourcesDir())) { emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error); } diff --git a/launcher/minecraft/launch/ScanModFolders.cpp b/launcher/minecraft/launch/ScanModFolders.cpp index 7e08a4e36..1a2ddf194 100644 --- a/launcher/minecraft/launch/ScanModFolders.cpp +++ b/launcher/minecraft/launch/ScanModFolders.cpp @@ -42,7 +42,7 @@ void ScanModFolders::executeTask() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto m_inst = m_parent->instance(); auto loaders = m_inst->loaderModList(); connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 1e7448089..bc950d673 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -46,7 +46,7 @@ void VerifyJavaInstall::executeTask() { - auto instance = std::dynamic_pointer_cast(m_parent->instance()); + auto instance = m_parent->instance(); auto packProfile = instance->getPackProfile(); auto settings = instance->settings(); auto storedVersion = settings->get("JavaVersion").toString(); From 98adcc60a30a95a86b564ebe69bb0ad276263424 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 9 Oct 2024 16:59:56 +0100 Subject: [PATCH 13/37] Use UTF-8 to decode logs on Java 18 or newer Signed-off-by: TheKodeToad --- launcher/LoggedProcess.cpp | 3 ++- launcher/LoggedProcess.h | 6 +++--- launcher/java/JavaVersion.cpp | 6 ++++++ launcher/java/JavaVersion.h | 2 +- launcher/minecraft/launch/LauncherPartLaunch.cpp | 4 +++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index fadd64e68..35ce4e0e5 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -39,7 +39,8 @@ #include #include "MessageLevel.h" -LoggedProcess::LoggedProcess(QObject* parent) : QProcess(parent) +LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent) + : QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec) { // QProcess has a strange interface... let's map a lot of those into a few. connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 46bdaa830..75ba15dfd 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -49,7 +49,7 @@ class LoggedProcess : public QProcess { enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted }; public: - explicit LoggedProcess(QObject* parent = 0); + explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0); virtual ~LoggedProcess(); State state() const; @@ -80,8 +80,8 @@ class LoggedProcess : public QProcess { QStringList reprocess(const QByteArray& data, QTextDecoder& decoder); private: - QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale()); - QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale()); + QTextDecoder m_err_decoder; + QTextDecoder m_out_decoder; QString m_leftover_line; bool m_killed = false; State m_state = NotRunning; diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp index 5e9700012..bca50f2c9 100644 --- a/launcher/java/JavaVersion.cpp +++ b/launcher/java/JavaVersion.cpp @@ -48,6 +48,12 @@ bool JavaVersion::requiresPermGen() const return !m_parseable || m_major < 8; } +bool JavaVersion::defaultsToUtf8() const +{ + // starting from Java 18, UTF-8 is the default charset: https://openjdk.org/jeps/400 + return m_parseable && m_major >= 18; +} + bool JavaVersion::isModular() const { return m_parseable && m_major >= 9; diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h index dfb4770da..c070bdeec 100644 --- a/launcher/java/JavaVersion.h +++ b/launcher/java/JavaVersion.h @@ -25,7 +25,7 @@ class JavaVersion { bool operator>(const JavaVersion& rhs); bool requiresPermGen() const; - + bool defaultsToUtf8() const; bool isModular() const; QString toString() const; diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 10c14a95f..d9a2b9b6b 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -48,7 +48,9 @@ #include "gamemode_client.h" #endif -LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent) +LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) + : LaunchStep(parent) + , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale()) { if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { std::shared_ptr connection{ new QMetaObject::Connection }; From 2bc6ae97568f9c9e522c4a64988cb325eb4d25ec Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 11 Oct 2024 19:53:28 +0100 Subject: [PATCH 14/37] Prevent directories from being removed on instance update Signed-off-by: TheKodeToad --- launcher/InstanceCreationTask.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 9c17dfc9f..d89a00d6c 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -42,11 +42,11 @@ void InstanceCreationTask::executeTask() setStatus(tr("Removing old conflicting files...")); qDebug() << "Removing old files"; - for (auto path : m_files_to_remove) { + for (const QString& path : m_files_to_remove) { if (!QFile::exists(path)) continue; qDebug() << "Removing" << path; - if (!FS::deletePath(path)) { + if (!QFile::remove(path)) { qCritical() << "Couldn't remove the old conflicting files."; emitFailed(tr("Failed to remove old conflicting files.")); return; @@ -55,5 +55,4 @@ void InstanceCreationTask::executeTask() } emitSucceeded(); - return; } From 09c9da268fdc833304f6159735cfb7e80d0e69bc Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:26:20 -0700 Subject: [PATCH 15/37] fix: prevent inf recursion when mod icon load fails; cut max pixmapcache to 1/4 previous value Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/MTPixmapCache.h | 6 +-- launcher/minecraft/mod/Mod.cpp | 41 +++++++++++-------- launcher/minecraft/mod/Mod.h | 8 ++-- .../minecraft/mod/tasks/LocalModParseTask.cpp | 41 ++++++++++--------- .../minecraft/mod/tasks/LocalModParseTask.h | 4 +- 5 files changed, 55 insertions(+), 45 deletions(-) diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h index b6bd13045..0ba9c5ac8 100644 --- a/launcher/MTPixmapCache.h +++ b/launcher/MTPixmapCache.h @@ -101,7 +101,7 @@ class PixmapCache final : public QObject { */ bool _markCacheMissByEviciton() { - static constexpr uint maxInt = static_cast(std::numeric_limits::max()); + static constexpr uint maxCache = static_cast(std::numeric_limits::max()) / 4; static constexpr uint step = 10240; static constexpr int oneSecond = 1000; @@ -118,8 +118,8 @@ class PixmapCache final : public QObject { if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) { // increase the cache size uint newSize = _cacheLimit() + step; - if (newSize >= maxInt) { // increase it until you overflow :D - newSize = maxInt; + if (newSize >= maxCache) { // increase it until you overflow :D + newSize = maxCache; qDebug() << m_consecutive_fast_evicitons << tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit"); } else { diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 5442df7fe..9663026cd 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -36,6 +36,7 @@ */ #include "Mod.h" +#include #include #include @@ -241,7 +242,7 @@ void Mod::finishResolvingWithDetails(ModDetails&& details) if (metadata) setMetadata(std::move(metadata)); if (!iconPath().isEmpty()) { - m_pack_image_cache_key.was_read_attempt = false; + m_packImageCacheKey.wasReadAttempt = false; } } @@ -290,45 +291,53 @@ auto Mod::issueTracker() const -> QString return details().issue_tracker; } -void Mod::setIcon(QImage new_image) const +QPixmap Mod::setIcon(QImage new_image) const { QMutexLocker locker(&m_data_lock); Q_ASSERT(!new_image.isNull()); - if (m_pack_image_cache_key.key.isValid()) - PixmapCache::remove(m_pack_image_cache_key.key); + if (m_packImageCacheKey.key.isValid()) + PixmapCache::remove(m_packImageCacheKey.key); // scale the image to avoid flooding the pixmapcache auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); - m_pack_image_cache_key.key = PixmapCache::insert(pixmap); - m_pack_image_cache_key.was_ever_used = true; - m_pack_image_cache_key.was_read_attempt = true; + m_packImageCacheKey.key = PixmapCache::insert(pixmap); + m_packImageCacheKey.wasEverUsed = true; + m_packImageCacheKey.wasReadAttempt = true; + return pixmap; } QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const { - QPixmap cached_image; - if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { + auto pixmap_transform = [&size, &mode](QPixmap pixmap) { if (size.isNull()) - return cached_image; - return cached_image.scaled(size, mode, Qt::SmoothTransformation); + return pixmap; + return pixmap.scaled(size, mode, Qt::SmoothTransformation); + }; + + QPixmap cached_image; + if (PixmapCache::find(m_packImageCacheKey.key, &cached_image)) { + return pixmap_transform(cached_image); } // No valid image we can get - if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty()) + if ((!m_packImageCacheKey.wasEverUsed && m_packImageCacheKey.wasReadAttempt) || iconPath().isEmpty()) return {}; - if (m_pack_image_cache_key.was_ever_used) { + if (m_packImageCacheKey.wasEverUsed) { qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading..."; PixmapCache::markCacheMissByEviciton(); } // Image got evicted from the cache or an attempt to load it has not been made. load it and retry. - m_pack_image_cache_key.was_read_attempt = true; - ModUtils::loadIconFile(*this); - return icon(size); + m_packImageCacheKey.wasReadAttempt = true; + if (ModUtils::loadIconFile(*this, &cached_image)) { + return pixmap_transform(cached_image); + } + // Image failed to load + return {}; } bool Mod::valid() const diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 9bd76c2fd..a0d9797ed 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -82,7 +82,7 @@ class Mod : public Resource { /** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */ [[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const; /** Thread-safe. */ - void setIcon(QImage new_image) const; + QPixmap setIcon(QImage new_image) const; auto metadata() -> std::shared_ptr; auto metadata() const -> const std::shared_ptr; @@ -111,7 +111,7 @@ class Mod : public Resource { struct { QPixmapCache::Key key; - bool was_ever_used = false; - bool was_read_attempt = false; - } mutable m_pack_image_cache_key; + bool wasEverUsed = false; + bool wasReadAttempt = false; + } mutable m_packImageCacheKey; }; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 60257ce0c..d456211f8 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -647,11 +647,11 @@ bool validate(QFileInfo file) return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid(); } -bool processIconPNG(const Mod& mod, QByteArray&& raw_data) +bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap) { auto img = QImage::fromData(raw_data); if (!img.isNull()) { - mod.setIcon(img); + *pixmap = mod.setIcon(img); } else { qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name(); return false; @@ -659,15 +659,15 @@ bool processIconPNG(const Mod& mod, QByteArray&& raw_data) return true; } -bool loadIconFile(const Mod& mod) +bool loadIconFile(const Mod& mod, QPixmap* pixmap) { if (mod.iconPath().isEmpty()) { qWarning() << "No Iconfile set, be sure to parse the mod first"; return false; } - auto png_invalid = [&mod]() { - qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon"; + auto png_invalid = [&mod](const QString& reason) { + qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon:" << reason; return false; }; @@ -676,24 +676,26 @@ bool loadIconFile(const Mod& mod) QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath())); if (icon_info.exists() && icon_info.isFile()) { QFile icon(icon_info.filePath()); - if (!icon.open(QIODevice::ReadOnly)) - return false; + if (!icon.open(QIODevice::ReadOnly)) { + return png_invalid("failed to open file " + icon_info.filePath()); + } auto data = icon.readAll(); - bool icon_result = ModUtils::processIconPNG(mod, std::move(data)); + bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap); icon.close(); if (!icon_result) { - return png_invalid(); // icon invalid + return png_invalid("invalid png image"); // icon invalid } + return true; } - return false; + return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file"); } case ResourceType::ZIPFILE: { QuaZip zip(mod.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) - return false; + return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive"); QuaZipFile file(&zip); @@ -701,28 +703,27 @@ bool loadIconFile(const Mod& mod) if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Failed to open file in zip."; zip.close(); - return png_invalid(); + return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive"); } auto data = file.readAll(); - bool icon_result = ModUtils::processIconPNG(mod, std::move(data)); + bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap); file.close(); if (!icon_result) { - return png_invalid(); // icon png invalid + return png_invalid("invalid png image"); // icon png invalid } - } else { - return png_invalid(); // could not set icon as current file. + return true; } - return false; + return png_invalid("Failed to set '" + mod.iconPath() + + "' as current file in zip archive"); // could not set icon as current file. } case ResourceType::LITEMOD: { - return false; // can lightmods even have icons? + return png_invalid("litemods do not have icons"); // can lightmods even have icons? } default: - qWarning() << "Invalid type for mod, can not load icon."; - return false; + return png_invalid("Invalid type for mod, can not load icon."); } } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index a03217093..91ee6f253 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -26,8 +26,8 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full); /** Checks whether a file is valid as a mod or not. */ bool validate(QFileInfo file); -bool processIconPNG(const Mod& mod, QByteArray&& raw_data); -bool loadIconFile(const Mod& mod); +bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap); +bool loadIconFile(const Mod& mod, QPixmap* pixmap); } // namespace ModUtils class LocalModParseTask : public Task { From 72cfad8fee7f81d85df7389ab9e32294d13a4707 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 11 Oct 2024 21:35:13 +0100 Subject: [PATCH 16/37] Continue deleting before failing Signed-off-by: TheKodeToad --- launcher/InstanceCreationTask.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index d89a00d6c..3e7b3142f 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -38,6 +38,8 @@ void InstanceCreationTask::executeTask() // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd // put the instance in an invalid state. if (shouldOverride()) { + bool deleteFailed = false; + setAbortable(false); setStatus(tr("Removing old conflicting files...")); qDebug() << "Removing old files"; @@ -45,13 +47,19 @@ void InstanceCreationTask::executeTask() for (const QString& path : m_files_to_remove) { if (!QFile::exists(path)) continue; + qDebug() << "Removing" << path; + if (!QFile::remove(path)) { - qCritical() << "Couldn't remove the old conflicting files."; - emitFailed(tr("Failed to remove old conflicting files.")); - return; + qCritical() << "Could not remove" << path; + deleteFailed = true; } } + + if (deleteFailed) { + emitFailed(tr("Failed to remove old conflicting files.")); + return; + } } emitSucceeded(); From 97f4ead0fbec5508f7ff6785f07dcf869ed663c2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 12 Oct 2024 00:19:54 +0300 Subject: [PATCH 17/37] remove client overrides instead of double deleting the normal ones Signed-off-by: Trial97 --- launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index ba97c441f..3442fd19c 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -136,7 +136,7 @@ bool ModrinthCreationTask::updateInstance() } auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); - for (const auto& entry : old_overrides) { + for (const auto& entry : old_client_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; From 3a1c06de027ab2755c2a27624f41cbd445a156b4 Mon Sep 17 00:00:00 2001 From: Mia Moir <63592337+archessmn@users.noreply.github.com> Date: Sat, 12 Oct 2024 23:47:58 +0100 Subject: [PATCH 18/37] fix: Tab order on custom command inputs The tab order on the custom command UI in the instance settings is off, it goes 0 -> 2 -> 1, This re-orders the ui file to fix the order the inputs are in. Signed-off-by: Mia Moir <63592337+archessmn@users.noreply.github.com> --- launcher/ui/widgets/CustomCommands.ui | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/launcher/ui/widgets/CustomCommands.ui b/launcher/ui/widgets/CustomCommands.ui index 4a39ff7f7..b485c293e 100644 --- a/launcher/ui/widgets/CustomCommands.ui +++ b/launcher/ui/widgets/CustomCommands.ui @@ -38,19 +38,6 @@ false - - - - P&ost-exit command: - - - postExitCmdTextBox - - - - - - @@ -61,8 +48,8 @@ - - + + @@ -77,6 +64,19 @@ + + + + P&ost-exit command: + + + postExitCmdTextBox + + + + + + From 3c836ece71c61c19eacd7bb4f324940071c6e89a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 13 Oct 2024 00:24:52 +0000 Subject: [PATCH 19/37] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/bc947f541ae55e999ffdb4013441347d83b00feb?narHash=sha256-NOiTvBbRLIOe5F6RbHaAh6%2B%2BBNjsb149fGZd1T4%2BKBg%3D' (2024-10-04) → 'github:NixOS/nixpkgs/5633bcff0c6162b9e4b5f1264264611e950c8ec7?narHash=sha256-9UTxR8eukdg%2BXZeHgxW5hQA9fIKHsKCdOIUycTryeVw%3D' (2024-10-09) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6897c162d..7bf13f75c 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1728018373, - "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=", + "lastModified": 1728492678, + "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bc947f541ae55e999ffdb4013441347d83b00feb", + "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7", "type": "github" }, "original": { From a108b5b420b7d2785c9850d4a0bcb9c628d073a4 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:32:12 -0700 Subject: [PATCH 20/37] fix: ensure all connections to the ProgressDialogs's Task the dialog made are dissconnected Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/ProgressDialog.cpp | 21 ++++++++++++--------- launcher/ui/dialogs/ProgressDialog.h | 2 ++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 0ca3a1bd9..9897687e3 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -90,6 +90,9 @@ void ProgressDialog::on_skipButton_clicked(bool checked) ProgressDialog::~ProgressDialog() { + for (auto conn : this->m_taskConnections) { + disconnect(conn); + } delete ui; } @@ -140,15 +143,15 @@ int ProgressDialog::execWithTask(Task* task) } // Connect signals. - connect(task, &Task::started, this, &ProgressDialog::onTaskStarted); - connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); - connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); - connect(task, &Task::status, this, &ProgressDialog::changeStatus); - connect(task, &Task::details, this, &ProgressDialog::changeStatus); - connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress); - connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, this, &ProgressDialog::hide); - connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); + this->m_taskConnections.push_back(connect(task, &Task::started, this, &ProgressDialog::onTaskStarted)); + this->m_taskConnections.push_back(connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed)); + this->m_taskConnections.push_back(connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded)); + this->m_taskConnections.push_back(connect(task, &Task::status, this, &ProgressDialog::changeStatus)); + this->m_taskConnections.push_back(connect(task, &Task::details, this, &ProgressDialog::changeStatus)); + this->m_taskConnections.push_back(connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress)); + this->m_taskConnections.push_back(connect(task, &Task::progress, this, &ProgressDialog::changeProgress)); + this->m_taskConnections.push_back(connect(task, &Task::aborted, this, &ProgressDialog::hide)); + this->m_taskConnections.push_back(connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled)); m_is_multi_step = task->isMultiStep(); ui->taskProgressScrollArea->setHidden(!m_is_multi_step); diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 15eadf4e7..4a696a49d 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -93,6 +93,8 @@ class ProgressDialog : public QDialog { Ui::ProgressDialog* ui; Task* m_task; + + QList m_taskConnections; bool m_is_multi_step = false; QHash taskProgress; From ac13579b9963a312b909e712c372cb115f21f0db Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 14 Oct 2024 11:10:54 +0300 Subject: [PATCH 21/37] fix heap-use-after-free in modrinth creation task Signed-off-by: Trial97 --- .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 3442fd19c..994dd774f 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -301,6 +301,13 @@ bool ModrinthCreationTask::createInstance() loop.exec(); + if (!ended_well) { + for (auto m : mods) { + delete m; + } + return ended_well; + } + QEventLoop ensureMetaLoop; QDir folder = FS::PathCombine(instance.modsRoot(), ".index"); auto ensureMetadataTask = makeShared(mods, folder, ModPlatform::ResourceProvider::MODRINTH); From 7519c63f2e8a5ebc9480a470a1e7702436178c51 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 19 Oct 2024 01:11:27 +0300 Subject: [PATCH 22/37] Replace http with https on skins Signed-off-by: Trial97 --- launcher/minecraft/auth/Parsers.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index 3aa458ace..f9d89baa2 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -180,6 +180,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output) if (!getString(skinObj.value("url"), skinOut.url)) { continue; } + skinOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net"); if (!getString(skinObj.value("variant"), skinOut.variant)) { continue; } @@ -221,9 +222,9 @@ namespace { // these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee) // they are needed because the session server doesn't return skin urls for default skins static const QString SKIN_URL_STEVE = - "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b"; + "https://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b"; static const QString SKIN_URL_ALEX = - "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032"; + "https://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032"; bool isDefaultModelSteve(QString uuid) { From 522d105dbe50d6392e1d74dd9f1dec2a3ef7a357 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 19 Oct 2024 11:59:32 -0400 Subject: [PATCH 23/37] fix(nix): enable checks Signed-off-by: seth --- nix/unwrapped.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index f75acf1de..7fc383ff3 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -87,6 +87,8 @@ stdenv.mkDerivation { (lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/") ]; + doCheck = true; + dontWrapQtApps = true; meta = { From 0bae14999f8e4997be96a8fadb4e4ed14dfd5b40 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 19 Oct 2024 12:33:40 -0400 Subject: [PATCH 24/37] chore(flatpak): don't bundle JREs After https://github.com/PrismLauncher/PrismLauncher/pull/2069, JREs can be managed at runtime. This is great for the Flatpak, as previously *all* JREs had to be installed and could not be updated independently of the launcher's Flatpak. It also makes using unsupported Java versions easier as the launcher can download any version in the sandbox We don't need to include these anymore Signed-off-by: seth --- flatpak/org.prismlauncher.PrismLauncher.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 71e6dd11e..f33432d23 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -3,9 +3,7 @@ runtime: org.kde.Platform runtime-version: 6.7 sdk: org.kde.Sdk sdk-extensions: - - org.freedesktop.Sdk.Extension.openjdk21 - org.freedesktop.Sdk.Extension.openjdk17 - - org.freedesktop.Sdk.Extension.openjdk8 command: prismlauncher finish-args: @@ -43,19 +41,6 @@ modules: - type: dir path: ../ - - name: openjdk - buildsystem: simple - build-commands: - - mkdir -p /app/jdk/ - - /usr/lib/sdk/openjdk21/install.sh - - mv /app/jre /app/jdk/21 - - /usr/lib/sdk/openjdk17/install.sh - - mv /app/jre /app/jdk/17 - - /usr/lib/sdk/openjdk8/install.sh - - mv /app/jre /app/jdk/8 - cleanup: - - /jre - - name: glfw buildsystem: cmake-ninja config-opts: From 01721b593b16bbbf9cd15c4f262176d0345681f8 Mon Sep 17 00:00:00 2001 From: seth Date: Sat, 19 Oct 2024 12:38:17 -0400 Subject: [PATCH 25/37] fix(flatpak): run tests for launcher Signed-off-by: seth --- flatpak/org.prismlauncher.PrismLauncher.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index f33432d23..09dd8d73b 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -37,6 +37,7 @@ modules: env: JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17 JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac + run-tests: true sources: - type: dir path: ../ From 2030041fcc14aa9d56f59e0a61c8a26ba6522a0f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 19 Oct 2024 01:10:04 +0300 Subject: [PATCH 26/37] Fix tests segfault Signed-off-by: Trial97 --- CMakeLists.txt | 12 ++++++------ launcher/Application.h | 6 ++++++ launcher/CMakeLists.txt | 10 +++------- launcher/minecraft/mod/ResourceFolderModel.cpp | 7 +++---- launcher/net/NetJob.cpp | 5 +++-- .../ui/pages/modplatform/ResourceModel.cpp | 18 +++++++++++------- 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d70fe79b..4c1ad3373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,7 +99,7 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off") if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") - # using clang with clang-cl front end + # using clang with clang-cl front end message(STATUS "Address Sanitizer available on Clang MSVC frontend") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-") @@ -225,7 +225,7 @@ set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build agains # Java downloader set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON) -# Although we recommend enabling this, we cannot guarantee binary compatibility on +# Although we recommend enabling this, we cannot guarantee binary compatibility on # differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this # feature if they know it will work with their distribution. if(UNIX AND NOT APPLE) @@ -438,10 +438,10 @@ elseif(UNIX) set(PLUGIN_DEST_DIR "plugins") set(BUNDLE_DEST_DIR ".") set(RESOURCES_DEST_DIR ".") - + # Apps to bundle set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}") - + # directories to look for dependencies set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) endif() @@ -495,7 +495,7 @@ if(FORCE_BUNDLED_ZLIB) set(SKIP_INSTALL_ALL ON) add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) - # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not. + # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not. # We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway. check_include_file(unistd.h NEED_GENERATED_ZCONF) if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF) @@ -533,7 +533,7 @@ endif() if(NOT cmark_FOUND) message(STATUS "Using bundled cmark") set(BUILD_TESTING 0) - set(BUILD_SHARED_LIBS 0) + set(BUILD_SHARED_LIBS 0) add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser add_library(cmark::cmark ALIAS cmark) else() diff --git a/launcher/Application.h b/launcher/Application.h index 7432c9683..bd1cb2dea 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -81,6 +81,12 @@ class Index; #endif #define APPLICATION (static_cast(QCoreApplication::instance())) +// Used for checking if is a test +#if defined(APPLICATION_DYN) +#undef APPLICATION_DYN +#endif +#define APPLICATION_DYN (dynamic_cast(QCoreApplication::instance())) + class Application : public QApplication { // friends for the purpose of limiting access to deprecated stuff Q_OBJECT diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dcbfe8fde..a70fe668a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -608,7 +608,7 @@ set(PRISMUPDATER_SOURCES updater/prismupdater/UpdaterDialogs.cpp updater/prismupdater/GitHubRelease.h updater/prismupdater/GitHubRelease.cpp - + Json.h Json.cpp FileSystem.h @@ -625,7 +625,7 @@ set(PRISMUPDATER_SOURCES # Zip MMCZip.h MMCZip.cpp - + # Time MMCTime.h MMCTime.cpp @@ -1265,14 +1265,10 @@ include(CompilerWarnings) # Add executable add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) -if(BUILD_TESTING) -target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST) -endif() set_project_warnings(Launcher_logic "${Launcher_MSVC_WARNINGS}" "${Launcher_CLANG_WARNINGS}" "${Launcher_GCC_WARNINGS}") -target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) target_link_libraries(Launcher_logic @@ -1376,7 +1372,7 @@ if(Launcher_BUILD_UPDATER) add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest) target_link_libraries("${Launcher_Name}_updater" prism_updater_logic) - + if(DEFINED Launcher_APP_BINARY_NAME) set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater") endif() diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 941e7ce58..ce3e16bce 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -35,10 +35,9 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); }); -#ifndef LAUNCHER_TEST - // in tests the application macro doesn't work - m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); -#endif + if (APPLICATION_DYN) { // in tests the application macro doesn't work + m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + } } ResourceFolderModel::~ResourceFolderModel() diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index e363c911d..3cd3958f7 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -48,7 +48,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr netwo : ConcurrentTask(nullptr, job_name), m_network(network) { #if defined(LAUNCHER_APPLICATION) - if (max_concurrent < 0) + if (APPLICATION_DYN && max_concurrent < 0) max_concurrent = APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt(); #endif if (max_concurrent > 0) @@ -161,7 +161,8 @@ bool NetJob::isOnline() void NetJob::emitFailed(QString reason) { #if defined(LAUNCHER_APPLICATION) - if (m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { + + if (APPLICATION_DYN && m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { m_manual_try++; auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", "The tasks failed.\n" diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index c8eb91570..50a170fff 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -31,9 +31,9 @@ QHash ResourceModel::s_running_models; ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api) { s_running_models.insert(this, true); -#ifndef LAUNCHER_TEST - m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); -#endif + if (APPLICATION_DYN) { + m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + } } ResourceModel::~ResourceModel() @@ -60,11 +60,15 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant return pack->description; } case Qt::DecorationRole: { - if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); - icon_or_none.has_value()) - return icon_or_none.value(); + if (APPLICATION_DYN) { + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); + icon_or_none.has_value()) + return icon_or_none.value(); - return APPLICATION->getThemedIcon("screenshot-placeholder"); + return APPLICATION->getThemedIcon("screenshot-placeholder"); + } else { + return {}; + } } case Qt::SizeHintRole: return QSize(0, 58); From 2d234b127b1561fcedd0beae5bd242d1012c7426 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 19 Oct 2024 22:07:31 +0300 Subject: [PATCH 27/37] make sure that tests are only disabled fro cmark Signed-off-by: Trial97 --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c1ad3373..b677b0b7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -532,10 +532,12 @@ else() endif() if(NOT cmark_FOUND) message(STATUS "Using bundled cmark") + set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING}) set(BUILD_TESTING 0) set(BUILD_SHARED_LIBS 0) add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser add_library(cmark::cmark ALIAS cmark) + set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING}) else() message(STATUS "Using system cmark") endif() From 0501c1e17a75172b8c3b8c15da27a86cf64c420b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 20 Oct 2024 00:25:22 +0000 Subject: [PATCH 28/37] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/5633bcff0c6162b9e4b5f1264264611e950c8ec7?narHash=sha256-9UTxR8eukdg%2BXZeHgxW5hQA9fIKHsKCdOIUycTryeVw%3D' (2024-10-09) → 'github:NixOS/nixpkgs/4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0?narHash=sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c%2BcHUJwA%3D' (2024-10-18) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 7bf13f75c..a82e6f65f 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1728492678, - "narHash": "sha256-9UTxR8eukdg+XZeHgxW5hQA9fIKHsKCdOIUycTryeVw=", + "lastModified": 1729256560, + "narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5633bcff0c6162b9e4b5f1264264611e950c8ec7", + "rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0", "type": "github" }, "original": { From 0586d38e03de6890bf5697c7e1a965aac9bd36fb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 20 Oct 2024 23:14:05 +0300 Subject: [PATCH 29/37] fix sparkle signature Signed-off-by: Trial97 --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc45252fa..a1ed0a25a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -410,9 +410,8 @@ jobs: if: matrix.name == 'macOS' run: | if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then - brew install openssl@3 echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem - signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) + signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n) rm ed25519-priv.pem cat >> $GITHUB_STEP_SUMMARY << EOF ### Artifact Information :information_source: From fcadbbb739076096a9057ec340f7f6d39bc4b72f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 21 Oct 2024 16:17:48 +0300 Subject: [PATCH 30/37] do not require java if auto-download is enabled Signed-off-by: Trial97 --- launcher/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index b8dcc1099..cfae3a807 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1071,6 +1071,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) bool Application::createSetupWizard() { bool javaRequired = [&]() { + if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) { + return false; + } bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool(); if (ignoreJavaWizard) { return false; @@ -1083,10 +1086,7 @@ bool Application::createSetupWizard() } QString currentJavaPath = settings()->get("JavaPath").toString(); QString actualPath = FS::ResolveExecutable(currentJavaPath); - if (actualPath.isNull()) { - return true; - } - return false; + return actualPath.isNull(); }(); bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() && !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool(); From 78e24962f93b5cc2350d04bebd7e5231d1bc4819 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 21 Oct 2024 14:41:48 +0100 Subject: [PATCH 31/37] Fix /norestart Signed-off-by: TheKodeToad --- program_info/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index ee2e336b1..e693d757a 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -57,7 +57,7 @@ Section "Visual Studio Runtime" Pop $0 ${If} $0 == "OK" DetailPrint "Download successful" - ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart\" + ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart" ${Else} DetailPrint "Download failed with error $0" ${EndIf} From f3f4c446206647b69b9c19e63617b163b99c6164 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 21 Oct 2024 16:30:08 +0100 Subject: [PATCH 32/37] Fix removing portable.txt on Linux portable build Signed-off-by: TheKodeToad --- launcher/Launcher.in | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 1a23f2555..706d7022b 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then # Just to be sure... chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" + ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}") + + if [ -f portable.txt ]; then + ARGS+=("-d" "${LAUNCHER_DIR}") + fi + + ARGS+=("$@") + # Run the launcher - exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" + exec -a "${ARGS[@]}" # Run the launcher in valgrind # valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@" From 9a5b773e69e7198cc37413ab7d69ba6d0a66f854 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 21 Oct 2024 19:28:34 +0300 Subject: [PATCH 33/37] do not try to import skin if path is empty Signed-off-by: Trial97 --- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 6c85ffa96..fbf4ef1f4 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -139,6 +139,9 @@ void SkinManageDialog::on_fileBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); + if (raw_path.isEmpty()) { + return; + } auto message = m_list.installSkin(raw_path, {}); if (!message.isEmpty()) { CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show(); From 69028969f1a6c42b698b900256ad2e6d9ee208eb Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Mon, 21 Oct 2024 22:59:51 +0300 Subject: [PATCH 34/37] Update launcher/ui/dialogs/skins/SkinManageDialog.cpp Co-authored-by: TheKodeToad Signed-off-by: Alexandru Ionut Tripon --- launcher/ui/dialogs/skins/SkinManageDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index fbf4ef1f4..65840eb08 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -139,7 +139,7 @@ void SkinManageDialog::on_fileBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); - if (raw_path.isEmpty()) { + if (raw_path.isNull()) { return; } auto message = m_list.installSkin(raw_path, {}); From 562c3013269dbb9cad411f58ded333dee1aea158 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 21 Oct 2024 23:53:23 +0300 Subject: [PATCH 35/37] skip QSaveFile temprary files Signed-off-by: Trial97 --- launcher/Application.cpp | 23 ++++++ launcher/Application.h | 11 +++ launcher/CMakeLists.txt | 1 + launcher/FileSystem.cpp | 22 +++--- launcher/PSaveFile.h | 70 +++++++++++++++++++ launcher/minecraft/World.cpp | 4 +- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 4 ++ .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 4 ++ launcher/modplatform/packwiz/Packwiz.cpp | 4 +- launcher/net/FileSink.cpp | 2 +- launcher/net/FileSink.h | 5 +- launcher/settings/INIFile.cpp | 1 - launcher/ui/dialogs/ExportInstanceDialog.cpp | 1 - 13 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 launcher/PSaveFile.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index cfae3a807..686d934c2 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1883,3 +1883,26 @@ const QString Application::javaPath() { return m_settings->get("JavaDir").toString(); } + +void Application::addQSavePath(QString path) +{ + QMutexLocker locker(&m_qsaveResourcesMutex); + m_qsaveResources.insert(path); +} + +void Application::removeQSavePath(QString path) +{ + QMutexLocker locker(&m_qsaveResourcesMutex); + m_qsaveResources.remove(path); +} + +bool Application::checkQSavePath(QString path) +{ + QMutexLocker locker(&m_qsaveResourcesMutex); + for (auto r : m_qsaveResources) { + if (path.contains(r)) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/launcher/Application.h b/launcher/Application.h index bd1cb2dea..363130cdd 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include @@ -303,4 +305,13 @@ class Application : public QApplication { QList m_urlsToImport; QString m_instanceIdToShowWindowOf; std::unique_ptr logFile; + + public: + void addQSavePath(QString); + void removeQSavePath(QString); + bool checkQSavePath(QString); + + private: + QSet m_qsaveResources; + mutable QMutex m_qsaveResourcesMutex; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a70fe668a..0f0949a3a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -30,6 +30,7 @@ set(CORE_SOURCES StringUtils.cpp QVariantUtils.h RuntimeContext.h + PSaveFile.h # Basic instance manipulation tasks (derived from InstanceTask) InstanceCreationTask.h diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 7f38cff04..512de28c2 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ #include #include "DesktopServices.h" +#include "PSaveFile.h" #include "StringUtils.h" #if defined Q_OS_WIN32 @@ -191,8 +191,8 @@ void ensureExists(const QDir& dir) void write(const QString& filename, const QByteArray& data) { ensureExists(QFileInfo(filename).dir()); - QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) { + PSaveFile file(filename); + if (!file.open(PSaveFile::WriteOnly)) { throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); } if (data.size() != file.write(data)) { @@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data) buffer = QByteArray(); } buffer.append(data); - QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) { + PSaveFile file(filename); + if (!file.open(PSaveFile::WriteOnly)) { throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); } if (buffer.size() != file.write(buffer)) { @@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri if (!args.empty()) argstring = " \"" + args.join("\" \"") + "\""; - stream << "#!/bin/bash" - << "\n"; + stream << "#!/bin/bash" << "\n"; stream << "\"" << target << "\" " << argstring << "\n"; stream.flush(); @@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri if (!args.empty()) argstring = " '" + args.join("' '") + "'"; - stream << "[Desktop Entry]" - << "\n"; - stream << "Type=Application" - << "\n"; - stream << "Categories=Game;ActionGame;AdventureGame;Simulation" - << "\n"; + stream << "[Desktop Entry]" << "\n"; + stream << "Type=Application" << "\n"; + stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n"; stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n"; if (!icon.isEmpty()) { diff --git a/launcher/PSaveFile.h b/launcher/PSaveFile.h new file mode 100644 index 000000000..e0b5a7a2c --- /dev/null +++ b/launcher/PSaveFile.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023-2024 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "Application.h" + +#if defined(LAUNCHER_APPLICATION) + +/* PSaveFile + * A class that mimics QSaveFile for Windows. + * + * When reading resources, we need to avoid accessing temporary files + * generated by QSaveFile. If we start reading such a file, we may + * inadvertently keep it open while QSaveFile is trying to remove it, + * or we might detect the file just before it is removed, leading to + * race conditions and errors. + * + * Unfortunately, QSaveFile doesn't provide a way to retrieve the + * temporary file name or to set a specific template for the temporary + * file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix + * to the original file name, where the `XXXXXX` part is dynamically + * generated to ensure uniqueness. + * + * This class acts like a lock by adding and removing the target file + * name into/from a global string set, helping to manage access to + * files during critical operations. + * + * Note: Please do not use the `setFileName` function directly, as it + * is not virtual and cannot be overridden. + */ +class PSaveFile : public QSaveFile { + public: + PSaveFile(const QString& name) : QSaveFile(name) + { + if (auto app = APPLICATION_DYN) { + app->addQSavePath(name + "."); + } + } + PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) + { + if (auto app = APPLICATION_DYN) { + app->addQSavePath(name + "."); + } + } + virtual ~PSaveFile() + { + if (auto app = APPLICATION_DYN) { + app->removeQSavePath(fileName() + "."); + } + } +}; +#else +#define PSaveFile QSaveFile +#endif \ No newline at end of file diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 1eba148a5..bd28f9e9a 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -38,7 +38,6 @@ #include #include #include -#include #include #include @@ -57,6 +56,7 @@ #include #include "FileSystem.h" +#include "PSaveFile.h" using std::nullopt; using std::optional; @@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data) if (fullFilePath.isNull()) { return false; } - QSaveFile f(fullFilePath); + PSaveFile f(fullFilePath); if (!f.open(QIODevice::WriteOnly)) { return false; } diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index 2bce2c137..fb2e22de6 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -7,6 +7,7 @@ #include +#include "Application.h" #include "FileSystem.h" #include "minecraft/mod/Resource.h" @@ -52,6 +53,9 @@ class BasicFolderLoadTask : public Task { m_dir.refresh(); for (auto entry : m_dir.entryInfoList()) { auto filePath = entry.absoluteFilePath(); + if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { + continue; + } auto newFilePath = FS::getUniqueResourceName(filePath); if (newFilePath != filePath) { FS::move(filePath, newFilePath); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 501d5be13..63996e584 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -36,6 +36,7 @@ #include "ModFolderLoadTask.h" +#include "Application.h" #include "FileSystem.h" #include "minecraft/mod/MetadataHandler.h" @@ -65,6 +66,9 @@ void ModFolderLoadTask::executeTask() m_mods_dir.refresh(); for (auto entry : m_mods_dir.entryInfoList()) { auto filePath = entry.absoluteFilePath(); + if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { + continue; + } auto newFilePath = FS::getUniqueResourceName(filePath); if (newFilePath != filePath) { FS::move(filePath, newFilePath); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 325b0a6e4..272900f0e 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -72,7 +72,7 @@ auto stringEntry(toml::table table, QString entry_name) -> QString { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qCritical() << "Failed to read str property '" + entry_name + "' in mod metadata."; + qWarning() << "Failed to read str property '" + entry_name + "' in mod metadata."; return {}; } @@ -83,7 +83,7 @@ auto intEntry(toml::table table, QString entry_name) -> int { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qCritical() << "Failed to read int property '" + entry_name + "' in mod metadata."; + qWarning() << "Failed to read int property '" + entry_name + "' in mod metadata."; return {}; } diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 1ecb21fdf..95c1a8f44 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -55,7 +55,7 @@ Task::State FileSink::init(QNetworkRequest& request) } wroteAnyData = false; - m_output_file.reset(new QSaveFile(m_filename)); + m_output_file.reset(new PSaveFile(m_filename)); if (!m_output_file->open(QIODevice::WriteOnly)) { qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing"; return Task::State::Failed; diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 816254ff9..272f8ddc3 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -35,8 +35,7 @@ #pragma once -#include - +#include "PSaveFile.h" #include "Sink.h" namespace Net { @@ -60,6 +59,6 @@ class FileSink : public Sink { protected: QString m_filename; bool wroteAnyData = false; - std::unique_ptr m_output_file; + std::unique_ptr m_output_file; }; } // namespace Net diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index e97741f20..2c7620e65 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -39,7 +39,6 @@ #include #include -#include #include #include #include diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 9f2b3ac42..7e00f18f4 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -51,7 +51,6 @@ #include #include #include -#include #include #include #include From 73d33f93b30f658f9671358ac52bf4e03afeaefd Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 22 Oct 2024 09:41:00 +0300 Subject: [PATCH 36/37] Replaced QSet with QHash Signed-off-by: Trial97 --- launcher/Application.cpp | 13 +++++++++---- launcher/Application.h | 3 +-- launcher/PSaveFile.h | 27 ++++++++++++++------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 686d934c2..ea749ca4c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1887,20 +1887,25 @@ const QString Application::javaPath() void Application::addQSavePath(QString path) { QMutexLocker locker(&m_qsaveResourcesMutex); - m_qsaveResources.insert(path); + m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1; } void Application::removeQSavePath(QString path) { QMutexLocker locker(&m_qsaveResourcesMutex); - m_qsaveResources.remove(path); + auto count = m_qsaveResources.value(path, 0) - 1; + if (count <= 0) { + m_qsaveResources.remove(path); + } else { + m_qsaveResources[path] = count; + } } bool Application::checkQSavePath(QString path) { QMutexLocker locker(&m_qsaveResourcesMutex); - for (auto r : m_qsaveResources) { - if (path.contains(r)) { + for (auto partialPath : m_qsaveResources.keys()) { + if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) { return true; } } diff --git a/launcher/Application.h b/launcher/Application.h index 363130cdd..692625f4a 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -43,7 +43,6 @@ #include #include #include -#include #include #include @@ -312,6 +311,6 @@ class Application : public QApplication { bool checkQSavePath(QString); private: - QSet m_qsaveResources; + QHash m_qsaveResources; mutable QMutex m_qsaveResourcesMutex; }; diff --git a/launcher/PSaveFile.h b/launcher/PSaveFile.h index e0b5a7a2c..ba6154ad8 100644 --- a/launcher/PSaveFile.h +++ b/launcher/PSaveFile.h @@ -17,6 +17,7 @@ */ #pragma once +#include #include #include "Application.h" @@ -46,24 +47,24 @@ */ class PSaveFile : public QSaveFile { public: - PSaveFile(const QString& name) : QSaveFile(name) - { - if (auto app = APPLICATION_DYN) { - app->addQSavePath(name + "."); - } - } - PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) - { - if (auto app = APPLICATION_DYN) { - app->addQSavePath(name + "."); - } - } + PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); } + PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); } virtual ~PSaveFile() { if (auto app = APPLICATION_DYN) { - app->removeQSavePath(fileName() + "."); + app->removeQSavePath(m_absoluteFilePath); } } + + private: + void addPath(const QString& path) + { + m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only + if (auto app = APPLICATION_DYN) { + app->addQSavePath(m_absoluteFilePath); + } + } + QString m_absoluteFilePath; }; #else #define PSaveFile QSaveFile From 836aebc0d91fe8658052b8c4a5a4ab31da4a4b15 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 22 Oct 2024 13:43:16 +0300 Subject: [PATCH 37/37] fix small leak Signed-off-by: Trial97 --- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index f2feb8c7f..a6fbf5088 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -124,7 +124,7 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->addAction(ui->actionVisitItemPage); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); - auto changeVersion = new QAction(tr("Change Version")); + auto changeVersion = new QAction(tr("Change Version"), this); changeVersion->setToolTip(tr("Change mod version")); changeVersion->setEnabled(false); ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, changeVersion);