From 40b7cab3ed0609be26480244f0925c0726abcf7d Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 27 Mar 2026 00:23:23 +0200 Subject: [PATCH 001/132] add a option to skip meta refresh on launch related to https://github.com/PrismLauncher/PrismLauncher/issues/3785 It doesn't fix it but it should at least allow users to skip the redownload of the meta files. So in a previous PR I added an automated way to refresh all the meta from the original index, to the component index to the actual index. Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/meta/Index.cpp | 3 ++- launcher/ui/pages/global/APIPage.cpp | 2 ++ launcher/ui/pages/global/APIPage.ui | 13 ++++++++++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 7af39b55e..f97cdf947 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -870,6 +870,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get()); } + m_settings->registerSetting("MetaRefreshOnLaunch", true); m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("QuitAfterGameStop", false); diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index d0c7075cd..bd58215c4 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -15,6 +15,7 @@ #include "Index.h" +#include "Application.h" #include "JsonFormat.h" #include "QObjectPtr.h" #include "VersionList.h" @@ -135,7 +136,7 @@ void Index::connectVersionList(const int row, const VersionList::Ptr& list) Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force) { - if (mode == Net::Mode::Offline) { + if (mode == Net::Mode::Offline || !APPLICATION->settings()->get("MetaRefreshOnLaunch").toBool()) { return get(uid, version)->loadTask(mode); } diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index c399df469..4ed05f98e 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -143,6 +143,7 @@ void APIPage::loadSettings() ui->msaClientID->setText(msaClientID); QString metaURL = s->get("MetaURLOverride").toString(); ui->metaURL->setText(metaURL); + ui->metaRefreshOnLaunchCB->setCheckState(s->get("MetaRefreshOnLaunch").toBool() ? Qt::Checked : Qt::Unchecked); QString resourceURL = s->get("ResourceURLOverride").toString(); ui->resourceURL->setText(resourceURL); QString fmlLibsURL = s->get("LegacyFMLLibsURLOverride").toString(); @@ -194,6 +195,7 @@ void APIPage::applySettings() s->set("FallbackMRBlockedMods", ui->FallbackMRBlockedMods->checkState()); s->set("MetaURLOverride", metaURL.toString()); + s->set("MetaRefreshOnLaunch", ui->metaRefreshOnLaunchCB->checkState() == Qt::Checked); s->set("ResourceURLOverride", resourceURL.toString()); s->set("LegacyFMLLibsURLOverride", fmlLibsURL.toString()); QString flameKey = ui->flameKey->text(); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 7d759d256..25d78a04d 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -32,9 +32,9 @@ 0 - -262 - 820 - 908 + 0 + 825 + 1236 @@ -126,6 +126,13 @@ + + + + Refresh on launch + + + From 66b5bd96188a8d56e397a7448f5b60ba86ff4288 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 3 Apr 2026 09:57:47 +0300 Subject: [PATCH 002/132] add more invalid chars for folder name check Signed-off-by: Trial97 --- launcher/FileSystem.cpp | 78 ++++++++++++----------------------------- 1 file changed, 22 insertions(+), 56 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index f53c9343e..938ef23e4 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,6 +36,7 @@ */ #include "FileSystem.h" +#include #include #include "BuildConfig.h" @@ -796,68 +797,33 @@ QString NormalizePath(QString path) } } -static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n"; -static const QString BAD_NTFS_CHARS = "<>:\"|?*"; -static const QString BAD_HFS_CHARS = ":"; - -static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/"; - -QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) +namespace { +const QString g_badChars = "<>:\"|?*\r\n!"; +QString removeChars(QString source, QChar replace, const QString& extraChars = "") { - for (int i = 0; i < string.length(); i++) - if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i))) - string[i] = replaceWith; - return string; -} - -QString RemoveInvalidPathChars(QString path, QChar replaceWith) -{ - QString invalidChars; -#ifdef Q_OS_WIN - invalidChars = BAD_WIN_CHARS; -#endif - - // the null character is ignored in this check as it was not a problem until now - switch (statFS(path).fsType) { - case FilesystemType::FAT: // similar to NTFS - /* fallthrough */ - case FilesystemType::NTFS: - /* fallthrough */ - case FilesystemType::REFS: // similar to NTFS(should be available only on windows) - invalidChars += BAD_NTFS_CHARS; - break; - // case FilesystemType::EXT: - // case FilesystemType::EXT_2_OLD: - // case FilesystemType::EXT_2_3_4: - // case FilesystemType::XFS: - // case FilesystemType::BTRFS: - // case FilesystemType::NFS: - // case FilesystemType::ZFS: - case FilesystemType::APFS: - /* fallthrough */ - case FilesystemType::HFS: - /* fallthrough */ - case FilesystemType::HFSPLUS: - /* fallthrough */ - case FilesystemType::HFSX: - invalidChars += BAD_HFS_CHARS; - break; - // case FilesystemType::FUSEBLK: - // case FilesystemType::F2FS: - // case FilesystemType::UNKNOWN: - default: - break; + auto badChars = g_badChars; + if (!extraChars.isEmpty()) { + badChars += extraChars; } - if (invalidChars.size() != 0) { - for (int i = 0; i < path.length(); i++) { - if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) { - path[i] = replaceWith; - } + for (auto& c : source) { + if (c.unicode() < 0x20 || !c.isPrint() || badChars.contains(c)) { + c = replace; } } - return path; + return source; +} +} // namespace + +QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) +{ + return removeChars(std::move(string), replaceWith, "\\/"); +} + +QString RemoveInvalidPathChars(QString string, QChar replaceWith) +{ + return removeChars(std::move(string), replaceWith); } QString DirNameFromString(QString string, QString inDir) From 983bf34807fe86d23e8317ffd5dfaa51ce947951 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Sat, 4 Apr 2026 07:17:58 +0000 Subject: [PATCH 003/132] Allow requesting project info without manual retry on fail Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/modplatform/ResourceAPI.cpp | 7 ++++--- launcher/modplatform/ResourceAPI.h | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/ResourceAPI.cpp b/launcher/modplatform/ResourceAPI.cpp index cda90b677..bd683ed21 100644 --- a/launcher/modplatform/ResourceAPI.cpp +++ b/launcher/modplatform/ResourceAPI.cpp @@ -148,9 +148,9 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback&& callbacks) const +Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback&& callbacks, bool askRetry) const { - auto [job, response] = getProject(args.pack->addonId.toString()); + auto [job, response] = getProject(args.pack->addonId.toString(), askRetry); QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] { auto pack = args.pack; @@ -284,7 +284,7 @@ QString ResourceAPI::mapMCVersionToModrinth(Version v) const return verStr; } -std::pair ResourceAPI::getProject(QString addonId) const +std::pair ResourceAPI::getProject(QString addonId, bool askRetry) const { auto project_url_optional = getInfoURL(addonId); if (!project_url_optional.has_value()) @@ -293,6 +293,7 @@ std::pair ResourceAPI::getProject(QString addonId) const auto project_url = project_url_optional.value(); auto netJob = makeShared(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + netJob->setAskRetry(askRetry); auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url)); netJob->addNetAction(action); diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 0ad55775c..51b6d4b50 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -115,10 +115,10 @@ class ResourceAPI { public slots: virtual Task::Ptr searchProjects(SearchArgs&&, Callback>&&) const; - virtual std::pair getProject(QString addonId) const; + virtual std::pair getProject(QString addonId, bool askRetry = true) const; virtual std::pair getProjects(QStringList addonIds) const = 0; - virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&) const; + virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&, bool askRetry = true) const; Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback>&& callbacks) const; virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback&&) const; From bf75d50bafdb589f1075be23f4ba29cc880bc90f Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Sat, 4 Apr 2026 07:27:29 +0000 Subject: [PATCH 004/132] Make search by id fail quietly Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 2 +- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 5d968d65a..56c655302 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -178,7 +178,7 @@ void ListModel::performPaginatedSearch() }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) { + if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) { m_jobPtr = job; m_jobPtr->start(); } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 05cd2970c..9c1af44c0 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -148,7 +148,7 @@ void ModpackListModel::performPaginatedSearch() }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) { + if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) { m_jobPtr = job; m_jobPtr->start(); } From 4706f894e315260be2a67b63127da6273d764354 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Sat, 4 Apr 2026 07:29:19 +0000 Subject: [PATCH 005/132] Activate search by project id only for numarical values for CurseForge Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 5 ++++- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 56c655302..cf2bbd890 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -165,7 +165,10 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { static const FlameAPI api; - if (m_currentSearchTerm.startsWith("#")) { + + // activate search by id only for numerical values because all CurseForge ids are numerical + static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$"); + if (s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 9c1af44c0..5cc515536 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -135,6 +135,7 @@ void ModpackListModel::performPaginatedSearch() return; static const ModrinthAPI api; + // Modrinth ids are not limited to numbers and can be any length if (m_currentSearchTerm.startsWith("#")) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { From 4151db6c94ac369595b2f07afcd9bab01b19bf22 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:12:23 +0000 Subject: [PATCH 006/132] Fallback to normal search on error and apply same changes to ResourceModel Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- .../ui/pages/modplatform/ResourceModel.cpp | 19 +++++++++++++++---- .../ui/pages/modplatform/flame/FlameModel.cpp | 9 ++++++--- .../modplatform/modrinth/ModrinthModel.cpp | 7 +++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index e90eafbf2..4e46a08d6 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -139,15 +139,16 @@ void ResourceModel::search() if (hasActiveSearchJob()) return; - if (m_search_term.startsWith("#")) { + if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) { auto projectId = m_search_term.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { + callbacks.on_fail = [this](QString reason, int network_error_code) { if (!s_running_models.constFind(this).value()) return; - searchRequestFailed(reason, -1); + m_search_state = SearchState::ResetRequested;; + searchRequestFailed(reason, network_error_code); }; callbacks.on_abort = [this] { if (!s_running_models.constFind(this).value()) @@ -407,6 +408,9 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); break; + case 404: + // 404 Not Found, some APIs return this when nothing is found, no need to bother the user + break; case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), @@ -414,7 +418,14 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net break; } - m_search_state = SearchState::Finished; + if (m_search_state == SearchState::ResetRequested) { + clearData(); + + m_next_search_offset = 0; + search(); + } else { + m_search_state = SearchState::Finished; + } } void ResourceModel::searchRequestAborted() diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index cf2bbd890..0fcdc841f 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -166,14 +166,17 @@ void ListModel::performPaginatedSearch() { static const FlameAPI api; - // activate search by id only for numerical values because all CurseForge ids are numerical + // activate id search only for numerical values because all CurseForge ids are numerical static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$"); - if (s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) { + if (m_searchState != ResetRequested && s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_fail = [this](QString reason, int) { + m_searchState = ResetRequested; + searchRequestFailed(reason); + }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 5cc515536..0308e7214 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -136,12 +136,15 @@ void ModpackListModel::performPaginatedSearch() static const ModrinthAPI api; // Modrinth ids are not limited to numbers and can be any length - if (m_currentSearchTerm.startsWith("#")) { + if (m_searchState != ResetRequested && m_currentSearchTerm.startsWith("#")) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_fail = [this](QString reason, int) { + m_searchState = ResetRequested; + searchRequestFailed(reason); + }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; From fdd1a5dde8f9eb0986365dd650e05bcc297d8ea3 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:35:06 +0000 Subject: [PATCH 007/132] oops forgot again Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/ui/pages/modplatform/ResourceModel.cpp | 2 +- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 4e46a08d6..b96061b43 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -163,7 +163,7 @@ void ResourceModel::search() }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks)); job) + if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) runSearchJob(job); return; } diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 0fcdc841f..c22d12da0 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -166,7 +166,7 @@ void ListModel::performPaginatedSearch() { static const FlameAPI api; - // activate id search only for numerical values because all CurseForge ids are numerical + // activate search by id only for numerical values because all CurseForge ids are numerical static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$"); if (m_searchState != ResetRequested && s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) { auto projectId = m_currentSearchTerm.mid(1); From 364968a6b44cfe5e6639ae34c9feba55336b35f8 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:57:09 +0000 Subject: [PATCH 008/132] Use network_error_code from callbacks Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- .../modplatform/modrinth/ModrinthModel.cpp | 19 +++++++++---------- .../modplatform/modrinth/ModrinthModel.h | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 0308e7214..fba9f3bc9 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -141,14 +141,14 @@ void ModpackListModel::performPaginatedSearch() if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { + callbacks.on_fail = [this](QString reason, int network_error_code) { m_searchState = ResetRequested; - searchRequestFailed(reason); + searchRequestFailed(reason, network_error_code); }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; - searchRequestFailed("Aborted"); + searchRequestFailed("Aborted", 0); }; auto project = std::make_shared(); project->addonId = projectId; @@ -165,10 +165,10 @@ void ModpackListModel::performPaginatedSearch() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); }; - callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_fail = [this](QString reason, int network_error_code) { searchRequestFailed(reason, network_error_code); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; - searchRequestFailed("Aborted"); + searchRequestFailed("Aborted", 0); }; auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders, @@ -320,13 +320,12 @@ void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Pt endInsertRows(); } -void ModpackListModel::searchRequestFailed(QString) +void ModpackListModel::searchRequestFailed(QString reason, int network_error_code) { - auto failed_action = dynamic_cast(m_jobPtr.get())->getFailedActions().at(0); - if (failed_action->replyStatusCode() == -1) { - // Network error + if (network_error_code == -1) { + // Unknown error in network stack QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); - } else if (failed_action->replyStatusCode() == 409) { + } else if (network_error_code == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 96f6fd128..3e0fc1686 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -85,7 +85,7 @@ class ModpackListModel : public QAbstractListModel { public slots: void searchRequestFinished(QList& doc_all); - void searchRequestFailed(QString reason); + void searchRequestFailed(QString reason, int network_error_code); void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr); protected slots: From 2fe0569bd6213205949bf5a1f49600958dfe0e24 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 17:41:25 +0500 Subject: [PATCH 009/132] fix(McClient): do not use unsigned type for response length Signed-off-by: Octol1ttle --- launcher/ui/pages/instance/McClient.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index 633e7aaed..f0a6808fa 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -20,7 +20,7 @@ class McClient : public QObject { // 1: read the response length, still reading the response // 2: finished reading the response unsigned m_responseReadState = 0; - unsigned m_wantedRespLength = 0; + int32_t m_wantedRespLength = 0; QByteArray m_resp; public: From 91616ae9b6379d18dec5131870554bfacc5f7543 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 18:57:33 +0500 Subject: [PATCH 010/132] refactor: McClient Signed-off-by: Octol1ttle --- launcher/ui/pages/instance/McClient.cpp | 90 +++++++++++++------------ launcher/ui/pages/instance/McClient.h | 59 ++++++++-------- 2 files changed, 78 insertions(+), 71 deletions(-) diff --git a/launcher/ui/pages/instance/McClient.cpp b/launcher/ui/pages/instance/McClient.cpp index f110e1fe8..0a719431d 100644 --- a/launcher/ui/pages/instance/McClient.cpp +++ b/launcher/ui/pages/instance/McClient.cpp @@ -1,18 +1,17 @@ +#include "McClient.h" + #include #include #include #include +#include -#include +#include "Exception.h" #include "Json.h" -#include "McClient.h" -// 7 first bits -#define SEGMENT_BITS 0x7F -// last bit -#define CONTINUE_BIT 0x80 - -McClient::McClient(QObject* parent, QString domain, QString ip, short port) : QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {} +McClient::McClient(QObject* parent, QString domain, QString ip, const uint16_t port) + : QObject(parent), m_domain(std::move(domain)), m_ip(std::move(ip)), m_port(port) +{} void McClient::getStatusData() { @@ -33,13 +32,12 @@ void McClient::getStatusData() void McClient::sendRequest() { QByteArray data; - writeVarInt(data, 0x00); // packet ID - writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) - writeVarInt(data, m_domain.size()); // server address length - writeString(data, m_domain.toStdString()); // server address - writeFixedInt(data, m_port, 2); // server port - writeVarInt(data, 0x01); // next state - writePacketToSocket(data); // send handshake packet + writeVarInt(data, 0x00); // packet ID + writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1) + writeString(data, m_domain); // server address + writeUInt16(data, m_port); // server port + writeVarInt(data, 0x01); // next state + writePacketToSocket(data); // send handshake packet writeVarInt(data, 0x00); // packet ID writePacketToSocket(data); // send status packet @@ -47,17 +45,17 @@ void McClient::sendRequest() void McClient::readRawResponse() { - if (m_responseReadState == 2) { + if (m_responseReadState == ResponseReadState::Finished) { return; } m_resp.append(m_socket.readAll()); - if (m_responseReadState == 0 && m_resp.size() >= 5) { + if (m_responseReadState == ResponseReadState::Waiting && m_resp.size() >= 5) { m_wantedRespLength = readVarInt(m_resp); - m_responseReadState = 1; + m_responseReadState = ResponseReadState::GotLength; } - if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) { + if (m_responseReadState == ResponseReadState::GotLength && m_resp.size() >= m_wantedRespLength) { if (m_resp.size() > m_wantedRespLength) { qDebug().nospace() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " << m_resp.size() << " received)"; @@ -67,7 +65,7 @@ void McClient::readRawResponse() } catch (const Exception& e) { emitFail(e.cause()); } - m_responseReadState = 2; + m_responseReadState = ResponseReadState::Finished; } } @@ -75,7 +73,7 @@ void McClient::parseResponse() { qDebug() << "Received response successfully"; - int packetID = readVarInt(m_resp); + const int packetID = readVarInt(m_resp); if (packetID != 0x00) { throw Exception(QString("Packet ID doesn't match expected value (0x00 vs 0x%1)").arg(packetID, 0, 16)); } @@ -84,7 +82,7 @@ void McClient::parseResponse() // 'resp' should now be the JSON string QJsonParseError parseError; - QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError); + const QJsonDocument doc = Json::parseUntilGarbage(m_resp, &parseError); if (parseError.error != QJsonParseError::NoError) { qDebug() << "Failed to parse JSON:" << parseError.errorString(); emitFail(parseError.errorString()); @@ -93,18 +91,23 @@ void McClient::parseResponse() emitSucceed(doc.object()); } +// NOLINTBEGIN(*-signed-bitwise) + // From https://wiki.vg/Protocol#VarInt_and_VarLong +constexpr uint8_t g_varIntValueMask = 0x7F; +constexpr uint8_t g_varIntContinue = 0x80; + void McClient::writeVarInt(QByteArray& data, int value) { - while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits + while ((value & ~g_varIntValueMask) != 0) { // check if the value is too big to fit in 7 bits // Write 7 bits - data.append((value & SEGMENT_BITS) | CONTINUE_BIT); + data.append(static_cast((value & ~g_varIntValueMask) | g_varIntContinue)); // NOLINT(*-narrowing-conversions) // Erase theses 7 bits from the value to write // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone value >>= 7; } - data.append(value); + data.append(static_cast(value)); // NOLINT(*-narrowing-conversions) } // From https://wiki.vg/Protocol#VarInt_and_VarLong @@ -112,53 +115,56 @@ int McClient::readVarInt(QByteArray& data) { int value = 0; int position = 0; - char currentByte; while (position < 32) { - currentByte = readByte(data); - value |= (currentByte & SEGMENT_BITS) << position; + const uint8_t currentByte = readByte(data); + value |= (currentByte & g_varIntValueMask) << position; - if ((currentByte & CONTINUE_BIT) == 0) + if ((currentByte & g_varIntContinue) == 0) { break; + } position += 7; } - if (position >= 32) + if (position >= 32) { throw Exception("VarInt is too big"); + } return value; } -char McClient::readByte(QByteArray& data) +// NOLINTEND(*-signed-bitwise) + +uint8_t McClient::readByte(QByteArray& data) { if (data.isEmpty()) { throw Exception("No more bytes to read"); } - char byte = data.at(0); + const uint8_t byte = data.at(0); data.remove(0, 1); return byte; } -// write number with specified size in big endian format -void McClient::writeFixedInt(QByteArray& data, int value, int size) +void McClient::writeUInt16(QByteArray& data, const uint16_t value) { - for (int i = size - 1; i >= 0; i--) { - data.append((value >> (i * 8)) & 0xFF); - } + QDataStream stream(&data, QIODeviceBase::Append); + stream.setByteOrder(QDataStream::BigEndian); + stream << value; } -void McClient::writeString(QByteArray& data, const std::string& value) +void McClient::writeString(QByteArray& data, const QString& value) { - data.append(value.c_str()); + writeVarInt(data, static_cast(value.size())); + data.append(value.toUtf8()); } void McClient::writePacketToSocket(QByteArray& data) { // we prefix the packet with its length QByteArray dataWithSize; - writeVarInt(dataWithSize, data.size()); + writeVarInt(dataWithSize, static_cast(data.size())); dataWithSize.append(data); // write it to the socket @@ -168,7 +174,7 @@ void McClient::writePacketToSocket(QByteArray& data) data.clear(); } -void McClient::emitFail(QString error) +void McClient::emitFail(const QString& error) { qDebug() << "Minecraft server ping for status error:" << error; emit failed(error); @@ -177,6 +183,6 @@ void McClient::emitFail(QString error) void McClient::emitSucceed(QJsonObject data) { - emit succeeded(data); + emit succeeded(std::move(data)); emit finished(); } diff --git a/launcher/ui/pages/instance/McClient.h b/launcher/ui/pages/instance/McClient.h index f0a6808fa..c1cb3d748 100644 --- a/launcher/ui/pages/instance/McClient.h +++ b/launcher/ui/pages/instance/McClient.h @@ -1,53 +1,54 @@ #pragma once + #include -#include #include #include #include -#include - // Client for the Minecraft protocol class McClient : public QObject { Q_OBJECT - QString m_domain; - QString m_ip; - short m_port; - QTcpSocket m_socket; - - // 0: did not start reading the response yet - // 1: read the response length, still reading the response - // 2: finished reading the response - unsigned m_responseReadState = 0; - int32_t m_wantedRespLength = 0; - QByteArray m_resp; - public: - explicit McClient(QObject* parent, QString domain, QString ip, short port); + explicit McClient(QObject* parent, QString domain, QString ip, uint16_t port); //! Read status data of the server, and calls the succeeded() signal with the parsed JSON data void getStatusData(); + signals: + void succeeded(QJsonObject data); + void failed(QString error); + void finished(); + + private: + static uint8_t readByte(QByteArray& data); + static int readVarInt(QByteArray& data); + static void writeUInt16(QByteArray& data, uint16_t value); + static void writeString(QByteArray& data, const QString& value); + static void writeVarInt(QByteArray& data, int value); + private: void sendRequest(); //! Accumulate data until we have a full response, then call parseResponse() once void readRawResponse(); void parseResponse(); - - void writeVarInt(QByteArray& data, int value); - int readVarInt(QByteArray& data); - char readByte(QByteArray& data); - //! write number with specified size in big endian format - void writeFixedInt(QByteArray& data, int value, int size); - void writeString(QByteArray& data, const std::string& value); - void writePacketToSocket(QByteArray& data); - void emitFail(QString error); + void emitFail(const QString& error); void emitSucceed(QJsonObject data); - signals: - void succeeded(QJsonObject data); - void failed(QString error); - void finished(); + private: + enum class ResponseReadState : uint8_t { + Waiting, + GotLength, + Finished + }; + + QString m_domain; + QString m_ip; + uint16_t m_port; + QTcpSocket m_socket; + + ResponseReadState m_responseReadState = ResponseReadState::Waiting; + int32_t m_wantedRespLength = 0; + QByteArray m_resp; }; From c044ed36af1ebb969b1b8d4a3ef80895ec681921 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 19:34:11 +0500 Subject: [PATCH 011/132] feat: allow disabling low RAM warning Signed-off-by: Octol1ttle --- launcher/Application.cpp | 6 + launcher/minecraft/MinecraftInstance.cpp | 1 + .../launch/EnsureAvailableMemory.cpp | 29 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 3 + launcher/ui/widgets/JavaSettingsWidget.ui | 316 +++++++++--------- 5 files changed, 189 insertions(+), 166 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 7af39b55e..e7acc9038 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -736,6 +736,12 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem()); m_settings->registerSetting("PermGen", 128); + // https://github.com/PrismLauncher/PrismLauncher/issues/5305 + // arm64 Macs have configurations with very little physical memory, but the OS can compensate by compressing and swapping + // According to user reports, this is not noticeable under normal usage due to fast storage speeds + const bool lowMemWarningDefault = !(SysInfo::currentSystem() == "osx" && SysInfo::useQTForArch() == "arm64"); + m_settings->registerSetting("LowMemWarning", lowMemWarningDefault); + // Java Settings m_settings->registerSetting("JavaPath", ""); m_settings->registerSetting("JavaSignature", ""); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 2848f1e99..e8fc642fa 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -209,6 +209,7 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting); + m_settings->registerOverride(global_settings->getSetting("LowMemWarning"), memorySetting); // Native library workarounds auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index 8a436a6f6..ae0ffe1ef 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -31,20 +31,25 @@ void EnsureAvailableMemory::executeTask() const uint64_t required = std::max(min, max); if (required > available) { - auto* dialog = CustomMessageBox::selectable( - nullptr, tr("Not enough RAM"), - tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n" - "Required: %1 MiB\nAvailable: %2 MiB\n\n" - "Continue anyway? This may cause slowdowns in the game and your system.") - .arg(required) - .arg(available), - QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, - QMessageBox::StandardButton::No); - const auto response = dialog->exec(); - dialog->deleteLater(); + bool shouldAbort = false; + + if (m_instance->settings()->get("LowMemWarning").toBool()) { + auto* dialog = CustomMessageBox::selectable( + nullptr, tr("Not enough RAM"), + tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n" + "Required: %1 MiB\nAvailable: %2 MiB\n\n" + "Continue anyway? This may cause slowdowns in the game and your system.") + .arg(required) + .arg(available), + QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::No); + + shouldAbort = dialog->exec() == QMessageBox::No; + dialog->deleteLater(); + } const auto message = tr("Not enough RAM available to launch this instance"); - if (response == QMessageBox::No) { + if (shouldAbort) { emit logLine(message, MessageLevel::Fatal); emitFailed(message); return; diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 4e74c610f..e13c847d0 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -151,6 +151,7 @@ void JavaSettingsWidget::loadSettings() m_ui->maxMemSpinBox->setValue(min); } m_ui->permGenSpinBox->setValue(settings->get("PermGen").toInt()); + m_ui->lowMemWarningCheckBox->setChecked(settings->get("LowMemWarning").toBool()); // Java arguments m_ui->javaArgumentsGroupBox->setChecked(m_instance == nullptr || settings->get("OverrideJavaArgs").toBool()); @@ -205,10 +206,12 @@ void JavaSettingsWidget::saveSettings() settings->set("MaxMemAlloc", min); } settings->set("PermGen", m_ui->permGenSpinBox->value()); + settings->set("LowMemWarning", m_ui->lowMemWarningCheckBox->isChecked()); } else { settings->reset("MinMemAlloc"); settings->reset("MaxMemAlloc"); settings->reset("PermGen"); + settings->reset("LowMemWarning"); } // Java arguments diff --git a/launcher/ui/widgets/JavaSettingsWidget.ui b/launcher/ui/widgets/JavaSettingsWidget.ui index 46f714b76..14638cf4e 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.ui +++ b/launcher/ui/widgets/JavaSettingsWidget.ui @@ -55,7 +55,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -86,7 +86,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -101,10 +101,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -160,10 +160,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Fixed + QSizePolicy::Policy::Fixed @@ -190,156 +190,166 @@ false - - - + + + + + + + M&inimum Memory Usage: + + + minMemSpinBox + + + + + + + + + + 0 + 0 + + + + The amount of memory Minecraft is started with. + + + MiB + + + 8 + + + 1048576 + + + 128 + + + 256 + + + + + + + (-Xms) + + + + + + + + + Ma&ximum Memory Usage: + + + maxMemSpinBox + + + + + + + + + + 0 + 0 + + + + The maximum amount of memory Minecraft is allowed to use. + + + MiB + + + 8 + + + 1048576 + + + 128 + + + 1024 + + + + + + + (-Xmx) + + + + + + + + + &PermGen Size: + + + permGenSpinBox + + + + + + + + + + 0 + 0 + + + + The amount of memory available to store loaded Java classes. + + + MiB + + + 4 + + + 1048576 + + + 8 + + + 64 + + + + + + + (-XX:PermSize) + + + + + + + + + - (-XX:PermSize) + Warn when there is not enough memory available - - - - - 0 - 0 - - - - The amount of memory available to store loaded Java classes. - - - MiB - - - 4 - - - 1048576 - - - 8 - - - 64 - - - - - - - - 0 - 0 - - - - The maximum amount of memory Minecraft is allowed to use. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 1024 - - - - - - - (-Xmx) - - - - - - - - 0 - 0 - - - - The amount of memory Minecraft is started with. - - - MiB - - - 8 - - - 1048576 - - - 128 - - - 256 - - - - - - - &PermGen Size: - - - permGenSpinBox - - - - - - - (-Xms) - - - - - - - Ma&ximum Memory Usage: - - - maxMemSpinBox - - - - - - - M&inimum Memory Usage: - - - minMemSpinBox - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - + Memory Notice @@ -382,9 +392,7 @@ autodownloadJavaCheckBox javaTestBtn javaDownloadBtn - minMemSpinBox maxMemSpinBox - permGenSpinBox jvmArgsTextBox From ec4484282c8af4b3744fdf9ad018b188fc2f0ba8 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 20:11:27 +0500 Subject: [PATCH 012/132] fix: don't count JAR mods when checking offline libraries Signed-off-by: Octol1ttle --- launcher/minecraft/LaunchProfile.cpp | 5 ++-- launcher/minecraft/LaunchProfile.h | 3 ++- .../launch/EnsureOfflineLibraries.cpp | 25 +++++++++++++------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp index 577b8581c..fb74d4a9a 100644 --- a/launcher/minecraft/LaunchProfile.cpp +++ b/launcher/minecraft/LaunchProfile.cpp @@ -349,7 +349,8 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, QStringList& jars, QStringList& nativeJars, const QString& overridePath, - const QString& tempPath) const + const QString& tempPath, + bool addJarMods) const { QStringList native32, native64; jars.clear(); @@ -360,7 +361,7 @@ void LaunchProfile::getLibraryFiles(const RuntimeContext& runtimeContext, // NOTE: order is important here, add main jar last to the lists if (m_mainJar) { // FIXME: HACK!! jar modding is weird and unsystematic! - if (m_jarMods.size()) { + if (m_jarMods.size() && addJarMods) { QDir tempDir(tempPath); jars.append(tempDir.absoluteFilePath("minecraft.jar")); } else { diff --git a/launcher/minecraft/LaunchProfile.h b/launcher/minecraft/LaunchProfile.h index 0d2d97c45..6dc3d9aeb 100644 --- a/launcher/minecraft/LaunchProfile.h +++ b/launcher/minecraft/LaunchProfile.h @@ -87,7 +87,8 @@ class LaunchProfile : public ProblemProvider { QStringList& jars, QStringList& nativeJars, const QString& overridePath, - const QString& tempPath) const; + const QString& tempPath, + bool addJarMods = true) const; bool hasTrait(const QString& trait) const; ProblemSeverity getProblemSeverity() const override; const QList getProblems() const override; diff --git a/launcher/minecraft/launch/EnsureOfflineLibraries.cpp b/launcher/minecraft/launch/EnsureOfflineLibraries.cpp index 59801a1d0..0165fbdf9 100644 --- a/launcher/minecraft/launch/EnsureOfflineLibraries.cpp +++ b/launcher/minecraft/launch/EnsureOfflineLibraries.cpp @@ -27,16 +27,27 @@ void EnsureOfflineLibraries::executeTask() { const auto profile = m_instance->getPackProfile()->getProfile(); QStringList allJars; - profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot()); + profile->getLibraryFiles(m_instance->runtimeContext(), allJars, allJars, m_instance->getLocalLibraryPath(), m_instance->binRoot(), + false); + + QStringList missing; for (const auto& jar : allJars) { if (!QFileInfo::exists(jar)) { - emit logLine(tr("This instance cannot be launched because some libraries are missing or have not been downloaded yet. Please " - "try again in online mode with a working Internet connection"), - MessageLevel::Fatal); - emitFailed("Required libraries are missing"); - return; + missing.append(jar); } } - emitSucceeded(); + if (missing.isEmpty()) { + emitSucceeded(); + return; + } + + emit logLine("Missing libraries:", MessageLevel::Error); + for (const auto& jar : missing) { + emit logLine(" " + jar, MessageLevel::Error); + } + emit logLine(tr("\nThis instance cannot be launched because some libraries are missing or have not been downloaded yet. Please " + "try again in online mode with a working Internet connection"), + MessageLevel::Fatal); + emitFailed("Required libraries are missing"); } From 724c9a4a2c07d54b188fa6a9f8185442208cf08c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 20:40:22 +0500 Subject: [PATCH 013/132] fix(CI/nix): bump macOS Signed-off-by: Octol1ttle --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a2398642f..0fea44f08 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -88,7 +88,7 @@ jobs: - os: ubuntu-22.04-arm system: aarch64-linux - - os: macos-14 + - os: macos-26 system: aarch64-darwin runs-on: ${{ matrix.os }} From 4cf8cf7d18d63b113a0c25e096074679e61f99f9 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 20:53:42 +0500 Subject: [PATCH 014/132] fix(PrintInstanceInfo): add break before OS info Signed-off-by: Octol1ttle --- launcher/minecraft/launch/PrintInstanceInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/minecraft/launch/PrintInstanceInfo.cpp b/launcher/minecraft/launch/PrintInstanceInfo.cpp index d837f5faf..7bfe73746 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.cpp +++ b/launcher/minecraft/launch/PrintInstanceInfo.cpp @@ -61,6 +61,7 @@ void PrintInstanceInfo::executeTask() auto instance = m_parent->instance(); QStringList log; + log << ""; log << "OS: " + QString("%1 | %2 | %3").arg(QSysInfo::prettyProductName(), QSysInfo::kernelType(), QSysInfo::kernelVersion()); #ifdef Q_OS_FREEBSD ::runSysctlHwModel(log); From 658a1391f8fbcf7b51ffc741d393728768b4a5b1 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 9 Apr 2026 22:35:32 +0500 Subject: [PATCH 015/132] change(EnsureAvailableMemory): add lenience Signed-off-by: Octol1ttle --- launcher/minecraft/launch/EnsureAvailableMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index ae0ffe1ef..a0e156770 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -30,7 +30,7 @@ void EnsureAvailableMemory::executeTask() const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt(); const uint64_t required = std::max(min, max); - if (required > available) { + if (static_cast(required) * 0.9 > static_cast(available)) { bool shouldAbort = false; if (m_instance->settings()->get("LowMemWarning").toBool()) { From 9bccda0a79a2e0e194ad1a282914208187ef0610 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 9 Apr 2026 23:32:02 +0300 Subject: [PATCH 016/132] chore: bump develop version to 12.0.0 Signed-off-by: Trial97 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80977e06e..7e9ba2d4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,7 +179,7 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.") ######## Set version numbers ######## -set(Launcher_VERSION_MAJOR 11) +set(Launcher_VERSION_MAJOR 12) set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_PATCH 0) From b7344af313397c00a80fc823ecfa46229eec1ff0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 Apr 2026 00:36:05 +0300 Subject: [PATCH 017/132] fix pack upgrade Signed-off-by: Trial97 --- launcher/InstanceList.cpp | 12 +++++------- launcher/ui/pages/instance/ManagedPackPage.cpp | 15 ++++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 1fe96144d..1339499c7 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -924,23 +924,23 @@ class InstanceStaging : public Task { connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded); - m_backoffTimer.setSingleShot(true); } - virtual ~InstanceStaging() {} + ~InstanceStaging() override = default; // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if (!canAbort()) + if (!canAbort()) { return false; + } return m_child->abort(); } bool canAbort() const override { return (m_child && m_child->canAbort()); } protected: - virtual void executeTask() override + void executeTask() override { if (m_stagingPath.isNull()) { emitFailed(tr("Could not create staging folder")); @@ -954,10 +954,8 @@ class InstanceStaging : public Task { private slots: void childSucceeded() { - if (!isRunning()) - return; unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, *m_child.get(), m_child->group(), *m_child.get())) { + if (m_parent->commitStagedInstance(m_stagingPath, *m_child, m_child->group(), *m_child)) { m_backoffTimer.stop(); emitSucceeded(); return; diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index ad6b39723..508bfeba0 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -202,23 +202,24 @@ bool ManagedPackPage::runUpdateTask(InstanceTask* task) unique_qobject_ptr wrapped_task(APPLICATION->instances()->wrapInstanceTask(task)); - connect(task, &Task::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); - connect(task, &Task::succeeded, [this, task]() { + connect(wrapped_task.get(), &Task::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); + connect(wrapped_task.get(), &Task::succeeded, [this, task]() { QStringList warnings = task->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } }); - connect(task, &Task::aborted, [this] { + connect(wrapped_task.get(), &Task::aborted, [this] { CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) ->show(); }); ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(task); + loadDialog.execWithTask(wrapped_task.get()); - return task->wasSuccessful(); + return wrapped_task->wasSuccessful(); } void ManagedPackPage::suggestVersion() From 2219c37d7f8ae7f1a00454e26c246677a9d1da9c Mon Sep 17 00:00:00 2001 From: morsz Date: Fri, 10 Apr 2026 02:00:02 +0200 Subject: [PATCH 018/132] fix: force metadata version list refreshes to reload manual refreshes on version selection screens could reuse cached metadata and skip downloading updated manifests, so new versions would not appear until Prism was restarted. pass an explicit forced reload through the shared version list loading path and use it from refresh actions so manual refresh always reloads metadata Signed-off-by: morsz --- launcher/BaseVersionList.h | 2 +- launcher/java/JavaInstallList.cpp | 3 ++- launcher/java/JavaInstallList.h | 2 +- launcher/meta/BaseEntity.cpp | 15 +++++++++++---- launcher/meta/BaseEntity.h | 5 +++-- launcher/meta/VersionList.cpp | 6 +++--- launcher/meta/VersionList.h | 2 +- launcher/ui/dialogs/InstallLoaderDialog.cpp | 2 +- launcher/ui/dialogs/VersionSelectDialog.cpp | 2 +- launcher/ui/java/InstallJavaDialog.cpp | 4 ++-- launcher/ui/java/VersionList.cpp | 4 ++-- launcher/ui/java/VersionList.h | 2 +- launcher/ui/pages/modplatform/CustomPage.cpp | 4 ++-- launcher/ui/widgets/VersionSelectWidget.cpp | 4 ++-- launcher/ui/widgets/VersionSelectWidget.h | 2 +- 15 files changed, 34 insertions(+), 25 deletions(-) diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index 673d13562..ba546e955 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -63,7 +63,7 @@ class BaseVersionList : public QAbstractListModel { * The task returned by this function should reset the model when it's done. * \return A pointer to a task that reloads the version list. */ - virtual Task::Ptr getLoadTask() = 0; + virtual Task::Ptr getLoadTask(bool forceReload = false) = 0; //! Checks whether or not the list is loaded. If this returns false, the list should be // loaded. diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 254d5f468..1d7b9cdff 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -51,8 +51,9 @@ JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions) : BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions) {} -Task::Ptr JavaInstallList::getLoadTask() +Task::Ptr JavaInstallList::getLoadTask(bool forceReload) { + Q_UNUSED(forceReload) load(); return getCurrentTask(); } diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index c68c2a3be..58d1ad8ca 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList { public: explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false); - Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask(bool forceReload = false) override; bool isLoaded() override; const BaseVersion::Ptr at(int i) const override; int count() const override; diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index c809f851a..d638fd8b9 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -82,12 +82,12 @@ QUrl BaseEntity::url() const return QUrl(metaOverride).resolved(localFilename()); } -Task::Ptr BaseEntity::loadTask(Net::Mode mode) +Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload) { if (m_task && m_task->isRunning()) { return m_task; } - m_task.reset(new BaseEntityLoadTask(this, mode)); + m_task.reset(new BaseEntityLoadTask(this, mode, forceReload)); return m_task; } @@ -107,7 +107,9 @@ BaseEntity::LoadStatus BaseEntity::status() const return m_load_status; } -BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {} +BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload) + : m_entity(parent), m_mode(mode), m_force_reload(forceReload) +{} void BaseEntityLoadTask::executeTask() { @@ -149,13 +151,18 @@ void BaseEntityLoadTask::executeTask() auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline; // if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches; - if (wasLoadedOffline || wasLoadedRemote) { + if (wasLoadedOffline || (wasLoadedRemote && !m_force_reload)) { emitSucceeded(); return; } m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network())); auto url = m_entity->url(); auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename()); + if (m_force_reload) { + // clear validators so manual refreshes fetch a fresh body + entry->setETag({}); + entry->setRemoteChangedTimestamp({}); + } entry->setStale(true); auto dl = Net::ApiDownload::makeCached(url, entry); /* diff --git a/launcher/meta/BaseEntity.h b/launcher/meta/BaseEntity.h index 17aa0cb87..32d8bdbb8 100644 --- a/launcher/meta/BaseEntity.h +++ b/launcher/meta/BaseEntity.h @@ -43,7 +43,7 @@ class BaseEntity { void setSha256(QString sha256); virtual void parse(const QJsonObject& obj) = 0; - [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online); + [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false); protected: QString m_sha256; // the expected sha256 @@ -58,7 +58,7 @@ class BaseEntityLoadTask : public Task { Q_OBJECT public: - explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode); + explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload); ~BaseEntityLoadTask() override = default; virtual void executeTask() override; @@ -68,6 +68,7 @@ class BaseEntityLoadTask : public Task { private: BaseEntity* m_entity; Net::Mode m_mode; + bool m_force_reload = false; NetJob::Ptr m_task; }; } // namespace Meta diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index dfca52d87..fa3a271a6 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -32,11 +32,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList( setObjectName("Version list: " + uid); } -Task::Ptr VersionList::getLoadTask() +Task::Ptr VersionList::getLoadTask(bool forceReload) { auto loadTask = makeShared(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid)); - loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online)); - loadTask->addTask(this->loadTask(Net::Mode::Online)); + loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online, forceReload)); + loadTask->addTask(this->loadTask(Net::Mode::Online, forceReload)); return loadTask; } diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 18681b8ed..709c361de 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -37,7 +37,7 @@ class VersionList : public BaseVersionList, public BaseEntity { enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole }; bool isLoaded() override; - Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask(bool forceReload = false) override; const BaseVersion::Ptr at(int i) const override; int count() const override; void sortVersions() override; diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index 2e9abe631..1780076e9 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -101,7 +101,7 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui buttonLayout->setContentsMargins(0, 0, 6, 6); #endif auto refreshButton = new QPushButton(tr("&Refresh"), this); - connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); }); + connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(true); }); buttonLayout->addWidget(refreshButton); buttons->setOrientation(Qt::Horizontal); diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp index 30377288b..c83fb0385 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.cpp +++ b/launcher/ui/dialogs/VersionSelectDialog.cpp @@ -144,7 +144,7 @@ BaseVersion::Ptr VersionSelectDialog::selectedVersion() const void VersionSelectDialog::on_refreshButton_clicked() { - m_versionWidget->loadList(); + m_versionWidget->loadList(true); } void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter) diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 81ffcfa83..fd8e43398 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -121,8 +121,8 @@ class InstallJavaPage : public QWidget, public BasePage { void selectSearch() { javaVersionSelect->selectSearch(); } void loadList() { - majorVersionSelect->loadList(); - javaVersionSelect->loadList(); + majorVersionSelect->loadList(true); + javaVersionSelect->loadList(true); } public slots: diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index f958f064f..7a4e84f33 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -33,9 +33,9 @@ VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVers sortVersions(); } -Task::Ptr VersionList::getLoadTask() +Task::Ptr VersionList::getLoadTask(bool forceReload) { - auto task = m_version->loadTask(Net::Mode::Online); + auto task = m_version->loadTask(Net::Mode::Online, forceReload); connect(task.get(), &Task::finished, this, &VersionList::sortVersions); return task; } diff --git a/launcher/ui/java/VersionList.h b/launcher/ui/java/VersionList.h index d334ed564..cf8a7448d 100644 --- a/launcher/ui/java/VersionList.h +++ b/launcher/ui/java/VersionList.h @@ -30,7 +30,7 @@ class VersionList : public BaseVersionList { public: explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0); - Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask(bool forceReload = false) override; bool isLoaded() override; const BaseVersion::Ptr at(int i) const override; int count() const override; diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index 87e126fd7..f24abf9fb 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -80,14 +80,14 @@ void CustomPage::openedImpl() void CustomPage::refresh() { - ui->versionList->loadList(); + ui->versionList->loadList(true); } void CustomPage::loaderRefresh() { if (ui->noneFilter->isChecked()) return; - ui->loaderVersionList->loadList(); + ui->loaderVersionList->loadList(true); } void CustomPage::filterChanged() diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp index 040355f4b..01b876146 100644 --- a/launcher/ui/widgets/VersionSelectWidget.cpp +++ b/launcher/ui/widgets/VersionSelectWidget.cpp @@ -127,9 +127,9 @@ void VersionSelectWidget::closeEvent(QCloseEvent* event) QWidget::closeEvent(event); } -void VersionSelectWidget::loadList() +void VersionSelectWidget::loadList(bool forceReload) { - m_load_task = m_vlist->getLoadTask(); + m_load_task = m_vlist->getLoadTask(forceReload); connect(m_load_task.get(), &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); connect(m_load_task.get(), &Task::failed, this, &VersionSelectWidget::onTaskFailed); connect(m_load_task.get(), &Task::progress, this, &VersionSelectWidget::changeProgress); diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h index c66d7e98e..ee9dbead7 100644 --- a/launcher/ui/widgets/VersionSelectWidget.h +++ b/launcher/ui/widgets/VersionSelectWidget.h @@ -57,7 +57,7 @@ class VersionSelectWidget : public QWidget { void initialize(BaseVersionList* vlist, bool forceLoad = false); //! Starts a task that loads the list. - void loadList(); + void loadList(bool forceReload = false); bool hasVersions() const; BaseVersion::Ptr selectedVersion() const; From f2c591620560384f5815a89d170ed2859e82c535 Mon Sep 17 00:00:00 2001 From: oosh Date: Thu, 9 Apr 2026 23:29:39 +1000 Subject: [PATCH 019/132] changed "Ok" to "OK" Signed-off-by: oosh --- launcher/ui/dialogs/InstallLoaderDialog.cpp | 2 +- launcher/ui/dialogs/VersionSelectDialog.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index 2e9abe631..7e00941ab 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -106,7 +106,7 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui buttons->setOrientation(Qt::Horizontal); buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - buttons->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + buttons->button(QDialogButtonBox::Ok)->setText(tr("OK")); buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp index 30377288b..7e777e09f 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.cpp +++ b/launcher/ui/dialogs/VersionSelectDialog.cpp @@ -69,7 +69,7 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList* vlist, QString title, m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); m_buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); m_horizontalLayout->addWidget(m_buttonBox); From 4b3aedd5d0567b57856453c96250637cf1495f67 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 10 Apr 2026 11:53:37 +0500 Subject: [PATCH 020/132] Change LowMemWarning default to always enabled Signed-off-by: Octol1ttle --- launcher/Application.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index e7acc9038..115b6489c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -735,12 +735,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::defaultMaxJvmMem()); m_settings->registerSetting("PermGen", 128); - - // https://github.com/PrismLauncher/PrismLauncher/issues/5305 - // arm64 Macs have configurations with very little physical memory, but the OS can compensate by compressing and swapping - // According to user reports, this is not noticeable under normal usage due to fast storage speeds - const bool lowMemWarningDefault = !(SysInfo::currentSystem() == "osx" && SysInfo::useQTForArch() == "arm64"); - m_settings->registerSetting("LowMemWarning", lowMemWarningDefault); + m_settings->registerSetting("LowMemWarning", true); // Java Settings m_settings->registerSetting("JavaPath", ""); From f3ff0a730a3cc220240405c6518e0fda2dd9cf3b Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 Apr 2026 19:29:56 +0300 Subject: [PATCH 021/132] enable modpack changelog for modrinth page Signed-off-by: Trial97 --- .../ui/pages/instance/ManagedPackPage.cpp | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index ad6b39723..0d264fb44 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -260,14 +260,16 @@ void ModrinthManagedPackPage::parseManagedPack() qDebug() << "Parsing Modrinth pack"; // No need for the extra work because we already have everything we need. - if (m_loaded) + if (m_loaded) { return; + } - if (m_fetch_job && m_fetch_job->isRunning()) + if (m_fetch_job && m_fetch_job->isRunning()) { m_fetch_job->abort(); + } ResourceAPI::Callback> callbacks{}; - m_pack = { m_inst->getManagedPackID() }; + m_pack = { .addonId = m_inst->getManagedPackID() }; // Use default if no callbacks are set callbacks.on_succeed = [this](auto& doc) { @@ -284,8 +286,9 @@ void ModrinthManagedPackPage::parseManagedPack() // NOTE: the id from version isn't the same id in the modpack format spec... // e.g. HexMC's 4.4.0 has versionId 4.0.0 in the modpack index.............. - if (version.version == m_inst->getManagedPackVersionName()) + if (version.version == m_inst->getManagedPackVersionName()) { name = tr("%1 (Current)").arg(name); + } ui->versionsComboBox->addItem(name, version.fileId); } @@ -294,10 +297,14 @@ void ModrinthManagedPackPage::parseManagedPack() m_loaded = true; }; - callbacks.on_fail = [this](QString reason, int) { setFailState(); }; + callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); }; callbacks.on_abort = [this]() { setFailState(); }; - m_fetch_job = m_api.getProjectVersions( - { std::make_shared(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); + m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared(m_pack), + .mcVersions = {}, + .loaders = {}, + .resourceType = ModPlatform::ResourceType::Modpack, + .includeChangelog = true }, + std::move(callbacks)); ui->changelogTextBrowser->setText(tr("Fetching changelogs...")); @@ -406,14 +413,16 @@ void FlameManagedPackPage::parseManagedPack() } // No need for the extra work because we already have everything we need. - if (m_loaded) + if (m_loaded) { return; + } - if (m_fetch_job && m_fetch_job->isRunning()) + if (m_fetch_job && m_fetch_job->isRunning()) { m_fetch_job->abort(); + } QString id = m_inst->getManagedPackID(); - m_pack = { id }; + m_pack = { .addonId = id }; ResourceAPI::Callback> callbacks{}; @@ -430,8 +439,9 @@ void FlameManagedPackPage::parseManagedPack() for (const auto& version : m_pack.versions) { QString name = version.getVersionDisplayString(); - if (version.fileId == m_inst->getManagedPackVersionID().toInt()) + if (version.fileId == m_inst->getManagedPackVersionID().toInt()) { name = tr("%1 (Current)").arg(name); + } ui->versionsComboBox->addItem(name, QVariant(version.fileId)); } @@ -440,10 +450,14 @@ void FlameManagedPackPage::parseManagedPack() m_loaded = true; }; - callbacks.on_fail = [this](QString reason, int) { setFailState(); }; + callbacks.on_fail = [this](const QString& /*reason*/, int) { setFailState(); }; callbacks.on_abort = [this]() { setFailState(); }; - m_fetch_job = m_api.getProjectVersions( - { std::make_shared(m_pack), {}, {}, ModPlatform::ResourceType::Modpack }, std::move(callbacks)); + m_fetch_job = m_api.getProjectVersions({ .pack = std::make_shared(m_pack), + .mcVersions = {}, + .loaders = {}, + .resourceType = ModPlatform::ResourceType::Modpack, + .includeChangelog = true }, + std::move(callbacks)); m_fetch_job->start(); } From ac54df366bc76f87116cd43b67d5fd07baeb8b32 Mon Sep 17 00:00:00 2001 From: Andrei Damian Date: Sat, 11 Apr 2026 12:04:08 +0300 Subject: [PATCH 022/132] fix pessimizing-move warning Signed-off-by: Andrei Damian --- launcher/screenshots/ImgurUpload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index e6ba372ed..dd41fc340 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -118,7 +118,7 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot) { auto up = makeShared(m_shot->m_file); - up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image"); + up->m_url = BuildConfig.IMGUR_BASE_URL + "image"; up->m_sink.reset(new Sink(m_shot)); up->addHeaderProxy(std::make_unique(QList{ { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } })); From 9b270f783ee1c54b77917fde76ec8efd3f84b2e9 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 12 Apr 2026 13:02:46 +0500 Subject: [PATCH 023/132] fix(updater): do not reset current task in finished signal The order of signals in case of a success is "succeeded"->"finished" The "succeeded" signal may launch another download if the updater needs to fetch more pages But if we reset the task then the newly started download will be disposed and the updater will softlock Signed-off-by: Octol1ttle --- launcher/updater/prismupdater/PrismUpdater.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 11e92efcd..6ad60a7c7 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -1160,8 +1160,6 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page) m_current_task.reset(download); connect(download.get(), &Net::Download::finished, this, [this]() { qDebug() << "Download" << m_current_task->getUid().toString() << "finished"; - m_current_task.reset(); - m_current_url = ""; }); QCoreApplication::processEvents(); From ae7e1435373aa07ec5dbcbb9f22973e1f3cd93b7 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 12 Apr 2026 13:48:15 +0500 Subject: [PATCH 024/132] change(Task): warn when disposing while running Signed-off-by: Octol1ttle --- launcher/tasks/Task.cpp | 7 +++++++ launcher/tasks/Task.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index a54b4e7c2..5857a0663 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -48,6 +48,13 @@ Task::Task(bool show_debug) : m_show_debug(show_debug) setAutoDelete(false); } +Task::~Task() +{ + if (isRunning()) { + qCWarning(taskLogC) << "Task" << describe() << "disposed while running!"; + } +} + void Task::setStatus(const QString& new_status) { if (m_status != new_status) { diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 94fb57783..6591ca302 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable { public: explicit Task(bool show_debug_log = true); - virtual ~Task() = default; + ~Task() override; bool isRunning() const; bool isFinished() const; From 88035b9815be5baaff414302098d3f7163c0c067 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 Apr 2026 21:05:30 +0500 Subject: [PATCH 025/132] change(Windows installer): disable skipping files Signed-off-by: Octol1ttle --- program_info/win_install.nsi.in | 1 + 1 file changed, 1 insertion(+) diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index ba6b7e061..83335ce9d 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -4,6 +4,7 @@ !include "x64.nsh" +AllowSkipFiles off Unicode true Name "@Launcher_DisplayName@" From 1b650622eae9a5ceb43d39159c6cad91fb47dcde Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 Apr 2026 22:57:13 +0500 Subject: [PATCH 026/132] fix(ScreenshotsPage): use correct selection collection Signed-off-by: Octol1ttle --- launcher/ui/pages/instance/ScreenshotsPage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 70647bed7..78f8ad97d 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -328,7 +328,7 @@ void ScreenshotsPage::ShowContextMenu(const QPoint& pos) { auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); - if (ui->listView->selectionModel()->selectedRows().size() > 1) { + if (ui->listView->selectionModel()->selectedIndexes().size() > 1) { menu->removeAction(ui->actionCopy_Image); } @@ -380,7 +380,7 @@ void ScreenshotsPage::on_actionView_Folder_triggered() void ScreenshotsPage::on_actionUpload_triggered() { - auto selection = ui->listView->selectionModel()->selectedRows(); + auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.isEmpty()) return; @@ -487,7 +487,7 @@ void ScreenshotsPage::on_actionUpload_triggered() void ScreenshotsPage::on_actionCopy_Image_triggered() { - auto selection = ui->listView->selectionModel()->selectedRows(); + auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() < 1) { return; } @@ -502,7 +502,7 @@ void ScreenshotsPage::on_actionCopy_Image_triggered() void ScreenshotsPage::on_actionCopy_File_s_triggered() { - auto selection = ui->listView->selectionModel()->selectedRows(); + auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() < 1) { // Don't do anything so we don't empty the users clipboard return; @@ -522,7 +522,7 @@ void ScreenshotsPage::on_actionDelete_triggered() { auto selected = ui->listView->selectionModel()->selectedIndexes(); - int count = ui->listView->selectionModel()->selectedRows().size(); + int count = selected.size(); QString text; if (count > 1) text = tr("You are about to delete %1 screenshots.\n" From 7a1d2e41a16e4303fefc22c4a0a7a106d63cdc63 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 Apr 2026 23:08:57 +0500 Subject: [PATCH 027/132] refactor(ScreenshotsPage): clang-tidy Signed-off-by: Octol1ttle --- .../ui/pages/instance/ScreenshotsPage.cpp | 207 ++++++++++-------- launcher/ui/pages/instance/ScreenshotsPage.h | 14 +- 2 files changed, 120 insertions(+), 101 deletions(-) diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 78f8ad97d..1d85dfd3b 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -53,6 +52,8 @@ #include #include #include +#include +#include #include #include "settings/SettingsObject.h" @@ -70,9 +71,13 @@ #include "RWStorage.h" class ScreenshotsFSModel : public QFileSystemModel { - bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override + public: + bool canDropMimeData(const QMimeData* data, + const Qt::DropAction action, + const int row, + const int column, const QModelIndex& parent) const override { - QUrl root = QUrl::fromLocalFile(rootPath()); + const QUrl root = QUrl::fromLocalFile(rootPath()); // this disables reordering items inside the model // by rejecting drops if the file is already inside the folder if (data->hasUrls()) { @@ -92,8 +97,8 @@ using SharedIconCachePtr = std::shared_ptr; class ThumbnailingResult : public QObject { Q_OBJECT public slots: - inline void emitResultsReady(const QString& path) { emit resultsReady(path); } - inline void emitResultsFailed(const QString& path) { emit resultsFailed(path); } + void emitResultsReady(const QString& path) { emit resultsReady(path); } + void emitResultsFailed(const QString& path) { emit resultsFailed(path); } signals: void resultsReady(const QString& path); void resultsFailed(const QString& path); @@ -101,32 +106,32 @@ class ThumbnailingResult : public QObject { class ThumbnailRunnable : public QRunnable { public: - ThumbnailRunnable(QString path, SharedIconCachePtr cache) + ThumbnailRunnable(QString path, SharedIconCachePtr cache) : m_path(std::move(path)), m_cache(std::move(cache)) {} + void run() override { - m_path = path; - m_cache = cache; - } - void run() - { - QFileInfo info(m_path); - if (info.isDir()) + const QFileInfo info(m_path); + if (info.isDir()) { return; - if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) + } + if (info.suffix().compare("png", Qt::CaseInsensitive) != 0) { return; - if (!m_cache->stale(m_path)) + } + if (!m_cache->stale(m_path)) { return; - QImage image(m_path); + } + const QImage image(m_path); if (image.isNull()) { m_resultEmitter.emitResultsFailed(m_path); qDebug() << "Error loading screenshot (perhaps too large?):" + m_path; return; } QImage small; - if (image.width() > image.height()) + if (image.width() > image.height()) { small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - else + } else { small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); - QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); + } + const QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); QImage square(QSize(256, 256), QImage::Format_ARGB32); square.fill(Qt::transparent); @@ -134,7 +139,7 @@ class ThumbnailRunnable : public QRunnable { painter.drawImage(offset, small); painter.end(); - QIcon icon(QPixmap::fromImage(square)); + const QIcon icon(QPixmap::fromImage(square)); m_cache->add(m_path, icon); m_resultEmitter.emitResultsReady(m_path); } @@ -148,59 +153,62 @@ class ThumbnailRunnable : public QRunnable { class FilterModel : public QIdentityProxyModel { Q_OBJECT public: - explicit FilterModel(QObject* parent = 0) : QIdentityProxyModel(parent) + explicit FilterModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) { m_thumbnailingPool.setMaxThreadCount(4); m_thumbnailCache = std::make_shared(); m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder")); connect(&watcher, &QFileSystemWatcher::fileChanged, this, &FilterModel::fileChanged); } - virtual ~FilterModel() + ~FilterModel() override { m_thumbnailingPool.clear(); - if (!m_thumbnailingPool.waitForDone(500)) + if (!m_thumbnailingPool.waitForDone(500)) { qDebug() << "Thumbnail pool took longer than 500ms to finish"; + } } - virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const + QVariant data(const QModelIndex& proxyIndex, const int role = Qt::DisplayRole) const override // NOLINT(*-default-arguments) { - auto model = sourceModel(); - if (!model) - return QVariant(); + const auto* model = sourceModel(); + if (!model) { + return {}; + } if (role == Qt::DisplayRole || role == Qt::EditRole) { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + const QVariant result = model->data(mapToSource(proxyIndex), role); static const QRegularExpression s_removeChars("\\.png$"); return result.toString().remove(s_removeChars); } if (role == Qt::DecorationRole) { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); - QString filePath = result.toString(); - QIcon temp; + const QVariant result = model->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + const QString filePath = result.toString(); if (!watched.contains(filePath)) { - ((QFileSystemWatcher&)watcher).addPath(filePath); - ((QSet&)watched).insert(filePath); + const_cast(watcher).addPath(filePath); + const_cast&>(watched).insert(filePath); } - if (m_thumbnailCache->get(filePath, temp)) { + if (QIcon temp; m_thumbnailCache->get(filePath, temp)) { return temp; } if (!m_failed.contains(filePath)) { - ((FilterModel*)this)->thumbnailImage(filePath); + const_cast(this)->thumbnailImage(filePath); } return (m_thumbnailCache->get("placeholder")); } - return sourceModel()->data(mapToSource(proxyIndex), role); + return model->data(mapToSource(proxyIndex), role); } - virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) + bool setData(const QModelIndex& index, const QVariant& value, const int role = Qt::EditRole) override // NOLINT(*-default-arguments) { - auto model = sourceModel(); - if (!model) + auto* model = sourceModel(); + if (!model) { return false; - if (role != Qt::EditRole) + } + if (role != Qt::EditRole) { return false; + } // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't // sort after renames { - ((QFileSystemModel*)model)->setNameFilterDisables(true); - ((QFileSystemModel*)model)->setNameFilterDisables(false); + static_cast(model)->setNameFilterDisables(true); + static_cast(model)->setNameFilterDisables(false); } return model->setData(mapToSource(index), value.toString() + ".png", role); } @@ -208,15 +216,15 @@ class FilterModel : public QIdentityProxyModel { private: void thumbnailImage(QString path) { - auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); + auto* runnable = new ThumbnailRunnable(std::move(path), m_thumbnailCache); connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsReady, this, &FilterModel::thumbnailReady); connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsFailed, this, &FilterModel::thumbnailFailed); m_thumbnailingPool.start(runnable); } private slots: - void thumbnailReady(QString path) { emit layoutChanged(); } - void thumbnailFailed(QString path) { m_failed.insert(path); } - void fileChanged(QString filepath) + void thumbnailReady(const QString& /*path*/) { emit layoutChanged(); } + void thumbnailFailed(const QString& path) { m_failed.insert(path); } + void fileChanged(const QString& filepath) { m_thumbnailCache->setStale(filepath); // reinsert the path... @@ -237,13 +245,12 @@ class FilterModel : public QIdentityProxyModel { class CenteredEditingDelegate : public QStyledItemDelegate { public: - explicit CenteredEditingDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {} - virtual ~CenteredEditingDelegate() {} - virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const + explicit CenteredEditingDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + ~CenteredEditingDelegate() override = default; + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override { - auto widget = QStyledItemDelegate::createEditor(parent, option, index); - auto foo = dynamic_cast(widget); - if (foo) { + auto* widget = QStyledItemDelegate::createEditor(parent, option, index); + if (auto* foo = dynamic_cast(widget)) { foo->setAlignment(Qt::AlignHCenter); foo->setFrame(true); foo->setMaximumWidth(192); @@ -252,10 +259,11 @@ class CenteredEditingDelegate : public QStyledItemDelegate { } }; -ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(parent), ui(new Ui::ScreenshotsPage) +ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) + : QMainWindow(parent), ui(new Ui::ScreenshotsPage), m_folder(std::move(path)) { - m_model.reset(new ScreenshotsFSModel()); - m_filterModel.reset(new FilterModel()); + m_model = std::make_shared(); + m_filterModel = std::make_shared(); m_filterModel->setSourceModel(m_model.get()); m_model->setFilter(QDir::Files); m_model->setReadOnly(false); @@ -266,7 +274,6 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa constexpr int file_modified_column_index = 3; m_model->sort(file_modified_column_index, Qt::DescendingOrder); - m_folder = path; m_valid = FS::ensureFolderPathExists(m_folder); ui->setupUi(this); @@ -283,18 +290,19 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); + connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::showContextMenu); connect(ui->listView, &QAbstractItemView::activated, this, &ScreenshotsPage::onItemActivated); } bool ScreenshotsPage::eventFilter(QObject* obj, QEvent* evt) { - if (obj != ui->listView) + if (obj != ui->listView) { return QWidget::eventFilter(obj, evt); + } if (evt->type() != QEvent::KeyPress) { return QWidget::eventFilter(obj, evt); } - QKeyEvent* keyEvent = static_cast(evt); + const auto* keyEvent = static_cast(evt); if (keyEvent->matches(QKeySequence::Copy)) { on_actionCopy_File_s_triggered(); @@ -324,9 +332,9 @@ ScreenshotsPage::~ScreenshotsPage() delete ui; } -void ScreenshotsPage::ShowContextMenu(const QPoint& pos) +void ScreenshotsPage::showContextMenu(const QPoint& pos) { - auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + auto* menu = ui->toolBar->createContextMenu(this, tr("Context menu")); if (ui->listView->selectionModel()->selectedIndexes().size() > 1) { menu->removeAction(ui->actionCopy_Image); @@ -343,27 +351,31 @@ QMenu* ScreenshotsPage::createPopupMenu() return filteredMenu; } -void ScreenshotsPage::onItemActivated(QModelIndex index) +void ScreenshotsPage::onItemActivated(QModelIndex index) const { - if (!index.isValid()) + if (!index.isValid()) { return; - auto info = m_model->fileInfo(index); + } + const auto info = m_model->fileInfo(index); DesktopServices::openPath(info); } -void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) +void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) const { bool allReadable = !selected.isEmpty(); bool allWritable = !selected.isEmpty(); for (auto index : selected.indexes()) { - if (!index.isValid()) + if (!index.isValid()) { break; + } auto info = m_model->fileInfo(index); - if (!info.isReadable()) + if (!info.isReadable()) { allReadable = false; - if (!info.isWritable()) + } + if (!info.isWritable()) { allWritable = false; + } } ui->actionUpload->setEnabled(allReadable); @@ -373,7 +385,7 @@ void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) ui->actionRename->setEnabled(allWritable); } -void ScreenshotsPage::on_actionView_Folder_triggered() +void ScreenshotsPage::on_actionView_Folder_triggered() const { DesktopServices::openPath(m_folder, true); } @@ -381,28 +393,31 @@ void ScreenshotsPage::on_actionView_Folder_triggered() void ScreenshotsPage::on_actionUpload_triggered() { auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) + if (selection.isEmpty()) { return; + } QString text; - QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); - if (selection.size() > 1) + const QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); + if (selection.size() > 1) { text = tr("You are about to upload %1 screenshots to %2.\n" "You should double-check for personal information.\n\n" "Are you sure?") .arg(QString::number(selection.size()), baseUrl.host()); - else + } else { text = tr("You are about to upload the selected screenshot to %1.\n" "You should double-check for personal information.\n\n" "Are you sure?") .arg(baseUrl.host()); + } auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } QList uploaded; auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network())); @@ -416,7 +431,7 @@ void ScreenshotsPage::on_actionUpload_triggered() auto screenshot = std::make_shared(info); job->addNetAction(ImgurUpload::make(screenshot)); - connect(job.get(), &Task::failed, [this](QString reason) { + connect(job.get(), &Task::failed, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show(); }); connect(job.get(), &Task::aborted, [this] { @@ -457,7 +472,7 @@ void ScreenshotsPage::on_actionUpload_triggered() task.addTask(job); task.addTask(albumTask); - connect(&task, &Task::failed, [this](QString reason) { + connect(&task, &Task::failed, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show(); }); connect(&task, &Task::aborted, [this] { @@ -485,7 +500,7 @@ void ScreenshotsPage::on_actionUpload_triggered() m_uploadActive = false; } -void ScreenshotsPage::on_actionCopy_Image_triggered() +void ScreenshotsPage::on_actionCopy_Image_triggered() const { auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() < 1) { @@ -493,14 +508,14 @@ void ScreenshotsPage::on_actionCopy_Image_triggered() } // You can only copy one image to the clipboard. In the case of multiple selected files, only the first one gets copied. - auto item = selection[0]; - auto info = m_model->fileInfo(item); - QImage image(info.absoluteFilePath()); + const auto item = selection.first(); + const auto info = m_model->fileInfo(item); + const QImage image(info.absoluteFilePath()); Q_ASSERT(!image.isNull()); QApplication::clipboard()->setImage(image, QClipboard::Clipboard); } -void ScreenshotsPage::on_actionCopy_File_s_triggered() +void ScreenshotsPage::on_actionCopy_File_s_triggered() const { auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() < 1) { @@ -513,7 +528,7 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered() auto info = m_model->fileInfo(item); buf += "file:///" + info.absoluteFilePath() + "\r\n"; } - QMimeData* mimeData = new QMimeData(); + auto* mimeData = new QMimeData(); mimeData->setData("text/uri-list", buf.toLocal8Bit()); QApplication::clipboard()->setMimeData(mimeData); } @@ -522,39 +537,43 @@ void ScreenshotsPage::on_actionDelete_triggered() { auto selected = ui->listView->selectionModel()->selectedIndexes(); - int count = selected.size(); + const qsizetype count = selected.size(); QString text; - if (count > 1) + if (count > 1) { text = tr("You are about to delete %1 screenshots.\n" "This may be permanent and they will be gone from the folder.\n\n" "Are you sure?") .arg(count); - else + } else { text = tr("You are about to delete the selected screenshot.\n" "This may be permanent and it will be gone from the folder.\n\n" "Are you sure?") .arg(count); + } - auto response = + const auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } for (auto item : selected) { - if (FS::trash(m_model->filePath(item))) + if (FS::trash(m_model->filePath(item))) { continue; + } m_model->remove(item); } } -void ScreenshotsPage::on_actionRename_triggered() +void ScreenshotsPage::on_actionRename_triggered() const { auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) + if (selection.isEmpty()) { return; - ui->listView->edit(selection[0]); + } + ui->listView->edit(selection.first()); // TODO: mass renaming } @@ -564,8 +583,8 @@ void ScreenshotsPage::openedImpl() m_valid = FS::ensureFolderPathExists(m_folder); } if (m_valid) { - QString path = QDir(m_folder).absolutePath(); - auto idx = m_model->setRootPath(path); + const QString path = QDir(m_folder).absolutePath(); + const auto idx = m_model->setRootPath(path); if (idx.isValid()) { ui->listView->setModel(m_filterModel.get()); connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, @@ -577,7 +596,7 @@ void ScreenshotsPage::openedImpl() } } - auto const setting_name = QString("WideBarVisibility_%1").arg(id()); + const auto setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index 0b068aa0a..7d1cf4fcc 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -78,14 +78,14 @@ class ScreenshotsPage : public QMainWindow, public BasePage { private slots: void on_actionUpload_triggered(); - void on_actionCopy_Image_triggered(); - void on_actionCopy_File_s_triggered(); + void on_actionCopy_Image_triggered() const; + void on_actionCopy_File_s_triggered() const; void on_actionDelete_triggered(); - void on_actionRename_triggered(); - void on_actionView_Folder_triggered(); - void onItemActivated(QModelIndex); - void onCurrentSelectionChanged(const QItemSelection& selected); - void ShowContextMenu(const QPoint& pos); + void on_actionRename_triggered() const; + void on_actionView_Folder_triggered() const; + void onItemActivated(QModelIndex) const; + void onCurrentSelectionChanged(const QItemSelection& selected) const; + void showContextMenu(const QPoint& pos); private: Ui::ScreenshotsPage* ui; From 08de904e21c8baedb78b0a9835fd74fe2c136567 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 Apr 2026 23:13:28 +0500 Subject: [PATCH 028/132] fix(ScreenshotsPage): disable "Copy Image" when selecting multiple screenshots Signed-off-by: Octol1ttle --- launcher/ui/pages/instance/ScreenshotsPage.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 1d85dfd3b..3e4a0098f 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -360,12 +360,14 @@ void ScreenshotsPage::onItemActivated(QModelIndex index) const DesktopServices::openPath(info); } -void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) const +void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& /*selected*/) const { + const auto selected = ui->listView->selectionModel()->selectedIndexes(); + bool allReadable = !selected.isEmpty(); bool allWritable = !selected.isEmpty(); - for (auto index : selected.indexes()) { + for (auto index : selected) { if (!index.isValid()) { break; } @@ -379,7 +381,7 @@ void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) } ui->actionUpload->setEnabled(allReadable); - ui->actionCopy_Image->setEnabled(allReadable); + ui->actionCopy_Image->setEnabled(allReadable && selected.size() == 1); ui->actionCopy_File_s->setEnabled(allReadable); ui->actionDelete->setEnabled(allWritable); ui->actionRename->setEnabled(allWritable); From 1fec7812512a3b1f78dd10455b5b0beaf471a05d Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 13 Apr 2026 23:21:12 +0500 Subject: [PATCH 029/132] fix(ScreenshotsPage): fix QString::arg in string with no arguments Signed-off-by: Octol1ttle --- launcher/ui/pages/instance/ScreenshotsPage.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 3e4a0098f..71dc7218e 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -75,7 +75,8 @@ class ScreenshotsFSModel : public QFileSystemModel { bool canDropMimeData(const QMimeData* data, const Qt::DropAction action, const int row, - const int column, const QModelIndex& parent) const override + const int column, + const QModelIndex& parent) const override { const QUrl root = QUrl::fromLocalFile(rootPath()); // this disables reordering items inside the model @@ -547,10 +548,10 @@ void ScreenshotsPage::on_actionDelete_triggered() "Are you sure?") .arg(count); } else { - text = tr("You are about to delete the selected screenshot.\n" - "This may be permanent and it will be gone from the folder.\n\n" - "Are you sure?") - .arg(count); + text = + tr("You are about to delete the selected screenshot.\n" + "This may be permanent and it will be gone from the folder.\n\n" + "Are you sure?"); } const auto response = From d5db0c6c1bb4a358c0433e846c7ce36157495539 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 14 Apr 2026 00:31:23 +0500 Subject: [PATCH 030/132] fix(SkinList): do not consider non-png files correctly Signed-off-by: Octol1ttle --- launcher/minecraft/skins/SkinList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index b47f17db5..c960b9493 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -121,7 +121,7 @@ bool SkinList::update() auto folderContents = m_dir.entryInfoList(); // if there are any untracked files... for (QFileInfo entry : folderContents) { - if (!entry.isFile() && entry.suffix() != "png") + if (!entry.isFile() || entry.suffix() != "png") continue; SkinModel w(entry.absoluteFilePath()); From 575be16d3e0c967a29a58185169870fe1d9a88b5 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 14 Apr 2026 12:47:48 +0500 Subject: [PATCH 031/132] fix(EnsureAvailableMemory): do not warn if available memory could not be read Signed-off-by: Octol1ttle --- launcher/minecraft/launch/EnsureAvailableMemory.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index a0e156770..6605f0ce8 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -26,9 +26,11 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan void EnsureAvailableMemory::executeTask() { const uint64_t available = HardwareInfo::availableRamMiB(); - const uint64_t min = m_instance->settings()->get("MinMemAlloc").toUInt(); - const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt(); - const uint64_t required = std::max(min, max); + if (available == 0) { + // could not read + emitSucceeded(); + return; + } if (static_cast(required) * 0.9 > static_cast(available)) { bool shouldAbort = false; From ae331cfc9af3b5eb5148167e0a2142057c255978 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 14 Apr 2026 18:43:52 +0500 Subject: [PATCH 032/132] change(EnsureAvailableMemory): rephrase warning message Signed-off-by: Octol1ttle --- .../launch/EnsureAvailableMemory.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index 6605f0ce8..cb7a04ab1 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -32,17 +32,22 @@ void EnsureAvailableMemory::executeTask() return; } - if (static_cast(required) * 0.9 > static_cast(available)) { + const uint64_t settingMin = m_instance->settings()->get("MinMemAlloc").toUInt(); + const uint64_t settingMax = m_instance->settings()->get("MaxMemAlloc").toUInt(); + const uint64_t max = std::max(settingMin, settingMax); + + if (static_cast(max) * 0.9 > static_cast(available)) { bool shouldAbort = false; if (m_instance->settings()->get("LowMemWarning").toBool()) { auto* dialog = CustomMessageBox::selectable( - nullptr, tr("Not enough RAM"), - tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n" - "Required: %1 MiB\nAvailable: %2 MiB\n\n" - "Continue anyway? This may cause slowdowns in the game and your system.") - .arg(required) - .arg(available), + nullptr, tr("Low free memory"), + tr("There might not be enough free RAM to launch this instance with the current memory settings.\n\n" + "Maximum allocated: %1 MiB\nFree: %2 MiB (out of %3 MiB total)\n\n" + "Launch anyway? This may cause slowdowns in the game and your system.") + .arg(max) + .arg(available) + .arg(HardwareInfo::totalRamMiB()), QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::No); From 0b578fa767119427b84d8d42440d4cdc50aedcbe Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 14 Apr 2026 18:44:53 +0500 Subject: [PATCH 033/132] fix(EnsureAvailableMemory/macOS): warn based on memory pressure rather than available RAM Signed-off-by: Octol1ttle --- launcher/HardwareInfo.cpp | 33 ++++++++++----- launcher/HardwareInfo.h | 13 ++++++ .../launch/EnsureAvailableMemory.cpp | 42 +++++++++++++++++++ .../minecraft/launch/PrintInstanceInfo.cpp | 5 +++ 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp index 5937c33bc..3e20cc19b 100644 --- a/launcher/HardwareInfo.cpp +++ b/launcher/HardwareInfo.cpp @@ -141,7 +141,6 @@ uint64_t HardwareInfo::availableRamMiB() } #elif defined(Q_OS_MACOS) -#include "mach/mach.h" #include "sys/sysctl.h" QString HardwareInfo::cpuInfo() @@ -171,18 +170,32 @@ uint64_t HardwareInfo::totalRamMiB() uint64_t HardwareInfo::availableRamMiB() { - mach_port_t host_port = mach_host_self(); - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + return 0; +} - vm_statistics64_data_t vm_stats; - - if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast(&vm_stats), &count) == KERN_SUCCESS) { - // transforming bytes -> mib - return (vm_stats.free_count + vm_stats.inactive_count) * vm_page_size / 1024 / 1024; +MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel() +{ + uint32_t level; + size_t levelSize = sizeof level; + if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) { + return static_cast(level); } - qWarning() << "Could not get available RAM: host_statistics64"; - return 0; + qWarning() << "Could not get memory pressure level: sysctlbyname"; + return MemoryPressureLevel::Normal; +} + +QString MacOSHardwareInfo::memoryPressureLevelName() +{ + // The names are internal, users refer to levels by their graph colors in Activity Monitor + switch (memoryPressureLevel()) { + case MemoryPressureLevel::Normal: + return "Green"; + case MemoryPressureLevel::Warning: + return "Yellow"; + case MemoryPressureLevel::Critical: + return "Red"; + } } #elif defined(Q_OS_LINUX) diff --git a/launcher/HardwareInfo.h b/launcher/HardwareInfo.h index 00e19f214..4efd339b6 100644 --- a/launcher/HardwareInfo.h +++ b/launcher/HardwareInfo.h @@ -27,3 +27,16 @@ uint64_t totalRamMiB(); uint64_t availableRamMiB(); QStringList gpuInfo(); } // namespace HardwareInfo + +#ifdef Q_OS_MACOS +namespace MacOSHardwareInfo { +enum class MemoryPressureLevel : uint8_t { + Normal = 1, + Warning = 2, + Critical = 4, +}; + +MemoryPressureLevel memoryPressureLevel(); +QString memoryPressureLevelName(); +} // namespace MacOSHardwareInfo +#endif \ No newline at end of file diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index cb7a04ab1..941aee2d0 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -25,6 +25,47 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan void EnsureAvailableMemory::executeTask() { +#ifdef Q_OS_MACOS + QString text; + switch (MacOSHardwareInfo::memoryPressureLevel()) { + case MacOSHardwareInfo::MemoryPressureLevel::Normal: + emitSucceeded(); + return; + case MacOSHardwareInfo::MemoryPressureLevel::Warning: + text = + tr("The system is under increased memory pressure. This may cause lag or slowdowns.\n" + "If possible, close other applications before continuing.\n\n" + "Launch anyway?"); + break; + case MacOSHardwareInfo::MemoryPressureLevel::Critical: + text = + tr("Your system is under critical memory pressure. This may lead to severe slowdowns or crashes.\n" + "It is highly recommended to close other applications or restart your system.\n\n" + "Launch anyway?"); + break; + } + + bool shouldAbort = false; + + if (m_instance->settings()->get("LowMemWarning").toBool()) { + auto* dialog = CustomMessageBox::selectable(nullptr, tr("High memory pressure"), text, QMessageBox::Icon::Warning, + QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, + QMessageBox::StandardButton::No); + + shouldAbort = dialog->exec() == QMessageBox::No; + dialog->deleteLater(); + } + + const auto message = tr("The system is under high memory pressure"); + if (shouldAbort) { + emit logLine(message, MessageLevel::Fatal); + emitFailed(message); + return; + } + + emit logLine(message, MessageLevel::Warning); + emitSucceeded(); +#else const uint64_t available = HardwareInfo::availableRamMiB(); if (available == 0) { // could not read @@ -66,4 +107,5 @@ void EnsureAvailableMemory::executeTask() } emitSucceeded(); +#endif } diff --git a/launcher/minecraft/launch/PrintInstanceInfo.cpp b/launcher/minecraft/launch/PrintInstanceInfo.cpp index 7bfe73746..df8c28c7b 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.cpp +++ b/launcher/minecraft/launch/PrintInstanceInfo.cpp @@ -68,7 +68,12 @@ void PrintInstanceInfo::executeTask() ::runPciconf(log); #else log << "CPU: " + HardwareInfo::cpuInfo(); +#ifdef Q_OS_MACOS + log << "Memory pressure level: " + MacOSHardwareInfo::memoryPressureLevelName(); +#else log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB()); +#endif + #endif log.append(HardwareInfo::gpuInfo()); log << ""; From 5d9622db214850104d16a7d1004617eb7054a24c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 14 Apr 2026 23:02:44 +0500 Subject: [PATCH 034/132] Use newlines more often in macOS dialog for a nicer look Signed-off-by: Octol1ttle --- launcher/minecraft/launch/EnsureAvailableMemory.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index 941aee2d0..0f349674a 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -33,14 +33,16 @@ void EnsureAvailableMemory::executeTask() return; case MacOSHardwareInfo::MemoryPressureLevel::Warning: text = - tr("The system is under increased memory pressure. This may cause lag or slowdowns.\n" + tr("The system is under increased memory pressure.\n" + "This may lead to lag or slowdowns.\n" "If possible, close other applications before continuing.\n\n" "Launch anyway?"); break; case MacOSHardwareInfo::MemoryPressureLevel::Critical: text = - tr("Your system is under critical memory pressure. This may lead to severe slowdowns or crashes.\n" - "It is highly recommended to close other applications or restart your system.\n\n" + tr("Your system is under critical memory pressure.\n" + "This may lead to severe slowdowns, crashes or system instability.\n" + "It is recommended to close other applications or restart your system.\n\n" "Launch anyway?"); break; } From 28c42d04b620bc707ce4fe7261abf1a7cc18fae2 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:51:34 +0000 Subject: [PATCH 035/132] Limit normal search fallback to 404 respnse Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/ui/pages/modplatform/ResourceModel.cpp | 4 +++- launcher/ui/pages/modplatform/flame/FlameModel.cpp | 6 ++++-- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index b96061b43..a3a043ba9 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -147,7 +147,9 @@ void ResourceModel::search() callbacks.on_fail = [this](QString reason, int network_error_code) { if (!s_running_models.constFind(this).value()) return; - m_search_state = SearchState::ResetRequested;; + if (network_error_code == 404) { + m_search_state = SearchState::ResetRequested; + } searchRequestFailed(reason, network_error_code); }; callbacks.on_abort = [this] { diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index c22d12da0..861dd9f22 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -173,8 +173,10 @@ void ListModel::performPaginatedSearch() if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { - m_searchState = ResetRequested; + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (network_error_code == 404) { + m_searchState = ResetRequested; + } searchRequestFailed(reason); }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index fba9f3bc9..03518c3af 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -142,7 +142,9 @@ void ModpackListModel::performPaginatedSearch() ResourceAPI::Callback callbacks; callbacks.on_fail = [this](QString reason, int network_error_code) { - m_searchState = ResetRequested; + if (network_error_code == 404) { + m_searchState = ResetRequested; + } searchRequestFailed(reason, network_error_code); }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; From da50f0e9e316d6a3049dce992d5d5670c8684822 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:43:28 +0000 Subject: [PATCH 036/132] chore(deps): update actions/cache action to v5.0.5 --- .github/actions/setup-dependencies/windows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index d2d0820d8..24ad51d8f 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -91,7 +91,7 @@ runs: - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} - uses: actions/cache@v5.0.4 + uses: actions/cache@v5.0.5 with: path: '${{ github.workspace }}\.ccache' key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }} From cddbb0e97011907461d780cce11ea8589c62c327 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:43:32 +0000 Subject: [PATCH 037/132] chore(deps): update softprops/action-gh-release action to v3 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecc23effa..e332488c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,7 +94,7 @@ jobs: - name: Create release id: create_release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ github.ref }} From fa54329711ce4b7453b7bd0d8c4a9c2fd2669cd5 Mon Sep 17 00:00:00 2001 From: so5iso4ka Date: Wed, 15 Apr 2026 22:40:54 +0300 Subject: [PATCH 038/132] fix text overlap in project item views Signed-off-by: so5iso4ka --- launcher/ui/widgets/ProjectItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 2fd5c97c2..dd38cafd1 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -142,7 +142,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o description_y -= opt.fontMetrics.height(); // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) - painter->drawText(description_x, description_y, remaining_width, cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap, + painter->drawText(description_x, description_y, remaining_width, num_lines * opt.fontMetrics.height(), Qt::TextWordWrap, description); } From 3ee45691ab3eef9c996b4c09c607dfb0549959b7 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 16 Apr 2026 11:32:58 +0500 Subject: [PATCH 039/132] change: improve checksum mismatch logging Signed-off-by: Octol1ttle --- launcher/meta/BaseEntity.cpp | 6 ++++-- launcher/minecraft/Library.cpp | 2 +- launcher/net/ChecksumValidator.h | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index d638fd8b9..1869e14d3 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -127,9 +127,11 @@ void BaseEntityLoadTask::executeTask() } // on online the hash needs to match - hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256; + const auto& expected = m_entity->m_sha256; + const auto& actual = m_entity->m_file_sha256; + hashMatches = expected == actual; if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) { - throw Exception("mismatched checksum"); + throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual)); } // load local file diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 026f9c281..2b43d4389 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -149,7 +149,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC if (sha1.size()) { auto dl = Net::ApiDownload::makeCached(url, entry, options); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1)); - qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; + qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url << "expected sha1:" << sha1; out.append(dl); } else { out.append(Net::ApiDownload::makeCached(url, entry, options)); diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h index 7663d5d12..c0d5a56aa 100644 --- a/launcher/net/ChecksumValidator.h +++ b/launcher/net/ChecksumValidator.h @@ -38,7 +38,6 @@ #include "Validator.h" #include -#include namespace Net { class ChecksumValidator : public Validator { @@ -69,10 +68,11 @@ class ChecksumValidator : public Validator { return true; } - auto validate(QNetworkReply&) -> bool override + auto validate(QNetworkReply& reply) -> bool override { - if (m_expected.size() && m_expected != hash()) { - qWarning() << "Checksum mismatch, download is bad."; + if (!m_expected.isEmpty() && m_expected != hash()) { + qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash() + << "algorithm:" << m_checksum.algorithm(); return false; } return true; From b9fa4ffc00327d4929ab89a6096bcf36c74d63c7 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 27 Nov 2025 20:18:13 +0500 Subject: [PATCH 040/132] fix(ProgressDialog): allow finished tasks to be re-displayed once restarted Cherry-picked from libcurl (lmao) Signed-off-by: Octol1ttle --- launcher/ui/dialogs/ProgressDialog.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 6aa0b4bdf..0a453bda5 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -252,10 +252,7 @@ void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress) task_bar->setValue(mapped_current); task_bar->setStatus(task_progress.status); task_bar->setDetails(task_progress.details); - - if (task_progress.isDone()) { - task_bar->setVisible(false); - } + task_bar->setVisible(!task_progress.isDone()); } void ProgressDialog::changeProgress(qint64 current, qint64 total) From 7d0d9a38275fa46a45be71e2790bcafcf14bec5f Mon Sep 17 00:00:00 2001 From: captivator <84224501+qaptivator@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:10:55 +0300 Subject: [PATCH 041/132] feat: add Manage Skins menu item to accounts button in MainWindow Assisted-by: Gemini:3-Flash Signed-off-by: captivator <84224501+qaptivator@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 55 +++++++++++++++++++------------------- launcher/ui/MainWindow.h | 2 ++ launcher/ui/MainWindow.ui | 8 ++++++ 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f4c170eb8..9e03874e6 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -105,6 +105,7 @@ #include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewsDialog.h" #include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/skins/SkinManageDialog.h" #include "ui/instanceview/InstanceDelegate.h" #include "ui/instanceview/InstanceProxyModel.h" #include "ui/instanceview/InstanceView.h" @@ -180,7 +181,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance); // restore the instance toolbar settings - auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); + const auto setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); @@ -396,6 +397,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. // Template hell sucks... connect(APPLICATION->accounts(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); }); + connect(APPLICATION->accounts(), &AccountList::listActivityChanged, [this] { defaultAccountChanged(); }); connect(APPLICATION->accounts(), &AccountList::listChanged, [this] { defaultAccountChanged(); }); // Show initial account @@ -653,6 +655,9 @@ void MainWindow::repopulateAccountsMenu() auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); + + bool canChangeSkin = defaultAccount && (defaultAccount->accountType() != AccountType::Offline) && !defaultAccount->isActive(); + ui->actionManageSkins->setEnabled(canChangeSkin); QString active_profileId = ""; if (defaultAccount) { @@ -709,6 +714,7 @@ void MainWindow::repopulateAccountsMenu() connect(ui->actionNoDefaultAccount, &QAction::triggered, this, &MainWindow::changeActiveAccount); ui->accountsMenu->addSeparator(); + ui->accountsMenu->addAction(ui->actionManageSkins); ui->accountsMenu->addAction(ui->actionManageAccounts); accountsButtonMenu->addActions(ui->accountsMenu->actions()); @@ -942,9 +948,7 @@ void MainWindow::processURLs(QList urls) QUrl local_url; if (!url.isLocalFile()) { // download the remote resource and identify - const bool isExternalURLImport = - (url.host().toLower() == "import") || - (url.path().startsWith("/import", Qt::CaseInsensitive)); + const bool isExternalURLImport = (url.host().toLower() == "import") || (url.path().startsWith("/import", Qt::CaseInsensitive)); QUrl dl_url; if (url.scheme() == "curseforge" || (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME && url.host() == "install")) { @@ -952,7 +956,7 @@ void MainWindow::processURLs(QList urls) // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE // format of url binaryname://install?platform=curseforge&addonId=IDHERE&fileId=IDHERE QUrlQuery query(url); - + // check if this is a binaryname:// url if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) { // check this is an curseforge platform request @@ -1015,8 +1019,7 @@ void MainWindow::processURLs(QList urls) receivedData.insert(it->first, it->second); emit APPLICATION->oauthReplyRecieved(receivedData); continue; - } else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) - && isExternalURLImport) { + } else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) && isExternalURLImport) { // PrismLauncher URL protocol modpack import // works for any prism fork // preferred import format: prismlauncher://import?url=ENCODED @@ -1035,7 +1038,6 @@ void MainWindow::processURLs(QList urls) // alternative import format: prismlauncher://import/ENCODED if (encodedTarget.isEmpty()) { - QString p = path; if (p.startsWith("/import/", Qt::CaseInsensitive)) { @@ -1050,12 +1052,9 @@ void MainWindow::processURLs(QList urls) } if (encodedTarget.isEmpty()) { - CustomMessageBox::selectable( - this, - tr("Error"), - tr("Invalid import link: missing 'url' parameter."), - QMessageBox::Critical - )->show(); + CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: missing 'url' parameter."), + QMessageBox::Critical) + ->show(); continue; } @@ -1065,23 +1064,15 @@ void MainWindow::processURLs(QList urls) // Validate: only allow http(s) if (!target.isValid() || (target.scheme() != "https" && target.scheme() != "http")) { - CustomMessageBox::selectable( - this, - tr("Error"), - tr("Invalid import link: URL must be http(s)."), - QMessageBox::Critical - )->show(); + CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: URL must be http(s)."), QMessageBox::Critical) + ->show(); continue; } const auto res = QMessageBox::question( - this, - tr("Install modpack"), - tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2") - .arg(target.host(), target.toString()), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes - ); + this, tr("Install modpack"), + tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2").arg(target.host(), target.toString()), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res != QMessageBox::Yes) { continue; } @@ -1396,6 +1387,16 @@ void MainWindow::on_actionEditInstance_triggered() } } +void MainWindow::on_actionManageSkins_triggered() +{ + auto account = APPLICATION->accounts()->defaultAccount(); + + if (account && (account->accountType() != AccountType::Offline) && !account->isActive()) { + SkinManageDialog dialog(this, account); + dialog.exec(); + } +} + void MainWindow::on_actionManageAccounts_triggered() { APPLICATION->ShowGlobalSettings(this, "accounts"); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 7dcba885a..80860deef 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -130,6 +130,8 @@ class MainWindow : public QMainWindow { void on_actionSettings_triggered(); + void on_actionManageSkins_triggered(); + void on_actionManageAccounts_triggered(); void on_actionReportBug_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 353788518..5c43577fb 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -322,6 +322,14 @@ QAction::PreferencesRole + + + + + + Manage &Skins... + + From 4ed3aa1f1c8f3a3ca32a2d3f9bdbee68d9da7dc9 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Fri, 17 Apr 2026 02:07:09 +0000 Subject: [PATCH 042/132] Fix(InstanceCreationTask): propagate abort signal to super Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/InstanceCreationTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index ecc9fe591..7e74a9336 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -18,7 +18,7 @@ bool InstanceCreationTask::abort() return m_gameFilesTask->abort(); } - return true; + return InstanceTask::abort(); } void InstanceCreationTask::executeTask() From 15b39af92e9147674c202448db8d586cd3981b8b Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Fri, 17 Apr 2026 02:11:06 +0000 Subject: [PATCH 043/132] Fix(Task): check if task is still running before calling emitAborted() Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/tasks/Task.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 6591ca302..38c09da90 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -165,7 +165,7 @@ class Task : public QObject, public QRunnable { //! used by external code to ask the task to abort virtual bool abort() { - if (canAbort()) + if (canAbort() && isRunning()) emitAborted(); return canAbort(); } From ffded2ccac953fa7843a6cd99bae1d0a62951f09 Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Fri, 17 Apr 2026 02:56:04 +0000 Subject: [PATCH 044/132] Fix(NetJob): do not call emitAborted() when not running Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/net/NetJob.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index dae149ab3..03705900c 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -100,13 +100,18 @@ auto NetJob::canAbort() const -> bool auto NetJob::abort() -> bool { - bool fullyAborted = true; - // fail all downloads on the queue for (auto task : m_queue) m_failed.insert(task.get(), task); m_queue.clear(); + if (m_doing.isEmpty()) { + // no downloads to abort, NetJob is not running + return true; + } + + bool fullyAborted = true; + // abort active downloads auto toKill = m_doing.values(); for (auto part : toKill) { From 4a59e6012d14584632c3d2f4104bf80770118b7e Mon Sep 17 00:00:00 2001 From: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> Date: Fri, 17 Apr 2026 03:45:33 +0000 Subject: [PATCH 045/132] NetJob: do not automatically retry on 404 response Signed-off-by: 0x189D7997 <199489335+0x189D7997@users.noreply.github.com> --- launcher/net/NetJob.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index dae149ab3..971f160d8 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -69,11 +69,15 @@ void NetJob::executeNextSubTask() // We're finished, check for failures and retry if we can (up to 3 times) if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) { m_try += 1; - while (!m_failed.isEmpty()) { - auto task = m_failed.take(*m_failed.keyBegin()); - m_done.remove(task.get()); - m_queue.enqueue(task); - } + m_failed.removeIf([this](QHash::iterator task) { + // there is no point in retying on 404 Not Found + if (static_cast(task->get())->replyStatusCode() == 404) { + return false; + } + m_done.remove(task->get()); + m_queue.enqueue(*task); + return true; + }); } ConcurrentTask::executeNextSubTask(); } From 85613cfadca8aacc3f6f0e0d0429dc0292c357a7 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 17 Apr 2026 11:12:46 +0500 Subject: [PATCH 046/132] Don't use new Qt method Signed-off-by: Octol1ttle --- launcher/net/ChecksumValidator.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h index c0d5a56aa..c7906cc13 100644 --- a/launcher/net/ChecksumValidator.h +++ b/launcher/net/ChecksumValidator.h @@ -71,8 +71,7 @@ class ChecksumValidator : public Validator { auto validate(QNetworkReply& reply) -> bool override { if (!m_expected.isEmpty() && m_expected != hash()) { - qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash() - << "algorithm:" << m_checksum.algorithm(); + qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash(); return false; } return true; From 4344f5eef912e4c74c8ab48e1d9004e79bf448a6 Mon Sep 17 00:00:00 2001 From: captivator <84224501+qaptivator@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:46:43 +0300 Subject: [PATCH 047/132] apply reviewer suggestion: use explicit MSA check Co-authored-by: Alexandru Ionut Tripon Signed-off-by: captivator <84224501+qaptivator@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 9e03874e6..b9e51a9f2 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -656,7 +656,7 @@ void MainWindow::repopulateAccountsMenu() auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); - bool canChangeSkin = defaultAccount && (defaultAccount->accountType() != AccountType::Offline) && !defaultAccount->isActive(); + bool canChangeSkin = defaultAccount && (defaultAccount->accountType() == AccountType::MSA) && !defaultAccount->isActive(); ui->actionManageSkins->setEnabled(canChangeSkin); QString active_profileId = ""; From 03799bf258ad0c1d875e64f6b13dc728c9a446d7 Mon Sep 17 00:00:00 2001 From: captivator <84224501+qaptivator@users.noreply.github.com> Date: Fri, 17 Apr 2026 13:47:08 +0300 Subject: [PATCH 048/132] apply reviewer suggestion: use explicit MSA check again Co-authored-by: Alexandru Ionut Tripon Signed-off-by: captivator <84224501+qaptivator@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index b9e51a9f2..71a64043e 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1391,7 +1391,7 @@ void MainWindow::on_actionManageSkins_triggered() { auto account = APPLICATION->accounts()->defaultAccount(); - if (account && (account->accountType() != AccountType::Offline) && !account->isActive()) { + if (account && (account->accountType() == AccountType::MSA) && !account->isActive()) { SkinManageDialog dialog(this, account); dialog.exec(); } From cbaf45084e47e4bd1c29e96ac4959431b12771e7 Mon Sep 17 00:00:00 2001 From: captivator <84224501+qaptivator@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:12:22 +0300 Subject: [PATCH 049/132] fix world size uninitialized memory and UI refresh signal Signed-off-by: captivator <84224501+qaptivator@users.noreply.github.com> --- launcher/minecraft/WorldList.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 4aa0f7532..ca8ac1aa8 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -157,7 +157,8 @@ bool WorldList::resetIcon(int row) return false; World& m = m_worlds[row]; if (m.resetIcon()) { - emit dataChanged(index(row), index(row), { WorldList::IconFileRole }); + QModelIndex modelIndex = index(row, NameColumn); + emit dataChanged(modelIndex, modelIndex, { WorldList::IconFileRole }); return true; } return false; @@ -426,7 +427,7 @@ void WorldList::loadWorldsAsync() m_worlds[row].setSize(size); // Notify views - QModelIndex modelIndex = index(row); + QModelIndex modelIndex = index(row, SizeColumn); emit dataChanged(modelIndex, modelIndex, { SizeRole }); } }, From 9621b59573c94da2c551192c3e0eab367c895fc3 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 17 Apr 2026 21:38:53 -0500 Subject: [PATCH 050/132] Change menu role for close action in MainWindow Fixes issue in Mac OS where pressing Cmd+Q closes only the current window instead of quitting the application. menuRole is consulted only by Qt's native macOS menu-bar integration, so this change has no effect on Windows, Linux, or BSD. Fixes #1382 Signed-off-by: Danny Assisted-by: Claude:claude-opus-4-7 --- launcher/ui/MainWindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 353788518..54b0b5e32 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -520,7 +520,7 @@ Close the current window - QAction::QuitRole + QAction::MenuRole::NoRole From c67de94b3ddb8c4761e1f8760816a6441e3cd73c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 19 Apr 2026 00:47:57 +0000 Subject: [PATCH 051/132] 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': 'https://releases.nixos.org/nixos/unstable/nixos-26.05pre971119.8110df5ad7ab/nixexprs.tar.xz?narHash=sha256-D4ely1FsBcvtj/qSrNhSWpq%2BCUZKNiKwJIxpxnfy9o4%3D' (2026-03-28) → 'https://releases.nixos.org/nixos/unstable/nixos-26.05pre980183.4bd9165a9165/nixexprs.tar.xz?narHash=sha256-Gk2T0tDDDAs319hp/ak%2BbAIUG5bPMvnNEjPV8CS86Fg%3D' (2026-04-14) --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index eb53cb844..beb1c8e40 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1774709303, - "narHash": "sha256-D4ely1FsBcvtj/qSrNhSWpq+CUZKNiKwJIxpxnfy9o4=", - "rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685", + "lastModified": 1776169885, + "narHash": "sha256-Gk2T0tDDDAs319hp/ak+bAIUG5bPMvnNEjPV8CS86Fg=", + "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre971119.8110df5ad7ab/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre980183.4bd9165a9165/nixexprs.tar.xz" }, "original": { "type": "tarball", From 418222cd6f657f6f7cd00a8bb70f7adb881344a9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 10 Apr 2026 20:12:03 +0300 Subject: [PATCH 052/132] add setting to controll game assets download Signed-off-by: Trial97 --- launcher/Application.cpp | 1 + launcher/InstanceCreationTask.cpp | 13 +++-- launcher/InstanceCreationTask.h | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 62 ++++++++++++----------- launcher/ui/pages/global/LauncherPage.ui | 25 +++++++-- 5 files changed, 66 insertions(+), 37 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index df849f7ca..ff4013b1c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -779,6 +779,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ModDependenciesDisabled", false); m_settings->registerSetting("SkipModpackUpdatePrompt", false); m_settings->registerSetting("ShowModIncompat", false); + m_settings->registerSetting("DownloadGameFilesDuringInstanceCreation", true); // Minecraft offline player name m_settings->registerSetting("LastOfflinePlayerName", ""); diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 7e74a9336..e58926660 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -3,6 +3,7 @@ #include #include +#include "Application.h" #include "InstanceTask.h" #include "minecraft/MinecraftLoadAndCheck.h" #include "tasks/SequentialTask.h" @@ -38,8 +39,9 @@ void InstanceCreationTask::executeTask() m_instance = createInstance(); if (!m_instance) { - if (m_abort) + if (m_abort) { return; + } qWarning() << "Instance creation failed!"; if (!m_error_message.isEmpty()) { @@ -63,8 +65,9 @@ void InstanceCreationTask::executeTask() qDebug() << "Removing old files"; for (const QString& path : m_filesToRemove) { - if (!QFile::exists(path)) + if (!QFile::exists(path)) { continue; + } qDebug() << "Removing" << path; @@ -81,6 +84,10 @@ void InstanceCreationTask::executeTask() } if (!m_abort) { + if (!APPLICATION->settings()->get("DownloadGameFilesDuringInstanceCreation").toBool()) { + emitSucceeded(); + return; + } setAbortable(true); setAbortButtonText(tr("Skip")); qDebug() << "Downloading game files"; @@ -110,7 +117,7 @@ void InstanceCreationTask::executeTask() } } -void InstanceCreationTask::scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled) +void InstanceCreationTask::scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled) { if (path.isEmpty()) { return; diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 416cf81db..39acaf8b2 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -38,7 +38,7 @@ class InstanceCreationTask : public InstanceTask { protected: void setError(const QString& message) { m_error_message = message; }; - void scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled = false); + void scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled = false); protected: bool m_abort = false; diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index d6d15a2c4..6276d3be6 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -90,12 +90,12 @@ bool LauncherPage::apply() void LauncherPage::on_instDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - if (FS::checkProblemticPathJava(QDir(cooked_dir))) { + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + if (FS::checkProblemticPathJava(QDir(cookedDir))) { QMessageBox warning; warning.setText( tr("You're trying to specify an instance folder which\'s path " @@ -108,9 +108,9 @@ void LauncherPage::on_instDirBrowseBtn_clicked() warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); int result = warning.exec(); if (result == QMessageBox::Ok) { - ui->instDirTextBox->setText(cooked_dir); + ui->instDirTextBox->setText(cookedDir); } - } else if (DesktopServices::isFlatpak() && raw_dir.startsWith("/run/user")) { + } else if (DesktopServices::isFlatpak() && rawDir.startsWith("/run/user")) { QMessageBox warning; warning.setText(tr("You're trying to specify an instance folder " "which was granted temporarily via Flatpak.\n" @@ -123,64 +123,64 @@ void LauncherPage::on_instDirBrowseBtn_clicked() warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); int result = warning.exec(); if (result == QMessageBox::Ok) { - ui->instDirTextBox->setText(cooked_dir); + ui->instDirTextBox->setText(cookedDir); } } else { - ui->instDirTextBox->setText(cooked_dir); + ui->instDirTextBox->setText(cookedDir); } } } void LauncherPage::on_iconsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->iconsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->iconsDirTextBox->setText(cookedDir); } } void LauncherPage::on_modsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->modsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->modsDirTextBox->setText(cookedDir); } } void LauncherPage::on_downloadsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->downloadsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->downloadsDirTextBox->setText(cookedDir); } } void LauncherPage::on_javaDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->javaDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->javaDirTextBox->setText(cookedDir); } } void LauncherPage::on_skinsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->skinsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->skinsDirTextBox->setText(cookedDir); } } @@ -191,7 +191,7 @@ void LauncherPage::on_metadataEnableBtn_clicked() void LauncherPage::applySettings() { - auto s = APPLICATION->settings(); + auto* s = APPLICATION->settings(); // Updates if (APPLICATION->updater()) { @@ -246,10 +246,11 @@ void LauncherPage::applySettings() s->set("ModDependenciesDisabled", !ui->dependenciesEnableBtn->isChecked()); s->set("ShowModIncompat", ui->showModIncompatCheckBox->isChecked()); s->set("SkipModpackUpdatePrompt", !ui->modpackUpdatePromptBtn->isChecked()); + s->set("DownloadGameFilesDuringInstanceCreation", ui->downloadGameFilesBtn->isChecked()); } void LauncherPage::loadSettings() { - auto s = APPLICATION->settings(); + auto* s = APPLICATION->settings(); // Updates if (APPLICATION->updater()) { ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates()); @@ -296,6 +297,7 @@ void LauncherPage::loadSettings() ui->dependenciesEnableBtn->setChecked(!s->get("ModDependenciesDisabled").toBool()); ui->showModIncompatCheckBox->setChecked(s->get("ShowModIncompat").toBool()); ui->modpackUpdatePromptBtn->setChecked(!s->get("SkipModpackUpdatePrompt").toBool()); + ui->downloadGameFilesBtn->setChecked(s->get("DownloadGameFilesDuringInstanceCreation").toBool()); } void LauncherPage::retranslate() diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index f5cfacf96..252f54b17 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -251,12 +251,12 @@ - - &Auto Java Download: - Folder where Prism Launcher stores automatically downloaded Java versions. Do NOT set this to your system Java installation. + + &Auto Java Download: + javaDirTextBox @@ -444,6 +444,25 @@ + + + + Instance Creation + + + + + + Downloads required game files while creating the instance. Disable this to skip the initial download and fetch files when the instance is launched instead. + + + Download game files during instance creation + + + + + + From 541e5ca9fe62c06a0e98335edf0ed75645560b21 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Mon, 20 Apr 2026 01:06:38 -0400 Subject: [PATCH 053/132] ci(container): actually use amd64 runner for amd64 Signed-off-by: Seth Flynn --- .github/workflows/container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index a5dcdc48a..7af2c1ccb 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -33,7 +33,7 @@ jobs: - arch: arm64 os: ubuntu-24.04-arm - arch: amd64 - os: ubuntu-24.04-arm + os: ubuntu-24.04 runs-on: ${{ matrix.os }} From a7c91796b3e1961067e0c36f3cdb1774e8c095c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:23:37 +0000 Subject: [PATCH 054/132] chore(deps): update hendrikmuhs/ccache-action action to v1.2.23 --- .github/actions/setup-dependencies/action.yml | 2 +- .github/workflows/clang-tidy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index 7d403ed0a..b73c7509a 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -55,7 +55,7 @@ runs: # TODO(@getchoo): Get this working on MSYS2! - name: Setup ccache if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} - uses: hendrikmuhs/ccache-action@v1.2.22 + uses: hendrikmuhs/ccache-action@v1.2.23 with: variant: sccache create-symlink: ${{ runner.os != 'Windows' }} diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 5251c149a..c6bf07214 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -29,7 +29,7 @@ jobs: submodules: "true" - name: Setup sccache - uses: hendrikmuhs/ccache-action@v1.2.22 + uses: hendrikmuhs/ccache-action@v1.2.23 with: variant: sccache From 672cd4d59c567a7235cdf1b871285ee6cc2c1386 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:05:23 +0000 Subject: [PATCH 055/132] chore(deps): update cachix/install-nix-action digest to 6165592 --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 1353166f1..bcbf0fad6 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31 + - uses: cachix/install-nix-action@616559265b40713947b9c190a8ff4b507b5df49b # v31 - uses: DeterminateSystems/update-flake-lock@v28 with: From f14701ffb7594ab60b7cb2cd6e4ace01f02fce26 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:05:28 +0000 Subject: [PATCH 056/132] chore(deps): update korthout/backport-action action to v4.4 --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 02fba2f68..5d4fa35c3 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -24,7 +24,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v4.3.0 + uses: korthout/backport-action@v4.4 with: # Config README: https://github.com/korthout/backport-action#backport-action pull_description: |- From 92eeeaf14fa81ae3e1f5f9d84aed6f875d64ce4b Mon Sep 17 00:00:00 2001 From: captivator <84224501+qaptivator@users.noreply.github.com> Date: Wed, 22 Apr 2026 01:33:40 +0300 Subject: [PATCH 057/132] fix: ignore non-existent or empty paths in processURLs Assisted-by: Gemini:3-Flash Signed-off-by: captivator <84224501+qaptivator@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 71a64043e..1bdcd3f68 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -937,6 +937,9 @@ void MainWindow::processURLs(QList urls) { // NOTE: This loop only processes one dropped file! for (auto& url : urls) { + if (url.isEmpty() || url.toString().trimmed().isEmpty()) + continue; + qDebug() << "Processing" << url; // The isLocalFile() check below doesn't work as intended without an explicit scheme. @@ -1116,6 +1119,11 @@ void MainWindow::processURLs(QList urls) auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile()); QFileInfo localFileInfo(localFileName); + if (localFileName.isEmpty() || !localFileInfo.exists()) { + qDebug() << "Ignoring invalid path" << localFileName; + continue; + } + auto type = ResourceUtils::identify(localFileInfo); if (ModPlatform::ResourceTypeUtils::VALID_RESOURCES.count(type) == 0) { // probably instance/modpack From 5ad8372e165dc0bcb13c2fad0be70ffbb187783b Mon Sep 17 00:00:00 2001 From: captivator <84224501+qaptivator@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:36:40 +0300 Subject: [PATCH 058/132] fix: trim whitespaces from ManagedPackURL Signed-off-by: captivator <84224501+qaptivator@users.noreply.github.com> --- launcher/ui/pages/instance/ManagedPackPage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 777dfbaa1..d2683fa92 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -128,7 +128,7 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi QDesktopServices::openUrl(url); }); - connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text); }); + connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); }); } ManagedPackPage::~ManagedPackPage() @@ -147,7 +147,7 @@ void ManagedPackPage::openedImpl() ui->updateToVersionLabel->setText(tr("URL:")); ui->updateButton->setText(tr("Update Pack")); ui->updateButton->setDisabled(false); - ui->urlLine->setText(m_inst->settings()->get("ManagedPackURL").toString()); + ui->urlLine->setText(m_inst->settings()->get("ManagedPackURL").toString().trimmed()); ui->packName->setText(m_inst->name()); ui->changelogTextBrowser->setText(tr("This is a local modpack.\n" @@ -357,7 +357,7 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const void ModrinthManagedPackPage::update() { - auto customURL = m_inst->settings()->get("ManagedPackURL").toString(); + auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { updatePack(customURL); return; @@ -486,7 +486,7 @@ void FlameManagedPackPage::suggestVersion() void FlameManagedPackPage::update() { - auto customURL = m_inst->settings()->get("ManagedPackURL").toString(); + auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { updatePack(customURL); return; From 6e0d9b8ca0179df92beaab8f34f2156ec85ec316 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 2 Apr 2026 03:18:45 +0500 Subject: [PATCH 059/132] change: improve clang-tidy CI speed by only running autogen & autorcc Signed-off-by: Octol1ttle --- .github/workflows/clang-tidy.yml | 9 +- CMakeLists.txt | 4 + launcher/CMakeLists.txt | 82 +------------------ .../modplatform/flame/FlameResourcePages.cpp | 2 +- .../modrinth/ModrinthResourcePages.cpp | 2 +- 5 files changed, 10 insertions(+), 89 deletions(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index c6bf07214..d72994c50 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -28,19 +28,14 @@ jobs: fetch-depth: 0 # Required for diffing later on submodules: "true" - - name: Setup sccache - uses: hendrikmuhs/ccache-action@v1.2.23 - with: - variant: sccache - - name: Install Nix uses: cachix/install-nix-action@v31 - - name: Run build + - name: Run source generators # TODO(@getchoo): Figure out how to make this work with PCH run: | nix develop --command bash -c ' - cmake -B build -D Launcher_USE_PCH=OFF -D CMAKE_CXX_COMPILER_LAUNCHER=sccache && cmake --build build + cmake -B build -D Launcher_USE_PCH=OFF && cmake --build build --target autogen autorcc ' # TODO: Use SARIF after https://github.com/psastras/sarif-rs/issues/638 is fixed diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e9ba2d4a..12afdefcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,10 @@ endif() ##################################### Set CMake options ##################################### set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOGEN_ORIGIN_DEPENDS OFF) +set(CMAKE_GLOBAL_AUTOGEN_TARGET ON) +set(CMAKE_GLOBAL_AUTORCC_TARGET ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c2f5e70cf..235ba2975 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1206,78 +1206,6 @@ if(WIN32) ) endif() -qt_wrap_ui(LAUNCHER_UI - ui/MainWindow.ui - ui/setupwizard/PasteWizardPage.ui - ui/setupwizard/AutoJavaWizardPage.ui - ui/setupwizard/LoginWizardPage.ui - ui/pages/global/AccountListPage.ui - ui/pages/global/JavaPage.ui - ui/pages/global/LauncherPage.ui - ui/pages/global/APIPage.ui - ui/pages/global/ProxyPage.ui - ui/pages/global/ExternalToolsPage.ui - ui/pages/instance/ExternalResourcesPage.ui - ui/pages/instance/NotesPage.ui - ui/pages/instance/LogPage.ui - ui/pages/instance/ServersPage.ui - ui/pages/instance/OtherLogsPage.ui - ui/pages/instance/VersionPage.ui - ui/pages/instance/ManagedPackPage.ui - ui/pages/instance/WorldListPage.ui - ui/pages/instance/ScreenshotsPage.ui - ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui - ui/pages/modplatform/atlauncher/AtlPage.ui - ui/pages/modplatform/CustomPage.ui - ui/pages/modplatform/ResourcePage.ui - ui/pages/modplatform/flame/FlamePage.ui - ui/pages/modplatform/legacy_ftb/Page.ui - ui/pages/modplatform/import_ftb/ImportFTBPage.ui - ui/pages/modplatform/ImportPage.ui - ui/pages/modplatform/OptionalModDialog.ui - ui/pages/modplatform/ftb/FtbPage.ui - ui/pages/modplatform/modrinth/ModrinthPage.ui - ui/pages/modplatform/technic/TechnicPage.ui - ui/widgets/CustomCommands.ui - ui/widgets/EnvironmentVariables.ui - ui/widgets/InfoFrame.ui - ui/widgets/ModFilterWidget.ui - ui/widgets/SubTaskProgressBar.ui - ui/widgets/AppearanceWidget.ui - ui/widgets/MinecraftSettingsWidget.ui - ui/widgets/JavaSettingsWidget.ui - ui/dialogs/CopyInstanceDialog.ui - ui/dialogs/CreateShortcutDialog.ui - ui/dialogs/ProfileSetupDialog.ui - ui/dialogs/ProgressDialog.ui - ui/dialogs/NewInstanceDialog.ui - ui/dialogs/NetworkJobFailedDialog.ui - ui/dialogs/NewComponentDialog.ui - ui/dialogs/NewsDialog.ui - ui/dialogs/ProfileSelectDialog.ui - ui/dialogs/ExportInstanceDialog.ui - ui/dialogs/ExportPackDialog.ui - ui/dialogs/ExportToModListDialog.ui - ui/dialogs/IconPickerDialog.ui - ui/dialogs/ImportResourceDialog.ui - ui/dialogs/MSALoginDialog.ui - ui/dialogs/AboutDialog.ui - ui/dialogs/ReviewMessageBox.ui - ui/dialogs/ScrollMessageBox.ui - ui/dialogs/BlockedModsDialog.ui - ui/dialogs/ChooseProviderDialog.ui - ui/dialogs/skins/SkinManageDialog.ui - ui/dialogs/ChooseOfflineNameDialog.ui -) - -qt_wrap_ui(PRISM_UPDATE_UI - ui/dialogs/UpdateAvailableDialog.ui -) - -if (NOT Apple) - set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI}) -endif() - qt_add_resources(LAUNCHER_RESOURCES resources/backgrounds/backgrounds.qrc resources/multimc/multimc.qrc @@ -1296,12 +1224,6 @@ qt_add_resources(LAUNCHER_RESOURCES "${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}" ) -qt_wrap_ui(PRISMUPDATER_UI - updater/prismupdater/SelectReleaseDialog.ui - ui/widgets/SubTaskProgressBar.ui - ui/dialogs/ProgressDialog.ui -) - ######## Windows resource files ######## if(WIN32) set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) @@ -1321,7 +1243,7 @@ endif() ####### Targets ######## # Add executable -add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) +add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_RESOURCES}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) @@ -1441,7 +1363,7 @@ endif() if(Launcher_BUILD_UPDATER) # Updater - add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) + add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if(${Launcher_USE_PCH}) diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 99a57f2bf..acdce29b6 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -41,7 +41,7 @@ #include #include #include "modplatform/flame/FlameAPI.h" -#include "ui_ResourcePage.h" +#include "../ui_ResourcePage.h" #include "FlameResourceModels.h" #include "ui/dialogs/ResourceDownloadDialog.h" diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index c290b6715..a1a7390bb 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -38,7 +38,7 @@ #include "ModrinthResourcePages.h" #include "ui/pages/modplatform/DataPackModel.h" -#include "ui_ResourcePage.h" +#include "../ui_ResourcePage.h" #include "modplatform/modrinth/ModrinthAPI.h" From e9cdef65e676d9850f00b7035b645d9c44c0b88a Mon Sep 17 00:00:00 2001 From: so5iso4ka Date: Sun, 26 Apr 2026 18:04:29 +0300 Subject: [PATCH 060/132] fix(OtherLogsPage): handle empty log lines Signed-off-by: so5iso4ka --- launcher/ui/pages/instance/OtherLogsPage.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 19a9db04f..b9f943777 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -277,12 +277,15 @@ void OtherLogsPage::reload() MessageLevel last = MessageLevel::Unknown; auto handleLine = [this, &last](QString line) { - if (line.isEmpty()) + if (!line.isEmpty() && line.back() == '\n') { + line.resize(line.size() - 1); + } + if (!line.isEmpty() && line.back() == '\r') { + line.resize(line.size() - 1); + } + if (line.isEmpty()) { return false; - if (line.back() == '\n') - line.resize(line.size() - 1); - if (line.back() == '\r') - line.resize(line.size() - 1); + } MessageLevel level = MessageLevel::Unknown; QString lineTemp = line; // don't edit out the time and level for clarity From 781e50cdbe28ff54da203c4e5f20a268fff676ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 3 May 2026 00:54:49 +0000 Subject: [PATCH 061/132] 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': 'https://releases.nixos.org/nixos/unstable/nixos-26.05pre980183.4bd9165a9165/nixexprs.tar.xz?narHash=sha256-Gk2T0tDDDAs319hp/ak%2BbAIUG5bPMvnNEjPV8CS86Fg%3D' (2026-04-14) → 'https://releases.nixos.org/nixos/unstable/nixos-26.05pre990025.15f4ee454b1d/nixexprs.tar.xz?narHash=sha256-fN6ynMvcdwPDB09LpWJNO5ogu%2BHFydrBWXJywoI/NNg%3D' (2026-04-30) --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index beb1c8e40..d6917f5a6 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776169885, - "narHash": "sha256-Gk2T0tDDDAs319hp/ak+bAIUG5bPMvnNEjPV8CS86Fg=", - "rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9", + "lastModified": 1777578337, + "narHash": "sha256-fN6ynMvcdwPDB09LpWJNO5ogu+HFydrBWXJywoI/NNg=", + "rev": "15f4ee454b1dce334612fa6843b3e05cf546efab", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre980183.4bd9165a9165/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre990025.15f4ee454b1d/nixexprs.tar.xz" }, "original": { "type": "tarball", From 6b5615ece918486a415f18fc514980de15ef6a7e Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 16:23:45 +0500 Subject: [PATCH 062/132] fix(TranslationsModel): use proper way to get system locale Signed-off-by: Octol1ttle --- launcher/translations/TranslationsModel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index dea7241b2..9d5400634 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -433,7 +433,7 @@ std::optional TranslationsModel::findLanguageAsOptional(const QString& void TranslationsModel::setUseSystemLocale(bool useSystemLocale) { APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); - QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode)); + QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(defaultLangCode)); } bool TranslationsModel::selectLanguage(QString key) @@ -467,8 +467,8 @@ bool TranslationsModel::selectLanguage(QString key) * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. * This function is not reentrant. */ - QLocale::setDefault( - QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode)); + const bool useSystemLocale = APPLICATION->settings()->get("UseSystemLocale").toBool(); + QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(langCode)); // if it's the default UI language, finish if (langCode == defaultLangCode) { From e449aae6c89992b1beba10995f16693952f127b4 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 16:25:32 +0500 Subject: [PATCH 063/132] fix(TranslationsModel): use current language instead of default when turning off 'Use system locale' Signed-off-by: Octol1ttle --- launcher/translations/TranslationsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 9d5400634..59d9ec3a8 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -433,7 +433,7 @@ std::optional TranslationsModel::findLanguageAsOptional(const QString& void TranslationsModel::setUseSystemLocale(bool useSystemLocale) { APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); - QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(defaultLangCode)); + QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(selectedLanguage())); } bool TranslationsModel::selectLanguage(QString key) From 8b159bacd87d0db57452263b1a932b1056002376 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 16:33:05 +0500 Subject: [PATCH 064/132] change(LanguageSelectionWidget): 'Use system locale' -> 'Use system regional standards' Closes #5358 Signed-off-by: Octol1ttle --- launcher/ui/widgets/LanguageSelectionWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp index a32f7034d..3f35df7b0 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.cpp +++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp @@ -63,7 +63,7 @@ void LanguageSelectionWidget::retranslate() QString text = tr("Don't see your language or the quality is poor?
Help us with translations!") .arg(BuildConfig.TRANSLATIONS_URL); helpUsLabel->setText(text); - formatCheckbox->setText(tr("Use system locales")); + formatCheckbox->setText(tr("Use system regional standards")); } void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) From 1f291a2d7991ebf76f9fcb9dc1ae8fcc59c3ddaf Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 16:49:10 +0500 Subject: [PATCH 065/132] change(updater): ignore skipped versions when update check is triggered by user Signed-off-by: Octol1ttle --- launcher/updater/PrismExternalUpdater.cpp | 6 +++--- launcher/updater/PrismExternalUpdater.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index a4e34e10a..879be9ca0 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -207,7 +207,7 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) qDebug() << "Update available:" << version_name << version_tag << release_timestamp; qDebug() << "Update release notes:" << release_notes; - offerUpdate(version_name, version_tag, release_notes); + offerUpdate(version_name, version_tag, release_notes, triggeredByUser); } break; default: @@ -309,10 +309,10 @@ void PrismExternalUpdater::autoCheckTimerFired() checkForUpdates(false); } -void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes) +void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes, const bool ignoreSkipped) { priv->settings->beginGroup("skip"); - auto should_skip = priv->settings->value(version_tag, false).toBool(); + auto should_skip = !ignoreSkipped && priv->settings->value(version_tag, false).toBool(); priv->settings->endGroup(); if (should_skip) { diff --git a/launcher/updater/PrismExternalUpdater.h b/launcher/updater/PrismExternalUpdater.h index b88676028..236829137 100644 --- a/launcher/updater/PrismExternalUpdater.h +++ b/launcher/updater/PrismExternalUpdater.h @@ -83,7 +83,7 @@ class PrismExternalUpdater : public ExternalUpdater { void disconnectTimer(); void connectTimer(); - void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes); + void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes, bool ignoreSkipped); void performUpdate(const QString& version_tag); public slots: From f6096d21dbf58a873de7e895321c924ebb5cdd2f Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 18:39:44 +0500 Subject: [PATCH 066/132] fix: use "Discrete" key from switcheroo if present Signed-off-by: Octol1ttle --- launcher/minecraft/MinecraftInstance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index e8fc642fa..ff6a46b11 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -131,7 +131,8 @@ for (const auto& gpu : gpus) { QString name = qvariant_cast(gpu[QStringLiteral("Name")]); bool defaultGpu = qvariant_cast(gpu[QStringLiteral("Default")]); - if (!defaultGpu) { + bool discrete = qvariant_cast(gpu.value(QStringLiteral("Discrete"), !defaultGpu)); + if (discrete) { QStringList envList = qvariant_cast(gpu[QStringLiteral("Environment")]); for (int i = 0; i + 1 < envList.size(); i += 2) { env.insert(envList[i], envList[i + 1]); From 53dda7cd323c60e2871903f8de7ad9051c1cfa6c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 17:33:20 +0500 Subject: [PATCH 067/132] PrismExternalUpdater: clang-tidy Signed-off-by: Octol1ttle --- launcher/updater/PrismExternalUpdater.cpp | 118 +++++++++++----------- launcher/updater/PrismExternalUpdater.h | 18 ++-- 2 files changed, 66 insertions(+), 70 deletions(-) diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index 879be9ca0..b34da5915 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -21,9 +21,8 @@ */ #include "PrismExternalUpdater.h" + #include -#include -#include #include #include #include @@ -58,19 +57,18 @@ PrismExternalUpdater::PrismExternalUpdater(QWidget* parent, const QString& appDi { priv->appDir = QDir(appDir); priv->dataDir = QDir(dataDir); - auto settings_file = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg"); - priv->settings = std::make_unique(settings_file, QSettings::Format::IniFormat); + auto settingsFile = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg"); + priv->settings = std::make_unique(settingsFile, QSettings::Format::IniFormat); priv->allowBeta = priv->settings->value("allow_beta", false).toBool(); priv->autoCheck = priv->settings->value("auto_check", true).toBool(); - bool interval_ok = false; + bool intervalOk = false; // default once per day - priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&interval_ok); - if (!interval_ok) { + priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&intervalOk); + if (!intervalOk) { priv->updateInterval = 86400; } - auto last_check = priv->settings->value("last_check"); - if (!last_check.isNull() && last_check.isValid()) { - priv->lastCheck = QDateTime::fromString(last_check.toString(), Qt::ISODate); + if (const auto lastCheck = priv->settings->value("last_check"); !lastCheck.isNull() && lastCheck.isValid()) { + priv->lastCheck = QDateTime::fromString(lastCheck.toString(), Qt::ISODate); } priv->parent = parent; connectTimer(); @@ -95,7 +93,7 @@ void PrismExternalUpdater::checkForUpdates() checkForUpdates(true); } -void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) +void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const { QProgressDialog progress(tr("Checking for updates..."), "", 0, 0, priv->parent); progress.setCancelButton(nullptr); @@ -106,15 +104,15 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) QCoreApplication::processEvents(); QProcess proc; - auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); -#if defined Q_OS_WIN32 - exe_name.append(".exe"); + auto exeName = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); +#ifdef Q_OS_WIN32 + exeName.append(".exe"); auto env = QProcessEnvironment::systemEnvironment(); env.insert("__COMPAT_LAYER", "RUNASINVOKER"); proc.setProcessEnvironment(env); #else - exe_name = QString("bin/%1").arg(exe_name); + exeName = QString("bin/%1").arg(exeName); #endif QStringList args = { "--check-only", "--dir", priv->dataDir.absolutePath(), "--debug" }; @@ -122,9 +120,8 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) args.append("--pre-release"); } - proc.start(priv->appDir.absoluteFilePath(exe_name), args); - auto result_start = proc.waitForStarted(5000); - if (!result_start) { + proc.start(priv->appDir.absoluteFilePath(exeName), args); + if (auto resultStart = proc.waitForStarted(5000); !resultStart) { auto err = proc.error(); qDebug() << "Failed to start updater after 5 seconds." << "reason:" << err << proc.errorString(); @@ -142,8 +139,7 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) } QCoreApplication::processEvents(); - auto result_finished = proc.waitForFinished(60000); - if (!result_finished) { + if (auto resultFinished = proc.waitForFinished(60000); !resultFinished) { proc.kill(); auto err = proc.error(); auto output = proc.readAll(); @@ -163,15 +159,15 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) return; } - auto exit_code = proc.exitCode(); + auto exitCode = proc.exitCode(); - auto std_output = proc.readAllStandardOutput(); - auto std_error = proc.readAllStandardError(); + auto stdOutput = proc.readAllStandardOutput(); + auto stdError = proc.readAllStandardError(); progress.hide(); QCoreApplication::processEvents(); - switch (exit_code) { + switch (exitCode) { case 0: // no update available if (triggeredByUser) { @@ -186,10 +182,10 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) case 1: // there was an error { - qDebug() << "Updater subprocess error" << qPrintable(std_error); + qDebug() << "Updater subprocess error" << qPrintable(stdError); auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Check Error"), tr("There was an error running the update check."), QMessageBox::Ok, priv->parent); - msgBox.setDetailedText(QString(std_error)); + msgBox.setDetailedText(QString(stdError)); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -198,28 +194,27 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) case 100: // update available { - auto [first_line, remainder1] = StringUtils::splitFirst(std_output, '\n'); - auto [second_line, remainder2] = StringUtils::splitFirst(remainder1, '\n'); - auto [third_line, release_notes] = StringUtils::splitFirst(remainder2, '\n'); - auto version_name = StringUtils::splitFirst(first_line, ": ").second.trimmed(); - auto version_tag = StringUtils::splitFirst(second_line, ": ").second.trimmed(); - auto release_timestamp = QDateTime::fromString(StringUtils::splitFirst(third_line, ": ").second.trimmed(), Qt::ISODate); - qDebug() << "Update available:" << version_name << version_tag << release_timestamp; - qDebug() << "Update release notes:" << release_notes; + auto [firstLine, remainder1] = StringUtils::splitFirst(stdOutput, '\n'); + auto [secondLine, remainder2] = StringUtils::splitFirst(remainder1, '\n'); + auto [thirdLine, releaseNotes] = StringUtils::splitFirst(remainder2, '\n'); + auto versionName = StringUtils::splitFirst(firstLine, ": ").second.trimmed(); + auto versionTag = StringUtils::splitFirst(secondLine, ": ").second.trimmed(); + auto releaseTimestamp = QDateTime::fromString(StringUtils::splitFirst(thirdLine, ": ").second.trimmed(), Qt::ISODate); + qDebug() << "Update available:" << versionName << versionTag << releaseTimestamp; + qDebug() << "Update release notes:" << releaseNotes; - offerUpdate(version_name, version_tag, release_notes, triggeredByUser); + offerUpdate(versionName, versionTag, releaseNotes, triggeredByUser); } break; default: // unknown error code { - qDebug() << "Updater exited with unknown code" << exit_code; - auto msgBox = - QMessageBox(QMessageBox::Information, tr("Unknown Update Error"), - tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exit_code)), - QMessageBox::Ok, priv->parent); - auto detail_txt = tr("StdOut: %1\nStdErr: %2").arg(QString(std_output)).arg(QString(std_error)); - msgBox.setDetailedText(detail_txt); + qDebug() << "Updater exited with unknown code" << exitCode; + auto msgBox = QMessageBox(QMessageBox::Information, tr("Unknown Update Error"), + tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exitCode)), + QMessageBox::Ok, priv->parent); + auto detailTxt = tr("StdOut: %1\nStdErr: %2").arg(QString(stdOutput)).arg(QString(stdError)); + msgBox.setDetailedText(detailTxt); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -269,7 +264,7 @@ void PrismExternalUpdater::setBetaAllowed(bool allowed) priv->settings->sync(); } -void PrismExternalUpdater::resetAutoCheckTimer() +void PrismExternalUpdater::resetAutoCheckTimer() const { if (priv->autoCheck && priv->updateInterval > 0) { auto now = QDateTime::currentDateTime(); @@ -277,9 +272,9 @@ void PrismExternalUpdater::resetAutoCheckTimer() qint64 timeoutMs = 0; if (priv->lastCheck.isValid()) { - qint64 diff = priv->lastCheck.secsTo(now); - qint64 secs_left = std::max(priv->updateInterval - diff, 0); - timeoutMs = secs_left * 1000; + const qint64 diff = priv->lastCheck.secsTo(now); + const qint64 secsLeft = std::max(priv->updateInterval - diff, 0); + timeoutMs = secsLeft * 1000; } timeoutMs = std::min(timeoutMs, static_cast(INT_MAX)); @@ -303,19 +298,22 @@ void PrismExternalUpdater::disconnectTimer() disconnect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired); } -void PrismExternalUpdater::autoCheckTimerFired() +void PrismExternalUpdater::autoCheckTimerFired() const { qDebug() << "Auto update Timer fired"; checkForUpdates(false); } -void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes, const bool ignoreSkipped) +void PrismExternalUpdater::offerUpdate(const QString& versionName, + const QString& versionTag, + const QString& releaseNotes, + const bool ignoreSkipped) const { priv->settings->beginGroup("skip"); - auto should_skip = !ignoreSkipped && priv->settings->value(version_tag, false).toBool(); + auto shouldSkip = !ignoreSkipped && priv->settings->value(versionTag, false).toBool(); priv->settings->endGroup(); - if (should_skip) { + if (shouldSkip) { auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), QMessageBox::Ok, priv->parent); msgBox.setMinimumWidth(460); @@ -324,18 +322,18 @@ void PrismExternalUpdater::offerUpdate(const QString& version_name, const QStrin return; } - UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), version_name, release_notes); + UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), versionName, releaseNotes); auto result = dlg.exec(); qDebug() << "offer dlg result" << result; switch (result) { case UpdateAvailableDialog::Install: { - performUpdate(version_tag); + performUpdate(versionTag); return; } case UpdateAvailableDialog::Skip: { priv->settings->beginGroup("skip"); - priv->settings->setValue(version_tag, true); + priv->settings->setValue(versionTag, true); priv->settings->endGroup(); priv->settings->sync(); return; @@ -346,26 +344,26 @@ void PrismExternalUpdater::offerUpdate(const QString& version_name, const QStrin } } -void PrismExternalUpdater::performUpdate(const QString& version_tag) +void PrismExternalUpdater::performUpdate(const QString& versionTag) const { QProcess proc; - auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); -#if defined Q_OS_WIN32 - exe_name.append(".exe"); + auto exeName = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); +#ifdef Q_OS_WIN32 + exeName.append(".exe"); auto env = QProcessEnvironment::systemEnvironment(); env.insert("__COMPAT_LAYER", "RUNASINVOKER"); proc.setProcessEnvironment(env); #else - exe_name = QString("bin/%1").arg(exe_name); + exeName = QString("bin/%1").arg(exeName); #endif - QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", version_tag }; + QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", versionTag }; if (priv->allowBeta) { args.append("--pre-release"); } - proc.setProgram(priv->appDir.absoluteFilePath(exe_name)); + proc.setProgram(priv->appDir.absoluteFilePath(exeName)); proc.setArguments(args); auto result = proc.startDetached(); if (!result) { diff --git a/launcher/updater/PrismExternalUpdater.h b/launcher/updater/PrismExternalUpdater.h index 236829137..9b0b25fbb 100644 --- a/launcher/updater/PrismExternalUpdater.h +++ b/launcher/updater/PrismExternalUpdater.h @@ -22,12 +22,10 @@ #pragma once -#include - #include "ExternalUpdater.h" /*! - * An implementation for the updater on windows and linux that uses out external updater. + * An implementation for the updater on Windows and linux that uses out external updater. */ class PrismExternalUpdater : public ExternalUpdater { @@ -41,7 +39,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Check for updates manually, showing the user a progress bar and an alert if no updates are found. */ void checkForUpdates() override; - void checkForUpdates(bool triggeredByUser); + void checkForUpdates(bool triggeredByUser) const; /*! * Indicates whether or not to check for updates automatically. @@ -62,7 +60,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Set whether or not to check for updates automatically. * * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately." + * reverting this property without kicking off a schedule change immediately. */ void setAutomaticallyChecksForUpdates(bool check) override; @@ -70,7 +68,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Set the current automatic update check interval in seconds. * * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately." + * reverting this property without kicking off a schedule change immediately. */ void setUpdateCheckInterval(double seconds) override; @@ -79,15 +77,15 @@ class PrismExternalUpdater : public ExternalUpdater { */ void setBetaAllowed(bool allowed) override; - void resetAutoCheckTimer(); + void resetAutoCheckTimer() const; void disconnectTimer(); void connectTimer(); - void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes, bool ignoreSkipped); - void performUpdate(const QString& version_tag); + void offerUpdate(const QString& versionName, const QString& versionTag, const QString& releaseNotes, bool ignoreSkipped) const; + void performUpdate(const QString& versionTag) const; public slots: - void autoCheckTimerFired(); + void autoCheckTimerFired() const; private: class Private; From ae33c82268fa5de29d7773844dc3bef876347cab Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 18:13:25 +0500 Subject: [PATCH 068/132] fix(PrismExternalUpdater): show progress dialog immediately Signed-off-by: Octol1ttle --- launcher/updater/PrismExternalUpdater.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index b34da5915..79714cc35 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -96,6 +96,7 @@ void PrismExternalUpdater::checkForUpdates() void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const { QProgressDialog progress(tr("Checking for updates..."), "", 0, 0, priv->parent); + progress.setMinimumDuration(0); // Appear immediately without waiting progress.setCancelButton(nullptr); progress.adjustSize(); if (triggeredByUser) { @@ -164,7 +165,7 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const auto stdOutput = proc.readAllStandardOutput(); auto stdError = proc.readAllStandardError(); - progress.hide(); + progress.cancel(); QCoreApplication::processEvents(); switch (exitCode) { From 0f9be64d6c575ce698bfddd2c45d4578bdf8d457 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 18:14:09 +0500 Subject: [PATCH 069/132] fix(PrismExternalUpdater): do not show "No updates available" when ignoring skipped version during autocheck Signed-off-by: Octol1ttle --- launcher/updater/PrismExternalUpdater.cpp | 16 +++++++++------- launcher/updater/PrismExternalUpdater.h | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index 79714cc35..ea086d174 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -308,18 +308,20 @@ void PrismExternalUpdater::autoCheckTimerFired() const void PrismExternalUpdater::offerUpdate(const QString& versionName, const QString& versionTag, const QString& releaseNotes, - const bool ignoreSkipped) const + const bool triggeredByUser) const { priv->settings->beginGroup("skip"); - auto shouldSkip = !ignoreSkipped && priv->settings->value(versionTag, false).toBool(); + auto shouldSkip = !triggeredByUser && priv->settings->value(versionTag, false).toBool(); priv->settings->endGroup(); if (shouldSkip) { - auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), - QMessageBox::Ok, priv->parent); - msgBox.setMinimumWidth(460); - msgBox.adjustSize(); - msgBox.exec(); + if (triggeredByUser) { + auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), + QMessageBox::Ok, priv->parent); + msgBox.setMinimumWidth(460); + msgBox.adjustSize(); + msgBox.exec(); + } return; } diff --git a/launcher/updater/PrismExternalUpdater.h b/launcher/updater/PrismExternalUpdater.h index 9b0b25fbb..b3f284b33 100644 --- a/launcher/updater/PrismExternalUpdater.h +++ b/launcher/updater/PrismExternalUpdater.h @@ -81,7 +81,7 @@ class PrismExternalUpdater : public ExternalUpdater { void disconnectTimer(); void connectTimer(); - void offerUpdate(const QString& versionName, const QString& versionTag, const QString& releaseNotes, bool ignoreSkipped) const; + void offerUpdate(const QString& versionName, const QString& versionTag, const QString& releaseNotes, bool triggeredByUser) const; void performUpdate(const QString& versionTag) const; public slots: From 2d920da737e7abe8b1566565ffad65023799bbdf Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 18:26:20 +0500 Subject: [PATCH 070/132] change(PrismExternalUpdater): allow unskipping versions by clicking "Remind Me Later" Signed-off-by: Octol1ttle --- launcher/updater/PrismExternalUpdater.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index ea086d174..00f8404cf 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -329,22 +329,18 @@ void PrismExternalUpdater::offerUpdate(const QString& versionName, auto result = dlg.exec(); qDebug() << "offer dlg result" << result; - switch (result) { - case UpdateAvailableDialog::Install: { + + priv->settings->beginGroup("skip"); + if (result == UpdateAvailableDialog::Skip) { + priv->settings->setValue(versionTag, true); + } else { + if (result == UpdateAvailableDialog::Install) { performUpdate(versionTag); - return; - } - case UpdateAvailableDialog::Skip: { - priv->settings->beginGroup("skip"); - priv->settings->setValue(versionTag, true); - priv->settings->endGroup(); - priv->settings->sync(); - return; - } - default: { - return; } + priv->settings->remove(versionTag); } + priv->settings->endGroup(); + priv->settings->sync(); } void PrismExternalUpdater::performUpdate(const QString& versionTag) const From 87f7c812c71e24d130ab787583b38fd8e511be9b Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 3 May 2026 17:20:05 +0500 Subject: [PATCH 071/132] TranslationsModel: clang-tidy and code quality Signed-off-by: Octol1ttle --- launcher/translations/TranslationsModel.cpp | 305 ++++++++++---------- launcher/translations/TranslationsModel.h | 48 +-- 2 files changed, 178 insertions(+), 175 deletions(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 59d9ec3a8..ebbd7cdb6 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -36,13 +36,9 @@ #include "TranslationsModel.h" -#include #include -#include -#include -#include -#include -#include +#include +#include #include "BuildConfig.h" #include "FileSystem.h" @@ -55,18 +51,26 @@ #include "Application.h" #include "settings/SettingsObject.h" -const static QLatin1String defaultLangCode("en_US"); +static constexpr QLatin1String g_defaultLangCode("en_US"); -enum class FileType { NONE, QM, PO }; +namespace { +enum class FileType : std::uint8_t { None, Qm, Po }; + +QString getSystemLocaleName() +{ + return QLocale::system().name(); +} + +QString getSystemLanguage() +{ + return getSystemLocaleName().split('_').front(); +} +} // namespace struct Language { - Language() { updated = true; } - Language(const QString& _key) - { - key = _key; - locale = QLocale(key); - updated = (key == defaultLangCode); - } + Language() : updated(true) {} + + explicit Language(QString _key) : key(std::move(_key)), updated(key == g_defaultLangCode) { locale = QLocale(key); } QString languageName() const { @@ -98,12 +102,12 @@ struct Language { float percentTranslated() const { if (total == 0) { - return 100.0f; + return 100.0F; } - return 100.0f * float(translated) / float(total); + return 100.0F * static_cast(translated) / static_cast(total); } - void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy) + void setTranslationStats(const unsigned _translated, const unsigned _untranslated, const unsigned _fuzzy) { translated = _translated; untranslated = _untranslated; @@ -115,18 +119,18 @@ struct Language { bool isIdenticalTo(const Language& other) const { - return (key == other.key && file_name == other.file_name && file_size == other.file_size && file_sha1 == other.file_sha1 && + return (key == other.key && fileName == other.fileName && fileSize == other.fileSize && fileSha1 == other.fileSha1 && translated == other.translated && fuzzy == other.fuzzy && total == other.fuzzy && localFileType == other.localFileType); } - Language& apply(Language& other) + Language& apply(const Language& other) { if (!isOfSameNameAs(other)) { return *this; } - file_name = other.file_name; - file_size = other.file_size; - file_sha1 = other.file_sha1; + fileName = other.fileName; + fileSize = other.fileSize; + fileSha1 = other.fileSha1; translated = other.translated; fuzzy = other.fuzzy; total = other.total; @@ -138,46 +142,42 @@ struct Language { QLocale locale; bool updated; - QString file_name = QString(); - std::size_t file_size = 0; - QString file_sha1 = QString(); + QString fileName = QString(); + std::size_t fileSize = 0; + QString fileSha1 = QString(); unsigned translated = 0; unsigned untranslated = 0; unsigned fuzzy = 0; unsigned total = 0; - FileType localFileType = FileType::NONE; + FileType localFileType = FileType::None; }; struct TranslationsModel::Private { QDir m_dir; // initial state is just english - QList m_languages = { Language(defaultLangCode) }; + QList m_languages = { Language(g_defaultLangCode) }; - QString m_selectedLanguage = defaultLangCode; - std::unique_ptr m_qt_translator; - std::unique_ptr m_app_translator; + QString m_selectedLanguage = g_defaultLangCode; + std::unique_ptr m_qtTranslator; + std::unique_ptr m_appTranslator; - Net::Download* m_index_task; + Net::Download* m_indexTask = nullptr; QString m_downloadingTranslation; - NetJob::Ptr m_dl_job; - NetJob::Ptr m_index_job; + NetJob::Ptr m_downloadJob; + NetJob::Ptr m_indexJob; QString m_nextDownload; - std::unique_ptr m_po_translator; - QFileSystemWatcher* watcher; + QFileSystemWatcher* watcher = nullptr; - const QString m_system_locale = QLocale::system().name(); - const QString m_system_language = m_system_locale.split('_').front(); - - bool no_language_set = false; + bool m_noLanguageSet = false; }; -TranslationsModel::TranslationsModel(QString path, QObject* parent) : QAbstractListModel(parent) +TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAbstractListModel(parent) { - d.reset(new Private); + d = std::make_unique(); d->m_dir.setPath(path); FS::ensureFolderPathExists(path); reloadLocalFiles(); @@ -187,12 +187,12 @@ TranslationsModel::TranslationsModel(QString path, QObject* parent) : QAbstractL d->watcher->addPath(d->m_dir.canonicalPath()); } -TranslationsModel::~TranslationsModel() {} +TranslationsModel::~TranslationsModel() = default; void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; - if (!d->no_language_set) { + if (!d->m_noLanguageSet) { reloadLocalFiles(); } selectLanguage(selectedLanguage()); @@ -201,24 +201,22 @@ void TranslationsModel::translationDirChanged(const QString& path) void TranslationsModel::indexReceived() { qDebug() << "Got translations index!"; - d->m_index_job.reset(); + d->m_indexJob.reset(); - if (d->no_language_set) { + if (d->m_noLanguageSet) { reloadLocalFiles(); - auto language = d->m_system_locale; + auto language = getSystemLocaleName(); if (!findLanguageAsOptional(language).has_value()) { - language = d->m_system_language; + language = getSystemLanguage(); } selectLanguage(language); - if (selectedLanguage() != defaultLangCode) { + if (selectedLanguage() != g_defaultLangCode) { updateLanguage(selectedLanguage()); } APPLICATION->settings()->set("Language", selectedLanguage()); - d->no_language_set = false; - } - - else if (d->m_selectedLanguage != defaultLangCode) { + d->m_noLanguageSet = false; + } else if (d->m_selectedLanguage != g_defaultLangCode) { downloadTranslation(d->m_selectedLanguage); } } @@ -235,27 +233,27 @@ void readIndex(const QString& path, QMap& languages) } try { - auto toplevel_doc = Json::requireDocument(data); - auto doc = Json::requireObject(toplevel_doc); - auto file_type = Json::requireString(doc, "file_type"); - if (file_type != "MMC-TRANSLATION-INDEX") { - qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type; + auto toplevelDoc = Json::requireDocument(data); + auto doc = Json::requireObject(toplevelDoc); + auto fileType = Json::requireString(doc, "file_type"); + if (fileType != "MMC-TRANSLATION-INDEX") { + qCritical() << "Translations Download Failed: index file is of unknown file type" << fileType; return; } auto version = Json::requireInteger(doc, "version"); if (version > 2) { - qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type; + qCritical() << "Translations Download Failed: index file is of unknown format version" << fileType; return; } auto langObjs = Json::requireObject(doc, "languages"); - for (auto iter = langObjs.begin(); iter != langObjs.end(); iter++) { + for (auto iter = langObjs.begin(); iter != langObjs.end(); ++iter) { Language lang(iter.key()); auto langObj = Json::requireObject(iter.value()); lang.setTranslationStats(langObj["translated"].toInt(), langObj["untranslated"].toInt(), langObj["fuzzy"].toInt()); - lang.file_name = Json::requireString(langObj, "file"); - lang.file_sha1 = Json::requireString(langObj, "sha1"); - lang.file_size = Json::requireInteger(langObj, "size"); + lang.fileName = Json::requireString(langObj, "file"); + lang.fileSha1 = Json::requireString(langObj, "sha1"); + lang.fileSize = Json::requireInteger(langObj, "size"); languages.insert(lang.key, lang); } @@ -267,20 +265,20 @@ void readIndex(const QString& path, QMap& languages) void TranslationsModel::reloadLocalFiles() { - QMap languages = { { defaultLangCode, Language(defaultLangCode) } }; + QMap languages = { { g_defaultLangCode, Language(g_defaultLangCode) } }; readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); auto entries = d->m_dir.entryInfoList({ "mmc_*.qm", "*.po" }, QDir::Files | QDir::NoDotAndDotDot); for (auto& entry : entries) { auto completeSuffix = entry.completeSuffix(); QString langCode; - FileType fileType = FileType::NONE; + FileType fileType = FileType::None; if (completeSuffix == "qm") { langCode = entry.baseName().remove(0, 4); - fileType = FileType::QM; + fileType = FileType::Qm; } else if (completeSuffix == "po") { langCode = entry.baseName(); - fileType = FileType::PO; + fileType = FileType::Po; } else { continue; } @@ -288,13 +286,14 @@ void TranslationsModel::reloadLocalFiles() auto langIter = languages.find(langCode); if (langIter != languages.end()) { auto& language = *langIter; - if (int(fileType) > int(language.localFileType)) { + // TODO: use std::to_underlying in C++23 + if (static_cast(fileType) > static_cast(language.localFileType)) { language.localFileType = fileType; } } else { - if (fileType == FileType::PO) { + if (fileType == FileType::Po) { Language localFound(langCode); - localFound.localFileType = FileType::PO; + localFound.localFileType = FileType::Po; languages.insert(langCode, localFound); } } @@ -314,7 +313,7 @@ void TranslationsModel::reloadLocalFiles() emit dataChanged(index(row), index(row)); languages.remove(language.key); } - iter++; + ++iter; } else { beginRemoveRows(QModelIndex(), row, row); iter = d->m_languages.erase(iter); @@ -329,34 +328,38 @@ void TranslationsModel::reloadLocalFiles() for (auto& language : languages) { d->m_languages.append(language); } - std::sort(d->m_languages.begin(), d->m_languages.end(), [this](const Language& a, const Language& b) { + + const auto comp = [systemLocale = getSystemLocaleName(), systemLanguage = getSystemLanguage()](const Language& a, const Language& b) { if (a.key != b.key) { - if (a.key == d->m_system_locale || a.key == d->m_system_language) { + if (a.key == systemLocale || a.key == systemLanguage) { return true; } - if (b.key == d->m_system_locale || b.key == d->m_system_language) { + if (b.key == systemLocale || b.key == systemLanguage) { return false; } } return a.languageName().toLower() < b.languageName().toLower(); - }); + }; + std::ranges::sort(d->m_languages, comp); endInsertRows(); } namespace { -enum class Column { Language, Completeness }; +enum class Column : std::uint8_t { Language, Completeness }; } -QVariant TranslationsModel::data(const QModelIndex& index, int role) const +QVariant TranslationsModel::data(const QModelIndex& index, const int role) const { - if (!index.isValid()) - return QVariant(); + if (!index.isValid()) { + return {}; + } - int row = index.row(); - auto column = static_cast(index.column()); + const int row = index.row(); + const auto column = static_cast(index.column()); - if (row < 0 || row >= d->m_languages.size()) - return QVariant(); + if (row < 0 || row >= d->m_languages.size()) { + return {}; + } auto& lang = d->m_languages[row]; switch (role) { @@ -378,11 +381,11 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const case Qt::UserRole: return lang.key; default: - return QVariant(); + return {}; } } -QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const +QVariant TranslationsModel::headerData(int section, const Qt::Orientation orientation, const int role) const { auto column = static_cast(section); if (role == Qt::DisplayRole) { @@ -417,49 +420,50 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c return 2; } -QList::Iterator TranslationsModel::findLanguage(const QString& key) +QList::Iterator TranslationsModel::findLanguage(const QString& key) const { - return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; }); + return std::ranges::find_if(d->m_languages, [key](const Language& lang) { return lang.key == key; }); } -std::optional TranslationsModel::findLanguageAsOptional(const QString& key) +std::optional TranslationsModel::findLanguageAsOptional(const QString& key) const { auto found = findLanguage(key); - if (found != d->m_languages.end()) + if (found != d->m_languages.end()) { return *found; + } return {}; } -void TranslationsModel::setUseSystemLocale(bool useSystemLocale) +void TranslationsModel::setUseSystemLocale(const bool useSystemLocale) const { APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(selectedLanguage())); } -bool TranslationsModel::selectLanguage(QString key) +bool TranslationsModel::selectLanguage(QString key) const { QString& langCode = key; auto langPtr = findLanguageAsOptional(key); if (langCode.isEmpty()) { - d->no_language_set = true; + d->m_noLanguageSet = true; } if (!langPtr.has_value()) { - qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; - langCode = defaultLangCode; + qWarning() << "Selected invalid language" << key << ", defaulting to" << g_defaultLangCode; + langCode = g_defaultLangCode; } else { langCode = langPtr->key; } // uninstall existing translators if there are any - if (d->m_app_translator) { - QCoreApplication::removeTranslator(d->m_app_translator.get()); - d->m_app_translator.reset(); + if (d->m_appTranslator) { + QCoreApplication::removeTranslator(d->m_appTranslator.get()); + d->m_appTranslator.reset(); } - if (d->m_qt_translator) { - QCoreApplication::removeTranslator(d->m_qt_translator.get()); - d->m_qt_translator.reset(); + if (d->m_qtTranslator) { + QCoreApplication::removeTranslator(d->m_qtTranslator.get()); + d->m_qtTranslator.reset(); } /* @@ -471,7 +475,7 @@ bool TranslationsModel::selectLanguage(QString key) QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(langCode)); // if it's the default UI language, finish - if (langCode == defaultLangCode) { + if (langCode == g_defaultLangCode) { d->m_selectedLanguage = langCode; return true; } @@ -479,89 +483,88 @@ bool TranslationsModel::selectLanguage(QString key) // otherwise install new translations bool successful = false; // FIXME: this is likely never present. FIX IT. - d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { + d->m_qtTranslator = std::make_unique(); + if (d->m_qtTranslator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) { + if (!QCoreApplication::installTranslator(d->m_qtTranslator.get())) { qCritical() << "Loading Qt Language File failed."; - d->m_qt_translator.reset(); + d->m_qtTranslator.reset(); } else { successful = true; } } else { - d->m_qt_translator.reset(); + d->m_qtTranslator.reset(); } - if (langPtr->localFileType == FileType::PO) { + if (langPtr->localFileType == FileType::Po) { qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po")); - if (!poTranslator->isEmpty()) { - if (!QCoreApplication::installTranslator(poTranslator)) { - delete poTranslator; + d->m_appTranslator = std::make_unique(FS::PathCombine(d->m_dir.path(), langCode + ".po")); + if (!d->m_appTranslator->isEmpty()) { + if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { qCritical() << "Installing Application Language File failed."; + d->m_appTranslator.reset(); } else { - d->m_app_translator.reset(poTranslator); successful = true; } } else { qCritical() << "Loading Application Language File failed."; - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } - } else if (langPtr->localFileType == FileType::QM) { - d->m_app_translator.reset(new QTranslator()); - if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) { + } else if (langPtr->localFileType == FileType::Qm) { + d->m_appTranslator = std::make_unique(); + if (d->m_appTranslator->load("mmc_" + langCode, d->m_dir.path())) { qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_app_translator.get())) { + if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { qCritical() << "Installing Application Language File failed."; - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } else { successful = true; } } else { - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } } else { - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } d->m_selectedLanguage = langCode; return successful; } -QModelIndex TranslationsModel::selectedIndex() +QModelIndex TranslationsModel::selectedIndex() const { auto found = findLanguage(d->m_selectedLanguage); if (found != d->m_languages.end()) { return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex()); } - return QModelIndex(); + return {}; } -QString TranslationsModel::selectedLanguage() +QString TranslationsModel::selectedLanguage() const { return d->m_selectedLanguage; } void TranslationsModel::downloadIndex() { - if (d->m_index_job || d->m_dl_job) { + if (d->m_indexJob || d->m_downloadJob) { return; } qDebug() << "Downloading Translations Index..."; - d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network())); - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); + d->m_indexJob.reset(new NetJob("Translations Index", APPLICATION->network())); + const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); entry->setStale(true); auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + "index_v2.json"), entry); - d->m_index_task = task.get(); - d->m_index_job->addNetAction(task); - d->m_index_job->setAskRetry(false); - connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); - connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); - d->m_index_job->start(); + d->m_indexTask = task.get(); + d->m_indexJob->addNetAction(task); + d->m_indexJob->setAskRetry(false); + connect(d->m_indexJob.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); + connect(d->m_indexJob.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); + d->m_indexJob->start(); } -void TranslationsModel::updateLanguage(QString key) +void TranslationsModel::updateLanguage(const QString& key) { - if (key == defaultLangCode) { + if (key == g_defaultLangCode) { qWarning() << "Cannot update builtin language" << key; return; } @@ -575,9 +578,9 @@ void TranslationsModel::updateLanguage(QString key) } } -void TranslationsModel::downloadTranslation(QString key) +void TranslationsModel::downloadTranslation(const QString& key) { - if (d->m_dl_job) { + if (d->m_downloadJob) { d->m_nextDownload = key; return; } @@ -588,21 +591,21 @@ void TranslationsModel::downloadTranslation(QString key) } d->m_downloadingTranslation = key; - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); + const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); entry->setStale(true); - auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->file_name), entry); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->file_sha1)); - dl->setProgress(dl->getProgress(), lang->file_size); + auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->fileName), entry); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->fileSha1)); + dl->setProgress(dl->getProgress(), lang->fileSize); - d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network())); - d->m_dl_job->addNetAction(dl); - d->m_dl_job->setAskRetry(false); + d->m_downloadJob.reset(new NetJob("Translation for " + key, APPLICATION->network())); + d->m_downloadJob->addNetAction(dl); + d->m_downloadJob->setAskRetry(false); - connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); - connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); + connect(d->m_downloadJob.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); + connect(d->m_downloadJob.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); - d->m_dl_job->start(); + d->m_downloadJob->start(); } void TranslationsModel::downloadNext() @@ -613,10 +616,10 @@ void TranslationsModel::downloadNext() } } -void TranslationsModel::dlFailed(QString reason) +void TranslationsModel::dlFailed(const QString& reason) { qCritical() << "Translations Download Failed:" << reason; - d->m_dl_job.reset(); + d->m_downloadJob.reset(); downloadNext(); } @@ -627,12 +630,12 @@ void TranslationsModel::dlGood() if (d->m_downloadingTranslation == d->m_selectedLanguage) { selectLanguage(d->m_selectedLanguage); } - d->m_dl_job.reset(); + d->m_downloadJob.reset(); downloadNext(); } -void TranslationsModel::indexFailed(QString reason) +void TranslationsModel::indexFailed(const QString& reason) const { qCritical() << "Translations Index Download Failed:" << reason; - d->m_index_job.reset(); + d->m_indexJob.reset(); } diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h index 945e689fc..1dd0b601c 100644 --- a/launcher/translations/TranslationsModel.h +++ b/launcher/translations/TranslationsModel.h @@ -24,38 +24,38 @@ struct Language; class TranslationsModel : public QAbstractListModel { Q_OBJECT public: - explicit TranslationsModel(QString path, QObject* parent = 0); - virtual ~TranslationsModel(); - - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent) const override; - - bool selectLanguage(QString key); - void updateLanguage(QString key); - QModelIndex selectedIndex(); - QString selectedLanguage(); - - void downloadIndex(); - void setUseSystemLocale(bool useSystemLocale); - - private: - QList::Iterator findLanguage(const QString& key); - std::optional findLanguageAsOptional(const QString& key); - void reloadLocalFiles(); - void downloadTranslation(QString key); - void downloadNext(); + explicit TranslationsModel(const QString& path, QObject* parent = nullptr); + ~TranslationsModel() override; // hide copy constructor TranslationsModel(const TranslationsModel&) = delete; // hide assign op TranslationsModel& operator=(const TranslationsModel&) = delete; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; // NOLINT(*-default-arguments) + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; // NOLINT(*-default-arguments) + bool selectLanguage(QString key) const; + void updateLanguage(const QString& key); + QModelIndex selectedIndex() const; + QString selectedLanguage() const; + + void downloadIndex(); + void setUseSystemLocale(bool useSystemLocale) const; + + private: + int columnCount(const QModelIndex& parent) const override; + + QList::Iterator findLanguage(const QString& key) const; + std::optional findLanguageAsOptional(const QString& key) const; + void reloadLocalFiles(); + void downloadTranslation(const QString& key); + void downloadNext(); + private slots: void indexReceived(); - void indexFailed(QString reason); - void dlFailed(QString reason); + void indexFailed(const QString& reason) const; + void dlFailed(const QString& reason); void dlGood(); void translationDirChanged(const QString& path); From 74308fcaa5cfc97a291710316f0f15b4b22fee2d Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 24 Nov 2025 15:23:18 +0100 Subject: [PATCH 072/132] add category selector to icon picker dialog it uses some regex shenanigans for this, probably not ideal, idk if theres a good way to filter the icons without adding extra metadata or storing them in subfolders Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 50 +++++++++++++++++--- launcher/ui/dialogs/IconPickerDialog.h | 9 ++++ launcher/ui/dialogs/IconPickerDialog.ui | 59 +++++++++++++++++++++++- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index b56e95dba..dfeea20a1 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -35,9 +35,25 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui ui->setupUi(this); setWindowModality(Qt::WindowModal); - searchBar = new QLineEdit(this); - searchBar->setPlaceholderText(tr("Search...")); - ui->verticalLayout->insertWidget(0, searchBar); + static const QString context_text[] = { + tr("All"), + tr("Modern"), + tr("Legacy"), + tr("Modpacks"), + }; + static const Context context_id[] = { + Any, + Modern, + Legacy, + Modpacks, + }; + const int cnt = sizeof(context_text) / sizeof(context_text[0]); + for (int i = 0; i < cnt; ++i) { + ui->contextCombo->addItem(context_text[i], context_id[i]); + if (i == 0) { + ui->contextCombo->insertSeparator(i + 1); + } + } proxyModel = new QSortFilterProxyModel(this); proxyModel->setSourceModel(APPLICATION->icons()); @@ -45,11 +61,8 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui ui->iconView->setModel(proxyModel); auto contentsWidget = ui->iconView; - contentsWidget->setViewMode(QListView::IconMode); contentsWidget->setFlow(QListView::LeftToRight); contentsWidget->setIconSize(QSize(48, 48)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setResizeMode(QListView::Adjust); contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); contentsWidget->setSpacing(5); contentsWidget->setWordWrap(false); @@ -86,7 +99,11 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); - connect(searchBar, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + connect(ui->searchLine, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + connect(ui->contextCombo, &QComboBox::currentIndexChanged, this, [this](int index) { + Context category = static_cast(ui->contextCombo->itemData(index).toInt()); + filterIconsByCategory(category); + }); // Prevent incorrect indices from e.g. filesystem changes connect(APPLICATION->icons(), &IconList::iconUpdated, this, [this]() { proxyModel->invalidate(); }); } @@ -182,3 +199,22 @@ void IconPickerDialog::filterIcons(const QString& query) { proxyModel->setFilterFixedString(query); } + +void IconPickerDialog::filterIconsByCategory(Context category) +{ + switch (category) { + default: + case Any: + proxyModel->setFilterRegularExpression(""); + break; + case Modern: + proxyModel->setFilterRegularExpression("^(?:ftb_logo|(?!.*_legacy$)(?!^(?:curseforge_|modrinth_|ftb_|technic_|atl_))[A-Za-z0-9._-]+)$"); + break; + case Legacy: + proxyModel->setFilterRegularExpression("^(?:[A-Za-z0-9._-]+_legacy|ftb_glow)$"); + break; + case Modpacks: + proxyModel->setFilterRegularExpression("^(?!(?:ftb_glow|ftb_logo))(?:curseforge_|modrinth_|ftb_|technic_|atl_)[A-Za-z0-9._-]*$"); + break; + } +} diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index db1315338..58a94bb9b 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -41,6 +41,14 @@ class IconPickerDialog : public QDialog { QLineEdit* searchBar; QSortFilterProxyModel* proxyModel; + enum Context { + Any, + Modern, + Legacy, + Modpacks, + }; + Q_ENUM(Context) + private slots: void selectionChanged(QItemSelection, QItemSelection); void activated(QModelIndex); @@ -49,4 +57,5 @@ class IconPickerDialog : public QDialog { void removeSelectedIcon(); void openFolder(); void filterIcons(const QString& text); + void filterIconsByCategory(Context); }; diff --git a/launcher/ui/dialogs/IconPickerDialog.ui b/launcher/ui/dialogs/IconPickerDialog.ui index c548edfb7..948e7043f 100644 --- a/launcher/ui/dialogs/IconPickerDialog.ui +++ b/launcher/ui/dialogs/IconPickerDialog.ui @@ -15,7 +15,64 @@
- + + + + + + + Icon category + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Search Icons... + + + true + + + + + + + + + + + + 60 + 60 + + + + QListView::Static + + + QListView::Adjust + + + QListView::IconMode + + + true + + From 0c4c8703a363d3a0c8dd1f5095330eb04b50f626 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 24 Nov 2025 18:06:09 +0100 Subject: [PATCH 073/132] rename IconPickerCategory and make public Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 6 +++--- launcher/ui/dialogs/IconPickerDialog.h | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index dfeea20a1..49b396b5e 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -41,7 +41,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui tr("Legacy"), tr("Modpacks"), }; - static const Context context_id[] = { + static const IconPickerCategory context_id[] = { Any, Modern, Legacy, @@ -101,7 +101,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); connect(ui->searchLine, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); connect(ui->contextCombo, &QComboBox::currentIndexChanged, this, [this](int index) { - Context category = static_cast(ui->contextCombo->itemData(index).toInt()); + IconPickerCategory category = static_cast(ui->contextCombo->itemData(index).toInt()); filterIconsByCategory(category); }); // Prevent incorrect indices from e.g. filesystem changes @@ -200,7 +200,7 @@ void IconPickerDialog::filterIcons(const QString& query) proxyModel->setFilterFixedString(query); } -void IconPickerDialog::filterIconsByCategory(Context category) +void IconPickerDialog::filterIconsByCategory(IconPickerCategory category) { switch (category) { default: diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index 58a94bb9b..b2d707ae8 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -32,6 +32,14 @@ class IconPickerDialog : public QDialog { int execWithSelection(QString selection); QString selectedIconKey; + enum IconPickerCategory { + Any, + Modern, + Legacy, + Modpacks, + }; + Q_ENUM(IconPickerCategory) + protected: virtual bool eventFilter(QObject*, QEvent*); @@ -41,14 +49,6 @@ class IconPickerDialog : public QDialog { QLineEdit* searchBar; QSortFilterProxyModel* proxyModel; - enum Context { - Any, - Modern, - Legacy, - Modpacks, - }; - Q_ENUM(Context) - private slots: void selectionChanged(QItemSelection, QItemSelection); void activated(QModelIndex); @@ -57,5 +57,5 @@ class IconPickerDialog : public QDialog { void removeSelectedIcon(); void openFolder(); void filterIcons(const QString& text); - void filterIconsByCategory(Context); + void filterIconsByCategory(IconPickerCategory); }; From bbd8c1e745916bea65343dc1ac1f012902b0f0e1 Mon Sep 17 00:00:00 2001 From: Tayou Date: Tue, 25 Nov 2025 15:27:18 +0100 Subject: [PATCH 074/132] use custom QSortFilterProxyModel impl Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 83 +++++++++++++++++++----- launcher/ui/dialogs/IconPickerDialog.h | 1 + 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index 49b396b5e..051d8495f 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -30,6 +30,71 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" +class IconProxyModel : public QSortFilterProxyModel +{ +public: + explicit IconProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + { + } + + void setCategory(IconPickerDialog::IconPickerCategory category) + { + if (m_category == category) + return; + m_category = category; + invalidateFilter(); + } + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + if (!QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) + return false; + + if (m_category == IconPickerDialog::Any) + return true; + + auto model = static_cast(sourceModel()); + QModelIndex index = model->index(source_row, 0, source_parent); + QString key = model->data(index, Qt::UserRole).toString(); + const MMCIcon* icon = model->icon(key); + + if (!icon) + return false; + + bool isModpack = false; + bool isBuiltin = icon->isBuiltIn(); + bool isLegacy = isBuiltin && icon->name().endsWith("_legacy", Qt::CaseInsensitive); + + if (!isBuiltin) { + const QString& name = icon->name(); + if (name.startsWith("curseforge_", Qt::CaseInsensitive) || + name.startsWith("modrinth_", Qt::CaseInsensitive) || + name.startsWith("ftb_", Qt::CaseInsensitive) || + name.startsWith("technic_", Qt::CaseInsensitive) || + name.startsWith("atl_", Qt::CaseInsensitive)) { + isModpack = true; + } + } + + switch (m_category) { + case IconPickerDialog::Legacy: + return isBuiltin && isLegacy; + case IconPickerDialog::Modpacks: + return isModpack; + case IconPickerDialog::Modern: + return isBuiltin && !isLegacy; + case IconPickerDialog::Custom: + return !isBuiltin && !isModpack; + default: + return true; + } + } + +private: + IconPickerDialog::IconPickerCategory m_category = IconPickerDialog::Any; +}; + IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::IconPickerDialog) { ui->setupUi(this); @@ -40,12 +105,14 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui tr("Modern"), tr("Legacy"), tr("Modpacks"), + tr("Custom"), }; static const IconPickerCategory context_id[] = { Any, Modern, Legacy, Modpacks, + Custom, }; const int cnt = sizeof(context_text) / sizeof(context_text[0]); for (int i = 0; i < cnt; ++i) { @@ -202,19 +269,5 @@ void IconPickerDialog::filterIcons(const QString& query) void IconPickerDialog::filterIconsByCategory(IconPickerCategory category) { - switch (category) { - default: - case Any: - proxyModel->setFilterRegularExpression(""); - break; - case Modern: - proxyModel->setFilterRegularExpression("^(?:ftb_logo|(?!.*_legacy$)(?!^(?:curseforge_|modrinth_|ftb_|technic_|atl_))[A-Za-z0-9._-]+)$"); - break; - case Legacy: - proxyModel->setFilterRegularExpression("^(?:[A-Za-z0-9._-]+_legacy|ftb_glow)$"); - break; - case Modpacks: - proxyModel->setFilterRegularExpression("^(?!(?:ftb_glow|ftb_logo))(?:curseforge_|modrinth_|ftb_|technic_|atl_)[A-Za-z0-9._-]*$"); - break; - } + static_cast(proxyModel)->setCategory(category); } diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index b2d707ae8..063f9b905 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -37,6 +37,7 @@ class IconPickerDialog : public QDialog { Modern, Legacy, Modpacks, + Custom, }; Q_ENUM(IconPickerCategory) From a021c86871791279871e86c7a291ad76cf0feaf7 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 4 May 2026 20:23:27 +0500 Subject: [PATCH 075/132] XboxAuthorizationStep: fix wrong failure string Signed-off-by: Octol1ttle --- .../minecraft/auth/steps/XboxAuthorizationStep.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 9e101d42a..256031f5f 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -63,13 +63,11 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); if (Net::isApplicationError(m_request->error())) { - if (!processSTSError(*response)) { - emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_request->error())); - } else { - emit finished(AccountTaskState::STATE_FAILED_SOFT, - tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); + if (processSTSError(*response)) { + return; } + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } else { emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); From 0a3f7da7e74974d785df706dc4c0e4afb0138fac Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 4 May 2026 20:27:47 +0500 Subject: [PATCH 076/132] XboxAuthorizationStep: clang-tidy Signed-off-by: Octol1ttle --- .../auth/steps/XboxAuthorizationStep.cpp | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 256031f5f..84320c7c1 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "Application.h" #include "Logging.h" @@ -12,7 +12,7 @@ #include "net/Upload.h" XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) - : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) + : AuthStep(data), m_token(token), m_relyingParty(std::move(relyingParty)), m_authorizationKind(std::move(authorizationKind)) {} QString XboxAuthorizationStep::describe() @@ -22,7 +22,7 @@ QString XboxAuthorizationStep::describe() void XboxAuthorizationStep::perform() { - QString xbox_auth_template = R"XXX( + const QString xboxAuthTemplate = R"XXX( { "Properties": { "SandboxId": "RETAIL", @@ -34,15 +34,13 @@ void XboxAuthorizationStep::perform() "TokenType": "JWT" } )XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty); + const auto xboxAuthData = xboxAuthTemplate.arg(m_data->userToken.token, m_relyingParty); // http://xboxlive.com - QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); - auto headers = QList{ - { "Content-Type", "application/json" }, - { "Accept", "application/json" }, - { "x-xbl-contract-version", "1" } - }; - auto [request, response] = Net::Upload::makeByteArray(url, xbox_auth_data.toUtf8()); + const QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); + auto headers = QList{ { .headerName = "Content-Type", .headerValue = "application/json" }, + { .headerName = "Accept", .headerValue = "application/json" }, + { .headerName = "x-xbl-contract-version", .headerValue = "1" } }; + auto [request, response] = Net::Upload::makeByteArray(url, xboxAuthData.toUtf8()); m_request = request; m_request->addHeaderProxy(std::make_unique(headers)); m_request->enableAutoRetry(true); @@ -97,8 +95,8 @@ bool XboxAuthorizationStep::processSTSError(const QByteArray& response) { if (m_request->error() == QNetworkReply::AuthenticationRequiredError) { QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); - if (jsonError.error) { + const QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); + if (jsonError.error != QJsonParseError::NoError) { qWarning() << "Cannot parse error XSTS response as JSON:" << jsonError.errorString(); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString())); From e7dbdf34899057732d556ef742c6aa79fd11e71d Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 5 May 2026 18:41:06 +0500 Subject: [PATCH 077/132] INIFile: add file name to error logging Signed-off-by: Octol1ttle --- launcher/settings/INIFile.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 75e888938..a1d1c01d9 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -35,6 +35,8 @@ */ #include "settings/INIFile.h" + +#include #include #include @@ -62,11 +64,10 @@ bool INIFile::saveFile(QString fileName) _settings_obj.sync(); if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { - // Shouldn't be possible! - Q_ASSERT(status != QSettings::Status::FormatError); - if (status == QSettings::Status::AccessError) - qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; + qCritical() << "An access error occurred while saving INI file" << fileName << "(is the file read-only?)"; + if (ASSERT_NEVER(status == QSettings::Status::FormatError)) + qCritical() << "A format error occurred while saving INI file" << fileName << "(this shouldn't be possible!)"; return false; } @@ -178,9 +179,9 @@ bool INIFile::loadFile(QString fileName) if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { if (status == QSettings::Status::AccessError) - qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; + qCritical() << "An access error occurred while loading INI file" << fileName; if (status == QSettings::Status::FormatError) - qCritical() << "A format error occurred (e.g. loading a malformed INI file)."; + qCritical() << "A format error occurred while loading INI file" << fileName << "(is the file malformed or corrupted?)"; return false; } if (!_settings_obj.value("ConfigVersion").isValid()) { From 4463c21c98c6eb176c45340518c56a707d82c2f6 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 00:03:03 +0300 Subject: [PATCH 078/132] chore(clang-tidy): fix clang tidy warnings Signed-off-by: Trial97 --- .../ui/dialogs/ResourceDownloadDialog.cpp | 189 ++++++++-------- launcher/ui/dialogs/ResourceDownloadDialog.h | 12 +- launcher/ui/pages/instance/DataPackPage.cpp | 106 +++++---- launcher/ui/pages/instance/ModFolderPage.cpp | 143 ++++++------ .../ui/pages/instance/ResourcePackPage.cpp | 73 ++++--- launcher/ui/pages/instance/ShaderPackPage.cpp | 71 +++--- .../ui/pages/instance/TexturePackPage.cpp | 73 ++++--- .../ui/pages/modplatform/ResourcePage.cpp | 203 ++++++++++-------- launcher/ui/pages/modplatform/ResourcePage.h | 10 +- launcher/ui/widgets/PageContainer.cpp | 57 ++--- launcher/ui/widgets/PageContainer.h | 6 +- 11 files changed, 527 insertions(+), 416 deletions(-) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 002f85e0f..41274de08 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "Application.h" #include "ResourceDownloadTask.h" @@ -49,9 +50,9 @@ namespace ResourceDownload { -ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* base_model) +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel) : QDialog(parent) - , m_base_model(base_model) + , m_base_model(baseModel) , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel) , m_vertical_layout(this) { @@ -61,34 +62,35 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderMo setWindowIcon(QIcon::fromTheme("new")); - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS +// small margins look ugly on macOS on modal windows +#ifndef Q_OS_MACOS m_buttons.setContentsMargins(0, 0, 6, 6); - #endif +#endif // Bonk Qt over its stupid head and make sure it understands which button is the default one... // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button - auto OkButton = m_buttons.button(QDialogButtonBox::Ok); - OkButton->setEnabled(false); - OkButton->setDefault(true); - OkButton->setAutoDefault(true); - OkButton->setText(tr("Review and confirm")); - OkButton->setShortcut(tr("Ctrl+Return")); + auto* okButton = m_buttons.button(QDialogButtonBox::Ok); + okButton->setEnabled(false); + okButton->setDefault(true); + okButton->setAutoDefault(true); + okButton->setText(tr("Review and confirm")); + okButton->setShortcut(tr("Ctrl+Return")); - auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); - CancelButton->setDefault(false); - CancelButton->setAutoDefault(false); + auto* cancelButton = m_buttons.button(QDialogButtonBox::Cancel); + cancelButton->setDefault(false); + cancelButton->setAutoDefault(false); - auto HelpButton = m_buttons.button(QDialogButtonBox::Help); - HelpButton->setDefault(false); - HelpButton->setAutoDefault(false); + auto* helpButton = m_buttons.button(QDialogButtonBox::Help); + helpButton->setDefault(false); + helpButton->setAutoDefault(false); setWindowModality(Qt::WindowModal); } void ResourceDownloadDialog::accept() { - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { APPLICATION->settings()->set(geometrySaveKey(), QString::fromUtf8(saveGeometry().toBase64())); + } QDialog::accept(); } @@ -108,8 +110,9 @@ void ResourceDownloadDialog::reject() } } - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { APPLICATION->settings()->set(geometrySaveKey(), QString::fromUtf8(saveGeometry().toBase64())); + } QDialog::reject(); } @@ -118,10 +121,10 @@ void ResourceDownloadDialog::reject() // won't work with subclasses if we put it in this ctor. void ResourceDownloadDialog::initializeContainer() { - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS +// small margins look ugly on macOS on modal windows +#ifndef Q_OS_MACOS layout()->setContentsMargins(0, 0, 0, 0); - #endif +#endif m_container = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); @@ -135,28 +138,28 @@ void ResourceDownloadDialog::initializeContainer() void ResourceDownloadDialog::connectButtons() { - auto OkButton = m_buttons.button(QDialogButtonBox::Ok); - OkButton->setToolTip( + auto* okButton = m_buttons.button(QDialogButtonBox::Ok); + okButton->setToolTip( tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString())); - connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); + connect(okButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); - auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); - connect(CancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); + auto* cancelButton = m_buttons.button(QDialogButtonBox::Cancel); + connect(cancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); - auto HelpButton = m_buttons.button(QDialogButtonBox::Help); - connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); + auto* helpButton = m_buttons.button(QDialogButtonBox::Help); + connect(helpButton, &QPushButton::clicked, m_container, &PageContainer::help); } void ResourceDownloadDialog::confirm() { - auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); - confirm_dialog->retranslateUi(resourcesString()); + auto* confirmDialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); + confirmDialog->retranslateUi(resourcesString()); QHash dependencyExtraInfo; QStringList depNames; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); auto weak = task.toWeakRef(); connect(task.get(), &Task::succeeded, this, [this, weak]() { @@ -170,57 +173,62 @@ void ResourceDownloadDialog::confirm() }); // Check for updates - ProgressDialog progress_dialog(this); - progress_dialog.setSkipButton(true, tr("Abort")); - progress_dialog.setWindowTitle(tr("Checking for dependencies...")); - auto ret = progress_dialog.execWithTask(task.get()); + ProgressDialog progressDialog(this); + progressDialog.setSkipButton(true, tr("Abort")); + progressDialog.setWindowTitle(tr("Checking for dependencies...")); + auto ret = progressDialog.execWithTask(task.get()); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; - } else { - for (auto dep : task->getDependecies()) { - addResource(dep->pack, dep->version); - depNames << dep->pack->name; - } - dependencyExtraInfo = task->getExtraInfo(); } + for (const auto& dep : task->getDependecies()) { + addResource(dep->pack, dep->version); + depNames << dep->pack->name; + } + dependencyExtraInfo = task->getExtraInfo(); } auto selected = getTasks(); - std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { + std::ranges::sort(selected, [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; }); for (auto& task : selected) { auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString()); - confirm_dialog->appendResource({ task->getName(), task->getFilename(), ModPlatform::ProviderCapabilities::name(task->getProvider()), - extraInfo.required_by, task->getVersion().version_type.toString(), !extraInfo.maybe_installed }); + confirmDialog->appendResource({ .name = task->getName(), + .filename = task->getFilename(), + .provider = ModPlatform::ProviderCapabilities::name(task->getProvider()), + .required_by = extraInfo.required_by, + .version_type = task->getVersion().version_type.toString(), + .enabled = !extraInfo.maybe_installed }); } - if (confirm_dialog->exec()) { - auto deselected = confirm_dialog->deselectedResources(); - for (auto page : m_container->getPages()) { - auto res = static_cast(page); - for (auto name : deselected) + if (confirmDialog->exec() != 0) { + auto deselected = confirmDialog->deselectedResources(); + for (auto* page : m_container->getPages()) { + auto* res = static_cast(page); + for (const auto& name : deselected) { res->removeResourceFromPage(name); + } } this->accept(); } else { - for (auto name : depNames) + for (const auto& name : depNames) { removeResource(name); + } } } bool ResourceDownloadDialog::selectPage(QString pageId) { - return m_container->selectPage(pageId); + return m_container->selectPage(std::move(pageId)); } ResourcePage* ResourceDownloadDialog::selectedPage() { - ResourcePage* result = dynamic_cast(m_container->selectedPage()); + auto* result = dynamic_cast(m_container->selectedPage()); Q_ASSERT(result != nullptr); return result; } @@ -232,10 +240,10 @@ void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, Mod setButtonStatus(); } -void ResourceDownloadDialog::removeResource(const QString& pack_name) +void ResourceDownloadDialog::removeResource(const QString& packName) { - for (auto page : m_container->getPages()) { - static_cast(page)->removeResourceFromPage(pack_name); + for (auto* page : m_container->getPages()) { + static_cast(page)->removeResourceFromPage(packName); } setButtonStatus(); } @@ -243,18 +251,18 @@ void ResourceDownloadDialog::removeResource(const QString& pack_name) void ResourceDownloadDialog::setButtonStatus() { auto selected = false; - for (auto page : m_container->getPages()) { - auto res = static_cast(page); + for (auto* page : m_container->getPages()) { + auto* res = static_cast(page); selected = selected || res->hasSelectedPacks(); } m_buttons.button(QDialogButtonBox::Ok)->setEnabled(selected); } -const QList ResourceDownloadDialog::getTasks() +QList ResourceDownloadDialog::getTasks() { QList selected; - for (auto page : m_container->getPages()) { - auto res = static_cast(page); + for (auto* page : m_container->getPages()) { + auto* res = static_cast(page); selected.append(res->selectedPacks()); } return selected; @@ -262,16 +270,16 @@ const QList ResourceDownloadDialog::get void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) { - auto* prev_page = dynamic_cast(previous); - if (!prev_page) { + auto* prevPage = dynamic_cast(previous); + if (!prevPage) { qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; return; } // Same effect as having a global search bar - ResourcePage* result = dynamic_cast(selected); + auto* result = dynamic_cast(selected); Q_ASSERT(result != nullptr); - result->setSearchTerm(prev_page->getSearchTerm()); + result->setSearchTerm(prevPage->getSearchTerm()); } ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance) @@ -282,8 +290,9 @@ ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, Base initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList ModDownloadDialog::getPages() @@ -292,10 +301,12 @@ QList ModDownloadDialog::getPages() auto loaders = static_cast(m_instance)->getPackProfile()->getSupportedModLoaders().value(); - if (ModrinthAPI::validateModLoaders(loaders)) + if (ModrinthAPI::validateModLoaders(loaders)) { pages.append(ModrinthModPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) + } + if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) { pages.append(FlameModPage::create(this, *m_instance)); + } return pages; } @@ -303,9 +314,9 @@ QList ModDownloadDialog::getPages() GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() { if (!APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - if (auto model = dynamic_cast(getBaseModel()); model) { + if (auto* model = dynamic_cast(getBaseModel()); model) { QList> selectedVers; - for (auto& selected : getTasks()) { + for (const auto& selected : getTasks()) { selectedVers.append(std::make_shared(selected->getPack(), selected->getVersion())); } @@ -315,16 +326,17 @@ GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() return nullptr; } -ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resource_packs, BaseInstance* instance) - : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) +ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance) + : ResourceDownloadDialog(parent, resourcePacks), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList ResourcePackDownloadDialog::getPages() @@ -332,22 +344,24 @@ QList ResourcePackDownloadDialog::getPages() QList pages; pages.append(ModrinthResourcePackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) + if (APPLICATION->capabilities() & Application::SupportsFlame) { pages.append(FlameResourcePackPage::create(this, *m_instance)); + } return pages; } -TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resource_packs, BaseInstance* instance) - : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) +TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance) + : ResourceDownloadDialog(parent, resourcePacks), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList TexturePackDownloadDialog::getPages() @@ -355,8 +369,9 @@ QList TexturePackDownloadDialog::getPages() QList pages; pages.append(ModrinthTexturePackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) + if (APPLICATION->capabilities() & Application::SupportsFlame) { pages.append(FlameTexturePackPage::create(this, *m_instance)); + } return pages; } @@ -369,16 +384,18 @@ ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, ShaderPackFo initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList ShaderPackDownloadDialog::getPages() { QList pages; pages.append(ModrinthShaderPackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) + if (APPLICATION->capabilities() & Application::SupportsFlame) { pages.append(FlameShaderPackPage::create(this, *m_instance)); + } return pages; } @@ -395,28 +412,30 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptrname)); m_container->hidePageList(); m_buttons.hide(); - auto page = selectedPage(); + auto* page = selectedPage(); page->openProject(meta->project_id); } -DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* data_packs, BaseInstance* instance) - : ResourceDownloadDialog(parent, data_packs), m_instance(instance) +DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance) + : ResourceDownloadDialog(parent, dataPacks), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); + } } QList DataPackDownloadDialog::getPages() { QList pages; pages.append(ModrinthDataPackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) + if (APPLICATION->capabilities() & Application::SupportsFlame) { pages.append(FlameDataPackPage::create(this, *m_instance)); + } return pages; } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index a85a85a09..dfcebd31e 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -51,7 +51,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { public: using DownloadTaskPtr = shared_qobject_ptr; - ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* base_model); + ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel); void initializeContainer(); void connectButtons(); @@ -67,7 +67,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResource(const QString&); - const QList getTasks(); + QList getTasks(); ResourceFolderModel* getBaseModel() const { return m_base_model; } void setResourceMetadata(const std::shared_ptr& meta); @@ -118,7 +118,7 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resource_packs, BaseInstance* instance); + explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance); ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) @@ -135,7 +135,7 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resource_packs, BaseInstance* instance); + explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -152,7 +152,7 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shader_packs, BaseInstance* instance); + explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance); ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) @@ -169,7 +169,7 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* data_packs, BaseInstance* instance); + explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance); ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index fb07a768b..8c4bd313f 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -39,9 +39,9 @@ DataPackPage::DataPackPage(BaseInstance* instance, DataPackFolderModel* model, Q connect(ui->actionUpdateItem, &QAction::triggered, this, &DataPackPage::updateDataPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &DataPackPage::updateDataPacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -64,8 +64,9 @@ void DataPackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] cons void DataPackPage::downloadDataPacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::DataPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -76,9 +77,9 @@ void DataPackPage::downloadDataPacks() void DataPackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -88,8 +89,9 @@ void DataPackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -108,14 +110,16 @@ void DataPackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void DataPackPage::updateDataPacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); @@ -130,27 +134,29 @@ void DataPackPage::updateDataPacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, { ModPlatform::ModLoaderType::DataPack }); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false, { ModPlatform::ModLoaderType::DataPack }); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The data pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All data packs are up-to-date! :)"); } else { message = tr("All selected data packs are up-to-date! :)"); @@ -160,9 +166,9 @@ void DataPackPage::updateDataPacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -178,7 +184,7 @@ void DataPackPage::updateDataPacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -194,8 +200,9 @@ void DataPackPage::deleteDataPackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedDataPacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 data packs.\n" @@ -204,8 +211,9 @@ void DataPackPage::deleteDataPackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -213,8 +221,9 @@ void DataPackPage::deleteDataPackMetadata() void DataPackPage::changeDataPackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); @@ -223,19 +232,21 @@ void DataPackPage::changeDataPackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance); mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { - auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (mdownload.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -245,8 +256,9 @@ void DataPackPage::changeDataPackVersion() }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -265,14 +277,15 @@ void DataPackPage::changeDataPackVersion() GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent) : QWidget(parent), m_instance(instance) { - auto layout = new QVBoxLayout(this); + auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); connect(instance->settings()->getSetting("GlobalDataPacksEnabled").get(), &Setting::SettingChanged, this, [this] { updateContent(); - if (m_container != nullptr) + if (m_container != nullptr) { m_container->refreshContainer(); + } }); connect(instance->settings()->getSetting("GlobalDataPacksPath").get(), &Setting::SettingChanged, this, @@ -281,24 +294,27 @@ GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* par QString GlobalDataPackPage::displayName() const { - if (m_underlyingPage == nullptr) + if (m_underlyingPage == nullptr) { return {}; + } return m_underlyingPage->displayName(); } QIcon GlobalDataPackPage::icon() const { - if (m_underlyingPage == nullptr) + if (m_underlyingPage == nullptr) { return {}; + } return m_underlyingPage->icon(); } QString GlobalDataPackPage::helpPage() const { - if (m_underlyingPage == nullptr) + if (m_underlyingPage == nullptr) { return {}; + } return m_underlyingPage->helpPage(); } @@ -315,21 +331,24 @@ bool GlobalDataPackPage::apply() void GlobalDataPackPage::openedImpl() { - if (m_underlyingPage != nullptr) + if (m_underlyingPage != nullptr) { m_underlyingPage->openedImpl(); + } } void GlobalDataPackPage::closedImpl() { - if (m_underlyingPage != nullptr) + if (m_underlyingPage != nullptr) { m_underlyingPage->closedImpl(); + } } void GlobalDataPackPage::updateContent() { if (m_underlyingPage != nullptr) { - if (m_container->selectedPage() == this) + if (m_container->selectedPage() == this) { m_underlyingPage->closedImpl(); + } m_underlyingPage->apply(); @@ -344,8 +363,9 @@ void GlobalDataPackPage::updateContent() m_underlyingPage->setParentContainer(m_container); m_underlyingPage->updateExtraInfo = [this](QString id, QString value) { updateExtraInfo(std::move(id), std::move(value)); }; - if (m_container->selectedPage() == this) + if (m_container->selectedPage() == this) { m_underlyingPage->openedImpl(); + } layout()->addWidget(m_underlyingPage); } diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 7ba72a9b0..be64cceac 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -81,9 +81,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(tr("Check for Updates")); + auto* update = updateMenu->addAction(tr("Check for Updates")); connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); updateMenu->addAction(ui->actionVerifyItemDependencies); @@ -134,8 +134,9 @@ void ModFolderPage::removeItems(const QItemSelection& selection) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto indexes = selection.indexes(); @@ -161,10 +162,11 @@ void ModFolderPage::removeItems(const QItemSelection& selection) void ModFolderPage::downloadMods() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } - auto profile = static_cast(m_instance)->getPackProfile(); + auto* profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -180,9 +182,9 @@ void ModFolderPage::downloadMods() void ModFolderPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -192,8 +194,9 @@ void ModFolderPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -212,16 +215,18 @@ void ModFolderPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void ModFolderPage::updateMods(bool includeDeps) { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } - auto profile = static_cast(m_instance)->getPackProfile(); + auto* profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -240,27 +245,29 @@ void ModFolderPage::updateMods(bool includeDeps) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, profile->getModLoadersList()); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, includeDeps, profile->getModLoadersList()); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The mod updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All mods are up-to-date! :)"); } else { message = tr("All selected mods are up-to-date! :)"); @@ -270,9 +277,9 @@ void ModFolderPage::updateMods(bool includeDeps) return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -288,7 +295,7 @@ void ModFolderPage::updateMods(bool includeDeps) tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -304,8 +311,9 @@ void ModFolderPage::deleteModMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedMods(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 mods.\n" @@ -314,8 +322,9 @@ void ModFolderPage::deleteModMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -323,10 +332,11 @@ void ModFolderPage::deleteModMetadata() void ModFolderPage::changeModVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } - auto profile = static_cast(m_instance)->getPackProfile(); + auto* profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -337,15 +347,16 @@ void ModFolderPage::changeModVersion() return; } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); - if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) + auto modsList = m_model->selectedMods(selection); + if (modsList.length() != 1 || modsList[0]->metadata() == nullptr) { return; + } m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); - m_downloadDialog->setResourceMetadata((*mods_list.begin())->metadata()); + m_downloadDialog->setResourceMetadata((*modsList.begin())->metadata()); m_downloadDialog->open(); } @@ -353,20 +364,21 @@ void ModFolderPage::exportModMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectedMods = m_model->selectedMods(selection); - if (selectedMods.length() == 0) + if (selectedMods.length() == 0) { selectedMods = m_model->allMods(); + } - std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); + std::ranges::sort(selectedMods, [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); ExportToModListDialog dlg(m_instance->name(), selectedMods, this); dlg.exec(); } CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, QWidget* parent) : ModFolderPage(inst, mods, parent) { - auto mcInst = dynamic_cast(m_instance); + auto* mcInst = dynamic_cast(m_instance); if (mcInst) { - auto version = mcInst->getPackProfile(); - if (version && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) { + auto* version = mcInst->getPackProfile(); + if ((version != nullptr) && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) { auto minecraftCmp = version->getComponent("net.minecraft"); if (!minecraftCmp->m_loaded) { version->reload(Net::Mode::Offline); @@ -389,13 +401,15 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, Q bool CoreModFolderPage::shouldDisplay() const { if (ModFolderPage::shouldDisplay()) { - auto inst = dynamic_cast(m_instance); - if (!inst) + auto* inst = dynamic_cast(m_instance); + if (!inst) { return true; + } - auto version = inst->getPackProfile(); - if (!version || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) + auto* version = inst->getPackProfile(); + if ((version == nullptr) || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) { return false; + } auto minecraftCmp = version->getComponent("net.minecraft"); return minecraftCmp->m_loaded && minecraftCmp->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate; } @@ -412,31 +426,22 @@ bool NilModFolderPage::shouldDisplay() const // Helper function so this doesn't need to be duplicated 3 times inline bool ModFolderPage::handleNoModLoader() { - int resp = - QMessageBox::question(this, this->tr("Missing Mod Loader"), - this->tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - switch (resp) { - case QMessageBox::Yes: { - // Should be safe - auto profile = static_cast(this->m_instance)->getPackProfile(); - InstallLoaderDialog dialog(profile, QString(), this); - bool ret = dialog.exec(); - this->m_container->refreshContainer(); + int resp = QMessageBox::question( + this, ModFolderPage::tr("Missing Mod Loader"), + ModFolderPage::tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (resp == QMessageBox::Yes) { + // Should be safe + auto* profile = static_cast(this->m_instance)->getPackProfile(); + InstallLoaderDialog dialog(profile, QString(), this); + bool ret = dialog.exec() != 0; + this->m_container->refreshContainer(); - // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed - // and false if the user went through and installed a loader - return !ret; - } - case QMessageBox::No: { - // Nothing happens the dialog is already closing - // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader - return true; - } - default: { - // Unreachable - // returning true as a safety measure - return true; - } + // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed + // and false if the user went through and installed a loader + return !ret; } + // Nothing happens the dialog is already closing + // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader + return true; } diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index eb085e29b..b9e3a20b8 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -56,9 +56,9 @@ ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, ResourcePackFold connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -75,14 +75,15 @@ void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& rp = static_cast(m_model->at(row)); + auto& rp = m_model->at(row); ui->frame->updateWithResourcePack(rp); } void ResourcePackPage::downloadResourcePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -93,9 +94,9 @@ void ResourcePackPage::downloadResourcePacks() void ResourcePackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -105,8 +106,9 @@ void ResourcePackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -125,14 +127,16 @@ void ResourcePackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void ResourcePackPage::updateResourcePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); @@ -147,27 +151,29 @@ void ResourcePackPage::updateResourcePacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All resource packs are up-to-date! :)"); } else { message = tr("All selected resource packs are up-to-date! :)"); @@ -177,9 +183,9 @@ void ResourcePackPage::updateResourcePacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -195,7 +201,7 @@ void ResourcePackPage::updateResourcePacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -211,8 +217,9 @@ void ResourcePackPage::deleteResourcePackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedResourcePacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 resource packs.\n" @@ -221,8 +228,9 @@ void ResourcePackPage::deleteResourcePackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -230,8 +238,9 @@ void ResourcePackPage::deleteResourcePackMetadata() void ResourcePackPage::changeResourcePackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); @@ -240,13 +249,15 @@ void ResourcePackPage::changeResourcePackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 3120d9013..c3b463527 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -61,9 +61,9 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderMode connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -78,8 +78,9 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderMode void ShaderPackPage::downloadShaderPack() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -90,9 +91,9 @@ void ShaderPackPage::downloadShaderPack() void ShaderPackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -102,8 +103,9 @@ void ShaderPackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -122,14 +124,16 @@ void ShaderPackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void ShaderPackPage::updateShaderPacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); @@ -144,27 +148,29 @@ void ShaderPackPage::updateShaderPacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All shader packs are up-to-date! :)"); } else { message = tr("All selected shader packs are up-to-date! :)"); @@ -174,9 +180,9 @@ void ShaderPackPage::updateShaderPacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -192,7 +198,7 @@ void ShaderPackPage::updateShaderPacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -208,8 +214,9 @@ void ShaderPackPage::deleteShaderPackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedShaderPacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 shader packs.\n" @@ -218,8 +225,9 @@ void ShaderPackPage::deleteShaderPackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -227,8 +235,9 @@ void ShaderPackPage::deleteShaderPackMetadata() void ShaderPackPage::changeShaderPackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); @@ -237,13 +246,15 @@ void ShaderPackPage::changeShaderPackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index ec0486fe4..a723db4f6 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -60,9 +60,9 @@ TexturePackPage::TexturePackPage(MinecraftInstance* instance, TexturePackFolderM connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -81,14 +81,15 @@ void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] c { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& rp = static_cast(m_model->at(row)); + auto& rp = m_model->at(row); ui->frame->updateWithTexturePack(rp); } void TexturePackPage::downloadTexturePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -98,9 +99,9 @@ void TexturePackPage::downloadTexturePacks() void TexturePackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -110,8 +111,9 @@ void TexturePackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -130,14 +132,16 @@ void TexturePackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void TexturePackPage::updateTexturePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); @@ -152,27 +156,29 @@ void TexturePackPage::updateTexturePacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All texture packs are up-to-date! :)"); } else { message = tr("All selected texture packs are up-to-date! :)"); @@ -182,9 +188,9 @@ void TexturePackPage::updateTexturePacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -200,7 +206,7 @@ void TexturePackPage::updateTexturePacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -216,8 +222,9 @@ void TexturePackPage::deleteTexturePackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedTexturePacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 texture packs.\n" @@ -226,8 +233,9 @@ void TexturePackPage::deleteTexturePackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -235,8 +243,9 @@ void TexturePackPage::deleteTexturePackMetadata() void TexturePackPage::changeTexturePackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); @@ -245,13 +254,15 @@ void TexturePackPage::changeTexturePackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 98aa650e0..fecb5b1b6 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -39,12 +39,14 @@ #include "ResourcePage.h" #include "modplatform/ModIndex.h" -#include "ui/dialogs/CustomMessageBox.h" #include "ui_ResourcePage.h" #include #include #include +#include +#include +#include #include "Markdown.h" @@ -55,8 +57,8 @@ namespace ResourceDownload { -ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) - : QWidget(parent), m_baseInstance(base_instance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) +ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& baseInstance) + : QWidget(parent), m_baseInstance(baseInstance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) { m_ui->setupUi(this); @@ -79,7 +81,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in m_ui->verticalLayout->insertWidget(1, &m_fetchProgress); - auto delegate = new ProjectItemDelegate(this); + auto* delegate = new ProjectItemDelegate(this); m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); m_ui->packView->viewport()->installEventFilter(this); @@ -93,8 +95,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in ResourcePage::~ResourcePage() { delete m_ui; - if (m_model) - delete m_model; + delete m_model; } void ResourcePage::retranslate() @@ -127,12 +128,13 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool triggerSearch(); keyEvent->accept(); return true; - } else { - if (m_searchTimer.isActive()) - m_searchTimer.stop(); - - m_searchTimer.start(350); } + if (m_searchTimer.isActive()) { + m_searchTimer.stop(); + } + + m_searchTimer.start(350); + } else if (watched == m_ui->packView) { // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { @@ -158,7 +160,7 @@ QString ResourcePage::getSearchTerm() const return m_ui->searchEdit->text(); } -void ResourcePage::setSearchTerm(QString term) +void ResourcePage::setSearchTerm(const QString& term) { m_ui->searchEdit->setText(term); } @@ -168,10 +170,11 @@ void ResourcePage::addSortings() Q_ASSERT(m_model); auto sorts = m_model->getSortingMethods(); - std::sort(sorts.begin(), sorts.end(), [](auto const& l, auto const& r) { return l.index < r.index; }); + std::ranges::sort(sorts, [](const auto& l, const auto& r) { return l.index < r.index; }); - for (auto&& sorting : sorts) + for (auto&& sorting : sorts) { m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index)); + } } bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack::Ptr pack) @@ -188,24 +191,26 @@ ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const void ResourcePage::updateUi(const QModelIndex& index) { - if (index != m_ui->packView->currentIndex()) + if (index != m_ui->packView->currentIndex()) { return; + } - auto current_pack = getCurrentPack(); - if (!current_pack) { + auto currentPack = getCurrentPack(); + if (!currentPack) { m_ui->packDescription->setHtml({}); m_ui->packDescription->flush(); return; } QString text = ""; - QString name = current_pack->name; + QString name = currentPack->name; - if (current_pack->websiteUrl.isEmpty()) + if (currentPack->websiteUrl.isEmpty()) { text = name; - else - text = "websiteUrl + "\">" + name + ""; + } else { + text = "websiteUrl + "\">" + name + ""; + } - if (!current_pack->authors.empty()) { + if (!currentPack->authors.empty()) { auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { if (author.url.isEmpty()) { return author.name; @@ -213,49 +218,53 @@ void ResourcePage::updateUi(const QModelIndex& index) return QString("%2").arg(author.url, author.name); }; QStringList authorStrs; - for (auto& author : current_pack->authors) { + for (auto& author : currentPack->authors) { authorStrs.push_back(authorToStr(author)); } text += "
" + tr(" by ") + authorStrs.join(", "); } - if (current_pack->extraDataLoaded) { - if (current_pack->extraData.status == "archived") { + if (currentPack->extraDataLoaded) { + if (currentPack->extraData.status == "archived") { text += "

" + tr("This project has been archived. It will not receive any further updates unless the author decides " "to unarchive the project."); } - if (!current_pack->extraData.donate.isEmpty()) { + if (!currentPack->extraData.donate.isEmpty()) { text += "

" + tr("Donate information: "); auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { return QString("%2").arg(donate.url, donate.platform); }; QStringList donates; - for (auto& donate : current_pack->extraData.donate) { + for (auto& donate : currentPack->extraData.donate) { donates.append(donateToStr(donate)); } text += donates.join(", "); } - if (!current_pack->extraData.issuesUrl.isEmpty() || !current_pack->extraData.sourceUrl.isEmpty() || - !current_pack->extraData.wikiUrl.isEmpty() || !current_pack->extraData.discordUrl.isEmpty()) { + if (!currentPack->extraData.issuesUrl.isEmpty() || !currentPack->extraData.sourceUrl.isEmpty() || + !currentPack->extraData.wikiUrl.isEmpty() || !currentPack->extraData.discordUrl.isEmpty()) { text += "

" + tr("External links:") + "
"; } - if (!current_pack->extraData.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current_pack->extraData.issuesUrl) + "
"; - if (!current_pack->extraData.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current_pack->extraData.wikiUrl) + "
"; - if (!current_pack->extraData.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current_pack->extraData.sourceUrl) + "
"; - if (!current_pack->extraData.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current_pack->extraData.discordUrl) + "
"; + if (!currentPack->extraData.issuesUrl.isEmpty()) { + text += "- " + tr("Issues: %1").arg(currentPack->extraData.issuesUrl) + "
"; + } + if (!currentPack->extraData.wikiUrl.isEmpty()) { + text += "- " + tr("Wiki: %1").arg(currentPack->extraData.wikiUrl) + "
"; + } + if (!currentPack->extraData.sourceUrl.isEmpty()) { + text += "- " + tr("Source code: %1").arg(currentPack->extraData.sourceUrl) + "
"; + } + if (!currentPack->extraData.discordUrl.isEmpty()) { + text += "- " + tr("Discord: %1").arg(currentPack->extraData.discordUrl) + "
"; + } } text += "
"; m_ui->packDescription->setHtml(StringUtils::htmlListPatch( - text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)))); + text + (currentPack->extraData.body.isEmpty() ? currentPack->description : markdownToHTML(currentPack->extraData.body)))); m_ui->packDescription->flush(); } @@ -267,14 +276,15 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (auto current_pack = getCurrentPack(); current_pack) { - if (current_pack->versionsLoaded && current_pack->versions.empty()) { + if (auto currentPack = getCurrentPack(); currentPack) { + if (currentPack->versionsLoaded && currentPack->versions.empty()) { m_ui->resourceSelectionButton->setEnabled(false); qWarning() << tr("No version available for the selected pack"); - } else if (!current_pack->isVersionSelected(m_selectedVersionIndex)) + } else if (!currentPack->isVersionSelected(m_selectedVersionIndex)) { m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); - else + } else { m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + } } else { qWarning() << "Tried to update the selected button but there is not a pack selected"; } @@ -283,19 +293,20 @@ void ResourcePage::updateSelectionButton() void ResourcePage::versionListUpdated(const QModelIndex& index) { if (index == m_ui->packView->currentIndex()) { - auto current_pack = getCurrentPack(); + auto currentPack = getCurrentPack(); m_ui->versionSelectionBox->blockSignals(true); m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->blockSignals(false); - if (current_pack) { - auto installedVersion = m_model->getInstalledPackVersion(current_pack); + if (currentPack) { + auto installedVersion = m_model->getInstalledPackVersion(currentPack); - for (int i = 0; i < current_pack->versions.size(); i++) { - auto& version = current_pack->versions[i]; - if (!m_model->checkVersionFilters(version)) + for (int i = 0; i < currentPack->versions.size(); i++) { + auto& version = currentPack->versions[i]; + if (!m_model->checkVersionFilters(version)) { continue; + } auto versionText = version.version; if (version.version_type.isValid()) { @@ -316,8 +327,9 @@ void ResourcePage::versionListUpdated(const QModelIndex& index) if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); onResourceToggle(index); - } else + } else { updateSelectionButton(); + } } else if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); onResourceToggle(index); @@ -330,27 +342,30 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI return; } - auto current_pack = getCurrentPack(); + auto currentPack = getCurrentPack(); - bool request_load = false; - if (!current_pack || !current_pack->versionsLoaded) { + bool requestLoad = false; + if (!currentPack || !currentPack->versionsLoaded) { m_ui->resourceSelectionButton->setText(tr("Loading versions...")); m_ui->resourceSelectionButton->setEnabled(false); - request_load = true; + requestLoad = true; } else { versionListUpdated(curr); } - if (current_pack && !current_pack->extraDataLoaded) - request_load = true; + if (currentPack && !currentPack->extraDataLoaded) { + requestLoad = true; + } // we are already requesting this - if (m_enableQueue.contains(curr.row())) - request_load = false; + if (m_enableQueue.contains(curr.row())) { + requestLoad = false; + } - if (request_load) + if (requestLoad) { m_model->loadEntry(curr); + } updateUi(curr); } @@ -363,18 +378,18 @@ void ResourcePage::onVersionSelectionChanged(int index) void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version) { - m_parentDialog->addResource(pack, version); + m_parentDialog->addResource(std::move(pack), version); } -void ResourcePage::removeResourceFromDialog(const QString& pack_name) +void ResourcePage::removeResourceFromDialog(const QString& packName) { - m_parentDialog->removeResource(pack_name); + m_parentDialog->removeResource(packName); } -void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, ResourceFolderModel* base_model) +void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, ResourceFolderModel* baseModel) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, ver, base_model, is_indexed); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(std::move(pack), ver, baseModel, isIndexed); } void ResourcePage::modelReset() @@ -389,22 +404,25 @@ void ResourcePage::removeResourceFromPage(const QString& name) void ResourcePage::onResourceSelected() { - if (m_selectedVersionIndex < 0) + if (m_selectedVersionIndex < 0) { return; + } - auto current_pack = getCurrentPack(); - if (!current_pack || !current_pack->versionsLoaded || current_pack->versions.size() < m_selectedVersionIndex) + auto currentPack = getCurrentPack(); + if (!currentPack || !currentPack->versionsLoaded || currentPack->versions.size() < m_selectedVersionIndex) { return; + } - auto& version = current_pack->versions[m_selectedVersionIndex]; + auto& version = currentPack->versions[m_selectedVersionIndex]; Q_ASSERT(!version.downloadUrl.isNull()); - if (version.is_currently_selected) - removeResourceFromDialog(current_pack->name); - else - addResourceToDialog(current_pack, version); + if (version.is_currently_selected) { + removeResourceFromDialog(currentPack->name); + } else { + addResourceToDialog(currentPack, version); + } // Save the modified pack (and prevent warning in release build) - [[maybe_unused]] bool set = setCurrentPack(current_pack); + [[maybe_unused]] bool set = setCurrentPack(currentPack); Q_ASSERT(set); updateSelectionButton(); @@ -419,26 +437,28 @@ void ResourcePage::onResourceToggle(const QModelIndex& index) auto pack = m_model->data(index, Qt::UserRole).value(); if (pack->versionsLoaded) { - if (pack->isAnyVersionSelected()) + if (pack->isAnyVersionSelected()) { removeResourceFromDialog(pack->name); - else { + } else { auto version = std::find_if(pack->versions.begin(), pack->versions.end(), [this](const ModPlatform::IndexedVersion& version) { return m_model->checkVersionFilters(version); }); if (version == pack->versions.end()) { - auto errorMessage = new QMessageBox( + auto* errorMessage = new QMessageBox( QMessageBox::Warning, tr("No versions available"), tr("No versions for '%1' are available.\nThe author likely blocked third-party launchers.").arg(pack->name), QMessageBox::Ok, this); errorMessage->open(); - } else + } else { addResourceToDialog(pack, *version); + } } - if (isSelected) + if (isSelected) { updateSelectionButton(); + } // force update QVariant variant; @@ -450,8 +470,9 @@ void ResourcePage::onResourceToggle(const QModelIndex& index) // we can't be sure that this hasn't already been requested... // but this does the job well enough and there's not much point preventing edgecases - if (!isSelected) + if (!isSelected) { m_model->loadEntry(index); + } } } @@ -482,13 +503,13 @@ void ResourcePage::openUrl(const QUrl& url) const QString slug = match.captured(1); // ensure the user isn't opening the same mod - if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) { + if (auto currentPack = getCurrentPack(); currentPack && slug != currentPack->slug) { m_parentDialog->selectPage(page); - auto newPage = m_parentDialog->selectedPage(); + auto* newPage = m_parentDialog->selectedPage(); QLineEdit* searchEdit = newPage->m_ui->searchEdit; - auto model = newPage->m_model; + auto* model = newPage->m_model; QListView* view = newPage->m_ui->packView; auto jump = [url, slug, model, view] { @@ -509,10 +530,11 @@ void ResourcePage::openUrl(const QUrl& url) searchEdit->setText(slug); newPage->triggerSearch(); - if (model->hasActiveSearchJob()) + if (model->hasActiveSearchJob()) { connect(model->activeSearchJob().get(), &Task::finished, jump); - else + } else { jump(); + } return; } @@ -522,7 +544,7 @@ void ResourcePage::openUrl(const QUrl& url) QDesktopServices::openUrl(url); } -void ResourcePage::openProject(QVariant projectID) +void ResourcePage::openProject(const QVariant& projectID) { m_ui->sortByBox->hide(); m_ui->searchEdit->hide(); @@ -531,16 +553,16 @@ void ResourcePage::openProject(QVariant projectID) m_ui->resourceSelectionButton->hide(); m_doNotJumpToMod = true; - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - auto okBtn = buttonBox->button(QDialogButtonBox::Ok); + auto* okBtn = buttonBox->button(QDialogButtonBox::Ok); okBtn->setDefault(true); okBtn->setAutoDefault(true); okBtn->setText(tr("Reinstall")); okBtn->setShortcut(tr("Ctrl+Return")); okBtn->setEnabled(false); - auto cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); + auto* cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); cancelBtn->setDefault(false); cancelBtn->setAutoDefault(false); cancelBtn->setText(tr("Cancel")); @@ -567,9 +589,10 @@ void ResourcePage::openProject(QVariant projectID) m_ui->searchEdit->setText("#" + projectID.toString()); triggerSearch(); - if (m_model->hasActiveSearchJob()) + if (m_model->hasActiveSearchJob()) { connect(m_model->activeSearchJob().get(), &Task::finished, jump); - else + } else { jump(); + } } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 6e219bf22..811503868 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -44,9 +44,9 @@ class ResourcePage : public QWidget, public BasePage { virtual auto debugName() const -> QString = 0; //: The plural version of 'resource' - virtual inline QString resourcesString() const { return tr("resources"); } + virtual QString resourcesString() const { return tr("resources"); } //: The singular version of 'resources' - virtual inline QString resourceString() const { return tr("resource"); } + virtual QString resourceString() const { return tr("resource"); } /* Features this resource's page supports */ virtual bool supportsFiltering() const = 0; @@ -58,7 +58,7 @@ class ResourcePage : public QWidget, public BasePage { /** Get the current term in the search bar. */ auto getSearchTerm() const -> QString; /** Programatically set the term in the search bar. */ - void setSearchTerm(QString); + void setSearchTerm(const QString&); bool setCurrentPack(ModPlatform::IndexedPack::Ptr); auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; @@ -76,7 +76,7 @@ class ResourcePage : public QWidget, public BasePage { virtual void versionListUpdated(const QModelIndex& index); void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); - void removeResourceFromDialog(const QString& pack_name); + void removeResourceFromDialog(const QString& packName); virtual void removeResourceFromPage(const QString& name); virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*); @@ -90,7 +90,7 @@ class ResourcePage : public QWidget, public BasePage { protected slots: virtual void triggerSearch() = 0; - void onSelectionChanged(QModelIndex first, QModelIndex second); + void onSelectionChanged(QModelIndex curr, QModelIndex prev); void onVersionSelectionChanged(int index); void onResourceSelected(); void onResourceToggle(const QModelIndex& index); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 58b092275..ad43e95e4 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" @@ -59,40 +60,43 @@ class PageEntryFilterModel : public QSortFilterProxyModel { public: - explicit PageEntryFilterModel(QObject* parent = 0) : QSortFilterProxyModel(parent) {} + explicit PageEntryFilterModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override { const QString pattern = filterRegularExpression().pattern(); - const auto model = static_cast(sourceModel()); - const auto page = model->pages().at(sourceRow); - if (!page->shouldDisplay()) + auto* const model = static_cast(sourceModel()); + auto* const page = model->pages().at(sourceRow); + if (!page->shouldDisplay()) { return false; + } // Regular contents check, then check page-filter. return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } }; -PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, QWidget* parent) : QWidget(parent) +PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, QWidget* parent) + : QWidget(parent) + , m_proxyModel(new PageEntryFilterModel(this)) + , m_model(new PageModel(this)) { createUI(); useSidebarStyle(true); - m_model = new PageModel(this); - m_proxyModel = new PageEntryFilterModel(this); int counter = 0; auto pages = pageProvider->getPages(); - for (auto page : pages) { - auto widget = dynamic_cast(page); + for (auto* page : pages) { + auto* widget = dynamic_cast(page); widget->setParent(this); page->stackIndex = m_pageStack->addWidget(widget); page->listIndex = counter; page->setParentContainer(this); counter++; - page->updateExtraInfo = [this](QString id, QString info) { - if (m_currentPage && id == m_currentPage->id()) + page->updateExtraInfo = [this](const QString& id, const QString& info) { + if (m_currentPage && id == m_currentPage->id()) { m_header->setText(m_currentPage->displayName() + info); + } }; } m_model->setPages(pages); @@ -108,13 +112,13 @@ PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, connect(m_pageList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &PageContainer::currentChanged); m_pageStack->setStackingMode(QStackedLayout::StackOne); m_pageList->setFocus(); - selectPage(defaultId); + selectPage(std::move(defaultId)); } bool PageContainer::selectPage(QString pageId) { // now find what we want to have selected... - auto page = m_model->findPageEntryById(pageId); + auto* page = m_model->findPageEntryById(pageId); QModelIndex index; if (page) { index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); @@ -166,11 +170,12 @@ void PageContainer::createUI() QFont headerLabelFont = m_header->font(); headerLabelFont.setBold(true); const int pointSize = headerLabelFont.pointSize(); - if (pointSize > 0) + if (pointSize > 0) { headerLabelFont.setPointSize(pointSize + 2); + } m_header->setFont(headerLabelFont); - QHBoxLayout* headerHLayout = new QHBoxLayout; + auto* headerHLayout = new QHBoxLayout; const int leftMargin = APPLICATION->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); headerHLayout->addWidget(m_header); @@ -190,11 +195,13 @@ void PageContainer::createUI() void PageContainer::retranslate() { - if (m_currentPage) + if (m_currentPage) { m_header->setText(m_currentPage->displayName()); + } - for (auto page : m_model->pages()) + for (auto* page : m_model->pages()) { page->retranslate(); + } } void PageContainer::addButtons(QWidget* buttons) @@ -236,22 +243,23 @@ void PageContainer::help() { if (m_currentPage) { QString pageId = m_currentPage->helpPage(); - if (pageId.isEmpty()) + if (pageId.isEmpty()) { return; + } DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(pageId))); } } void PageContainer::currentChanged(const QModelIndex& current) { - int selected_index = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; + int selectedIndex = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; - auto* selected = m_model->pages().at(selected_index); + auto* selected = m_model->pages().at(selectedIndex); auto* previous = m_currentPage; emit selectedPageChanged(previous, selected); - showPage(selected_index); + showPage(selectedIndex); } bool PageContainer::prepareToClose() @@ -267,9 +275,10 @@ bool PageContainer::prepareToClose() bool PageContainer::saveAll() { - for (auto page : m_model->pages()) { - if (!page->apply()) + for (auto* page : m_model->pages()) { + if (!page->apply()) { return false; + } } return true; } diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 2c7ca9e39..b73326c02 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -56,8 +56,10 @@ class QGridLayout; class PageContainer : public QWidget, public BasePageContainer { Q_OBJECT public: - explicit PageContainer(BasePageProvider* pageProvider, QString defaultId = QString(), QWidget* parent = 0); - virtual ~PageContainer() {} + explicit PageContainer(BasePageProvider* pageProvider, + QString defaultId = QString(), + QWidget* parent = nullptr); + ~PageContainer() override = default; void addButtons(QWidget* buttons); void addButtons(QLayout* buttons); From b174dec0d2b8f83ca5e6d4927df85f3dce766cee Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 00:03:57 +0300 Subject: [PATCH 079/132] fix change version triggering an intial search Signed-off-by: Trial97 --- .../ui/dialogs/ResourceDownloadDialog.cpp | 81 ++++++++++++++----- launcher/ui/dialogs/ResourceDownloadDialog.h | 15 ++-- launcher/ui/pages/instance/DataPackPage.cpp | 2 +- launcher/ui/pages/instance/ModFolderPage.cpp | 2 +- .../ui/pages/instance/ResourcePackPage.cpp | 2 +- launcher/ui/pages/instance/ShaderPackPage.cpp | 2 +- .../ui/pages/instance/TexturePackPage.cpp | 2 +- .../ui/pages/modplatform/ResourcePage.cpp | 11 ++- launcher/ui/pages/modplatform/ResourcePage.h | 7 +- 9 files changed, 90 insertions(+), 34 deletions(-) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 41274de08..49f263406 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -50,11 +50,12 @@ namespace ResourceDownload { -ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel) +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel, bool suppressInitialSearch) : QDialog(parent) , m_base_model(baseModel) , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel) , m_vertical_layout(this) + , m_suppressInitialSearch(suppressInitialSearch) { setObjectName(QStringLiteral("ResourceDownloadDialog")); @@ -270,6 +271,11 @@ QList ResourceDownloadDialog::getTasks( void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) { + // If previous is null (first selection), nothing to sync + if (!previous) { + return; + } + auto* prevPage = dynamic_cast(previous); if (!prevPage) { qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; @@ -282,8 +288,8 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s result->setSearchTerm(prevPage->getSearchTerm()); } -ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance) - : ResourceDownloadDialog(parent, mods), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance, bool suppressInitialSearch) + : ResourceDownloadDialog(parent, mods, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); @@ -302,10 +308,14 @@ QList ModDownloadDialog::getPages() auto loaders = static_cast(m_instance)->getPackProfile()->getSupportedModLoaders().value(); if (ModrinthAPI::validateModLoaders(loaders)) { - pages.append(ModrinthModPage::create(this, *m_instance)); + auto* page = ModrinthModPage::create(this, *m_instance); + page->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(page); } if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) { - pages.append(FlameModPage::create(this, *m_instance)); + auto* page = FlameModPage::create(this, *m_instance); + page->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(page); } return pages; @@ -326,8 +336,11 @@ GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() return nullptr; } -ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance) - : ResourceDownloadDialog(parent, resourcePacks), m_instance(instance) +ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, + ResourcePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, resourcePacks, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); @@ -343,16 +356,23 @@ QList ResourcePackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthResourcePackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthResourcePackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); if (APPLICATION->capabilities() & Application::SupportsFlame) { - pages.append(FlameResourcePackPage::create(this, *m_instance)); + auto* flamePage = FlameResourcePackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); } return pages; } -TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance) - : ResourceDownloadDialog(parent, resourcePacks), m_instance(instance) +TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, + TexturePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, resourcePacks, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); @@ -368,16 +388,23 @@ QList TexturePackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthTexturePackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthTexturePackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); if (APPLICATION->capabilities() & Application::SupportsFlame) { - pages.append(FlameTexturePackPage::create(this, *m_instance)); + auto* flamePage = FlameTexturePackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); } return pages; } -ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance) - : ResourceDownloadDialog(parent, shaders), m_instance(instance) +ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, + ShaderPackFolderModel* shaders, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, shaders, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); @@ -392,9 +419,13 @@ ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, ShaderPackFo QList ShaderPackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthShaderPackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthShaderPackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); if (APPLICATION->capabilities() & Application::SupportsFlame) { - pages.append(FlameShaderPackPage::create(this, *m_instance)); + auto* flamePage = FlameShaderPackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); } return pages; } @@ -409,6 +440,7 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptrname)); m_container->hidePageList(); m_buttons.hide(); @@ -416,8 +448,11 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptropenProject(meta->project_id); } -DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance) - : ResourceDownloadDialog(parent, dataPacks), m_instance(instance) +DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, + DataPackFolderModel* dataPacks, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, dataPacks, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); @@ -432,9 +467,13 @@ DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, DataPackFolderMo QList DataPackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthDataPackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthDataPackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); if (APPLICATION->capabilities() & Application::SupportsFlame) { - pages.append(FlameDataPackPage::create(this, *m_instance)); + auto* flamePage = FlameDataPackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); } return pages; } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index dfcebd31e..c9d9e568c 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -51,7 +51,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { public: using DownloadTaskPtr = shared_qobject_ptr; - ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel); + ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel, bool suppressInitialSearch = false); void initializeContainer(); void connectButtons(); @@ -94,13 +94,16 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; + + protected: + bool m_suppressInitialSearch = false; }; class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance); + explicit ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance, bool suppressInitialSearch = false); ~ModDownloadDialog() override = default; //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) @@ -118,7 +121,7 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance); + explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false); ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) @@ -135,7 +138,7 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance); + explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -152,7 +155,7 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance); + explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance, bool suppressInitialSearch = false); ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) @@ -169,7 +172,7 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance); + explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance, bool suppressInitialSearch = false); ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index 8c4bd313f..eb59fbb1e 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -242,7 +242,7 @@ void DataPackPage::changeDataPackVersion() return; } - ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance); + ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance, true); mdownload.setResourceMetadata(resource.metadata()); if (mdownload.exec() != 0) { auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index be64cceac..99c78647c 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -352,7 +352,7 @@ void ModFolderPage::changeModVersion() return; } - m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index b9e3a20b8..e4709ab2b 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -259,7 +259,7 @@ void ResourcePackPage::changeResourcePackVersion() return; } - m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index c3b463527..a29564abc 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -256,7 +256,7 @@ void ShaderPackPage::changeShaderPackVersion() return; } - m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index a723db4f6..01325e3f6 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -264,7 +264,7 @@ void TexturePackPage::changeTexturePackVersion() return; } - m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index fecb5b1b6..931b4a311 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -115,10 +115,19 @@ void ResourcePage::openedImpl() m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); updateSelectionButton(); - triggerSearch(); + if (!m_suppressInitialSearch) { + triggerSearch(); + } else { + m_suppressInitialSearch = false; + } m_ui->searchEdit->setFocus(); } +void ResourcePage::setSuppressInitialSearch(bool suppress) +{ + m_suppressInitialSearch = suppress; +} + auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool { if (event->type() == QEvent::KeyPress) { diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 811503868..c11edb1d7 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -85,7 +85,9 @@ class ResourcePage : public QWidget, public BasePage { QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } - virtual void openProject(QVariant projectID); + virtual void openProject(const QVariant& projectID); + + void setSuppressInitialSearch(bool suppress); protected slots: virtual void triggerSearch() = 0; @@ -118,6 +120,9 @@ class ResourcePage : public QWidget, public BasePage { bool m_doNotJumpToMod = false; QSet m_enableQueue; + + private: + bool m_suppressInitialSearch = false; }; } // namespace ResourceDownload From 4cbfe7fb0e10734de4c61205f462728b4e01b07f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 14:43:44 +0300 Subject: [PATCH 080/132] fix atl path traversal Signed-off-by: Trial97 --- .../atlauncher/ATLPackInstallTask.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 7a7365fbc..96e577cb2 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -59,6 +59,16 @@ #include "BuildConfig.h" #include "ui/dialogs/BlockedModsDialog.h" +namespace { +bool isPathTraversal(const QString& basePath, const QString& entryName) +{ + auto safeName = FS::RemoveInvalidPathChars(entryName); + auto fullPath = FS::PathCombine(basePath, safeName); + auto baseUrl = QUrl::fromLocalFile(basePath); + return !baseUrl.isParentOf(QUrl::fromLocalFile(fullPath)); +} +} // namespace + namespace ATLauncher { static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version); @@ -938,6 +948,10 @@ bool PackInstallTask::extractMods(const QMap& toExtract, folderToExtract = mod.extractFolder; static const QRegularExpression s_regex("^/"); folderToExtract.remove(s_regex); + if (isPathTraversal(extractToPath, folderToExtract)) { + qWarning() << "Blocked path traversal in" << mod.extractFolder; + return false; + } } qDebug() << "Extracting " + mod.file + " to " + extractToDir; @@ -955,6 +969,11 @@ bool PackInstallTask::extractMods(const QMap& toExtract, QDir extractDir(m_stagingPath); auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile); + if (isPathTraversal(extractToPath, mod.decompFile)) { + qWarning() << "Blocked path traversal in decompFile" << mod.decompFile; + return false; + } + qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir; if (!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) { qWarning() << "Failed to extract" << mod.decompFile; From daa9e07e33f8f8f3959dc5d57d3d511f91b08b6c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 15:02:50 +0300 Subject: [PATCH 081/132] chore(clang-tidy): fix clang tidy warnings Signed-off-by: Trial97 --- .../atlauncher/ATLPackInstallTask.cpp | 221 ++++++++++-------- .../atlauncher/ATLPackInstallTask.h | 19 +- 2 files changed, 127 insertions(+), 113 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 96e577cb2..f3f66997b 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -38,6 +38,7 @@ #include #include +#include #include "FileSystem.h" #include "Json.h" @@ -67,20 +68,20 @@ bool isPathTraversal(const QString& basePath, const QString& entryName) auto baseUrl = QUrl::fromLocalFile(basePath); return !baseUrl.isParentOf(QUrl::fromLocalFile(fullPath)); } + +Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) +{ + return APPLICATION->metadataIndex()->getLoadedVersion(uid, version); +} } // namespace namespace ATLauncher { -static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version); - PackInstallTask::PackInstallTask(UserInteractionSupport* support, QString packName, QString version, InstallMode installMode) + : m_support(support), m_install_mode(installMode), m_pack_name(packName), m_version_name(std::move(version)) { - m_support = support; - m_pack_name = packName; static const QRegularExpression s_regex("[^A-Za-z0-9]"); m_pack_safe_name = packName.replace(s_regex, ""); - m_version_name = version; - m_install_mode = installMode; } bool PackInstallTask::abort() @@ -117,11 +118,10 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) QByteArray response = std::move(*responsePtr); jobPtr.reset(); - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ATLauncher at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ATLauncher at" << parseError.offset << "reason:" << parseError.errorString(); qWarning() << response; return; } @@ -138,7 +138,7 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) // Derived from the installation mode QString message; - bool resetDirectory; + bool resetDirectory = false; switch (m_install_mode) { case InstallMode::Reinstall: @@ -158,8 +158,9 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) } // Display message if one exists - if (!message.isEmpty()) + if (!message.isEmpty()) { m_support->displayMessage(message); + } auto ver = getComponentVersion("net.minecraft", m_version.minecraft); if (!ver) { @@ -183,7 +184,7 @@ void PackInstallTask::onDownloadFailed(QString reason) { qDebug() << "PackInstallTask::onDownloadFailed:" << QThread::currentThreadId(); jobPtr.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); } void PackInstallTask::onDownloadAborted() @@ -212,26 +213,30 @@ void PackInstallTask::deleteExistingFiles() keeps.files.append(VersionKeep{ "root", "servers.dat" }); // Merge with version deletes and keeps - for (const auto& item : m_version.deletes.files) + for (const auto& item : m_version.deletes.files) { deletes.files.append(item); - for (const auto& item : m_version.deletes.folders) + } + for (const auto& item : m_version.deletes.folders) { deletes.folders.append(item); - for (const auto& item : m_version.keeps.files) + } + for (const auto& item : m_version.keeps.files) { keeps.files.append(item); - for (const auto& item : m_version.keeps.folders) + } + for (const auto& item : m_version.keeps.folders) { keeps.folders.append(item); + } auto getPathForBase = [this](const QString& base) { auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft"); if (base == "root") { return minecraftPath; - } else if (base == "config") { - return FS::PathCombine(minecraftPath, "config"); - } else { - qWarning() << "Unrecognised base path" << base; - return minecraftPath; } + if (base == "config") { + return FS::PathCombine(minecraftPath, "config"); + } + qWarning() << "Unrecognised base path" << base; + return minecraftPath; }; auto convertToSystemPath = [](const QString& path) { @@ -241,24 +246,22 @@ void PackInstallTask::deleteExistingFiles() }; auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) { - for (const auto& item : keeps.files) { - auto basePath = getPathForBase(item.base); - auto targetPath = convertToSystemPath(item.target); - auto path = FS::PathCombine(basePath, targetPath); - - if (fullPath == path) { - return true; - } + if (std::ranges::any_of(keeps.files, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + return fullPath == path; + })) { + return true; } - for (const auto& item : keeps.folders) { - auto basePath = getPathForBase(item.base); - auto targetPath = convertToSystemPath(item.target); - auto path = FS::PathCombine(basePath, targetPath); - - if (fullPath.startsWith(path)) { - return true; - } + if (std::ranges::any_of(keeps.folders, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + return fullPath.startsWith(path); + })) { + return true; } return false; @@ -272,8 +275,9 @@ void PackInstallTask::deleteExistingFiles() auto targetPath = convertToSystemPath(item.target); auto fullPath = FS::PathCombine(basePath, targetPath); - if (shouldKeep(fullPath)) + if (shouldKeep(fullPath)) { continue; + } filesToDelete.insert(fullPath); } @@ -287,8 +291,9 @@ void PackInstallTask::deleteExistingFiles() while (it.hasNext()) { auto path = it.next(); - if (shouldKeep(path)) + if (shouldKeep(path)) { continue; + } filesToDelete.insert(path); } @@ -300,7 +305,7 @@ void PackInstallTask::deleteExistingFiles() } } -QString PackInstallTask::getDirForModType(ModType type, QString raw) +QString PackInstallTask::getDirForModType(ModType type, const QString& raw) { switch (type) { // Mod types that can either be ignored at this stage, or ignored @@ -348,7 +353,7 @@ QString PackInstallTask::getDirForModType(ModType type, QString raw) return Q_NULLPTR; } -QString PackInstallTask::getVersionForLoader(QString uid) +QString PackInstallTask::getVersionForLoader(const QString& uid) { if (m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) { auto vlist = APPLICATION->metadataIndex()->get(uid); @@ -369,16 +374,19 @@ QString PackInstallTask::getVersionForLoader(QString uid) // filtering for those loaders. if (m_version.loader.type != "fabric") { auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) { return req.uid == "net.minecraft"; }); - if (iter == reqs.end()) + if (iter == reqs.end()) { continue; - if (iter->equalsVersion != m_version.minecraft) + } + if (iter->equalsVersion != m_version.minecraft) { continue; + } } if (m_version.loader.recommended) { // first recommended build we find, we use. - if (!version->isRecommended()) + if (!version->isRecommended()) { continue; + } } return version->descriptor(); @@ -386,7 +394,8 @@ QString PackInstallTask::getVersionForLoader(QString uid) emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type)); return Q_NULLPTR; - } else if (m_version.loader.choose) { + } + if (m_version.loader.choose) { // Fabric Loader doesn't depend on a given Minecraft version. if (m_version.loader.type == "fabric") { return m_support->chooseVersion(vlist, Q_NULLPTR); @@ -430,7 +439,8 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library) if (name == QString("guava")) { return "com.google.guava:guava:" + version; - } else if (name == QString("commons-lang3")) { + } + if (name == QString("commons-lang3")) { return "org.apache.commons:commons-lang3:" + version; } } @@ -438,7 +448,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library) return "org.multimc.atlauncher:" + library.md5 + ":1"; } -bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile* profile) +bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, PackProfile* profile) { if (m_version.libraries.isEmpty()) { return true; @@ -463,18 +473,18 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile } auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto target_id = "org.multimc.atlauncher." + id; + auto targetId = "org.multimc.atlauncher." + id; auto patchDir = FS::PathCombine(instanceRoot, "patches"); if (!FS::ensureFolderPathExists(patchDir)) { return false; } - auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); auto f = std::make_shared(); f->name = m_pack_name + " " + m_version_name + " (libraries)"; - const static QMap liteLoaderMap = { + const static QMap s_liteLoaderMap = { { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" }, { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" }, { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" }, @@ -494,8 +504,8 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile for (const auto& lib : m_version.libraries) { // If the library is LiteLoader, we need to ignore it and handle it separately. - if (liteLoaderMap.contains(lib.md5)) { - auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5)); + if (s_liteLoaderMap.contains(lib.md5)) { + auto ver = getComponentVersion("com.mumfrey.liteloader", s_liteLoaderMap.value(lib.md5)); if (ver) { componentsToInstall.insert("com.mumfrey.liteloader", ver); continue; @@ -512,8 +522,9 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile libExempt = Version(libSpecifier.version()) >= Version(existingLib.version()); } } - if (libExempt) + if (libExempt) { continue; + } auto library = std::make_shared(); library->setRawName(libName); @@ -546,11 +557,11 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) }); + profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); return true; } -bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* profile) +bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfile* profile) { if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) { return true; @@ -581,13 +592,13 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* pro } auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto target_id = "org.multimc.atlauncher." + id; + auto targetId = "org.multimc.atlauncher." + id; auto patchDir = FS::PathCombine(instanceRoot, "patches"); if (!FS::ensureFolderPathExists(patchDir)) { return false; } - auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); QStringList mainClasses; QStringList tweakers; @@ -614,8 +625,9 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* pro for (auto arg : args) { if (arg.startsWith("--tweakClass=") || previous == "--tweakClass") { auto tweakClass = arg.remove("--tweakClass="); - if (tweakers.contains(tweakClass)) + if (tweakers.contains(tweakClass)) { continue; + } f->addTweakers.append(tweakClass); } @@ -634,7 +646,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* pro file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) }); + profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); return true; } @@ -664,7 +676,7 @@ void PackInstallTask::installConfigs() connect(jobPtr.get(), &NetJob::failed, [this](QString reason) { abortable = false; jobPtr.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); }); connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) { abortable = true; @@ -721,15 +733,17 @@ void PackInstallTask::downloadMods() jarmods.clear(); jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - QList blocked_mods; + QList blockedMods; for (const auto& mod : m_version.mods) { // skip non-client mods - if (!mod.client) + if (!mod.client) { continue; + } // skip optional mods that were not selected - if (mod.optional && !selectedMods.contains(mod.name)) + if (mod.optional && !selectedMods.contains(mod.name)) { continue; + } QString url; switch (mod.download) { @@ -737,7 +751,7 @@ void PackInstallTask::downloadMods() url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url; break; case DownloadType::Browser: { - blocked_mods.append(mod); + blockedMods.append(mod); continue; } case DownloadType::Direct: @@ -773,8 +787,9 @@ void PackInstallTask::downloadMods() jobPtr->addNetAction(dl); } else { auto relpath = getDirForModType(mod.type, mod.type_raw); - if (relpath == Q_NULLPTR) + if (relpath == Q_NULLPTR) { continue; + } auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName); entry->setStale(true); @@ -808,49 +823,51 @@ void PackInstallTask::downloadMods() modsToCopy[entry->getFullPath()] = path; } } - if (!blocked_mods.isEmpty()) { + if (!blockedMods.isEmpty()) { QList mods; - for (auto mod : blocked_mods) { - BlockedMod blocked_mod; - blocked_mod.name = mod.file; - blocked_mod.websiteUrl = mod.url; - blocked_mod.hash = mod.md5; - blocked_mod.matched = false; - blocked_mod.localPath = ""; + for (const auto& mod : blockedMods) { + BlockedMod blockedMod; + blockedMod.name = mod.file; + blockedMod.websiteUrl = mod.url; + blockedMod.hash = mod.md5; + blockedMod.matched = false; + blockedMod.localPath = ""; - mods.append(blocked_mod); + mods.append(blockedMod); } qWarning() << "Blocked mods found, displaying mod list"; - BlockedModsDialog message_dialog(nullptr, tr("Blocked mods found"), - tr("The following files are not available for download in third party launchers.
" - "You will need to manually download them and add them to the instance."), - mods, "md5"); + BlockedModsDialog messageDialog(nullptr, tr("Blocked mods found"), + tr("The following files are not available for download in third party launchers.
" + "You will need to manually download them and add them to the instance."), + mods, "md5"); - message_dialog.setModal(true); + messageDialog.setModal(true); - if (message_dialog.exec()) { + if (messageDialog.exec() != 0) { qDebug() << "Post dialog blocked mods list:" << mods; - for (auto blocked : mods) { + for (const auto& blocked : mods) { if (!blocked.matched) { qDebug() << blocked.name << "was not matched to a local file, skipping copy"; continue; } - auto modIter = std::find_if(blocked_mods.begin(), blocked_mods.end(), - [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); - if (modIter == blocked_mods.end()) + auto modIter = + std::ranges::find_if(blockedMods, [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); + if (modIter == blockedMods.end()) { continue; - auto mod = *modIter; + } + const auto& mod = *modIter; if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) { modsToExtract.insert(blocked.localPath, mod); } else if (mod.type == ModType::Decomp) { modsToDecomp.insert(blocked.localPath, mod); } else { auto relpath = getDirForModType(mod.type, mod.type_raw); - if (relpath == Q_NULLPTR) + if (relpath == Q_NULLPTR) { continue; + } auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); @@ -928,8 +945,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, setStatus(tr("Extracting mods...")); for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) { - auto& modPath = iter.key(); - auto& mod = iter.value(); + const auto& modPath = iter.key(); + const auto& mod = iter.value(); QString extractToDir; if (mod.type == ModType::Extract) { @@ -962,8 +979,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, } for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) { - auto& modPath = iter.key(); - auto& mod = iter.value(); + const auto& modPath = iter.key(); + const auto& mod = iter.value(); auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw); QDir extractDir(m_stagingPath); @@ -982,8 +999,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, } for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { - auto& from = iter.key(); - auto& to = iter.value(); + const auto& from = iter.key(); + const auto& to = iter.value(); // If the file already exists, assume the mod is the correct copy - and remove // the copy from the Configs.zip @@ -1013,7 +1030,7 @@ void PackInstallTask::install() MinecraftInstance instance(m_globalSettings, std::make_unique(instanceConfigPath), m_stagingPath); { SettingsObject::Lock lock(instance.settings()); - auto components = instance.getPackProfile(); + auto* components = instance.getPackProfile(); components->buildingFromScratch(); // Use a component to add libraries BEFORE Minecraft @@ -1028,20 +1045,23 @@ void PackInstallTask::install() // Loader if (m_version.loader.type == QString("forge")) { auto version = getVersionForLoader("net.minecraftforge"); - if (version == Q_NULLPTR) + if (version == Q_NULLPTR) { return; + } components->setComponentVersion("net.minecraftforge", version); } else if (m_version.loader.type == QString("neoforge")) { auto version = getVersionForLoader("net.neoforged"); - if (version == Q_NULLPTR) + if (version == Q_NULLPTR) { return; + } components->setComponentVersion("net.neoforged", version); } else if (m_version.loader.type == QString("fabric")) { auto version = getVersionForLoader("net.fabricmc.fabric-loader"); - if (version == Q_NULLPTR) + if (version == Q_NULLPTR) { return; + } components->setComponentVersion("net.fabricmc.fabric-loader", version); } else if (m_version.loader.type != QString()) { @@ -1074,9 +1094,4 @@ void PackInstallTask::install() emitSucceeded(); } -static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) -{ - return APPLICATION->metadataIndex()->getLoadedVersion(uid, version); -} - } // namespace ATLauncher diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index d1ffdfe7d..e8df132be 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -44,14 +44,13 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "net/NetJob.h" -#include "settings/INISettingsObject.h" -#include +#include #include namespace ATLauncher { -enum class InstallMode { +enum class InstallMode : std::uint8_t { Install, Reinstall, Update, @@ -86,13 +85,13 @@ class PackInstallTask : public InstanceTask { QString packName, QString version, InstallMode installMode = InstallMode::Install); - virtual ~PackInstallTask() { delete m_support; } + ~PackInstallTask() override { delete m_support; } bool canAbort() const override { return true; } bool abort() override; protected: - virtual void executeTask() override; + void executeTask() override; private slots: void onDownloadSucceeded(QByteArray* responsePtr); @@ -103,12 +102,12 @@ class PackInstallTask : public InstanceTask { void onModsExtracted(); private: - QString getDirForModType(ModType type, QString raw); - QString getVersionForLoader(QString uid); - QString detectLibrary(const VersionLibrary& library); + QString getDirForModType(ModType type, const QString& raw); + QString getVersionForLoader(const QString& uid); + static QString detectLibrary(const VersionLibrary& library); - bool createLibrariesComponent(QString instanceRoot, PackProfile* profile); - bool createPackComponent(QString instanceRoot, PackProfile* profile); + bool createLibrariesComponent(const QString& instanceRoot, PackProfile* profile); + bool createPackComponent(const QString& instanceRoot, PackProfile* profile); void deleteExistingFiles(); void installConfigs(); From 49aef77f3fe14c7f38d09944d69d5bbccf71ff15 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 7 May 2026 18:33:40 +0500 Subject: [PATCH 082/132] fix(translations): redownload index if it is missing after file reloading Signed-off-by: Octol1ttle --- launcher/Application.cpp | 21 ++++++++++----------- launcher/translations/TranslationsModel.cpp | 7 ++++++- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ff4013b1c..710b75162 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -937,15 +937,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) qInfo() << "<> Network done."; } - // load translations - { - m_translations.reset(new TranslationsModel("translations")); - auto bcp47Name = m_settings->get("Language").toString(); - m_translations->selectLanguage(bcp47Name); - qInfo() << "Your language is" << bcp47Name; - qInfo() << "<> Translations loaded."; - } - // Instance icons { auto setting = APPLICATION->settings()->getSetting("IconsDir"); @@ -1024,8 +1015,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) qInfo() << "<> Cache initialized."; } - // now we have network, download translation updates - m_translations->downloadIndex(); + // load translations + { + m_translations.reset(new TranslationsModel("translations")); + auto bcp47Name = m_settings->get("Language").toString(); + m_translations->selectLanguage(bcp47Name); + qInfo() << "Your language is" << bcp47Name; + qInfo() << "<> Translations loaded."; + + m_translations->downloadIndex(); + } // FIXME: what to do with these? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index ebbd7cdb6..54d792f0c 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -267,7 +267,12 @@ void TranslationsModel::reloadLocalFiles() { QMap languages = { { g_defaultLangCode, Language(g_defaultLangCode) } }; - readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); + const auto indexPath = d->m_dir.absoluteFilePath("index_v2.json"); + if (!QFileInfo::exists(indexPath)) { + downloadIndex(); + return; + } + readIndex(indexPath, languages); auto entries = d->m_dir.entryInfoList({ "mmc_*.qm", "*.po" }, QDir::Files | QDir::NoDotAndDotDot); for (auto& entry : entries) { auto completeSuffix = entry.completeSuffix(); From d59b4b0ad7b8377b9f7f3a2d834ba02fa5487fa2 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 7 May 2026 20:15:38 +0500 Subject: [PATCH 083/132] fix(translations): do not reset user language if translations index is missing Signed-off-by: Octol1ttle --- launcher/Application.cpp | 7 ++----- launcher/translations/TranslationsModel.cpp | 13 ++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 710b75162..efd63714d 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1018,12 +1018,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // load translations { m_translations.reset(new TranslationsModel("translations")); - auto bcp47Name = m_settings->get("Language").toString(); - m_translations->selectLanguage(bcp47Name); - qInfo() << "Your language is" << bcp47Name; - qInfo() << "<> Translations loaded."; - m_translations->downloadIndex(); + qInfo() << "Your language is" << m_translations->selectedLanguage(); + qInfo() << "<> Translations loaded."; } // FIXME: what to do with these? diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 54d792f0c..7f905608b 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -179,6 +179,7 @@ TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAb { d = std::make_unique(); d->m_dir.setPath(path); + d->m_selectedLanguage = APPLICATION->settings()->get("Language").toString(); FS::ensureFolderPathExists(path); reloadLocalFiles(); @@ -202,22 +203,20 @@ void TranslationsModel::indexReceived() { qDebug() << "Got translations index!"; d->m_indexJob.reset(); + reloadLocalFiles(); if (d->m_noLanguageSet) { - reloadLocalFiles(); - auto language = getSystemLocaleName(); if (!findLanguageAsOptional(language).has_value()) { language = getSystemLanguage(); } selectLanguage(language); - if (selectedLanguage() != g_defaultLangCode) { - updateLanguage(selectedLanguage()); - } APPLICATION->settings()->set("Language", selectedLanguage()); d->m_noLanguageSet = false; - } else if (d->m_selectedLanguage != g_defaultLangCode) { - downloadTranslation(d->m_selectedLanguage); + } + + if (selectedLanguage() != g_defaultLangCode) { + updateLanguage(selectedLanguage()); } } From 564fca8c9b3077335a1ea00b1dc803c97429f5b3 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 7 May 2026 20:36:46 +0500 Subject: [PATCH 084/132] fix: don't delete base directories when evicting metacache Signed-off-by: Octol1ttle --- launcher/FileSystem.cpp | 26 ++++++++++++++++++++++++++ launcher/FileSystem.h | 7 +++++++ launcher/net/HttpMetaCache.cpp | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 938ef23e4..ef56e3e65 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -684,6 +684,32 @@ bool deletePath(QString path) return err.value() == 0; } +bool deleteContents(const QString& path) +{ + const QFileInfo info(path); + if (!info.exists()) { + return true; + } + if (!info.isDir()) { + qWarning() << "Attempted to delete contents of non-directory path:" << path; + return false; + } + + bool ret = true; + + for (const auto& entry : fs::directory_iterator(StringUtils::toStdString(path))) { + std::error_code err; + + fs::remove_all(entry.path(), err); + if (err.value() != 0) { + qWarning().nospace() << "Could not delete directory entry " << entry.path() << ": " << QString::fromStdString(err.message()); + ret = false; + } + } + + return ret; +} + bool trash(QString path, QString* pathInTrash) { // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f2676b147..6d9b01178 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -291,6 +291,13 @@ bool move(const QString& source, const QString& dest); */ bool deletePath(QString path); +/** + * Delete a folder's contents recursively but not the folder itself. + * @param path The path to the folder. + * @return Whether the deletion was completely successful. + */ +bool deleteContents(const QString& path); + bool removeFiles(QStringList listFile); /** diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 6b81bfe0d..5c1e47dfd 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -182,7 +182,7 @@ auto HttpMetaCache::evictAll() -> bool } map.entry_list.clear(); // AND all return codes together so the result is true iff all runs of deletePath() are true - ret &= FS::deletePath(map.base_path); + ret &= FS::deleteContents(map.base_path); } return ret; } From ca721f9d67f2bfd4e3b083e2a3b655ea66a36bd5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 13:29:35 +0300 Subject: [PATCH 085/132] add special modrinth header Signed-off-by: Trial97 --- launcher/ResourceDownloadTask.cpp | 35 ++++++++++++++++-- launcher/ResourceDownloadTask.h | 4 ++- launcher/minecraft/mod/ResourceFolderModel.h | 1 + .../modrinth/ModrinthInstanceCreationTask.cpp | 32 +++++++++++++++-- launcher/net/ApiDownload.cpp | 4 +-- launcher/net/ApiDownload.h | 4 ++- launcher/net/ApiHeaderProxy.h | 36 +++++++++++++++++-- .../ui/dialogs/ResourceDownloadDialog.cpp | 6 ++-- launcher/ui/dialogs/ResourceDownloadDialog.h | 2 +- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 2 +- launcher/ui/pages/modplatform/ModPage.cpp | 4 +-- launcher/ui/pages/modplatform/ModPage.h | 2 +- .../ui/pages/modplatform/ResourceModel.cpp | 5 +-- launcher/ui/pages/modplatform/ResourceModel.h | 3 +- .../ui/pages/modplatform/ResourcePage.cpp | 5 ++- launcher/ui/pages/modplatform/ResourcePage.h | 2 +- .../ui/pages/modplatform/ShaderPackPage.cpp | 5 +-- .../ui/pages/modplatform/ShaderPackPage.h | 2 +- 18 files changed, 127 insertions(+), 27 deletions(-) diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index d50b3d5cf..f9eeea790 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -19,20 +19,49 @@ #include "ResourceDownloadTask.h" +#include + #include "Application.h" #include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" #include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" +#include "modplatform/ModIndex.h" #include "modplatform/helpers/HashUtils.h" #include "net/ApiDownload.h" #include "net/ChecksumValidator.h" +namespace { +Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString reason) +{ + auto* mcInstance = dynamic_cast(instance); + if (!mcInstance) { + return {}; + } + + auto* profile = mcInstance->getPackProfile(); + if (!profile) { + return {}; + } + + auto loaders = profile->getModLoadersList(); + + return { + .reason = std::move(reason), + .gameVersion = profile->getComponentVersion("net.minecraft"), + .loader = !loaders.isEmpty() ? ModPlatform::getModLoaderAsString(loaders.first()) : "", + }; +} +} // namespace + ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed) + bool is_indexed, + QString downloadReason) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) { if (is_indexed) { @@ -45,7 +74,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); - auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename())); + auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()), + Net::Download::Option::NoOptions, + createModrinthMeta(m_pack_model->instance(), std::move(downloadReason))); if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) { switch (Hashing::algorithmFromString(m_pack_version.hash_type)) { case Hashing::Algorithm::Md4: diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 7a04c6f1c..57dfa5eac 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -20,6 +20,7 @@ #pragma once #include "net/NetJob.h" +#include "net/ApiHeaderProxy.h" #include "tasks/SequentialTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h" @@ -33,7 +34,8 @@ class ResourceDownloadTask : public SequentialTask { explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed = true); + bool is_indexed = true, + QString downloadReason = "standalone"); const QString& getFilename() const { return m_pack_version.fileName; } const QVariant& getVersionID() const { return m_pack_version.fileId; } const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 81bc6f5fc..124b9ea77 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -183,6 +183,7 @@ class ResourceFolderModel : public QAbstractListModel { }; QString instDirPath() const; + BaseInstance* instance() const { return m_instance; } signals: void updateFinished(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index f308c88bc..482a27204 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -16,7 +16,10 @@ #include "net/ChecksumValidator.h" #include "net/ApiDownload.h" +#include "net/ApiHeaderProxy.h" #include "net/NetJob.h" + +#include "modplatform/ModIndex.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -256,15 +259,38 @@ std::unique_ptr ModrinthCreationTask::createInstance() return nullptr; } qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; - auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); + + QString loader; + if (m_instance.has_value()) { + auto* mcInstance = dynamic_cast(m_instance.value()); + if (mcInstance) { + auto* profile = mcInstance->getPackProfile(); + if (profile) { + auto loaders = profile->getModLoadersList(); + if (!loaders.isEmpty()) { + loader = ModPlatform::getModLoaderAsString(loaders.first()); + } + } + } + } + + Net::ModrinthDownloadMeta meta{ + .reason = "modpack", + .gameVersion = m_minecraft_version, + .loader = loader, + }; + + QUrl downloadUrl = file.downloads.dequeue(); + auto dl = Net::ApiDownload::makeFile(downloadUrl, file_path, Net::Download::Option::NoOptions, meta); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(dl); if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] { - auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); + connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods, meta] { + QUrl fallbackUrl = file.downloads.dequeue(); + auto ndl = Net::ApiDownload::makeFile(fallbackUrl, file_path, Net::Download::Option::NoOptions, meta); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(ndl); if (auto shared = param.lock()) diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 9a5a44104..3a3804f5b 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -36,10 +36,10 @@ std::pair ApiDownload::makeByteArray(QUrl url, Downl return { dl, response }; } -Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options) +Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta) { auto dl = Download::makeFile(url, path, options); - dl->addHeaderProxy(std::make_unique()); + dl->addHeaderProxy(std::make_unique(std::move(meta))); return dl; } diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h index 01a31eb17..fe61887c8 100644 --- a/launcher/net/ApiDownload.h +++ b/launcher/net/ApiDownload.h @@ -20,13 +20,15 @@ #pragma once #include "Download.h" +#include "net/ApiHeaderProxy.h" namespace Net { namespace ApiDownload { Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); std::pair makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions); -Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions); +Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions, + ModrinthDownloadMeta meta = ModrinthDownloadMeta()); }; // namespace ApiDownload } // namespace Net diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h index 789a6fada..feb1913e3 100644 --- a/launcher/net/ApiHeaderProxy.h +++ b/launcher/net/ApiHeaderProxy.h @@ -23,11 +23,35 @@ #include "BuildConfig.h" #include "net/HeaderProxy.h" +#include +#include + namespace Net { +struct ModrinthDownloadMeta { + QString reason; + QString gameVersion; + QString loader; + + bool isEmpty() const { return reason.isEmpty(); } + + QByteArray toJson() const + { + QJsonObject obj; + if (!reason.isEmpty()) + obj["reason"] = reason; + if (!gameVersion.isEmpty()) + obj["game_version"] = gameVersion; + if (!loader.isEmpty()) + obj["loader"] = loader; + return QJsonDocument(obj).toJson(QJsonDocument::Compact); + } +}; + class ApiHeaderProxy : public HeaderProxy { public: ApiHeaderProxy() : HeaderProxy() {} + explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {} virtual ~ApiHeaderProxy() = default; public: @@ -39,11 +63,19 @@ class ApiHeaderProxy : public HeaderProxy { } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); - if (!token.isNull()) - hdrs.append({ "Authorization", token.toUtf8() }); + if (!token.isNull()) { + hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() }); + } + } + + if (request.url().host() == "cdn.modrinth.com" && !m_meta.isEmpty()) { + hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() }); } return hdrs; }; + + private: + ModrinthDownloadMeta m_meta; }; } // namespace Net diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 49f263406..8569fadac 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -185,7 +185,7 @@ void ResourceDownloadDialog::confirm() return; } for (const auto& dep : task->getDependecies()) { - addResource(dep->pack, dep->version); + addResource(dep->pack, dep->version, "dependency"); depNames << dep->pack->name; } dependencyExtraInfo = task->getExtraInfo(); @@ -234,10 +234,10 @@ ResourcePage* ResourceDownloadDialog::selectedPage() return result; } -void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, QString downloadReason) { removeResource(pack->name); - selectedPage()->addResourceToPage(pack, ver, getBaseModel()); + selectedPage()->addResourceToPage(pack, ver, getBaseModel(), downloadReason); setButtonStatus(); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index c9d9e568c..3e8da4c33 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -64,7 +64,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* selectedPage(); - void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); + void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, QString downloadReason = "standalone"); void removeResource(const QString&); QList getTasks(); diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 99b01c35d..d4efea78f 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -234,7 +234,7 @@ void ResourceUpdateDialog::checkCandidates() auto changelog = dep->version.changelog; if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - auto download_task = makeShared(dep->pack, dep->version, m_resourceModel); + auto download_task = makeShared(dep->pack, dep->version, m_resourceModel, true, "dependency"); auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); CheckUpdateTask::Update updatable = { dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 706d35378..d655d2d29 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -112,10 +112,10 @@ QMap ModPage::urlHandlers() const /******** Make changes to the UI ********/ -void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* base_model) +void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* base_model, QString downloadReason) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed); + m_model->addPack(pack, version, base_model, is_indexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index a69ee53f7..112a88bb2 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -49,7 +49,7 @@ class ModPage : public ResourcePage { QMap urlHandlers() const override; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*, QString downloadReason = "standalone") override; virtual std::unique_ptr createFilterWidget() = 0; diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index a3a043ba9..2111b4328 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -486,10 +486,11 @@ void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, con void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed) + bool is_indexed, + QString downloadReason) { version.is_currently_selected = true; - m_selected.append(makeShared(pack, version, packs, is_indexed)); + m_selected.append(makeShared(pack, version, packs, is_indexed, downloadReason)); } void ResourceModel::removePack(const QString& rem) diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 573ad8b75..2139588c0 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -94,7 +94,8 @@ class ResourceModel : public QAbstractListModel { void addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed = false); + bool is_indexed = false, + QString downloadReason = "standalone"); void removePack(const QString& rem); QList selectedPacks() { return m_selected; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 931b4a311..126a4c387 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -395,7 +395,10 @@ void ResourcePage::removeResourceFromDialog(const QString& packName) m_parentDialog->removeResource(packName); } -void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, ResourceFolderModel* baseModel) +void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& ver, + ResourceFolderModel* baseModel, + QString downloadReason) { bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_model->addPack(std::move(pack), ver, baseModel, isIndexed); diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index c11edb1d7..7a5acdffe 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -78,7 +78,7 @@ class ResourcePage : public QWidget, public BasePage { void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResourceFromDialog(const QString& packName); virtual void removeResourceFromPage(const QString& name); - virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*); + virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*, QString downloadReason = "standalone"); virtual void modelReset(); diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 99c50352a..3634b0ab4 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -44,10 +44,11 @@ QMap ShaderPackResourcePage::urlHandlers() const void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* base_model) + ResourceFolderModel* base_model, + QString downloadReason) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed); + m_model->addPack(pack, version, base_model, is_indexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 92ddd9f8a..5726cbc16 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -39,7 +39,7 @@ class ShaderPackResourcePage : public ResourcePage { bool supportsFiltering() const override { return false; }; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*, QString downloadReason = "standalone") override; QMap urlHandlers() const override; From a63048d7e2c25bfeaf9d108712e29a847d4727ae Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 13:43:57 +0300 Subject: [PATCH 086/132] chore(clang-tidy): fix clang tidy warnings Signed-off-by: Trial97 --- launcher/ResourceDownloadTask.cpp | 14 +- launcher/ResourceDownloadTask.h | 5 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 236 +++++++------- .../modrinth/ModrinthInstanceCreationTask.h | 18 +- launcher/net/ApiDownload.cpp | 8 +- launcher/net/ApiDownload.h | 4 +- launcher/net/ApiHeaderProxy.h | 17 +- .../ui/dialogs/ResourceDownloadDialog.cpp | 2 +- launcher/ui/dialogs/ResourceDownloadDialog.h | 20 +- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 291 +++++++++--------- launcher/ui/dialogs/ResourceUpdateDialog.h | 2 +- launcher/ui/pages/modplatform/ModPage.cpp | 22 +- launcher/ui/pages/modplatform/ModPage.h | 17 +- .../ui/pages/modplatform/ResourceModel.cpp | 199 +++++++----- launcher/ui/pages/modplatform/ResourceModel.h | 12 +- .../ui/pages/modplatform/ResourcePage.cpp | 2 +- launcher/ui/pages/modplatform/ResourcePage.h | 6 +- .../ui/pages/modplatform/ShaderPackPage.cpp | 11 +- .../ui/pages/modplatform/ShaderPackPage.h | 13 +- 19 files changed, 494 insertions(+), 405 deletions(-) diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index f9eeea790..bb44d2495 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -60,11 +60,11 @@ Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString rea ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed, + bool isIndexed, QString downloadReason) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) { - if (is_indexed) { + if (isIndexed) { m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version)); connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource); @@ -113,8 +113,9 @@ void ResourceDownloadTask::downloadSucceeded() auto oldName = std::get<0>(to_delete); auto oldFilename = std::get<1>(to_delete); - if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) + if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) { return; + } m_pack_model->uninstallResource(oldFilename, true); @@ -126,8 +127,9 @@ void ResourceDownloadTask::downloadSucceeded() if (oldConfig.exists() && !newConfig.exists()) { bool success = FS::move(oldConfig.filePath(), newConfig.filePath()); - if (!success) + if (!success) { emit logWarning(tr("Failed to rename shader config from '%1' to '%2'").arg(oldConfig.fileName(), newConfig.fileName())); + } } } } @@ -135,7 +137,7 @@ void ResourceDownloadTask::downloadSucceeded() void ResourceDownloadTask::downloadFailed(QString reason) { m_filesNetJob.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); } void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) @@ -145,7 +147,7 @@ void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) // This indirection is done so that we don't delete a mod before being sure it was // downloaded successfully! -void ResourceDownloadTask::hasOldResource(QString name, QString filename) +void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename) { to_delete = { name, filename }; } diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 57dfa5eac..84324e99a 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -20,7 +20,6 @@ #pragma once #include "net/NetJob.h" -#include "net/ApiHeaderProxy.h" #include "tasks/SequentialTask.h" #include "minecraft/mod/tasks/LocalResourceUpdateTask.h" @@ -34,7 +33,7 @@ class ResourceDownloadTask : public SequentialTask { explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed = true, + bool isIndexed = true, QString downloadReason = "standalone"); const QString& getFilename() const { return m_pack_version.fileName; } const QVariant& getVersionID() const { return m_pack_version.fileId; } @@ -58,5 +57,5 @@ class ResourceDownloadTask : public SequentialTask { std::tuple to_delete{ "", "" }; private slots: - void hasOldResource(QString name, QString filename); + void hasOldResource(const QString& name, const QString& filename); }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 482a27204..f1717fefe 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -32,114 +32,119 @@ bool ModrinthCreationTask::abort() { - if (!canAbort()) + if (!canAbort()) { return false; + } - if (m_task) + if (m_task) { m_task->abort(); + } return InstanceCreationTask::abort(); } bool ModrinthCreationTask::updateInstance() { - auto instance_list = APPLICATION->instances(); + auto* instanceList = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - BaseInstance* inst; - if (auto original_id = originalInstanceID(); !original_id.isEmpty()) { - inst = instance_list->getInstanceById(original_id); + BaseInstance* inst = nullptr; + if (auto originalId = originalInstanceID(); !originalId.isEmpty()) { + inst = instanceList->getInstanceById(originalId); Q_ASSERT(inst); } else { - inst = instance_list->getInstanceByManagedName(originalName()); + inst = instanceList->getInstanceByManagedName(originalName()); if (!inst) { - inst = instance_list->getInstanceById(originalName()); + inst = instanceList->getInstanceById(originalName()); - if (!inst) + if (!inst) { return false; + } } } - QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (!parseManifest(index_path, m_files, true, false)) + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (!parseManifest(indexPath, m_files, true, false)) { return false; + } - auto version_name = inst->getManagedPackVersionName(); + auto versionName = inst->getManagedPackVersionName(); m_root_path = QFileInfo(inst->gameRoot()).fileName(); - auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; + auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : ""; if (shouldConfirmUpdate()) { - auto should_update = askIfShouldUpdate(m_parent, version_str); - if (should_update == ShouldUpdate::SkipUpdating) + auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr); + if (shouldUpdate == ShouldUpdate::SkipUpdating) { return false; - if (should_update == ShouldUpdate::Cancel) { + } + if (shouldUpdate == ShouldUpdate::Cancel) { m_abort = true; return false; } } // Remove repeated files, we don't need to download them! - QDir old_inst_dir(inst->instanceRoot()); + QDir oldInstDir(inst->instanceRoot()); - QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack")); + QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack")); - QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json")); - QFileInfo old_index_file(old_index_path); - if (old_index_file.exists()) { - std::vector old_files; - parseManifest(old_index_path, old_files, false, false); + QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json")); + QFileInfo oldIndexFile(oldIndexPath); + if (oldIndexFile.exists()) { + std::vector oldFiles; + parseManifest(oldIndexPath, oldFiles, false, false); // Let's remove all duplicated, identical resources! - auto files_iterator = m_files.begin(); + auto filesIterator = m_files.begin(); begin: - while (files_iterator != m_files.end()) { - auto const& file = *files_iterator; + while (filesIterator != m_files.end()) { + const auto& file = *filesIterator; - auto old_files_iterator = old_files.begin(); - while (old_files_iterator != old_files.end()) { - auto const& old_file = *old_files_iterator; + auto oldFilesIterator = oldFiles.begin(); + while (oldFilesIterator != oldFiles.end()) { + const auto& oldFile = *oldFilesIterator; - if (old_file.hash == file.hash) { + if (oldFile.hash == file.hash) { qDebug() << "Removed file at" << file.path << "from list of downloads"; - files_iterator = m_files.erase(files_iterator); - old_files_iterator = old_files.erase(old_files_iterator); + filesIterator = m_files.erase(filesIterator); + oldFilesIterator = oldFiles.erase(oldFilesIterator); goto begin; // Sorry :c } - old_files_iterator++; + oldFilesIterator++; } - files_iterator++; + filesIterator++; } - QDir old_minecraft_dir(inst->gameRoot()); + QDir oldMinecraftDir(inst->gameRoot()); // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! - if (!old_files.empty()) { - for (auto const& file : old_files) { - scheduleToDelete(m_parent, old_minecraft_dir, file.path, true); + if (!oldFiles.empty()) { + for (const auto& file : oldFiles) { + scheduleToDelete(m_parent, oldMinecraftDir, file.path, true); } } // We will remove all the previous overrides, to prevent duplicate files! // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. - auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (const auto& entry : old_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder); + for (const auto& entry : oldOverrides) { + scheduleToDelete(m_parent, oldMinecraftDir, entry); } - auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); - for (const auto& entry : old_client_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + auto oldClientOverrides = Override::readOverrides("client-overrides", oldIndexFolder); + for (const auto& entry : oldClientOverrides) { + scheduleToDelete(m_parent, oldMinecraftDir, entry); } } else { // We don't have an old index file, so we may duplicate stuff! - auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), - tr("We couldn't find a suitable index file for the older version. This may cause some " - "of the files to be duplicated. Do you want to continue?"), - QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + auto* dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some " + "of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); if (dialog->exec() == QDialog::DialogCode::Rejected) { m_abort = true; @@ -161,39 +166,40 @@ std::unique_ptr ModrinthCreationTask::createInstance() { QEventLoop loop; - QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); + QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack")); - QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (m_files.empty() && !parseManifest(index_path, m_files, true, true)) + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) { return nullptr; + } // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); - FS::ensureFilePathExists(new_index_place); - FS::move(index_path, new_index_place); + QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json")); + FS::ensureFilePathExists(newIndexPlace); + FS::move(indexPath, newIndexPlace); auto mcPath = FS::PathCombine(m_stagingPath, m_root_path); - auto override_path = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(override_path)) { + auto overridePath = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(overridePath)) { // Create a list of overrides in "overrides.txt" inside mrpack/ - Override::createOverrides("overrides", parent_folder, override_path); + Override::createOverrides("overrides", parentFolder, overridePath); // Apply the overrides - if (!FS::move(override_path, mcPath)) { + if (!FS::move(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return nullptr; } } // Do client overrides - auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); - if (QFile::exists(client_override_path)) { + auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides"); + if (QFile::exists(clientOverridePath)) { // Create a list of overrides in "client-overrides.txt" inside mrpack/ - Override::createOverrides("client-overrides", parent_folder, client_override_path); + Override::createOverrides("client-overrides", parentFolder, clientOverridePath); // Apply the overrides - if (!FS::overrideFolder(mcPath, client_override_path)) { + if (!FS::overrideFolder(mcPath, clientOverridePath)) { setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); return nullptr; } @@ -203,18 +209,22 @@ std::unique_ptr ModrinthCreationTask::createInstance() auto instanceSettings = std::make_unique(configPath); auto instance = std::make_unique(m_globalSettings, std::move(instanceSettings), m_stagingPath); - auto components = instance->getPackProfile(); + auto* components = instance->getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_minecraft_version, true); - if (!m_fabric_version.isEmpty()) + if (!m_fabric_version.isEmpty()) { components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); - if (!m_quilt_version.isEmpty()) + } + if (!m_quilt_version.isEmpty()) { components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); - if (!m_forge_version.isEmpty()) + } + if (!m_forge_version.isEmpty()) { components->setComponentVersion("net.minecraftforge", m_forge_version); - if (!m_neoForge_version.isEmpty()) + } + if (!m_neoForge_version.isEmpty()) { components->setComponentVersion("net.neoforged", m_neoForge_version); + } if (m_instIcon != "default") { instance->setIconKey(m_instIcon); @@ -223,34 +233,35 @@ std::unique_ptr ModrinthCreationTask::createInstance() } // Don't add managed info to packs without an ID (most likely imported from ZIP) - if (!m_managed_id.isEmpty()) + if (!m_managed_id.isEmpty()) { instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); - else + } else { instance->setManagedPack("modrinth", "", name(), "", ""); + } instance->setName(name()); instance->saveNow(); auto downloadMods = makeShared(tr("Mod Download Modrinth"), APPLICATION->network()); - auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); - auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); + auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path); + auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath); // TODO make this work with other sorts of resource QHash resources; for (auto& file : m_files) { auto fileName = file.path; fileName = FS::RemoveInvalidPathChars(fileName); - auto file_path = FS::PathCombine(root_modpack_path, fileName); - if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) { + auto filePath = FS::PathCombine(rootModpackPath, fileName); + if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) { // This means we somehow got out of the root folder, so abort here to prevent exploits setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.") .arg(fileName)); return nullptr; } if (fileName.startsWith("mods/")) { - auto mod = new Mod(file_path); + auto* mod = new Mod(filePath); ModDetails d; - d.mod_id = file_path; + d.mod_id = filePath; mod->setDetails(d); resources[file.hash.toHex()] = mod; } @@ -258,7 +269,7 @@ std::unique_ptr ModrinthCreationTask::createInstance() setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName)); return nullptr; } - qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; + qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath; QString loader; if (m_instance.has_value()) { @@ -281,29 +292,30 @@ std::unique_ptr ModrinthCreationTask::createInstance() }; QUrl downloadUrl = file.downloads.dequeue(); - auto dl = Net::ApiDownload::makeFile(downloadUrl, file_path, Net::Download::Option::NoOptions, meta); + auto dl = Net::ApiDownload::makeFile(downloadUrl, filePath, Net::Download::Option::NoOptions, meta); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(dl); if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods, meta] { + connect(dl.get(), &Task::failed, [&file, filePath, param, downloadMods, meta] { QUrl fallbackUrl = file.downloads.dequeue(); - auto ndl = Net::ApiDownload::makeFile(fallbackUrl, file_path, Net::Download::Option::NoOptions, meta); + auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(ndl); - if (auto shared = param.lock()) + if (auto shared = param.lock()) { shared->succeeded(); + } }); } } - bool ended_well = false; + bool endedWell = false; - connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; }); - connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) { - ended_well = false; + connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; }); + connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) { + endedWell = false; setError(reason); }); connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit); @@ -319,8 +331,8 @@ std::unique_ptr ModrinthCreationTask::createInstance() loop.exec(); - if (!ended_well) { - for (auto resource : resources) { + if (!endedWell) { + for (auto* resource : resources) { delete resource; } return nullptr; @@ -329,7 +341,7 @@ std::unique_ptr ModrinthCreationTask::createInstance() QEventLoop ensureMetaLoop; QDir folder = FS::PathCombine(instance->modsRoot(), ".index"); auto ensureMetadataTask = makeShared(resources, folder, ModPlatform::ResourceProvider::MODRINTH); - connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; }); + connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; }); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); @@ -341,40 +353,38 @@ std::unique_ptr ModrinthCreationTask::createInstance() m_task = ensureMetadataTask; ensureMetaLoop.exec(); - for (auto resource : resources) { + for (auto* resource : resources) { delete resource; } resources.clear(); // Update information of the already installed instance, if any. - if (m_instance && ended_well) { + if (m_instance && endedWell) { setAbortable(false); - auto inst = m_instance.value(); + auto* inst = m_instance.value(); // Only change the name if it didn't use a custom name, so that the previous custom name // is preserved, but if we're using the original one, we update the version string. // NOTE: This needs to come before the copyManagedPack call! if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) { - if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) + if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) { inst->setName(instance->name()); + } } inst->copyManagedPack(*instance); } - if (ended_well) { + if (endedWell) { return instance; } return nullptr; } -bool ModrinthCreationTask::parseManifest(const QString& index_path, - std::vector& files, - bool set_internal_data, - bool show_optional_dialog) +bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector& files, bool setInternalData, bool showOptionalDialog) { try { - auto doc = Json::requireDocument(index_path); + auto doc = Json::requireDocument(indexPath); auto obj = Json::requireObject(doc, "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); if (formatVersion == 1) { @@ -383,9 +393,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, throw JSONValidationError("Unknown game: " + game); } - if (set_internal_data) { - if (m_managed_version_id.isEmpty()) + if (setInternalData) { + if (m_managed_version_id.isEmpty()) { m_managed_version_id = obj["versionId"].toString(); + } m_managed_name = obj["name"].toString(); } @@ -401,7 +412,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, QString support = env["client"].toString("unsupported"); if (support == "unsupported") { continue; - } else if (support == "optional") { + } + if (support == "optional") { file.required = false; } } @@ -413,20 +425,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) - auto download_arr = modInfo["downloads"].toArray(); - for (auto download : download_arr) { + auto downloadArr = modInfo["downloads"].toArray(); + for (auto download : downloadArr) { qWarning() << download.toString(); - bool is_last = download.toString() == download_arr.last().toString(); + bool isLast = download.toString() == downloadArr.last().toString(); - auto download_url = QUrl(download.toString()); + auto downloadUrl = QUrl(download.toString()); - if (!download_url.isValid()) { + if (!downloadUrl.isValid()) { qDebug() - << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path); - if (is_last && file.downloads.isEmpty()) + << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(downloadUrl.toString(), file.path); + if (isLast && file.downloads.isEmpty()) { throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } } else { - file.downloads.push_back(download_url); + file.downloads.push_back(downloadUrl); } } @@ -434,10 +447,11 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } if (!optionalFiles.empty()) { - if (show_optional_dialog) { + if (showOptionalDialog) { QStringList oFiles; - for (auto file : optionalFiles) + for (const auto& file : optionalFiles) { oFiles.push_back(file.path); + } OptionalModDialog optionalModDialog(m_parent, oFiles); if (optionalModDialog.exec() == QDialog::Rejected) { emitAborted(); @@ -460,7 +474,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } } } - if (set_internal_data) { + if (setInternalData) { auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 01cc8755a..c8835e142 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -24,18 +24,18 @@ class ModrinthCreationTask final : public InstanceCreationTask { }; public: - ModrinthCreationTask(QString staging_path, - SettingsObject* global_settings, + ModrinthCreationTask(const QString& stagingPath, + SettingsObject* globalSettings, QWidget* parent, QString id, - QString version_id = {}, - QString original_instance_id = {}) - : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id)) + QString versionId = {}, + QString originalInstanceId = {}) + : m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(versionId)) { - setStagingPath(staging_path); - setParentSettings(global_settings); + setStagingPath(stagingPath); + setParentSettings(globalSettings); - m_original_instance_id = std::move(original_instance_id); + m_original_instance_id = std::move(originalInstanceId); } bool abort() override; @@ -44,7 +44,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { std::unique_ptr createInstance() override; private: - bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool setInternalData = true, bool showOptionalDialog = true); private: QWidget* m_parent = nullptr; diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 3a3804f5b..54d3e746e 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -18,27 +18,29 @@ */ #include "net/ApiDownload.h" + +#include #include "net/ApiHeaderProxy.h" namespace Net { Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options) { - auto dl = Download::makeCached(url, entry, options); + auto dl = Download::makeCached(std::move(url), std::move(entry), options); dl->addHeaderProxy(std::make_unique()); return dl; } std::pair ApiDownload::makeByteArray(QUrl url, Download::Options options) { - auto [dl, response] = Download::makeByteArray(url, options); + auto [dl, response] = Download::makeByteArray(std::move(url), options); dl->addHeaderProxy(std::make_unique()); return { dl, response }; } Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta) { - auto dl = Download::makeFile(url, path, options); + auto dl = Download::makeFile(std::move(url), std::move(path), options); dl->addHeaderProxy(std::make_unique(std::move(meta))); return dl; } diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h index fe61887c8..033dd0e12 100644 --- a/launcher/net/ApiDownload.h +++ b/launcher/net/ApiDownload.h @@ -27,7 +27,9 @@ namespace Net { namespace ApiDownload { Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); std::pair makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions); -Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions, +Download::Ptr makeFile(QUrl url, + QString path, + Download::Options options = Download::Option::NoOptions, ModrinthDownloadMeta meta = ModrinthDownloadMeta()); }; // namespace ApiDownload diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h index feb1913e3..d48379617 100644 --- a/launcher/net/ApiHeaderProxy.h +++ b/launcher/net/ApiHeaderProxy.h @@ -38,28 +38,31 @@ struct ModrinthDownloadMeta { QByteArray toJson() const { QJsonObject obj; - if (!reason.isEmpty()) + if (!reason.isEmpty()) { obj["reason"] = reason; - if (!gameVersion.isEmpty()) + } + if (!gameVersion.isEmpty()) { obj["game_version"] = gameVersion; - if (!loader.isEmpty()) + } + if (!loader.isEmpty()) { obj["loader"] = loader; + } return QJsonDocument(obj).toJson(QJsonDocument::Compact); } }; class ApiHeaderProxy : public HeaderProxy { public: - ApiHeaderProxy() : HeaderProxy() {} + ApiHeaderProxy() = default; explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {} - virtual ~ApiHeaderProxy() = default; + ~ApiHeaderProxy() override = default; public: - virtual QList headers(const QNetworkRequest& request) const override + QList headers(const QNetworkRequest& request) const override { QList hdrs; if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { - hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() }); + hdrs.append({ .headerName = "x-api-key", .headerValue = APPLICATION->getFlameAPIKey().toUtf8() }); } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 8569fadac..bcb30c761 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -237,7 +237,7 @@ ResourcePage* ResourceDownloadDialog::selectedPage() void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, QString downloadReason) { removeResource(pack->name); - selectedPage()->addResourceToPage(pack, ver, getBaseModel(), downloadReason); + selectedPage()->addResourceToPage(pack, ver, getBaseModel(), std::move(downloadReason)); setButtonStatus(); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index 3e8da4c33..bea6c7689 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -121,7 +121,10 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false); + explicit ResourcePackDownloadDialog(QWidget* parent, + ResourcePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) @@ -138,7 +141,10 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false); + explicit TexturePackDownloadDialog(QWidget* parent, + TexturePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -155,7 +161,10 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance, bool suppressInitialSearch = false); + explicit ShaderPackDownloadDialog(QWidget* parent, + ShaderPackFolderModel* shaders, + BaseInstance* instance, + bool suppressInitialSearch = false); ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) @@ -172,7 +181,10 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance, bool suppressInitialSearch = false); + explicit DataPackDownloadDialog(QWidget* parent, + DataPackFolderModel* dataPacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index d4efea78f..95589b8e9 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -29,10 +29,12 @@ #include -static std::vector mcVersions(BaseInstance* inst) +namespace { +std::vector mcVersions(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } +} // namespace ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, @@ -58,8 +60,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, void ResourceUpdateDialog::checkCandidates() { // Ensure mods have valid metadata - auto went_well = ensureMetadata(); - if (!went_well) { + auto wentWell = ensureMetadata(); + if (!wentWell) { m_aborted = true; return; } @@ -73,12 +75,12 @@ void ResourceUpdateDialog::checkCandidates() text += tr("Mod name: %1
File name: %2
Reason: %3

").arg(mod->name(), mod->fileinfo().fileName(), reason); } - ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), - tr("Could not generate metadata for the following resources:
" - "Do you wish to proceed without those resources?"), - text); - message_dialog.setModal(true); - if (message_dialog.exec() == QDialog::Rejected) { + ScrollMessageBox messageDialog(m_parent, tr("Metadata generation failed"), + tr("Could not generate metadata for the following resources:
" + "Do you wish to proceed without those resources?"), + text); + messageDialog.setModal(true); + if (messageDialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; @@ -87,40 +89,41 @@ void ResourceUpdateDialog::checkCandidates() auto versions = mcVersions(m_instance); - SequentialTask check_task(tr("Checking for updates")); + SequentialTask checkTask(tr("Checking for updates")); if (!m_modrinthToUpdate.empty()) { m_modrinthCheckTask.reset(new ModrinthCheckUpdate(m_modrinthToUpdate, versions, m_loadersList, m_resourceModel)); connect(m_modrinthCheckTask.get(), &CheckUpdateTask::checkFailed, this, - [this](Resource* resource, QString reason, QUrl recover_url) { - m_failedCheckUpdate.append({ resource, reason, recover_url }); + [this](Resource* resource, const QString& reason, const QUrl& recoverUrl) { + m_failedCheckUpdate.append({ resource, reason, recoverUrl }); }); - check_task.addTask(m_modrinthCheckTask); + checkTask.addTask(m_modrinthCheckTask); } if (!m_flameToUpdate.empty()) { m_flameCheckTask.reset(new FlameCheckUpdate(m_flameToUpdate, versions, m_loadersList, m_resourceModel)); - connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { - m_failedCheckUpdate.append({ resource, reason, recover_url }); - }); - check_task.addTask(m_flameCheckTask); + connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, + [this](Resource* resource, const QString& reason, const QUrl& recoverUrl) { + m_failedCheckUpdate.append({ resource, reason, recoverUrl }); + }); + checkTask.addTask(m_flameCheckTask); } - connect(&check_task, &Task::failed, this, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(&checkTask, &Task::failed, this, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - connect(&check_task, &Task::succeeded, this, [this, &check_task]() { - QStringList warnings = check_task.warnings(); + connect(&checkTask, &Task::succeeded, this, [this, &checkTask]() { + QStringList warnings = checkTask.warnings(); if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); } }); // Check for updates - ProgressDialog progress_dialog(m_parent); - progress_dialog.setSkipButton(true, tr("Abort")); - progress_dialog.setWindowTitle(tr("Checking for updates...")); - auto ret = progress_dialog.execWithTask(&check_task); + ProgressDialog progressDialog(m_parent); + progressDialog.setSkipButton(true, tr("Abort")); + progressDialog.setWindowTitle(tr("Checking for updates...")); + auto ret = progressDialog.execWithTask(&checkTask); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { @@ -133,8 +136,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updates for Modrinth if (m_modrinthCheckTask) { - auto modrinth_updates = m_modrinthCheckTask->getUpdates(); - for (auto& updatable : modrinth_updates) { + auto modrinthUpdates = m_modrinthCheckTask->getUpdates(); + for (auto& updatable : modrinthUpdates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -145,8 +148,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updated for Flame if (m_flameCheckTask) { - auto flame_updates = m_flameCheckTask->getUpdates(); - for (auto& updatable : flame_updates) { + auto flameUpdates = m_flameCheckTask->getUpdates(); + for (auto& updatable : flameUpdates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -161,33 +164,35 @@ void ResourceUpdateDialog::checkCandidates() for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); const auto& reason = std::get<1>(failed); - const auto& recover_url = std::get<2>(failed); + const auto& recoverUrl = std::get<2>(failed); qDebug() << mod->name() << "failed to check for updates!"; text += tr("Mod name: %1").arg(mod->name()) + "
"; - if (!reason.isEmpty()) + if (!reason.isEmpty()) { text += tr("Reason: %1").arg(reason) + "
"; - if (!recover_url.isEmpty()) + } + if (!recoverUrl.isEmpty()) { //: %1 is the link to download it manually text += tr("Possible solution: Getting the latest version manually:
%1
") - .arg(QString("%1").arg(recover_url.toString())); + .arg(QString("%1").arg(recoverUrl.toString())); + } text += "
"; } - ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), - tr("Could not check or get the following resources for updates:
" - "Do you wish to proceed without those resources?"), - text, "Disable unavailable mods"); - message_dialog.setModal(true); - if (message_dialog.exec() == QDialog::Rejected) { + ScrollMessageBox messageDialog(m_parent, tr("Failed to check for updates"), + tr("Could not check or get the following resources for updates:
" + "Do you wish to proceed without those resources?"), + text, "Disable unavailable mods"); + messageDialog.setModal(true); + if (messageDialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } // Disable unavailable mods - if (message_dialog.isOptionChecked()) { + if (messageDialog.isOptionChecked()) { for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); mod->enable(EnableAction::DISABLE); @@ -196,10 +201,10 @@ void ResourceUpdateDialog::checkCandidates() } if (m_includeDeps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto* mod_model = dynamic_cast(m_resourceModel); + auto* modModel = dynamic_cast(m_resourceModel); - if (mod_model != nullptr) { - auto depTask = makeShared(m_instance, mod_model, selectedVers); + if (modModel != nullptr) { + auto depTask = makeShared(m_instance, modModel, selectedVers); connect(depTask.get(), &Task::failed, this, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); @@ -215,10 +220,10 @@ void ResourceUpdateDialog::checkCandidates() } }); - ProgressDialog progress_dialog_deps(m_parent); - progress_dialog_deps.setSkipButton(true, tr("Abort")); - progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); - auto dret = progress_dialog_deps.execWithTask(depTask.get()); + ProgressDialog progressDialogDeps(m_parent); + progressDialogDeps.setSkipButton(true, tr("Abort")); + progressDialogDeps.setWindowTitle(tr("Checking for dependencies...")); + auto dret = progressDialogDeps.execWithTask(depTask.get()); // If the dialog was skipped / some download error happened if (dret == QDialog::DialogCode::Rejected) { @@ -226,19 +231,20 @@ void ResourceUpdateDialog::checkCandidates() QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } - static FlameAPI api; + static FlameAPI s_api; auto dependencyExtraInfo = depTask->getExtraInfo(); for (const auto& dep : depTask->getDependecies()) { auto changelog = dep->version.changelog; - if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) - changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - auto download_task = makeShared(dep->pack, dep->version, m_resourceModel, true, "dependency"); + if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) { + changelog = s_api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); + } + auto downloadTask = makeShared(dep->pack, dep->version, m_resourceModel, true, "dependency"); auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); CheckUpdateTask::Update updatable = { dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed + changelog, dep->pack->provider, downloadTask, !extraInfo.maybe_installed }; appendResource(updatable, extraInfo.required_by); @@ -264,56 +270,58 @@ void ResourceUpdateDialog::checkCandidates() } } - if (m_aborted || m_noUpdates) + if (m_aborted || m_noUpdates) { QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); + } } // Part 1: Ensure we have a valid metadata auto ResourceUpdateDialog::ensureMetadata() -> bool { - auto index_dir = indexDir(); + auto indexDir2 = indexDir(); SequentialTask seq(tr("Looking for metadata")); // A better use of data structures here could remove the need for this QHash - QHash should_try_others; - QList modrinth_tmp; - QList flame_tmp; + QHash shouldTryOthers; + QList modrinthTmp; + QList flameTmp; - bool confirm_rest = false; - bool try_others_rest = false; - bool skip_rest = false; - ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; + bool confirmRest = false; + bool tryOthersRest = false; + bool skipRest = false; + ModPlatform::ResourceProvider providerRest = ModPlatform::ResourceProvider::MODRINTH; // adds resource to list based on provider - auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { + auto addToTmp = [&modrinthTmp, &flameTmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: - modrinth_tmp.push_back(resource); + modrinthTmp.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - flame_tmp.push_back(resource); + flameTmp.push_back(resource); break; } }; // ask the user on what provider to seach for the mod first - for (auto candidate : m_candidates) { + for (auto* candidate : m_candidates) { if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); continue; } - if (skip_rest) + if (skipRest) { continue; + } if (candidate->type() == ResourceType::FOLDER) { continue; } - if (confirm_rest) { - addToTmp(candidate, provider_rest); - should_try_others.insert(candidate->internal_id(), try_others_rest); + if (confirmRest) { + addToTmp(candidate, providerRest); + shouldTryOthers.insert(candidate->internal_id(), tryOthersRest); continue; } @@ -326,68 +334,73 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool auto response = chooser.getResponse(); - if (response.skip_all) - skip_rest = true; + if (response.skip_all) { + skipRest = true; + } if (response.confirm_all) { - confirm_rest = true; - provider_rest = response.chosen; - try_others_rest = response.try_others; + confirmRest = true; + providerRest = response.chosen; + tryOthersRest = response.try_others; } - should_try_others.insert(candidate->internal_id(), response.try_others); + shouldTryOthers.insert(candidate->internal_id(), response.try_others); - if (confirmed) + if (confirmed) { addToTmp(candidate, response.chosen); + } } // prepare task for the modrinth mods - if (!modrinth_tmp.empty()) { - auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); + if (!modrinthTmp.empty()) { + auto modrinthTask = makeShared(modrinthTmp, indexDir2, ModPlatform::ResourceProvider::MODRINTH); + connect(modrinthTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(modrinthTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); - connect(modrinth_task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(modrinthTask.get(), &EnsureMetadataTask::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (modrinth_task->getHashingTask()) - seq.addTask(modrinth_task->getHashingTask()); + if (modrinthTask->getHashingTask()) { + seq.addTask(modrinthTask->getHashingTask()); + } - seq.addTask(modrinth_task); + seq.addTask(modrinthTask); } // prepare task for the flame mods - if (!flame_tmp.empty()) { - auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); - connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); + if (!flameTmp.empty()) { + auto flameTask = makeShared(flameTmp, indexDir2, ModPlatform::ResourceProvider::FLAME); + connect(flameTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(flameTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); - connect(flame_task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(flameTask.get(), &EnsureMetadataTask::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (flame_task->getHashingTask()) - seq.addTask(flame_task->getHashingTask()); + if (flameTask->getHashingTask()) { + seq.addTask(flameTask->getHashingTask()); + } - seq.addTask(flame_task); + seq.addTask(flameTask); } seq.addTask(m_secondTryMetadata); // execute all the tasks - ProgressDialog checking_dialog(m_parent); - checking_dialog.setSkipButton(true, tr("Abort")); - checking_dialog.setWindowTitle(tr("Generating metadata...")); - auto ret_metadata = checking_dialog.execWithTask(&seq); + ProgressDialog checkingDialog(m_parent); + checkingDialog.setSkipButton(true, tr("Abort")); + checkingDialog.setWindowTitle(tr("Generating metadata...")); + auto retMetadata = checkingDialog.execWithTask(&seq); - return (ret_metadata != QDialog::DialogCode::Rejected); + return (retMetadata != QDialog::DialogCode::Rejected); } void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) { // When the mod is a folder, for instance - if (!resource->metadata()) + if (!resource->metadata()) { return; + } switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: @@ -411,12 +424,12 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) return ModPlatform::ResourceProvider::FLAME; } -void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice) +void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, ModPlatform::ResourceProvider firstChoice) { - if (try_others) { - auto index_dir = indexDir(); + if (tryOthers) { + auto indexDir2 = indexDir(); - auto task = makeShared(resource, index_dir, next(first_choice)); + auto task = makeShared(resource, indexDir2, next(firstChoice)); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::failed, @@ -436,57 +449,57 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, } } -void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) +void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy) { - auto item_top = new QTreeWidgetItem(ui->modTreeWidget); - item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + auto* itemTop = new QTreeWidgetItem(ui->modTreeWidget); + itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); if (!info.enabled) { - item_top->setToolTip(0, tr("Mod was disabled as it may be already installed.")); + itemTop->setToolTip(0, tr("Mod was disabled as it may be already installed.")); } - item_top->setText(0, info.name); - item_top->setExpanded(true); + itemTop->setText(0, info.name); + itemTop->setExpanded(true); - auto provider_item = new QTreeWidgetItem(item_top); - QString provider_name = ModPlatform::ProviderCapabilities::readableName(info.provider); - provider_item->setText(0, tr("Provider: %1").arg(provider_name)); - provider_item->setData(0, Qt::UserRole, provider_name); + auto* providerItem = new QTreeWidgetItem(itemTop); + QString providerName = ModPlatform::ProviderCapabilities::readableName(info.provider); + providerItem->setText(0, tr("Provider: %1").arg(providerName)); + providerItem->setData(0, Qt::UserRole, providerName); - auto old_version_item = new QTreeWidgetItem(item_top); - old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); - old_version_item->setData(0, Qt::UserRole, info.old_version); + auto* oldVersionItem = new QTreeWidgetItem(itemTop); + oldVersionItem->setText(0, tr("Old version: %1").arg(info.old_version)); + oldVersionItem->setData(0, Qt::UserRole, info.old_version); - auto new_version_item = new QTreeWidgetItem(item_top); - new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); - new_version_item->setData(0, Qt::UserRole, info.new_version); + auto* newVersionItem = new QTreeWidgetItem(itemTop); + newVersionItem->setText(0, tr("New version: %1").arg(info.new_version)); + newVersionItem->setData(0, Qt::UserRole, info.new_version); if (info.new_version_type.has_value()) { - auto new_version_type_item = new QTreeWidgetItem(item_top); - new_version_type_item->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString())); - new_version_type_item->setData(0, Qt::UserRole, info.new_version_type.value().toString()); + auto* newVersionTypeItem = new QTreeWidgetItem(itemTop); + newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString())); + newVersionTypeItem->setData(0, Qt::UserRole, info.new_version_type.value().toString()); } if (!requiredBy.isEmpty()) { - auto requiredByItem = new QTreeWidgetItem(item_top); + auto* requiredByItem = new QTreeWidgetItem(itemTop); if (requiredBy.length() == 1) { requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back())); requiredByItem->setData(0, Qt::UserRole, requiredBy.back()); } else { requiredByItem->setText(0, tr("Required by:")); - for (auto req : requiredBy) { - auto reqItem = new QTreeWidgetItem(requiredByItem); + for (const auto& req : requiredBy) { + auto* reqItem = new QTreeWidgetItem(requiredByItem); reqItem->setText(0, req); } } ui->toggleDepsButton->show(); - m_deps << item_top; + m_deps << itemTop; } - auto changelog_item = new QTreeWidgetItem(item_top); - changelog_item->setText(0, tr("Changelog of the latest version")); + auto* changelogItem = new QTreeWidgetItem(itemTop); + changelogItem->setText(0, tr("Changelog of the latest version")); - auto changelog = new QTreeWidgetItem(changelog_item); - auto changelog_area = new QTextBrowser(); + auto* changelog = new QTreeWidgetItem(changelogItem); + auto* changelogArea = new QTextBrowser(); QString text = info.changelog; changelog->setData(0, Qt::UserRole, text); @@ -494,14 +507,14 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q text = markdownToHTML(info.changelog.toUtf8()); } - changelog_area->setHtml(StringUtils::htmlListPatch(text)); - changelog_area->setOpenExternalLinks(true); - changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); - changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + changelogArea->setHtml(StringUtils::htmlListPatch(text)); + changelogArea->setOpenExternalLinks(true); + changelogArea->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); + changelogArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); + ui->modTreeWidget->setItemWidget(changelog, 0, changelogArea); - ui->modTreeWidget->addTopLevelItem(item_top); + ui->modTreeWidget->addTopLevelItem(itemTop); } auto ResourceUpdateDialog::getTasks() -> const QList diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index ea81aeb7a..9de1ee246 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -39,7 +39,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Resource* resource); void onMetadataFailed(Resource* resource, - bool try_others = false, + bool tryOthers = false, ModPlatform::ResourceProvider firstChoice = ModPlatform::ResourceProvider::MODRINTH); private: diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index d655d2d29..1cdce5e33 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -50,7 +50,6 @@ #include "ResourceDownloadTask.h" #include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" #include "ui/dialogs/ResourceDownloadDialog.h" @@ -63,15 +62,15 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa void ModPage::setFilterWidget(std::unique_ptr& widget) { - if (m_filter_widget) + if (m_filter_widget) { disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); - - auto old = m_ui->splitter->replaceWidget(0, widget.get()); - // because we replaced the widget we also need to delete it - if (old) { - delete old; } + auto* old = m_ui->splitter->replaceWidget(0, widget.get()); + // because we replaced the widget we also need to delete it + + delete old; + m_filter_widget.swap(widget); m_filter = m_filter_widget->getFilter(); @@ -112,10 +111,13 @@ QMap ModPage::urlHandlers() const /******** Make changes to the UI ********/ -void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* base_model, QString downloadReason) +void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + ResourceFolderModel* baseModel, + QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed, downloadReason); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 112a88bb2..7f75995a3 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -29,10 +29,10 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto model = static_cast(page->getModel()); + auto* model = static_cast(page->getModel()); - auto filter_widget = page->createFilterWidget(); - page->setFilterWidget(filter_widget); + auto filterWidget = page->createFilterWidget(); + page->setFilterWidget(filterWidget); model->setFilter(page->getFilter()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); @@ -43,18 +43,21 @@ class ModPage : public ResourcePage { } //: The plural version of 'mod' - inline QString resourcesString() const override { return tr("mods"); } + QString resourcesString() const override { return tr("mods"); } //: The singular version of 'mods' - inline QString resourceString() const override { return tr("mod"); } + QString resourceString() const override { return tr("mod"); } QMap urlHandlers() const override; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*, QString downloadReason = "standalone") override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, + ModPlatform::IndexedVersion& /*unused*/, + ResourceFolderModel* /*unused*/, + QString downloadReason = "standalone") override; virtual std::unique_ptr createFilterWidget() = 0; bool supportsFiltering() const override { return true; }; - auto getFilter() const -> const std::shared_ptr { return m_filter; } + auto getFilter() const -> std::shared_ptr { return m_filter; } void setFilterWidget(std::unique_ptr&); protected: diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 2111b4328..edd8564b6 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -12,10 +12,11 @@ #include #include #include +#include #include "Application.h" -#include "settings/SettingsObject.h" #include "BuildConfig.h" +#include "settings/SettingsObject.h" #include "modplatform/ResourceAPI.h" #include "net/ApiDownload.h" @@ -29,7 +30,7 @@ namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api) +ResourceModel::ResourceModel(ResourceAPI* api) : m_api(api) { s_running_models.insert(this, true); if (APPLICATION_DYN) { @@ -62,14 +63,14 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant } case Qt::DecorationRole: { 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(); + if (auto iconOrNone = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); + iconOrNone.has_value()) { + return iconOrNone.value(); + } return QIcon::fromTheme("screenshot-placeholder"); - } else { - return {}; } + return {}; } case Qt::SizeHintRole: return QSize(0, 58); @@ -112,8 +113,9 @@ QHash ResourceModel::roleNames() const bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); - if (pos >= m_packs.size() || pos < 0 || !index.isValid()) + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { return false; + } m_packs[pos] = value.value(); emit dataChanged(index, index); @@ -128,45 +130,51 @@ QString ResourceModel::debugName() const void ResourceModel::fetchMore(const QModelIndex& parent) { - if (parent.isValid() || m_search_state == SearchState::Finished) + if (parent.isValid() || m_search_state == SearchState::Finished) { return; + } search(); } void ResourceModel::search() { - if (hasActiveSearchJob()) + if (hasActiveSearchJob()) { return; + } if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) { auto projectId = m_search_term.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](QString reason, int networkErrorCode) { + if (!s_running_models.constFind(this).value()) { return; - if (network_error_code == 404) { + } + if (networkErrorCode == 404) { m_search_state = SearchState::ResetRequested; } - searchRequestFailed(reason, network_error_code); + searchRequestFailed(std::move(reason), networkErrorCode); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestAborted(); }; callbacks.on_succeed = [this](auto& pack) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestForOneSucceeded(pack); }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) + if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) { runSearchJob(job); + } return; } } @@ -175,31 +183,36 @@ void ResourceModel::search() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestSucceeded(doc); }; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](QString reason, int networkErrorCode) { + if (!s_running_models.constFind(this).value()) { return; - searchRequestFailed(reason, network_error_code); + } + searchRequestFailed(std::move(reason), networkErrorCode); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestAborted(); }; - if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) { runSearchJob(job); + } } void ResourceModel::loadEntry(const QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; + const auto& pack = m_packs[entry.row()]; - if (!hasActiveInfoJob()) + if (!hasActiveInfoJob()) { m_current_info_job.clear(); + } if (!pack->versionsLoaded) { auto args{ createVersionsArguments(entry) }; @@ -207,20 +220,24 @@ void ResourceModel::loadEntry(const QModelIndex& entry) auto addonId = pack->addonId; // Use default if no callbacks are set - if (!callbacks.on_succeed) + if (!callbacks.on_succeed) { callbacks.on_succeed = [this, entry, addonId](auto& doc) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } versionRequestSucceeded(doc, addonId, entry); }; - if (!callbacks.on_fail) - callbacks.on_fail = [](QString reason, int) { + } + if (!callbacks.on_fail) { + callbacks.on_fail = [](const QString& reason, int) { QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions: %1").arg(reason)); }; + } - if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) { runInfoJob(job); + } } if (!pack->extraDataLoaded) { @@ -228,41 +245,45 @@ void ResourceModel::loadEntry(const QModelIndex& entry) ResourceAPI::Callback callbacks{}; callbacks.on_succeed = [this, entry](auto& newpack) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } infoRequestSucceeded(newpack, entry); }; - callbacks.on_fail = [this](QString reason, int) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](const QString& reason, int) { + if (!s_running_models.constFind(this).value()) { return; + } QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason)); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } qCritical() << tr("The request was aborted for an unknown reason"); }; - if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) { runInfoJob(job); + } } } void ResourceModel::refresh() { - bool reset_requested = false; + bool resetRequested = false; if (hasActiveInfoJob()) { m_current_info_job.abort(); - reset_requested = true; + resetRequested = true; } if (hasActiveSearchJob()) { m_current_search_job->abort(); - reset_requested = true; + resetRequested = true; } - if (reset_requested) { + if (resetRequested) { m_search_state = SearchState::ResetRequested; return; } @@ -288,13 +309,15 @@ void ResourceModel::runSearchJob(Task::Ptr ptr) } void ResourceModel::runInfoJob(Task::Ptr ptr) { - if (!m_current_info_job.isRunning()) + if (!m_current_info_job.isRunning()) { m_current_info_job.clear(); + } - m_current_info_job.addTask(ptr); + m_current_info_job.addTask(std::move(ptr)); - if (!m_current_info_job.isRunning()) + if (!m_current_info_job.isRunning()) { m_current_info_job.run(); + } } std::optional ResourceModel::getCurrentSortingMethodByIndex() const @@ -302,11 +325,12 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional sort{}; { // Find sorting method by ID - auto sorting_methods = getSortingMethods(); - auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(), - [this](auto const& e) { return m_current_sort_index == e.index; }); - if (method != sorting_methods.constEnd()) + auto sortingMethods = getSortingMethods(); + auto method = std::find_if(sortingMethods.constBegin(), sortingMethods.constEnd(), + [this](const auto& e) { return m_current_sort_index == e.index; }); + if (method != sortingMethods.constEnd()) { sort = *method; + } } return sort; @@ -315,43 +339,47 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) { QPixmap pixmap; - if (QPixmapCache::find(url.toString(), &pixmap)) + if (QPixmapCache::find(url.toString(), &pixmap)) { return { pixmap }; + } if (!m_current_icon_job) { m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network())); m_current_icon_job->setAskRetry(false); } - if (m_currently_running_icon_actions.contains(url)) + if (m_currently_running_icon_actions.contains(url)) { return {}; - if (m_failed_icon_actions.contains(url)) + } + if (m_failed_icon_actions.contains(url)) { return {}; + } - auto cache_entry = APPLICATION->metacache()->resolveEntry( + auto cacheEntry = APPLICATION->metacache()->resolveEntry( metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); - auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); + auto iconFetchAction = Net::ApiDownload::makeCached(url, cacheEntry); - auto full_file_path = cache_entry->getFullPath(); - connect(icon_fetch_action.get(), &Task::succeeded, this, [this, url, full_file_path, index] { - auto icon = QIcon(full_file_path); + auto fullFilePath = cacheEntry->getFullPath(); + connect(iconFetchAction.get(), &Task::succeeded, this, [this, url, fullFilePath, index] { + auto icon = QIcon(fullFilePath); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); m_currently_running_icon_actions.remove(url); emit dataChanged(index, index, { Qt::DecorationRole }); }); - connect(icon_fetch_action.get(), &Task::failed, this, [this, url] { + connect(iconFetchAction.get(), &Task::failed, this, [this, url] { m_currently_running_icon_actions.remove(url); m_failed_icon_actions.insert(url); }); m_currently_running_icon_actions.insert(url); - m_current_icon_job->addNetAction(icon_fetch_action); - if (!m_current_icon_job->isRunning()) + m_current_icon_job->addNetAction(iconFetchAction); + if (!m_current_icon_job->isRunning()) { QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); + } return {}; } @@ -363,11 +391,11 @@ void ResourceModel::searchRequestSucceeded(QList& QList filteredNewList; for (auto pack : newList) { ModPlatform::IndexedPack::Ptr p; - if (auto sel = std::find_if(m_selected.begin(), m_selected.end(), - [&pack](const DownloadTaskPtr i) { - const auto ipack = i->getPack(); - return ipack->provider == pack->provider && ipack->addonId == pack->addonId; - }); + if (auto sel = std::ranges::find_if(m_selected, + [&pack](const DownloadTaskPtr& i) { + const auto ipack = i->getPack(); + return ipack->provider == pack->provider && ipack->addonId == pack->addonId; + }); sel != m_selected.end()) { p = sel->get()->getPack(); } else { @@ -386,8 +414,9 @@ void ResourceModel::searchRequestSucceeded(QList& } // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (filteredNewList.size() == 0) + if (filteredNewList.size() == 0) { return; + } beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1); m_packs.append(filteredNewList); @@ -403,9 +432,9 @@ void ResourceModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr p endInsertRows(); } -void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) +void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int networkErrorCode) { - switch (network_error_code) { + switch (networkErrorCode) { default: // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); @@ -432,8 +461,9 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net void ResourceModel::searchRequestAborted() { - if (m_search_state != SearchState::ResetRequested) + if (m_search_state != SearchState::ResetRequested) { qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; + } // Retry fetching clearData(); @@ -444,19 +474,20 @@ void ResourceModel::searchRequestAborted() void ResourceModel::versionRequestSucceeded(QVector& doc, QVariant pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto currentPack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack != current_pack->addonId) + if (pack != currentPack->addonId) { return; + } - current_pack->versions = doc; - current_pack->versionsLoaded = true; + currentPack->versions = doc; + currentPack->versionsLoaded = true; // Cache info :^) - QVariant new_pack; - new_pack.setValue(current_pack); - if (!setData(index, new_pack, Qt::UserRole)) { + QVariant newPack; + newPack.setValue(currentPack); + if (!setData(index, newPack, Qt::UserRole)) { qWarning() << "Failed to cache resource versions!"; return; } @@ -466,16 +497,17 @@ void ResourceModel::versionRequestSucceeded(QVector void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto currentPack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack->addonId != current_pack->addonId) + if (pack->addonId != currentPack->addonId) { return; + } // Cache info :^) - QVariant new_pack; - new_pack.setValue(pack); - if (!setData(index, new_pack, Qt::UserRole)) { + QVariant newPack; + newPack.setValue(pack); + if (!setData(index, newPack, Qt::UserRole)) { qWarning() << "Failed to cache resource info!"; return; } @@ -486,16 +518,16 @@ void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, con void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed, + bool isIndexed, QString downloadReason) { version.is_currently_selected = true; - m_selected.append(makeShared(pack, version, packs, is_indexed, downloadReason)); + m_selected.append(makeShared(std::move(pack), version, packs, isIndexed, std::move(downloadReason))); } void ResourceModel::removePack(const QString& rem) { - auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); }; + auto pred = [&rem](const DownloadTaskPtr& i) { return rem == i->getName(); }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) m_selected.removeIf(pred); #else @@ -507,15 +539,16 @@ void ResourceModel::removePack(const QString& rem) ++it; } #endif - auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; }); + auto pack = std::ranges::find_if(m_packs, [&rem](const ModPlatform::IndexedPack::Ptr& i) { return rem == i->name; }); if (pack == m_packs.end()) { // ignore it if is not in the current search return; } if (!pack->get()->versionsLoaded) { return; } - for (auto& ver : pack->get()->versions) + for (auto& ver : pack->get()->versions) { ver.is_currently_selected = false; + } } bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 2139588c0..9124c0e66 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -36,16 +36,16 @@ class ResourceModel : public QAbstractListModel { ResourceModel(ResourceAPI* api); ~ResourceModel() override; - auto data(const QModelIndex&, int role) const -> QVariant override; + auto data(const QModelIndex& /*index*/, int role) const -> QVariant override; auto roleNames() const -> QHash override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; virtual auto debugName() const -> QString; virtual auto metaEntryBase() const -> QString = 0; - inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } - inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } - inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } + int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } + int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } + auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); } bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); } @@ -66,7 +66,7 @@ class ResourceModel : public QAbstractListModel { public slots: void fetchMore(const QModelIndex& parent) override; - inline bool canFetchMore(const QModelIndex& parent) const override + bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; } @@ -94,7 +94,7 @@ class ResourceModel : public QAbstractListModel { void addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed = false, + bool isIndexed = false, QString downloadReason = "standalone"); void removePack(const QString& rem); QList selectedPacks() { return m_selected; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 126a4c387..8baa0bbec 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -401,7 +401,7 @@ void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, QString downloadReason) { bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(std::move(pack), ver, baseModel, isIndexed); + m_model->addPack(std::move(pack), ver, baseModel, isIndexed, std::move(downloadReason)); } void ResourcePage::modelReset() diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 7a5acdffe..03895dcd4 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -9,7 +9,6 @@ #include "ResourceDownloadTask.h" #include "modplatform/ModIndex.h" -#include "modplatform/ResourceAPI.h" #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ResourceModel.h" @@ -78,7 +77,10 @@ class ResourcePage : public QWidget, public BasePage { void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResourceFromDialog(const QString& packName); virtual void removeResourceFromPage(const QString& name); - virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*, QString downloadReason = "standalone"); + virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, + ModPlatform::IndexedVersion&, + ResourceFolderModel*, + QString downloadReason = "standalone"); virtual void modelReset(); diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 3634b0ab4..ec5fe7967 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -36,19 +36,18 @@ QMap ShaderPackResourcePage::urlHandlers() const { QMap map; map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth"); - map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), - "curseforge"); - map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern(R"((?:www\.)?curseforge\.com\/minecraft\/customization\/([^\/]+)\/?)"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern(R"(minecraft\.curseforge\.com\/projects\/([^\/]+)\/?)"), "curseforge"); return map; } void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* base_model, + ResourceFolderModel* baseModel, QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed, downloadReason); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 5726cbc16..37e3cadef 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -23,7 +23,7 @@ class ShaderPackResourcePage : public ResourcePage { static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto model = static_cast(page->getModel()); + auto* model = static_cast(page->getModel()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); @@ -33,17 +33,20 @@ class ShaderPackResourcePage : public ResourcePage { } //: The plural version of 'shader pack' - inline QString resourcesString() const override { return tr("shader packs"); } + QString resourcesString() const override { return tr("shader packs"); } //: The singular version of 'shader packs' - inline QString resourceString() const override { return tr("shader pack"); } + QString resourceString() const override { return tr("shader pack"); } bool supportsFiltering() const override { return false; }; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*, QString downloadReason = "standalone") override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, + ModPlatform::IndexedVersion& /*unused*/, + ResourceFolderModel* /*unused*/, + QString downloadReason = "standalone") override; QMap urlHandlers() const override; - inline auto helpPage() const -> QString override { return "shaderpack-platform"; } + auto helpPage() const -> QString override { return "shaderpack-platform"; } protected: ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); From f9e007ca2b6d8c361024b9cbc4e45de7c71e936c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 19:57:40 +0300 Subject: [PATCH 087/132] add update reason Signed-off-by: Trial97 --- .../modplatform/flame/FlameCheckUpdate.cpp | 2 +- .../modrinth/ModrinthCheckUpdate.cpp | 2 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 21 ++++++------------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 37f0bcb32..c38c2047e 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -120,7 +120,7 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* old_version = tr("Unknown"); } - auto download_task = makeShared(pack, latest_ver.value(), m_resourceModel); + auto download_task = makeShared(pack, latest_ver.value(), m_resourceModel, true, "update"); m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index bd5a50d1b..26059b88b 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -178,7 +178,7 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto download_task = makeShared(pack, project_ver, m_resourceModel); + auto download_task = makeShared(pack, project_ver, m_resourceModel, true, "update"); QString old_version = resource->metadata()->version_number; if (old_version.isEmpty()) { diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index f1717fefe..0cb2c547d 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -213,17 +213,22 @@ std::unique_ptr ModrinthCreationTask::createInstance() components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_minecraft_version, true); + QString loader; if (!m_fabric_version.isEmpty()) { components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric); } if (!m_quilt_version.isEmpty()) { components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt); } if (!m_forge_version.isEmpty()) { components->setComponentVersion("net.minecraftforge", m_forge_version); + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge); } if (!m_neoForge_version.isEmpty()) { components->setComponentVersion("net.neoforged", m_neoForge_version); + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge); } if (m_instIcon != "default") { @@ -271,22 +276,8 @@ std::unique_ptr ModrinthCreationTask::createInstance() } qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath; - QString loader; - if (m_instance.has_value()) { - auto* mcInstance = dynamic_cast(m_instance.value()); - if (mcInstance) { - auto* profile = mcInstance->getPackProfile(); - if (profile) { - auto loaders = profile->getModLoadersList(); - if (!loaders.isEmpty()) { - loader = ModPlatform::getModLoaderAsString(loaders.first()); - } - } - } - } - Net::ModrinthDownloadMeta meta{ - .reason = "modpack", + .reason = m_instance.has_value() ? "update" : "modpack", .gameVersion = m_minecraft_version, .loader = loader, }; From 97d570b3431502fca3a88689b81c2f159d6a1547 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 20:20:01 +0300 Subject: [PATCH 088/132] chore(clang-tidy): fix clang tidy warnings Signed-off-by: Trial97 --- launcher/modplatform/CheckUpdateTask.h | 31 +++--- .../modplatform/flame/FlameCheckUpdate.cpp | 97 ++++++++++--------- .../modrinth/ModrinthCheckUpdate.cpp | 76 ++++++++------- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 14 +-- 4 files changed, 111 insertions(+), 107 deletions(-) diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index d3f577f44..ad68fd963 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -15,15 +15,15 @@ class CheckUpdateTask : public Task { std::vector& mcVersions, QList loadersList, ResourceFolderModel* resourceModel) - : Task(), m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) + : m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) {} struct Update { QString name; - QString old_hash; - QString old_version; - QString new_version; - std::optional new_version_type; + QString oldHash; + QString oldVersion; + QString newVersion; + std::optional newVersionType; QString changelog; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; @@ -31,19 +31,19 @@ class CheckUpdateTask : public Task { public: Update(QString name, - QString old_h, - QString old_v, - QString new_v, - std::optional new_v_type, + QString oldH, + QString oldV, + QString newV, + std::optional newVType, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr t, bool enabled = true) : name(std::move(name)) - , old_hash(std::move(old_h)) - , old_version(std::move(old_v)) - , new_version(std::move(new_v)) - , new_version_type(std::move(new_v_type)) + , oldHash(std::move(oldH)) + , oldVersion(std::move(oldV)) + , newVersion(std::move(newV)) + , newVersionType(newVType) , changelog(std::move(changelog)) , provider(p) , download(std::move(t)) @@ -54,14 +54,11 @@ class CheckUpdateTask : public Task { auto getUpdates() -> std::vector&& { return std::move(m_updates); } auto getDependencies() -> QList>&& { return std::move(m_deps); } - public slots: - bool abort() override = 0; - protected slots: void executeTask() override = 0; signals: - void checkFailed(Resource* failed, QString reason, QUrl recover_url = {}); + void checkFailed(Resource* failed, QString reason, QUrl recoverUrl = {}); protected: QList& m_resources; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index c38c2047e..5bd13220a 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -18,8 +18,6 @@ #include "net/NetJob.h" #include "tasks/Task.h" -static FlameAPI api; - bool FlameCheckUpdate::abort() { bool result = false; @@ -39,7 +37,7 @@ void FlameCheckUpdate::executeTask() { setStatus(tr("Preparing resources for CurseForge...")); - auto netJob = new NetJob("Get latest versions", APPLICATION->network()); + auto* netJob = new NetJob("Get latest versions", APPLICATION->network()); connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods); connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress); @@ -48,9 +46,10 @@ void FlameCheckUpdate::executeTask() for (auto* resource : m_resources) { auto project = std::make_shared(); project->addonId = resource->metadata()->project_id.toString(); - auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions }); - if (!versionsUrlOptional.has_value()) + auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions }); + if (!versionsUrlOptional.has_value()) { continue; + } auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value()); @@ -63,11 +62,11 @@ void FlameCheckUpdate::executeTask() void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response) { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from latest mod version at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from latest mod version at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; return; } @@ -88,100 +87,104 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* qCritical() << e.what(); qDebug() << doc; } - auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); + auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); - if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { + if (!latestVer.has_value() || !latestVer->addonId.isValid()) { QString reason; - if (dynamic_cast(resource) != nullptr) + if (dynamic_cast(resource) != nullptr) { reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - else + } else { reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + } emit checkFailed(resource, reason); return; } - if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { - m_blocked[resource] = latest_ver->fileId.toString(); + if (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) { + m_blocked[resource] = latestVer->fileId.toString(); return; } - if (!latest_ver->hash.isEmpty() && - (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); + if (!latestVer->hash.isEmpty() && + (resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto oldVersion = resource->metadata()->version_number; + if (oldVersion.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) { + oldVersion = tr("Not installed"); + } else { + oldVersion = tr("Unknown"); + } } - auto download_task = makeShared(pack, latest_ver.value(), m_resourceModel, true, "update"); - m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, - api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); + auto downloadTask = makeShared(pack, latestVer.value(), m_resourceModel, true, "update"); + m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->version_type, + FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()), + ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled()); } - m_deps.append(std::make_shared(pack, latest_ver.value())); + m_deps.append(std::make_shared(pack, latestVer.value())); } void FlameCheckUpdate::collectBlockedMods() { QStringList addonIds; QHash quickSearch; - for (auto const& resource : m_blocked.keys()) { + for (const auto& resource : m_blocked.keys()) { auto addonId = resource->metadata()->project_id.toString(); addonIds.append(addonId); quickSearch[addonId] = resource; } Task::Ptr projTask; - QByteArray* response; + QByteArray* response = nullptr; if (addonIds.isEmpty()) { emitSucceeded(); return; - } else if (addonIds.size() == 1) { - std::tie(projTask, response) = api.getProject(*addonIds.begin()); + } + if (addonIds.size() == 1) { + std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin()); } else { - std::tie(projTask, response) = api.getProjects(addonIds); + std::tie(projTask, response) = FlameAPI().getProjects(addonIds); } connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] { - QJsonParseError parse_error{}; - auto doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame projects task at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + auto doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame projects task at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; return; } try { QJsonArray entries; - if (addonIds.size() == 1) + if (addonIds.size() == 1) { entries = { Json::requireObject(Json::requireObject(doc), "data") }; - else + } else { entries = Json::requireArray(Json::requireObject(doc), "data"); + } for (auto entry : entries) { - auto entry_obj = Json::requireObject(entry); + auto entryObj = Json::requireObject(entry); - auto id = QString::number(Json::requireInteger(entry_obj, "id")); + auto id = QString::number(Json::requireInteger(entryObj, "id")); - auto resource = quickSearch.find(id).value(); + auto* resource = quickSearch.find(id).value(); ModPlatform::IndexedPack pack; try { setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); - FlameMod::loadIndexedPack(pack, entry_obj); - auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); + FlameMod::loadIndexedPack(pack, entryObj); + auto recoverUrl = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), - recover_url); + recoverUrl); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 26059b88b..cb2d57aba 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -53,7 +53,7 @@ void ModrinthCheckUpdate::executeTask() setStatus(tr("Preparing resources for Modrinth...")); setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1); - auto hashing_task = + auto hashingTask = makeShared("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); bool startHasing = false; for (auto* resource : m_resources) { @@ -63,10 +63,11 @@ void ModrinthCheckUpdate::executeTask() // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) if (resource->metadata()->hash_format != m_hashType) { - auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); }); - connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); - hashing_task->addTask(hash_task); + auto hashTask = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, + [this, resource](const QString& hash) { m_mappings.insert(hash, resource); }); + connect(hashTask.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); + hashingTask->addTask(hashTask); startHasing = true; } else { m_mappings.insert(hash, resource); @@ -74,9 +75,9 @@ void ModrinthCheckUpdate::executeTask() } if (startHasing) { - connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); - m_job = hashing_task; - hashing_task->start(); + connect(hashingTask.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); + m_job = hashingTask; + hashingTask->start(); } else { checkNextLoader(); } @@ -120,14 +121,14 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio setStatus(tr("Parsing the API response from Modrinth...")); setProgress(m_progress + 1, m_progressTotal); - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; - emitFailed(parse_error.errorString()); + emitFailed(parseError.errorString()); return; } @@ -138,11 +139,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio const QString hash = iter.key(); Resource* resource = iter.value(); - auto project_obj = doc[hash].toObject(); + auto projectObj = doc[hash].toObject(); // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available - if (project_obj.isEmpty()) { + if (projectObj.isEmpty()) { qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash; ++iter; continue; @@ -150,11 +151,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it - QString loader_filter; + QString loaderFilter; if (loader.has_value() && loader != 0) { auto modLoaders = ModPlatform::modLoaderTypesToList(*loader); if (!modLoaders.isEmpty()) { - loader_filter = ModPlatform::getModLoaderAsString(modLoaders.first()); + loaderFilter = ModPlatform::getModLoaderAsString(modLoaders.first()); } } @@ -164,9 +165,9 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) // Such is the pain of having arbitrary files for a given version .-. - auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hashType, loader_filter); - if (project_ver.downloadUrl.isEmpty()) { - qCritical() << "Modrinth mod without download url!" << project_ver.fileName; + auto projectVer = Modrinth::loadIndexedPackVersion(projectObj, m_hashType, loaderFilter); + if (projectVer.downloadUrl.isEmpty()) { + qCritical() << "Modrinth mod without download url!" << projectVer.fileName; ++iter; continue; } @@ -177,21 +178,22 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio pack->slug = resource->metadata()->slug; pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto download_task = makeShared(pack, project_ver, m_resourceModel, true, "update"); + if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto downloadTask = makeShared(pack, projectVer, m_resourceModel, true, "update"); - QString old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); + QString oldVersion = resource->metadata()->version_number; + if (oldVersion.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) { + oldVersion = tr("Not installed"); + } else { + oldVersion = tr("Unknown"); + } } - m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type, - project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled()); + m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type, + projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled()); } - m_deps.append(std::make_shared(pack, project_ver)); + m_deps.append(std::make_shared(pack, projectVer)); iter = m_mappings.erase(iter); } @@ -211,20 +213,22 @@ void ModrinthCheckUpdate::checkNextLoader() if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize); return; - } else if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader + } + if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader getUpdateModsForLoader(); return; } - for (auto resource : m_mappings) { + for (auto* resource : m_mappings) { QString reason; - if (dynamic_cast(resource) != nullptr) + if (dynamic_cast(resource) != nullptr) { reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - else + } else { reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + } emit checkFailed(resource, reason); } diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 95589b8e9..e229bd672 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -465,17 +465,17 @@ void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, Q providerItem->setData(0, Qt::UserRole, providerName); auto* oldVersionItem = new QTreeWidgetItem(itemTop); - oldVersionItem->setText(0, tr("Old version: %1").arg(info.old_version)); - oldVersionItem->setData(0, Qt::UserRole, info.old_version); + oldVersionItem->setText(0, tr("Old version: %1").arg(info.oldVersion)); + oldVersionItem->setData(0, Qt::UserRole, info.oldVersion); auto* newVersionItem = new QTreeWidgetItem(itemTop); - newVersionItem->setText(0, tr("New version: %1").arg(info.new_version)); - newVersionItem->setData(0, Qt::UserRole, info.new_version); + newVersionItem->setText(0, tr("New version: %1").arg(info.newVersion)); + newVersionItem->setData(0, Qt::UserRole, info.newVersion); - if (info.new_version_type.has_value()) { + if (info.newVersionType.has_value()) { auto* newVersionTypeItem = new QTreeWidgetItem(itemTop); - newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString())); - newVersionTypeItem->setData(0, Qt::UserRole, info.new_version_type.value().toString()); + newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.newVersionType.value().toString())); + newVersionTypeItem->setData(0, Qt::UserRole, info.newVersionType.value().toString()); } if (!requiredBy.isEmpty()) { From 4c9081a934cf2487844918cdc3d7028be95ee183 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 09:12:57 +0300 Subject: [PATCH 089/132] add file name column Signed-off-by: Trial97 --- .../minecraft/mod/DataPackFolderModel.cpp | 19 ++++++++++++------ launcher/minecraft/mod/DataPackFolderModel.h | 2 +- launcher/minecraft/mod/ModFolderModel.cpp | 20 ++++++++++++------- launcher/minecraft/mod/ModFolderModel.h | 1 + launcher/minecraft/mod/Resource.cpp | 10 +++++++++- launcher/minecraft/mod/Resource.h | 1 + .../minecraft/mod/ResourceFolderModel.cpp | 5 +++++ launcher/minecraft/mod/ResourceFolderModel.h | 13 ++++++------ .../minecraft/mod/ResourcePackFolderModel.cpp | 20 ++++++++++++------- .../minecraft/mod/ResourcePackFolderModel.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 16 ++++++++++----- .../minecraft/mod/TexturePackFolderModel.h | 2 +- 12 files changed, 76 insertions(+), 35 deletions(-) diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index f1497b809..446e648d5 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -47,12 +47,13 @@ DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "File Name" }); + m_column_names_translated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("File Name") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::FILENAME }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true }; } QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const @@ -109,6 +110,9 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const case ProviderColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; // FIXME: there is no size column due to an oversight } @@ -129,6 +133,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case PackFormatColumn: case DateColumn: case ImageColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -145,6 +150,8 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this data pack was last changed (or added)."); + case FileNameColumn: + return tr("The file name of the data pack."); default: return {}; } diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h index 2b90e1a2a..3eae4cf84 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.h +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -44,7 +44,7 @@ class DataPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, FileNameColumn, NUM_COLUMNS }; explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 4d54be921..befd3c9dd 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -63,18 +63,18 @@ ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_ : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", - "Minecraft Versions", "Release Type", "Requires", "Required By" }); + "Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" }); m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"), - tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By") }); + tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS, - SortType::RELEASE_TYPE, SortType::REQUIRES, SortType::REQUIRED_BY }; + SortType::RELEASE_TYPE, SortType::REQUIRES, SortType::REQUIRED_BY, SortType::FILENAME }; m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, - QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true }; + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true, true }; connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished); } @@ -155,6 +155,9 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case SizeColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; } if (mappedIndex.isValid()) { @@ -182,6 +185,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case SizeColumn: case RequiredByColumn: case RequiresColumn: + case FileNameColumn: return columnNames().at(section); default: return QVariant(); @@ -213,6 +217,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("For each mod, the number of other mods which depend on it."); case RequiresColumn: return tr("For each mod, the number of other mods it depends on."); + case FileNameColumn: + return tr("The file name of the mod."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 4de875abc..fb1add9e0 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -73,6 +73,7 @@ class ModFolderModel : public ResourceFolderModel { ReleaseTypeColumn, RequiresColumn, RequiredByColumn, + FileNameColumn, NUM_COLUMNS }; ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 692622521..196ea75e6 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -173,6 +173,9 @@ int Resource::compare(const Resource& other, SortType type) const if (dateTimeChanged() < other.dateTimeChanged()) return -1; break; + case SortType::FILENAME: + return fileinfo().fileName().localeAwareCompare(other.fileinfo().fileName()); + case SortType::SIZE: { if (this->type() != other.type()) { if (this->type() == ResourceType::FOLDER) @@ -187,6 +190,7 @@ int Resource::compare(const Resource& other, SortType type) const return -1; break; } + case SortType::PROVIDER: { auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); if (compare_result != 0) @@ -200,7 +204,11 @@ int Resource::compare(const Resource& other, SortType type) const bool Resource::applyFilter(QRegularExpression filter) const { - return filter.match(name()).hasMatch(); + if (filter.match(name()).hasMatch()) + return true; + if (filter.match(fileinfo().fileName()).hasMatch()) + return true; + return false; } bool Resource::enable(EnableAction action) diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 485405b24..8f30c1bf3 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -78,6 +78,7 @@ enum class SortType { RELEASE_TYPE, REQUIRES, REQUIRED_BY, + FILENAME, }; enum class EnableAction { ENABLE, DISABLE, TOGGLE }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 9d3f3e749..2e91cebc3 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -530,6 +530,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->provider(); case SizeColumn: return m_resources[row]->sizeStr(); + case FileNameColumn: + return m_resources[row]->fileinfo().fileName(); default: return {}; } @@ -601,6 +603,7 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case DateColumn: case ProviderColumn: case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -618,6 +621,8 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien return tr("The source provider of the resource."); case SizeColumn: return tr("The size of the resource."); + case FileNameColumn: + return tr("The file name of the resource."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 124b9ea77..e30485d3e 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -137,7 +137,7 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NUM_COLUMNS }; QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } @@ -240,12 +240,13 @@ class ResourceFolderModel : public QAbstractListModel { protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; - QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE, + SortType::FILENAME }; + QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size", "File Name" }; + QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }; QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive }; - QList m_columnsHideable = { false, false, true, true, true }; + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + QList m_columnsHideable = { false, false, true, true, true, true }; QDir m_dir; BaseInstance* m_instance; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index b2a28290a..84bc5ee98 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -46,14 +46,14 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" }); - m_column_names_translated = - QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") }); + m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size", "File Name" }); + m_column_names_translated = QStringList( + { tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, - SortType::DATE, SortType::PROVIDER, SortType::SIZE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true }; + SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::FILENAME }; + m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const @@ -112,6 +112,9 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case SizeColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; } if (mappedIndex.isValid()) { @@ -133,6 +136,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case ImageColumn: case ProviderColumn: case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -153,6 +157,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The source provider of the resource pack."); case SizeColumn: return tr("The size of the resource pack."); + case FileNameColumn: + return tr("The file name of the resource pack."); default: return {}; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index b552c324e..02029cd00 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,7 +7,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NUM_COLUMNS }; explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index d96b768db..59c46fcea 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -41,12 +41,12 @@ TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; + m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size", "File Name" }); + m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::FILENAME }; m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true }; + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true }; } Task* TexturePackFolderModel::createParseTask(Resource& resource) @@ -96,6 +96,9 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case SizeColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; } if (mappedIndex.isValid()) { @@ -116,6 +119,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case ImageColumn: case ProviderColumn: case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -132,6 +136,8 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or return tr("The source provider of the texture pack."); case SizeColumn: return tr("The size of the texture pack."); + case FileNameColumn: + return tr("The file name of the texture pack."); default: return {}; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 37f78d8d7..feef2f5d5 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,7 +44,7 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NUM_COLUMNS }; explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); From bac959bc6f79fd5e2f07b9ca88a52326e0938b91 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 10:22:37 +0300 Subject: [PATCH 090/132] chore(clang-tidy): fix clang tidy warnings Signed-off-by: Trial97 --- launcher/minecraft/mod/DataPack.cpp | 2 +- .../minecraft/mod/DataPackFolderModel.cpp | 37 +- launcher/minecraft/mod/DataPackFolderModel.h | 9 +- launcher/minecraft/mod/Mod.cpp | 24 +- launcher/minecraft/mod/ModFolderModel.cpp | 133 ++--- launcher/minecraft/mod/ModFolderModel.h | 17 +- launcher/minecraft/mod/Resource.cpp | 163 +++--- launcher/minecraft/mod/Resource.h | 61 ++- .../minecraft/mod/ResourceFolderModel.cpp | 483 ++++++++++-------- launcher/minecraft/mod/ResourceFolderModel.h | 58 +-- .../minecraft/mod/ResourcePackFolderModel.cpp | 29 +- .../minecraft/mod/ResourcePackFolderModel.h | 16 +- .../minecraft/mod/ShaderPackFolderModel.h | 2 +- .../minecraft/mod/TexturePackFolderModel.cpp | 28 +- .../minecraft/mod/TexturePackFolderModel.h | 17 +- .../mod/tasks/ResourceFolderLoadTask.cpp | 69 +-- .../mod/tasks/ResourceFolderLoadTask.h | 14 +- launcher/modplatform/EnsureMetadataTask.cpp | 6 +- .../modplatform/flame/FlameCheckUpdate.cpp | 4 +- .../modrinth/ModrinthCheckUpdate.cpp | 4 +- launcher/ui/dialogs/ResourceUpdateDialog.cpp | 10 +- launcher/ui/widgets/InfoFrame.cpp | 2 +- tests/ResourceFolderModel_test.cpp | 8 +- 23 files changed, 650 insertions(+), 546 deletions(-) diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 140596a29..c2b477d45 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -216,7 +216,7 @@ static std::pair map(int format, const QMap(other); - if (type == SortType::PACK_FORMAT) { + if (type == SortType::PackFormat) { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index 446e648d5..5f34ce6e1 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -40,26 +40,25 @@ #include #include -#include "Version.h" - #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "File Name" }); - m_column_names_translated = + m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "File Name" }); + m_columnNamesTranslated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("File Name") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::FILENAME }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, SortType::Date, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true }; } QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -68,11 +67,9 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const case Qt::BackgroundRole: return rowBackground(row); case Qt::DisplayRole: - switch (column) { - case PackFormatColumn: { - const auto& resource = at(row); - return resource.packFormatStr(); - } + if (column == PackFormatColumn) { + const auto& resource = at(row); + return resource.packFormatStr(); } break; case Qt::DecorationRole: { @@ -93,6 +90,8 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const return QSize(32, 32); } break; + default: + break; } // map the columns to the base equivilents @@ -113,7 +112,9 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const case FileNameColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); break; - // FIXME: there is no size column due to an oversight + // FIXME: there is no size column due to an oversight + default: + break; } if (mappedIndex.isValid()) { @@ -167,7 +168,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien int DataPackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } Resource* DataPackFolderModel::createResource(const QFileInfo& file) @@ -177,5 +178,5 @@ Resource* DataPackFolderModel::createResource(const QFileInfo& file) Task* DataPackFolderModel::createParseTask(Resource& resource) { - return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast(&resource)); + return new LocalDataPackParseTask(m_nextResolutionTicket, static_cast(&resource)); } diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h index 3eae4cf84..04153b893 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.h +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -39,16 +39,15 @@ #include "ResourceFolderModel.h" #include "DataPack.h" -#include "ResourcePack.h" class DataPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, FileNameColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, FileNameColumn, NumColumns }; - explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); - virtual QString id() const override { return "datapacks"; } + QString id() const override { return "datapacks"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -56,7 +55,7 @@ class DataPackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override; - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; RESOURCE_HELPERS(DataPack) }; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 661192d67..d9e9e901a 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -61,18 +61,18 @@ void Mod::setDetails(const ModDetails& details) int Mod::compare(const Resource& other, SortType type) const { - auto cast_other = dynamic_cast(&other); + auto cast_other = dynamic_cast(&other); if (!cast_other) return Resource::compare(other, type); switch (type) { default: - case SortType::ENABLED: - case SortType::NAME: - case SortType::DATE: - case SortType::SIZE: + case SortType::Enabled: + case SortType::Name: + case SortType::Date: + case SortType::Size: return Resource::compare(other, type); - case SortType::VERSION: { + case SortType::Version: { auto this_ver = Version(version()); auto other_ver = Version(cast_other->version()); if (this_ver > other_ver) @@ -81,38 +81,38 @@ int Mod::compare(const Resource& other, SortType type) const return -1; break; } - case SortType::SIDE: { + case SortType::Side: { auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::MC_VERSIONS: { + case SortType::McVersions: { auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::LOADERS: { + case SortType::Loaders: { auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::RELEASE_TYPE: { + case SortType::ReleaseType: { auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::REQUIRED_BY: { + case SortType::RequiredBy: { if (requiredByCount() > cast_other->requiredByCount()) return 1; if (requiredByCount() < cast_other->requiredByCount()) return -1; break; } - case SortType::REQUIRES: { + case SortType::Requires: { if (requiresCount() > cast_other->requiresCount()) return 1; if (requiresCount() < cast_other->requiresCount()) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index befd3c9dd..b5e5b2e87 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -52,28 +52,27 @@ #include #include -#include "minecraft/Component.h" #include "minecraft/mod/Resource.h" #include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "modplatform/ModIndex.h" #include "ui/dialogs/CustomMessageBox.h" -ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", - "Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" }); - m_column_names_translated = + m_columnNames = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", + "Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" }); + m_columnNamesTranslated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"), - tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, - SortType::PROVIDER, SortType::SIZE, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS, - SortType::RELEASE_TYPE, SortType::REQUIRES, SortType::REQUIRED_BY, SortType::FILENAME }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive }; + tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Version, SortType::Date, + SortType::Provider, SortType::Size, SortType::Side, SortType::Loaders, SortType::McVersions, + SortType::ReleaseType, SortType::Requires, SortType::RequiredBy, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true, true }; connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished); @@ -81,8 +80,9 @@ ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_ QVariant ModFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -120,6 +120,8 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case RequiresColumn: { return at(row).requiresCount(); } + default: + break; } break; case Qt::DecorationRole: { @@ -158,6 +160,8 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case FileNameColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); break; + default: + break; } if (mappedIndex.isValid()) { @@ -229,12 +233,12 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio int ModFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } Task* ModFolderModel::createParseTask(Resource& resource) { - return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); + return new LocalModParseTask(m_nextResolutionTicket, resource.type(), resource.fileinfo()); } bool ModFolderModel::isValid() @@ -242,35 +246,37 @@ bool ModFolderModel::isValid() return m_dir.exists() && m_dir.isReadable(); } -void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) +void ModFolderModel::onParseSucceeded(int ticket, const QString& resourceId) { - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd()) + auto iter = m_activeParseTasks.constFind(ticket); + if (iter == m_activeParseTasks.constEnd()) { return; + } - int row = m_resources_index[mod_id]; + int row = m_resourcesIndex[resourceId]; - auto parse_task = *iter; - auto cast_task = static_cast(parse_task.get()); + const auto& parseTask = *iter; + auto* castTask = static_cast(parseTask.get()); - Q_ASSERT(cast_task->token() == ticket); + Q_ASSERT(castTask->token() == ticket); - auto resource = find(mod_id); + auto resource = find(resourceId); - auto result = cast_task->result(); + auto result = castTask->result(); if (result && resource) { auto* mod = static_cast(resource.get()); mod->finishResolvingWithDetails(std::move(result->details)); - } emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); } -Mod* findById(QSet mods, QString modId) +namespace { +Mod* findById(QSet mods, const QString& resourceId) { - auto found = std::find_if(mods.begin(), mods.end(), [modId](Mod* m) { return m->mod_id() == modId; }); + auto found = std::ranges::find_if(mods, [resourceId](Mod* m) { return m->mod_id() == resourceId; }); return found != mods.end() ? *found : nullptr; } +} // namespace void ModFolderModel::onParseFinished() { @@ -283,25 +289,25 @@ void ModFolderModel::onParseFinished() m_requires.clear(); m_requiredBy.clear(); - auto findByProjectID = [mods](QVariant modId, ModPlatform::ResourceProvider provider) -> Mod* { - auto found = std::find_if(mods.begin(), mods.end(), [modId, provider](Mod* m) { + auto findByProjectID = [mods](const QVariant& modId, ModPlatform::ResourceProvider provider) -> Mod* { + auto found = std::ranges::find_if(mods, [modId, provider](Mod* m) { return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId; }); return found != mods.end() ? *found : nullptr; }; - for (auto mod : mods) { + for (auto* mod : mods) { auto id = mod->mod_id(); - for (auto dep : mod->dependencies()) { - auto d = findById(mods, dep); + for (const auto& dep : mod->dependencies()) { + auto* d = findById(mods, dep); if (d) { m_requires[id] << d; m_requiredBy[d->mod_id()] << mod; } } if (mod->metadata()) { - for (auto dep : mod->metadata()->dependencies) { + for (const auto& dep : mod->metadata()->dependencies) { if (dep.type == ModPlatform::DependencyType::REQUIRED) { - auto d = findByProjectID(dep.addonId, mod->metadata()->provider); + auto* d = findByProjectID(dep.addonId, mod->metadata()->provider); if (d) { m_requires[id] << d; m_requiredBy[d->mod_id()] << mod; @@ -310,29 +316,31 @@ void ModFolderModel::onParseFinished() } } } - for (auto mod : mods) { + for (auto* mod : mods) { auto id = mod->mod_id(); if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { mod->setRequiredByCount(m_requiredBy[id].count()); mod->setRequiresCount(m_requires[id].count()); - int row = m_resources_index[mod->internal_id()]; + int row = m_resourcesIndex[mod->internalId()]; emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } } } -QSet collectMods(QSet mods, QHash> relation, std::set& seen, bool shouldBeEnabled) +namespace { + +QSet collectMods(const QSet& mods, QHash> relation, std::set& seen, bool shouldBeEnabled) { QSet affectedList = {}; QSet needToCheck = {}; - for (auto mod : mods) { + for (auto* mod : mods) { auto id = mod->mod_id(); - if (seen.count(id) == 0) { + if (!seen.contains(id)) { seen.insert(id); - for (auto affected : relation[id]) { + for (auto* affected : relation[id]) { auto affectedId = affected->mod_id(); - if (findById(mods, affectedId) == nullptr && seen.count(affectedId) == 0) { + if (findById(mods, affectedId) == nullptr && !seen.contains(affectedId)) { seen.insert(affectedId); if (shouldBeEnabled != affected->enabled()) { affectedList << affected; @@ -348,11 +356,13 @@ QSet collectMods(QSet mods, QHash> relation, std } return affectedList; } +} // namespace QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return {}; + } QModelIndexList affectedList = {}; auto affectedModsList = selectedMods(indexes); @@ -372,9 +382,9 @@ QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, return {}; // this function should not be called with TOGGLE } } - for (auto affected : affectedMods) { + for (auto* affected : affectedMods) { auto affectedId = affected->mod_id(); - auto row = m_resources_index[affected->internal_id()]; + auto row = m_resourcesIndex[affected->internalId()]; affectedList << index(row, 0); } return affectedList; @@ -382,8 +392,9 @@ QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return {}; + } auto indexedModsList = selectedMods(indexes); auto indexedMods = QSet(indexedModsList.begin(), indexedModsList.end()); @@ -402,7 +413,7 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc break; } case EnableAction::TOGGLE: { - for (auto mod : indexedMods) { + for (auto* mod : indexedMods) { if (mod->enabled()) { toDisable << mod; } else { @@ -417,10 +428,10 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc auto requiredToDisable = collectMods(toDisable, m_requiredBy, seen, false); toDisable.removeIf([toEnable](Mod* m) { return toEnable.contains(m); }); - auto toList = [this](QSet mods) { + auto toList = [this](const QSet& mods) { QModelIndexList list; - for (auto mod : mods) { - auto row = m_resources_index[mod->internal_id()]; + for (auto* mod : mods) { + auto row = m_resourcesIndex[mod->internalId()]; list << index(row, 0); } return list; @@ -453,8 +464,8 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc yesButton = tr("Disable Required"); } - auto box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); + auto* box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); box->button(QMessageBox::No)->setText(noButton); box->button(QMessageBox::Yes)->setText(yesButton); auto response = box->exec(); @@ -472,21 +483,23 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc return disableStatus && enableStatus; } -QStringList reqToList(QSet l) +namespace { +QStringList reqToList(const QSet& l) { QStringList req; - for (auto m : l) { + for (auto* m : l) { req << m->name(); } return req; } +} // namespace -QStringList ModFolderModel::requiresList(QString id) +QStringList ModFolderModel::requiresList(const QString& id) { return reqToList(m_requires[id]); } -QStringList ModFolderModel::requiredByList(QString id) +QStringList ModFolderModel::requiredByList(const QString& id) { return reqToList(m_requiredBy[id]); } @@ -495,7 +508,7 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes) { auto deleteInvalid = [](QSet& mods) { for (auto it = mods.begin(); it != mods.end();) { - auto mod = *it; + auto* mod = *it; // the QFileInfo::exists is used instead of mod->fileinfo().exists // because the later somehow caches that the file exists if (!mod || !QFileInfo::exists(mod->fileinfo().absoluteFilePath())) { @@ -506,14 +519,14 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes) } }; auto rsp = ResourceFolderModel::deleteResources(indexes); - for (auto mod : allMods()) { + for (auto* mod : allMods()) { auto id = mod->mod_id(); deleteInvalid(m_requiredBy[id]); deleteInvalid(m_requires[id]); if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { mod->setRequiredByCount(m_requiredBy[id].count()); mod->setRequiresCount(m_requires[id].count()); - int row = m_resources_index[mod->internal_id()]; + int row = m_resourcesIndex[mod->internalId()]; emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); } } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index fb1add9e0..a5c6ba483 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -46,7 +46,6 @@ #include "Mod.h" #include "ResourceFolderModel.h" -#include "minecraft/Component.h" #include "minecraft/mod/Resource.h" class BaseInstance; @@ -59,7 +58,7 @@ class QFileSystemWatcher; class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { + enum Columns : std::uint8_t { ActiveColumn = 0, ImageColumn, NameColumn, @@ -74,11 +73,11 @@ class ModFolderModel : public ResourceFolderModel { RequiresColumn, RequiredByColumn, FileNameColumn, - NUM_COLUMNS + NumColumns }; - ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); - virtual QString id() const override { return "mods"; } + QString id() const override { return "mods"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -86,7 +85,7 @@ class ModFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; bool isValid(); @@ -98,11 +97,11 @@ class ModFolderModel : public ResourceFolderModel { RESOURCE_HELPERS(Mod) public: - QStringList requiresList(QString id); - QStringList requiredByList(QString id); + QStringList requiresList(const QString& id); + QStringList requiredByList(const QString& id); private slots: - void onParseSucceeded(int ticket, QString resource_id) override; + void onParseSucceeded(int ticket, const QString& resourceId) override; void onParseFinished(); private: diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 196ea75e6..06d9a0af7 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -4,71 +4,75 @@ #include #include #include +#include #include "FileSystem.h" #include "StringUtils.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -Resource::Resource(QObject* parent) : QObject(parent) {} +Resource::Resource(QObject* parent) : QObject(parent), m_size_info(0) {} -Resource::Resource(QFileInfo file_info) : QObject() +Resource::Resource(QFileInfo fileInfo) : m_size_info(0) { - setFile(file_info); + setFile(fileInfo); } -void Resource::setFile(QFileInfo file_info) +void Resource::setFile(QFileInfo fileInfo) { - m_file_info = file_info; + m_file_info = std::move(fileInfo); parseFile(); } -static std::tuple calculateFileSize(const QFileInfo& file) +namespace { +std::tuple calculateFileSize(const QFileInfo& file) { if (file.isDir()) { auto dir = QDir(file.absoluteFilePath()); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); auto count = dir.count(); auto str = QObject::tr("item"); - if (count != 1) + if (count != 1) { str = QObject::tr("items"); + } return { QString("%1 %2").arg(QString::number(count), str), count }; } return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; } +} // namespace void Resource::parseFile() { - QString file_name{ m_file_info.fileName() }; + QString fileName{ m_file_info.fileName() }; m_type = ResourceType::UNKNOWN; - m_internal_id = file_name; + m_internal_id = fileName; std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; - m_name = file_name; + m_name = fileName; } else if (m_file_info.isFile()) { - if (file_name.endsWith(".disabled")) { - file_name.chop(9); + if (fileName.endsWith(".disabled")) { + fileName.chop(9); m_enabled = false; } - if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) { + if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) { m_type = ResourceType::ZIPFILE; - file_name.chop(4); - } else if (file_name.endsWith(".nilmod")) { + fileName.chop(4); + } else if (fileName.endsWith(".nilmod")) { m_type = ResourceType::ZIPFILE; - file_name.chop(7); - } else if (file_name.endsWith(".litemod")) { + fileName.chop(7); + } else if (fileName.endsWith(".litemod")) { m_type = ResourceType::LITEMOD; - file_name.chop(8); + fileName.chop(8); } else { m_type = ResourceType::SINGLEFILE; } - m_name = file_name; + m_name = fileName; } m_changed_date_time = m_file_info.lastModified(); @@ -76,39 +80,45 @@ void Resource::parseFile() auto Resource::name() const -> QString { - if (metadata()) + if (metadata()) { return metadata()->name; + } return m_name; } -static void removeThePrefix(QString& string) +namespace { +void removeThePrefix(QString& string) { static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); string.remove(s_regex); string = string.trimmed(); } +} // namespace auto Resource::provider() const -> QString { - if (metadata()) + if (metadata()) { return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); + } return tr("Unknown"); } auto Resource::homepage() const -> QString { - if (metadata()) + if (metadata()) { return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); + } return {}; } void Resource::setMetadata(std::shared_ptr&& metadata) { - if (status() == ResourceStatus::NO_METADATA) - setStatus(ResourceStatus::INSTALLED); + if (status() == ResourceStatus::NoMetadata) { + setStatus(ResourceStatus::Installed); + } m_metadata = metadata; } @@ -133,12 +143,12 @@ void Resource::updateIssues(const BaseInstance* inst) return; } - auto mcInst = dynamic_cast(inst); + const auto* mcInst = dynamic_cast(inst); if (mcInst == nullptr) { return; } - auto profile = mcInst->getPackProfile(); + auto* profile = mcInst->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); if (!m_metadata->mcVersions.empty() && !m_metadata->mcVersions.contains(mcVersion)) { @@ -151,50 +161,59 @@ int Resource::compare(const Resource& other, SortType type) const { switch (type) { default: - case SortType::ENABLED: - if (enabled() && !other.enabled()) + case SortType::Enabled: + if (enabled() && !other.enabled()) { return 1; - if (!enabled() && other.enabled()) + } + if (!enabled() && other.enabled()) { return -1; + } break; - case SortType::NAME: { - QString this_name{ name() }; - QString other_name{ other.name() }; + case SortType::Name: { + QString thisName{ name() }; + QString otherName{ other.name() }; // TODO do we need this? it could result in 0 being returned - removeThePrefix(this_name); - removeThePrefix(other_name); + removeThePrefix(thisName); + removeThePrefix(otherName); - return QString::compare(this_name, other_name, Qt::CaseInsensitive); + return QString::compare(thisName, otherName, Qt::CaseInsensitive); } - case SortType::DATE: - if (dateTimeChanged() > other.dateTimeChanged()) + case SortType::Date: + if (dateTimeChanged() > other.dateTimeChanged()) { return 1; - if (dateTimeChanged() < other.dateTimeChanged()) + } + if (dateTimeChanged() < other.dateTimeChanged()) { return -1; + } break; - case SortType::FILENAME: + case SortType::Filename: return fileinfo().fileName().localeAwareCompare(other.fileinfo().fileName()); - case SortType::SIZE: { + case SortType::Size: { if (this->type() != other.type()) { - if (this->type() == ResourceType::FOLDER) + if (this->type() == ResourceType::FOLDER) { return -1; - if (other.type() == ResourceType::FOLDER) + } + if (other.type() == ResourceType::FOLDER) { return 1; + } } - if (sizeInfo() > other.sizeInfo()) + if (sizeInfo() > other.sizeInfo()) { return 1; - if (sizeInfo() < other.sizeInfo()) + } + if (sizeInfo() < other.sizeInfo()) { return -1; + } break; } - case SortType::PROVIDER: { - auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); - if (compare_result != 0) - return compare_result; + case SortType::Provider: { + auto compareResult = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); + if (compareResult != 0) { + return compareResult; + } break; } } @@ -204,17 +223,20 @@ int Resource::compare(const Resource& other, SortType type) const bool Resource::applyFilter(QRegularExpression filter) const { - if (filter.match(name()).hasMatch()) + if (filter.match(name()).hasMatch()) { return true; - if (filter.match(fileinfo().fileName()).hasMatch()) + } + if (filter.match(fileinfo().fileName()).hasMatch()) { return true; + } return false; } bool Resource::enable(EnableAction action) { - if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) + if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) { return false; + } QString path = m_file_info.absoluteFilePath(); QFile file(path); @@ -233,14 +255,16 @@ bool Resource::enable(EnableAction action) break; } - if (m_enabled == enable) + if (m_enabled == enable) { return false; + } if (enable) { // m_enabled is false, but there's no '.disabled' suffix. // TODO: Report error? - if (!path.endsWith(".disabled")) + if (!path.endsWith(".disabled")) { return false; + } path.chop(9); } else { path += ".disabled"; @@ -248,8 +272,9 @@ bool Resource::enable(EnableAction action) path = FS::getUniqueResourceName(path); } } - if (!file.rename(path)) + if (!file.rename(path)) { return false; + } setFile(QFileInfo(path)); @@ -257,33 +282,34 @@ bool Resource::enable(EnableAction action) return true; } -auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool +auto Resource::destroy(const QDir& indexDir, bool preserveMetadata, bool attemptTrash) -> bool { m_type = ResourceType::UNKNOWN; - if (!preserve_metadata) { + if (!preserveMetadata) { qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); - destroyMetadata(index_dir); + destroyMetadata(indexDir); } - return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); + return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } -auto Resource::destroyMetadata(const QDir& index_dir) -> void +auto Resource::destroyMetadata(const QDir& indexDir) -> void { if (metadata()) { - Metadata::remove(index_dir, metadata()->slug); + Metadata::remove(indexDir, metadata()->slug); } else { auto n = name(); - Metadata::remove(index_dir, n); + Metadata::remove(indexDir, n); } m_metadata = nullptr; } bool Resource::isSymLinkUnder(const QString& instPath) const { - if (isSymLink()) + if (isSymLink()) { return true; + } auto instDir = QDir(instPath); @@ -301,8 +327,9 @@ bool Resource::isMoreThanOneHardLink() const auto Resource::getOriginalFileName() const -> QString { auto fileName = m_file_info.fileName(); - if (!m_enabled) + if (!m_enabled) { fileName.chop(9); + } return fileName; } @@ -332,16 +359,16 @@ QDebug operator<<(QDebug debug, ResourceType type) QDebug operator<<(QDebug debug, ResourceStatus status) { switch (status) { - case ResourceStatus::INSTALLED: + case ResourceStatus::Installed: debug << "INSTALLED"; break; - case ResourceStatus::NOT_INSTALLED: + case ResourceStatus::NotInstalled: debug << "NOT_INSTALLED"; break; - case ResourceStatus::NO_METADATA: + case ResourceStatus::NoMetadata: debug << "NO_METADATA"; break; - case ResourceStatus::UNKNOWN: + case ResourceStatus::Unknown: default: debug << "UNKNOWN"; break; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 8f30c1bf3..694d74ec0 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,7 +45,7 @@ class BaseInstance; -enum class ResourceType { +enum class ResourceType : std::uint8_t { UNKNOWN, //!< Indicates an unspecified resource type. ZIPFILE, //!< The resource is a zip file containing the resource's class files. SINGLEFILE, //!< The resource is a single file (not a zip file). @@ -55,33 +55,33 @@ enum class ResourceType { QDebug operator<<(QDebug debug, ResourceType type); -enum class ResourceStatus { - INSTALLED, // Both JAR and Metadata are present - NOT_INSTALLED, // Only the Metadata is present - NO_METADATA, // Only the JAR is present - UNKNOWN, // Default status +enum class ResourceStatus : std::uint8_t { + Installed, // Both JAR and Metadata are present + NotInstalled, // Only the Metadata is present + NoMetadata, // Only the JAR is present + Unknown, // Default status }; QDebug operator<<(QDebug debug, ResourceStatus status); -enum class SortType { - NAME, - DATE, - VERSION, - ENABLED, - PACK_FORMAT, - PROVIDER, - SIZE, - SIDE, - MC_VERSIONS, - LOADERS, - RELEASE_TYPE, - REQUIRES, - REQUIRED_BY, - FILENAME, +enum class SortType : std::uint8_t { + Name, + Date, + Version, + Enabled, + PackFormat, + Provider, + Size, + Side, + McVersions, + Loaders, + ReleaseType, + Requires, + RequiredBy, + Filename, }; -enum class EnableAction { ENABLE, DISABLE, TOGGLE }; +enum class EnableAction : std::uint8_t { ENABLE, DISABLE, TOGGLE }; /** General class for managed resources. It mirrors a file in disk, with some more info * for display and house-keeping purposes. @@ -93,20 +93,19 @@ class Resource : public QObject { Q_DISABLE_COPY(Resource) public: using Ptr = shared_qobject_ptr; - using WeakPtr = QPointer; Resource(QObject* parent = nullptr); - Resource(QFileInfo file_info); - Resource(QString file_path) : Resource(QFileInfo(file_path)) {} + Resource(QFileInfo fileInfo); + Resource(const QString& filePath) : Resource(QFileInfo(filePath)) {} ~Resource() override = default; - void setFile(QFileInfo file_info); + void setFile(QFileInfo fileInfo); void parseFile(); auto fileinfo() const -> QFileInfo { return m_file_info; } auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } - auto internal_id() const -> QString { return m_internal_id; } + auto internalId() const -> QString { return m_internal_id; } auto type() const -> ResourceType { return m_type; } bool enabled() const { return m_enabled; } auto getOriginalFileName() const -> QString; @@ -139,7 +138,7 @@ class Resource : public QObject { * = 0: 'this' is equal to 'other' * < 0: 'this' comes before 'other' */ - virtual int compare(const Resource& other, SortType type = SortType::NAME) const; + virtual int compare(const Resource& other, SortType type = SortType::Name) const; /** Returns whether the given filter should filter out 'this' (false), * or if such filter includes the Resource (true). @@ -164,9 +163,9 @@ class Resource : public QObject { } // Delete all files of this resource. - auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; + auto destroy(const QDir& indexDir, bool preserveMetadata = false, bool attemptTrash = true) -> bool; // Delete the metadata only. - auto destroyMetadata(const QDir& index_dir) -> void; + auto destroyMetadata(const QDir& indexDir) -> void; auto isSymLink() const -> bool { return m_file_info.isSymLink(); } @@ -196,7 +195,7 @@ class Resource : public QObject { ResourceType m_type = ResourceType::UNKNOWN; /* Installation status of the resource. */ - ResourceStatus m_status = ResourceStatus::UNKNOWN; + ResourceStatus m_status = ResourceStatus::Unknown; std::shared_ptr m_metadata = nullptr; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 2e91cebc3..ddc132089 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "Application.h" @@ -27,10 +28,10 @@ #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" -ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed) +ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_isIndexed(isIndexed) { - if (create_dir) { + if (createDir) { FS::ensureFolderPathExists(m_dir.absolutePath()); } @@ -49,70 +50,75 @@ ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance ResourceFolderModel::~ResourceFolderModel() { - while (!QThreadPool::globalInstance()->waitForDone(100)) + while (!QThreadPool::globalInstance()->waitForDone(100)) { QCoreApplication::processEvents(); + } } bool ResourceFolderModel::startWatching(const QStringList& paths) { // Remove orphaned metadata next time - m_first_folder_load = true; + m_firstFolderLoad = true; - if (m_is_watching) + if (m_isWatching) { return false; + } - auto couldnt_be_watched = m_watcher.addPaths(paths); - for (auto path : paths) { - if (couldnt_be_watched.contains(path)) + auto couldntBeWatched = m_watcher.addPaths(paths); + for (const auto& path : paths) { + if (couldntBeWatched.contains(path)) { qDebug() << "Failed to start watching" << path; - else + } else { qDebug() << "Started watching" << path; + } } update(); - m_is_watching = !m_is_watching; - return m_is_watching; + m_isWatching = !m_isWatching; + return m_isWatching; } bool ResourceFolderModel::stopWatching(const QStringList& paths) { - if (!m_is_watching) + if (!m_isWatching) { return false; - - auto couldnt_be_stopped = m_watcher.removePaths(paths); - for (auto path : paths) { - if (couldnt_be_stopped.contains(path)) - qDebug() << "Failed to stop watching" << path; - else - qDebug() << "Stopped watching" << path; } - m_is_watching = !m_is_watching; - return !m_is_watching; + auto couldntBeStopped = m_watcher.removePaths(paths); + for (const auto& path : paths) { + if (couldntBeStopped.contains(path)) { + qDebug() << "Failed to stop watching" << path; + } else { + qDebug() << "Stopped watching" << path; + } + } + + m_isWatching = !m_isWatching; + return !m_isWatching; } -bool ResourceFolderModel::installResource(QString original_path) +bool ResourceFolderModel::installResource(QString originalPath) { // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - original_path = FS::NormalizePath(original_path); - QFileInfo file_info(original_path); + originalPath = FS::NormalizePath(originalPath); + QFileInfo fileInfo(originalPath); - if (!file_info.exists() || !file_info.isReadable()) { - qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path; + if (!fileInfo.exists() || !fileInfo.isReadable()) { + qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; return false; } - qDebug() << "Installing:" << file_info.absoluteFilePath(); + qDebug() << "Installing:" << fileInfo.absoluteFilePath(); - Resource resource(file_info); + Resource resource(fileInfo); if (!resource.valid()) { - qWarning() << original_path << "is not a valid resource. Ignoring it."; + qWarning() << originalPath << "is not a valid resource. Ignoring it."; return false; } - auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName())); - if (original_path == new_path) { - qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense..."; + auto newPath = FS::NormalizePath(m_dir.filePath(fileInfo.fileName())); + if (originalPath == newPath) { + qWarning() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; return false; } @@ -120,45 +126,47 @@ bool ResourceFolderModel::installResource(QString original_path) case ResourceType::SINGLEFILE: case ResourceType::ZIPFILE: case ResourceType::LITEMOD: { - if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) { - if (!FS::deletePath(new_path)) { - qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!"; + if (QFile::exists(newPath) || QFile::exists(newPath + QString(".disabled"))) { + if (!FS::deletePath(newPath)) { + qCritical() << "Cleaning up new location (" << newPath << ") was unsuccessful!"; return false; } - qDebug() << new_path << "has been deleted."; + qDebug() << newPath << "has been deleted."; } - if (!QFile::copy(original_path, new_path)) { - qCritical() << "Copy from" << original_path << "to" << new_path << "has failed."; + if (!QFile::copy(originalPath, newPath)) { + qCritical() << "Copy from" << originalPath << "to" << newPath << "has failed."; return false; } - FS::updateTimestamp(new_path); + FS::updateTimestamp(newPath); - QFileInfo new_path_file_info(new_path); - resource.setFile(new_path_file_info); + QFileInfo newPathFileInfo(newPath); + resource.setFile(newPathFileInfo); - if (!m_is_watching) + if (!m_isWatching) { return update(); + } return true; } case ResourceType::FOLDER: { - if (QFile::exists(new_path)) { - qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path; + if (QFile::exists(newPath)) { + qDebug() << "Ignoring folder '" << originalPath << "', it would merge with" << newPath; return false; } - if (!FS::copy(original_path, new_path)()) { - qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed."; + if (!FS::copy(originalPath, newPath)()) { + qWarning() << "Copy of folder from" << originalPath << "to" << newPath << "has (potentially partially) failed."; return false; } - QFileInfo newpathInfo(new_path); + QFileInfo newpathInfo(newPath); resource.setFile(newpathInfo); - if (!m_is_watching) + if (!m_isWatching) { return update(); + } return true; } @@ -168,24 +176,24 @@ bool ResourceFolderModel::installResource(QString original_path) return false; } -void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers) +void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers) { - auto install = [this, path] { installResource(std::move(path)); }; + auto install = [this, path] { installResource(path); }; if (vers.addonId.isValid()) { ModPlatform::IndexedPack pack{ - vers.addonId, - ModPlatform::ResourceProvider::FLAME, + .addonId = vers.addonId, + .provider = ModPlatform::ResourceProvider::FLAME, }; auto [job, response] = FlameAPI().getProject(vers.addonId.toString()); connect(job.get(), &Task::failed, this, install); connect(job.get(), &Task::aborted, this, install); connect(job.get(), &Task::succeeded, [response, this, &vers, install, &pack] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at" << parseError.offset + << "reason:" << parseError.errorString(); qDebug() << *response; return; } @@ -196,9 +204,9 @@ void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlat qDebug() << doc; qWarning() << "Error while reading mod info:" << e.cause(); } - LocalResourceUpdateTask update_metadata(indexDir(), pack, vers); - connect(&update_metadata, &Task::finished, this, install); - update_metadata.start(); + LocalResourceUpdateTask updateMetadata(indexDir(), pack, vers); + connect(&updateMetadata, &Task::finished, this, install); + updateMetadata.start(); }); job->start(); @@ -207,7 +215,7 @@ void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlat } } -bool ResourceFolderModel::uninstallResource(const QString& file_name, bool preserve_metadata) +bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preserveMetadata) { for (auto& resource : m_resources) { auto resourceFileInfo = resource->fileinfo(); @@ -216,8 +224,8 @@ bool ResourceFolderModel::uninstallResource(const QString& file_name, bool prese resourceFileName.chop(9); } - if (resourceFileName == file_name) { - auto res = resource->destroy(indexDir(), preserve_metadata, false); + if (resourceFileName == fileName) { + auto res = resource->destroy(indexDir(), preserveMetadata, false); update(); @@ -229,14 +237,16 @@ bool ResourceFolderModel::uninstallResource(const QString& file_name, bool prese bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return true; + } for (auto i : indexes) { - if (i.column() != 0) + if (i.column() != 0) { continue; + } - auto& resource = m_resources.at(i.row()); + const auto& resource = m_resources.at(i.row()); resource->destroy(indexDir()); } @@ -247,14 +257,16 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return; + } for (auto i : indexes) { - if (i.column() != 0) + if (i.column() != 0) { continue; + } - auto& resource = m_resources.at(i.row()); + const auto& resource = m_resources.at(i.row()); resource->destroyMetadata(indexDir()); } @@ -271,33 +283,36 @@ bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, Ena QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return false; + } } - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return true; + } bool succeeded = true; - for (auto const& idx : indexes) { - if (!validateIndex(idx) || idx.column() != 0) + for (const auto& idx : indexes) { + if (!validateIndex(idx) || idx.column() != 0) { continue; + } int row = idx.row(); auto& resource = m_resources[row]; // Preserve the row, but change its ID - auto old_id = resource->internal_id(); + auto oldId = resource->internalId(); if (!resource->enable(action)) { succeeded = false; continue; } - auto new_id = resource->internal_id(); + auto newId = resource->internalId(); - m_resources_index.remove(old_id); - m_resources_index[new_id] = row; + m_resourcesIndex.remove(oldId); + m_resourcesIndex[newId] = row; emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } @@ -312,24 +327,25 @@ bool ResourceFolderModel::update() QMutexLocker lock(&s_update_task_mutex); // Already updating, so we schedule a future update and return. - if (m_current_update_task) { - m_scheduled_update = true; + if (m_currentUpdateTask) { + m_scheduledUpdate = true; return false; } - m_current_update_task.reset(createUpdateTask()); - if (!m_current_update_task) + m_currentUpdateTask.reset(createUpdateTask()); + if (!m_currentUpdateTask) { return false; + } - connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, + connect(m_currentUpdateTask.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); - connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); + connect(m_currentUpdateTask.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); connect( - m_current_update_task.get(), &Task::finished, this, + m_currentUpdateTask.get(), &Task::finished, this, [this] { - m_current_update_task.reset(); - if (m_scheduled_update) { - m_scheduled_update = false; + m_currentUpdateTask.reset(); + if (m_scheduledUpdate) { + m_scheduledUpdate = false; update(); } else { emit updateFinished(); @@ -340,16 +356,16 @@ bool ResourceFolderModel::update() Task::Ptr preUpdate{ createPreUpdateTask() }; if (preUpdate != nullptr) { - auto task = new SequentialTask("ResourceFolderModel::update"); + auto* task = new SequentialTask("ResourceFolderModel::update"); task->addTask(preUpdate); - task->addTask(m_current_update_task); + task->addTask(m_currentUpdateTask); connect(task, &Task::finished, [task] { task->deleteLater(); }); QThreadPool::globalInstance()->start(task); } else { - QThreadPool::globalInstance()->start(m_current_update_task.get()); + QThreadPool::globalInstance()->start(m_currentUpdateTask.get()); } return true; @@ -362,24 +378,25 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) } Task::Ptr task{ createParseTask(*res) }; - if (!task) + if (!task) { return; + } - int ticket = m_next_resolution_ticket.fetch_add(1); + int ticket = m_nextResolutionTicket.fetch_add(1); res->setResolving(true, ticket); - m_active_parse_tasks.insert(ticket, task); + m_activeParseTasks.insert(ticket, task); connect( - task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); }, + task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internalId()); }, Qt::ConnectionType::QueuedConnection); connect( - task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); }, + task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internalId()); }, Qt::ConnectionType::QueuedConnection); connect( task.get(), &Task::finished, this, [this, ticket] { - m_active_parse_tasks.remove(ticket); + m_activeParseTasks.remove(ticket); emit parseFinished(); }, Qt::ConnectionType::QueuedConnection); @@ -394,44 +411,45 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) void ResourceFolderModel::onUpdateSucceeded() { - auto update_results = static_cast(m_current_update_task.get())->result(); + auto updateResults = static_cast(m_currentUpdateTask.get())->result(); - auto& new_resources = update_results->resources; + auto& newResources = updateResults->resources; - auto current_list = m_resources_index.keys(); - QSet current_set(current_list.begin(), current_list.end()); + auto currentList = m_resourcesIndex.keys(); + QSet currentSet(currentList.begin(), currentList.end()); - auto new_list = new_resources.keys(); - QSet new_set(new_list.begin(), new_list.end()); + auto newList = newResources.keys(); + QSet newSet(newList.begin(), newList.end()); - applyUpdates(current_set, new_set, new_resources); + applyUpdates(currentSet, newSet, newResources); } -void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) +void ResourceFolderModel::onParseSucceeded(int ticket, const QString& resourceId) { - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) + auto iter = m_activeParseTasks.constFind(ticket); + if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { return; + } - int row = m_resources_index[resource_id]; + int row = m_resourcesIndex[resourceId]; emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } Task* ResourceFolderModel::createUpdateTask() { - auto index_dir = indexDir(); - auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, - [this](const QFileInfo& file) { return createResource(file); }); - m_first_folder_load = false; + auto indexDir2 = indexDir(); + auto* task = new ResourceFolderLoadTask(dir(), indexDir2, m_isIndexed, m_firstFolderLoad, + [this](const QFileInfo& file) { return createResource(file); }); + m_firstFolderLoad = false; return task; } bool ResourceFolderModel::hasPendingParseTasks() const { - return !m_active_parse_tasks.isEmpty(); + return !m_activeParseTasks.isEmpty(); } -void ResourceFolderModel::directoryChanged(QString path) +void ResourceFolderModel::directoryChanged(const QString& /*path*/) { update(); } @@ -446,8 +464,9 @@ Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); auto flags = defaultFlags | Qt::ItemIsDropEnabled; - if (index.isValid()) + if (index.isValid()) { flags |= Qt::ItemIsUserCheckable; + } return flags; } @@ -458,21 +477,25 @@ QStringList ResourceFolderModel::mimeTypes() const return types; } -bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) +bool ResourceFolderModel::dropMimeData(const QMimeData* data, + Qt::DropAction action, + int /*row*/, + int /*column*/, + const QModelIndex& /*parent*/) { if (action == Qt::IgnoreAction) { return true; } // check if the action is supported - if (!data || !(action & supportedDropActions())) { + if ((data == nullptr) || !(action & supportedDropActions())) { return false; } // files dropped from outside? if (data->hasUrls()) { auto urls = data->urls(); - for (auto url : urls) { + for (const auto& url : urls) { // only local files may be dropped... if (!url.isLocalFile()) { continue; @@ -488,14 +511,12 @@ bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction act bool ResourceFolderModel::validateIndex(const QModelIndex& index) const { - if (!index.isValid()) + if (!index.isValid()) { return false; + } int row = index.row(); - if (row < 0 || row >= m_resources.size()) - return false; - - return true; + return row >= 0 && row < m_resources.size(); } // HACK: all subclasses need to call this to have the whole row painted @@ -504,15 +525,15 @@ QBrush ResourceFolderModel::rowBackground(int row) const { if (APPLICATION->settings()->get("ShowModIncompat").toBool() && m_resources[row]->hasIssues()) { return { QColor(255, 0, 0, 40) }; - } else { - return {}; } + return {}; } QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -536,7 +557,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::ToolTipRole: { - QString tooltip = m_resources[row]->internal_id(); + QString tooltip = m_resources[row]->internalId(); if (column == NameColumn) { if (APPLICATION->settings()->get("ShowModIncompat").toBool()) { @@ -547,7 +568,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (at(row).isSymLinkUnder(instDirPath())) { tooltip += - m_resources[row]->internal_id() + + m_resources[row]->internalId() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") .arg(at(row).fileinfo().canonicalFilePath()); @@ -564,7 +585,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (column == NameColumn) { if (APPLICATION->settings()->get("ShowModIncompat").toBool() && at(row).hasIssues()) { return QIcon::fromTheme("status-bad"); - } else if (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()) { + } + if (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()) { return QIcon::fromTheme("status-yellow"); } } @@ -572,8 +594,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::CheckStateRole: - if (column == ActiveColumn) + if (column == ActiveColumn) { return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; + } return {}; default: return {}; @@ -583,8 +606,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role) { int row = index.row(); - if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) + if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) { return false; + } if (role == Qt::CheckStateRole) { return setResourceEnabled({ index }, EnableAction::TOGGLE); @@ -643,22 +667,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column) void ResourceFolderModel::saveColumns(QTreeView* tree) { - auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id()); - auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); - auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); + const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); + const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto stateSetting = m_instance->settings()->getSetting(stateSettingName); stateSetting->set(QString::fromUtf8(tree->header()->saveState().toBase64())); // neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false - auto settings = m_instance->settings(); + auto* settings = m_instance->settings(); if (!settings->get(overrideSettingName).toBool()) { settings = APPLICATION->settings(); } auto visibility = Json::toMap(settings->get(visibilitySettingName).toString()); - for (auto i = 0; i < m_column_names.size(); ++i) { + for (auto i = 0; i < m_columnNames.size(); ++i) { if (m_columnsHideable[i]) { - auto name = m_column_names[i]; + auto name = m_columnNames[i]; visibility[name] = !tree->isColumnHidden(i); } } @@ -667,24 +691,24 @@ void ResourceFolderModel::saveColumns(QTreeView* tree) void ResourceFolderModel::loadColumns(QTreeView* tree) { - auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id()); - auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); - auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); + const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); + const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto stateSetting = m_instance->settings()->getOrRegisterSetting(stateSettingName, ""); tree->header()->restoreState(QByteArray::fromBase64(stateSetting->get().toString().toUtf8())); - auto setVisible = [this, tree](QVariant value) { + auto setVisible = [this, tree](const QVariant& value) { auto visibility = Json::toMap(value.toString()); - for (auto i = 0; i < m_column_names.size(); ++i) { + for (auto i = 0; i < m_columnNames.size(); ++i) { if (m_columnsHideable[i]) { - auto name = m_column_names[i]; + auto name = m_columnNames[i]; tree->setColumnHidden(i, !visibility.value(name, false).toBool()); } } }; - auto const defaultValue = Json::fromMap({ + const auto defaultValue = Json::fromMap({ { "Image", true }, { "Version", true }, { "Last Modified", true }, @@ -692,7 +716,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) { "Pack Format", true }, }); // neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false - auto settings = m_instance->settings(); + auto* settings = m_instance->settings(); if (!settings->getOrRegisterSetting(overrideSettingName, false)->get().toBool()) { settings = APPLICATION->settings(); } @@ -701,7 +725,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) // allways connect the signal in case the setting is toggled on and off auto gSetting = APPLICATION->settings()->getOrRegisterSetting(visibilitySettingName, defaultValue); - connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, QVariant value) { + connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, const QVariant& value) { if (!m_instance->settings()->get(overrideSettingName).toBool()) { setVisible(value); } @@ -710,11 +734,11 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) { - auto menu = new QMenu(tree); + auto* menu = new QMenu(tree); { // action to decide if the visibility is per instance or not - auto act = new QAction(tr("Override Columns Visibility"), menu); - auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + auto* act = new QAction(tr("Override Columns Visibility"), menu); + const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); act->setCheckable(true); act->setChecked(m_instance->settings()->getOrRegisterSetting(overrideSettingName, false)->get().toBool()); @@ -730,9 +754,10 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) for (int col = 0; col < columnCount(); ++col) { // Skip creating actions for columns that should not be hidden - if (!m_columnsHideable.at(col)) + if (!m_columnsHideable.at(col)) { continue; - auto act = new QAction(menu); + } + auto* act = new QAction(menu); setupHeaderAction(act, col); act->setCheckable(true); @@ -741,8 +766,9 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled) { tree->setColumnHidden(col, !toggled); for (int c = 0; c < columnCount(); ++c) { - if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents) + if (m_columnResizeModes.at(c) == QHeaderView::ResizeToContents) { tree->resizeColumnToContents(c); + } } saveColumns(tree); }); @@ -760,41 +786,43 @@ QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* pare SortType ResourceFolderModel::columnToSortKey(size_t column) const { - Q_ASSERT(m_column_sort_keys.size() == columnCount()); - return m_column_sort_keys.at(column); + Q_ASSERT(m_columnSortKeys.size() == columnCount()); + return m_columnSortKeys.at(column); } /* Standard Proxy Model for createFilterProxyModel */ -bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, [[maybe_unused]] const QModelIndex& source_parent) const +bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const { auto* model = qobject_cast(sourceModel()); - if (!model) + if (!model) { return true; + } - const auto& resource = model->at(source_row); + const auto& resource = model->at(sourceRow); return resource.applyFilter(filterRegularExpression()); } -bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const +bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const { auto* model = qobject_cast(sourceModel()); - if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { - return QSortFilterProxyModel::lessThan(source_left, source_right); + if (!model || !sourceLeft.isValid() || !sourceRight.isValid() || sourceLeft.column() != sourceRight.column()) { + return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); } // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and // proceed. - auto column_sort_key = model->columnToSortKey(source_left.column()); - auto const& resource_left = model->at(source_left.row()); - auto const& resource_right = model->at(source_right.row()); + auto columnSortKey = model->columnToSortKey(sourceLeft.column()); + const auto& resourceLeft = model->at(sourceLeft.row()); + const auto& resourceRight = model->at(sourceRight.row()); - auto compare_result = resource_left.compare(resource_right, column_sort_key); - if (compare_result == 0) - return QSortFilterProxyModel::lessThan(source_left, source_right); + auto compareResult = resourceLeft.compare(resourceRight, columnSortKey); + if (compareResult == 0) { + return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); + } - return compare_result < 0; + return compareResult < 0; } QString ResourceFolderModel::instDirPath() const @@ -802,50 +830,51 @@ QString ResourceFolderModel::instDirPath() const return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } -void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) +void ResourceFolderModel::onParseFailed(int ticket, const QString& resourceId) { - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) + auto iter = m_activeParseTasks.constFind(ticket); + if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { return; + } - auto removed_index = m_resources_index[resource_id]; - auto removed_it = m_resources.begin() + removed_index; - Q_ASSERT(removed_it != m_resources.end()); + auto removedIndex = m_resourcesIndex[resourceId]; + auto removedIt = m_resources.begin() + removedIndex; + Q_ASSERT(removedIt != m_resources.end()); - beginRemoveRows(QModelIndex(), removed_index, removed_index); - m_resources.erase(removed_it); + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + m_resources.erase(removedIt); // update index - m_resources_index.clear(); + m_resourcesIndex.clear(); int idx = 0; - for (auto const& mod : qAsConst(m_resources)) { - m_resources_index[mod->internal_id()] = idx; + for (const auto& mod : qAsConst(m_resources)) { + m_resourcesIndex[mod->internalId()] = idx; idx++; } endRemoveRows(); } -void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) +void ResourceFolderModel::applyUpdates(QSet& currentSet, QSet& newSet, QMap& newResources) { // see if the kept resources changed in some way { - QSet kept_set = current_set; - kept_set.intersect(new_set); + QSet keptSet = currentSet; + keptSet.intersect(newSet); - for (auto const& kept : kept_set) { - auto row_it = m_resources_index.constFind(kept); - Q_ASSERT(row_it != m_resources_index.constEnd()); - auto row = row_it.value(); + for (const auto& kept : keptSet) { + auto rowIt = m_resourcesIndex.constFind(kept); + Q_ASSERT(rowIt != m_resourcesIndex.constEnd()); + auto row = rowIt.value(); - auto& new_resource = new_resources[kept]; - auto const& current_resource = m_resources.at(row); + auto& newResource = newResources[kept]; + const auto& currentResource = m_resources.at(row); - if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { + if (newResource->dateTimeChanged() == currentResource->dateTimeChanged()) { // no significant change - bool hadIssues = !current_resource->hasIssues(); - current_resource->updateIssues(m_instance); + bool hadIssues = !currentResource->hasIssues(); + currentResource->updateIssues(m_instance); - if (hadIssues != current_resource->hasIssues()) { + if (hadIssues != currentResource->hasIssues()) { emit dataChanged(index(row, 0), index(row, columnCount({}) - 1)); } continue; @@ -853,16 +882,16 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // If the resource is resolving, but something about it changed, we don't want to // continue the resolving. - if (current_resource->isResolving()) { - auto ticket = current_resource->resolutionTicket(); - if (m_active_parse_tasks.contains(ticket)) { - auto task = (*m_active_parse_tasks.find(ticket)).get(); + if (currentResource->isResolving()) { + auto ticket = currentResource->resolutionTicket(); + if (m_activeParseTasks.contains(ticket)) { + auto* task = (*m_activeParseTasks.find(ticket)).get(); task->abort(); } } - m_resources[row].reset(new_resource); - new_resource->updateIssues(m_instance); + m_resources[row].reset(newResource); + newResource->updateIssues(m_instance); resolveResource(m_resources.at(row)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); @@ -871,46 +900,47 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // remove resources no longer present { - QSet removed_set = current_set; - removed_set.subtract(new_set); + QSet removedSet = currentSet; + removedSet.subtract(newSet); - QList removed_rows; - for (auto& removed : removed_set) - removed_rows.append(m_resources_index[removed]); + QList removedRows; + for (const auto& removed : removedSet) { + removedRows.append(m_resourcesIndex[removed]); + } - std::sort(removed_rows.begin(), removed_rows.end(), std::greater()); + std::ranges::sort(removedRows, std::greater()); - for (auto& removed_index : removed_rows) { - auto removed_it = m_resources.begin() + removed_index; + for (auto& removedIndex : removedRows) { + auto removedIt = m_resources.begin() + removedIndex; - Q_ASSERT(removed_it != m_resources.end()); + Q_ASSERT(removedIt != m_resources.end()); - if ((*removed_it)->isResolving()) { - auto ticket = (*removed_it)->resolutionTicket(); - if (m_active_parse_tasks.contains(ticket)) { - auto task = (*m_active_parse_tasks.find(ticket)).get(); + if ((*removedIt)->isResolving()) { + auto ticket = (*removedIt)->resolutionTicket(); + if (m_activeParseTasks.contains(ticket)) { + auto* task = (*m_activeParseTasks.find(ticket)).get(); task->abort(); } } - beginRemoveRows(QModelIndex(), removed_index, removed_index); - m_resources.erase(removed_it); + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + m_resources.erase(removedIt); endRemoveRows(); } } // add new resources to the end { - QSet added_set = new_set; - added_set.subtract(current_set); + QSet addedSet = newSet; + addedSet.subtract(currentSet); // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (added_set.size() > 0) { + if (addedSet.size() > 0) { beginInsertRows(QModelIndex(), static_cast(m_resources.size()), - static_cast(m_resources.size() + added_set.size() - 1)); + static_cast(m_resources.size() + addedSet.size() - 1)); - for (auto& added : added_set) { - auto res = new_resources[added]; + for (const auto& added : addedSet) { + auto res = newResources[added]; res->updateIssues(m_instance); m_resources.append(res); resolveResource(m_resources.last()); @@ -922,10 +952,10 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // update index { - m_resources_index.clear(); + m_resourcesIndex.clear(); int idx = 0; - for (auto const& mod : qAsConst(m_resources)) { - m_resources_index[mod->internal_id()] = idx; + for (const auto& mod : qAsConst(m_resources)) { + m_resourcesIndex[mod->internalId()] = idx; idx++; } } @@ -933,17 +963,19 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet Resource::Ptr ResourceFolderModel::find(QString id) { auto iter = - std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; }); - if (iter == m_resources.constEnd()) + std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](const Resource::Ptr& r) { return r->internalId() == id; }); + if (iter == m_resources.constEnd()) { return nullptr; + } return *iter; } QList ResourceFolderModel::allResources() { QList result; result.reserve(m_resources.size()); - for (const Resource ::Ptr& resource : m_resources) + for (const Resource ::Ptr& resource : m_resources) { result.append((resource.get())); + } return result; } @@ -951,8 +983,9 @@ QList ResourceFolderModel::selectedResources(const QModelIndexList& i { QList result; for (const QModelIndex& index : indexes) { - if (index.column() != 0) + if (index.column() != 0) { continue; + } result.append(&at(index.row())); } return result; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index e30485d3e..7699394ab 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -61,7 +61,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); ~ResourceFolderModel() override; virtual QString id() const { return "resource"; } @@ -93,13 +93,13 @@ class ResourceFolderModel : public QAbstractListModel { */ virtual bool installResource(QString path); - virtual void installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers); + virtual void installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers); /** Uninstall (i.e. remove all data about it) a resource, given its file name. * * Returns whether the removal was successful. */ - virtual bool uninstallResource(const QString& file_name, bool preserve_metadata = false); + virtual bool uninstallResource(const QString& fileName, bool preserveMetadata = false); virtual bool deleteResources(const QModelIndexList&); virtual void deleteMetadata(const QModelIndexList&); @@ -125,7 +125,7 @@ class ResourceFolderModel : public QAbstractListModel { Resource::Ptr find(QString id); - QDir const& dir() const { return m_dir; } + const QDir& dir() const { return m_dir; } /** Checks whether there's any parse tasks being done. * @@ -137,12 +137,12 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NumColumns }; - QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } + QStringList columnNames(bool translated = true) const { return translated ? m_columnNamesTranslated : m_columnNames; } int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } - int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; } + int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NumColumns; } Qt::DropActions supportedDropActions() const override; @@ -171,15 +171,15 @@ class ResourceFolderModel : public QAbstractListModel { QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); SortType columnToSortKey(size_t column) const; - QList columnResizeModes() const { return m_column_resize_modes; } + QList columnResizeModes() const { return m_columnResizeModes; } class ProxyModel : public QSortFilterProxyModel { public: explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} protected: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; - bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const override; }; QString instDirPath() const; @@ -207,7 +207,7 @@ class ResourceFolderModel : public QAbstractListModel { * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed * in the background, so it slowly updates the UI as tasks get done. */ - [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; } + [[nodiscard]] virtual Task* createParseTask(Resource& /*unused*/) { return nullptr; } /** Standard implementation of the model update logic. * @@ -215,10 +215,10 @@ class ResourceFolderModel : public QAbstractListModel { * to act only on those disparities. * */ - void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources); + void applyUpdates(QSet& currentSet, QSet& newSet, QMap& newResources); protected slots: - void directoryChanged(QString); + void directoryChanged(const QString&); /** Called when the update task is successful. * @@ -234,40 +234,40 @@ class ResourceFolderModel : public QAbstractListModel { * This is just a simple reference implementation. You probably want to override it with your own logic in a subclass * if the resource is complex and has more stuff to parse. */ - virtual void onParseSucceeded(int ticket, QString resource_id); - virtual void onParseFailed(int ticket, QString resource_id); + virtual void onParseSucceeded(int ticket, const QString& resourceId); + virtual void onParseFailed(int ticket, const QString& resourceId); protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE, - SortType::FILENAME }; - QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size", "File Name" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }; - QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + QList m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Date, + SortType::Provider, SortType::Size, SortType::Filename }; + QStringList m_columnNames = { "Enable", "Name", "Last Modified", "Provider", "Size", "File Name" }; + QStringList m_columnNamesTranslated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }; + QList m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; QList m_columnsHideable = { false, false, true, true, true, true }; QDir m_dir; BaseInstance* m_instance; QFileSystemWatcher m_watcher; - bool m_is_watching = false; + bool m_isWatching = false; - bool m_is_indexed; - bool m_first_folder_load = true; + bool m_isIndexed; + bool m_firstFolderLoad = true; - Task::Ptr m_current_update_task = nullptr; - bool m_scheduled_update = false; + Task::Ptr m_currentUpdateTask = nullptr; + bool m_scheduledUpdate = false; QList m_resources; // Represents the relationship between a resource's internal ID and it's row position on the model. - QMap m_resources_index; + QMap m_resourcesIndex; // Runs off-thread ConcurrentTask m_resourceResolver; bool m_resourceResolverRunning = false; - QMap m_active_parse_tasks; - std::atomic m_next_resolution_ticket = 0; + QMap m_activeParseTasks; + std::atomic m_nextResolutionTicket = 0; }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 84bc5ee98..4dd8e314c 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -39,27 +39,26 @@ #include #include -#include "Version.h" - #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) +ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(dir, instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size", "File Name" }); - m_column_names_translated = QStringList( + m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size", "File Name" }); + m_columnNamesTranslated = QStringList( { tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, - SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::FILENAME }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, + SortType::Date, SortType::Provider, SortType::Size, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -92,6 +91,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const return QSize(32, 32); } break; + default: + break; } // map the columns to the base equivilents @@ -115,6 +116,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case FileNameColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); break; + default: + break; } if (mappedIndex.isValid()) { @@ -174,10 +177,10 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } Task* ResourcePackFolderModel::createParseTask(Resource& resource) { - return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast(&resource)); + return new LocalDataPackParseTask(m_nextResolutionTicket, dynamic_cast(&resource)); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 02029cd00..186bbb75d 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,9 +7,19 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { + ActiveColumn = 0, + ImageColumn, + NameColumn, + PackFormatColumn, + DateColumn, + ProviderColumn, + SizeColumn, + FileNameColumn, + NumColumns + }; - explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); QString id() const override { return "resourcepacks"; } @@ -19,7 +29,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; RESOURCE_HELPERS(ResourcePack) }; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index 9b0180180..ded641770 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -18,7 +18,7 @@ class ShaderPackFolderModel : public ResourceFolderModel { [[nodiscard]] Task* createParseTask(Resource& resource) override { - return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast(resource)); + return new LocalShaderPackParseTask(m_nextResolutionTicket, static_cast(resource)); } QDir indexDir() const override { return m_dir; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 59c46fcea..976bf3854 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -36,28 +36,30 @@ #include "TexturePackFolderModel.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" -#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size", "File Name" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE, SortType::FILENAME }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnNames = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size", "File Name" }); + m_columnNamesTranslated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Date, + SortType::Provider, SortType::Size, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; m_columnsHideable = { false, true, false, true, true, true, true }; } Task* TexturePackFolderModel::createParseTask(Resource& resource) { - return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); + return new LocalTexturePackParseTask(m_nextResolutionTicket, static_cast(resource)); } QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -76,6 +78,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return QSize(32, 32); } break; + default: + break; } // map the columns to the base equivilents @@ -99,6 +103,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case FileNameColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); break; + default: + break; } if (mappedIndex.isValid()) { @@ -151,5 +157,5 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or int TexturePackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index feef2f5d5..3e7343092 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,11 +44,20 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { + ActiveColumn = 0, + ImageColumn, + NameColumn, + DateColumn, + ProviderColumn, + SizeColumn, + FileNameColumn, + NumColumns + }; - explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); - virtual QString id() const override { return "texturepacks"; } + QString id() const override { return "texturepacks"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -56,7 +65,7 @@ class TexturePackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; RESOURCE_HELPERS(TexturePack) }; diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp index 3b98e053b..a90e9ca5e 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp @@ -41,26 +41,28 @@ #include "minecraft/mod/MetadataHandler.h" #include +#include -ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir, - const QDir& index_dir, - bool is_indexed, - bool clean_orphan, - std::function create_function) +ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resourceDir, + const QDir& indexDir, + bool isIndexed, + bool cleanOrphan, + std::function createFunction) : Task(false) - , m_resource_dir(resource_dir) - , m_index_dir(index_dir) - , m_is_indexed(is_indexed) - , m_clean_orphan(clean_orphan) - , m_create_func(create_function) + , m_resource_dir(resourceDir) + , m_index_dir(indexDir) + , m_is_indexed(isIndexed) + , m_clean_orphan(cleanOrphan) + , m_create_func(std::move(createFunction)) , m_result(new Result()) , m_thread_to_spawn_into(thread()) {} void ResourceFolderLoadTask::executeTask() { - if (thread() != m_thread_to_spawn_into) + if (thread() != m_thread_to_spawn_into) { connect(this, &Task::finished, this->thread(), &QThread::quit); + } if (m_is_indexed) { // Read metadata first @@ -71,7 +73,7 @@ void ResourceFolderLoadTask::executeTask() m_resource_dir.refresh(); for (auto entry : m_resource_dir.entryInfoList()) { auto filePath = entry.absoluteFilePath(); - if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { + if (auto* app = APPLICATION_DYN; (app != nullptr) && app->checkQSavePath(filePath)) { continue; } auto newFilePath = FS::getUniqueResourceName(filePath); @@ -83,29 +85,29 @@ void ResourceFolderLoadTask::executeTask() Resource* resource = m_create_func(entry); if (resource->enabled()) { - if (m_result->resources.contains(resource->internal_id())) { - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); + if (m_result->resources.contains(resource->internalId())) { + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); // Delete the object we just created, since a valid one is already in the mods list. delete resource; } else { - m_result->resources[resource->internal_id()].reset(resource); - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); + m_result->resources[resource->internalId()].reset(resource); + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); } } else { - QString chopped_id = resource->internal_id().chopped(9); - if (m_result->resources.contains(chopped_id)) { - m_result->resources[resource->internal_id()].reset(resource); + QString choppedId = resource->internalId().chopped(9); + if (m_result->resources.contains(choppedId)) { + m_result->resources[resource->internalId()].reset(resource); - auto metadata = m_result->resources[chopped_id]->metadata(); + auto metadata = m_result->resources[choppedId]->metadata(); if (metadata) { resource->setMetadata(*metadata); - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); - m_result->resources.remove(chopped_id); + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); + m_result->resources.remove(choppedId); } } else { - m_result->resources[resource->internal_id()].reset(resource); - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); + m_result->resources[resource->internalId()].reset(resource); + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); } } } @@ -116,38 +118,41 @@ void ResourceFolderLoadTask::executeTask() QMutableMapIterator iter(m_result->resources); while (iter.hasNext()) { auto resource = iter.next().value(); - if (resource->status() == ResourceStatus::NOT_INSTALLED) { + if (resource->status() == ResourceStatus::NotInstalled) { resource->destroy(m_index_dir, false, false); iter.remove(); } } } - for (auto mod : m_result->resources) + for (const auto& mod : m_result->resources) { mod->moveToThread(m_thread_to_spawn_into); + } - if (m_aborted) + if (m_aborted) { emit finished(); - else + } else { emitSucceeded(); + } } void ResourceFolderLoadTask::getFromMetadata() { m_index_dir.refresh(); - for (auto entry : m_index_dir.entryList(QDir::Files)) { + for (const auto& entry : m_index_dir.entryList(QDir::Files)) { if (!entry.endsWith(".pw.toml")) { continue; } auto metadata = Metadata::get(m_index_dir, entry); - if (!metadata.isValid()) + if (!metadata.isValid()) { continue; + } auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename))); resource->setMetadata(metadata); - resource->setStatus(ResourceStatus::NOT_INSTALLED); - m_result->resources[resource->internal_id()].reset(resource); + resource->setStatus(ResourceStatus::NotInstalled); + m_result->resources[resource->internalId()].reset(resource); } } diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 7c872c13d..6489176b7 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -41,7 +41,7 @@ #include #include #include -#include "minecraft/mod/Mod.h" +#include "minecraft/mod/Resource.h" #include "tasks/Task.h" class ResourceFolderLoadTask : public Task { @@ -54,11 +54,11 @@ class ResourceFolderLoadTask : public Task { ResultPtr result() const { return m_result; } public: - ResourceFolderLoadTask(const QDir& resource_dir, - const QDir& index_dir, - bool is_indexed, - bool clean_orphan, - std::function create_function); + ResourceFolderLoadTask(const QDir& resourceDir, + const QDir& indexDir, + bool isIndexed, + bool cleanOrphan, + std::function createFunction); bool canAbort() const override { return true; } bool abort() override @@ -76,7 +76,7 @@ class ResourceFolderLoadTask : public Task { QDir m_resource_dir, m_index_dir; bool m_is_indexed; bool m_clean_orphan; - std::function m_create_func; + std::function m_create_func; ResultPtr m_result; std::atomic m_aborted = false; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 1ef3a56c9..2c7e485e5 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -99,7 +99,7 @@ void EnsureMetadataTask::executeTask() } // They already have the right metadata :o - if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) { + if (resource->status() != ResourceStatus::NoMetadata && resource->metadata() && resource->metadata()->provider == m_provider) { qDebug() << "Resource" << resource->name() << "already has metadata!"; emitReady(resource); continue; @@ -263,7 +263,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() Task::Ptr EnsureMetadataTask::modrinthProjectsTask() { QHash addonIds; - for (auto const& data : m_tempVersions) + for (const auto& data : m_tempVersions) addonIds.insert(data.addonId.toString(), data.hash); Task::Ptr proj_task; @@ -404,7 +404,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash addonIds; - for (auto const& hash : m_resources.keys()) { + for (const auto& hash : m_resources.keys()) { if (m_tempVersions.contains(hash)) { auto data = m_tempVersions.find(hash).value(); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 5bd13220a..577f9967a 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -111,10 +111,10 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* } if (!latestVer->hash.isEmpty() && - (resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + (resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NotInstalled)) { auto oldVersion = resource->metadata()->version_number; if (oldVersion.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) { + if (resource->status() == ResourceStatus::NotInstalled) { oldVersion = tr("Not installed"); } else { oldVersion = tr("Unknown"); diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index cb2d57aba..0fda37e57 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -178,12 +178,12 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio pack->slug = resource->metadata()->slug; pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { + if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NotInstalled)) { auto downloadTask = makeShared(pack, projectVer, m_resourceModel, true, "update"); QString oldVersion = resource->metadata()->version_number; if (oldVersion.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) { + if (resource->status() == ResourceStatus::NotInstalled) { oldVersion = tr("Not installed"); } else { oldVersion = tr("Unknown"); diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index e229bd672..e9776c660 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -306,7 +306,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool // ask the user on what provider to seach for the mod first for (auto* candidate : m_candidates) { - if (candidate->status() != ResourceStatus::NO_METADATA) { + if (candidate->status() != ResourceStatus::NoMetadata) { onMetadataEnsured(candidate); continue; } @@ -321,7 +321,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool if (confirmRest) { addToTmp(candidate, providerRest); - shouldTryOthers.insert(candidate->internal_id(), tryOthersRest); + shouldTryOthers.insert(candidate->internalId(), tryOthersRest); continue; } @@ -343,7 +343,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool tryOthersRest = response.try_others; } - shouldTryOthers.insert(candidate->internal_id(), response.try_others); + shouldTryOthers.insert(candidate->internalId(), response.try_others); if (confirmed) { addToTmp(candidate, response.chosen); @@ -355,7 +355,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool auto modrinthTask = makeShared(modrinthTmp, indexDir2, ModPlatform::ResourceProvider::MODRINTH); connect(modrinthTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); connect(modrinthTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { - onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internalId()).value(), ModPlatform::ResourceProvider::MODRINTH); }); connect(modrinthTask.get(), &EnsureMetadataTask::failed, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); @@ -372,7 +372,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool auto flameTask = makeShared(flameTmp, indexDir2, ModPlatform::ResourceProvider::FLAME); connect(flameTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); connect(flameTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { - onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internalId()).value(), ModPlatform::ResourceProvider::FLAME); }); connect(flameTask.get(), &EnsureMetadataTask::failed, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 354211439..3bf9fef82 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -87,7 +87,7 @@ void InfoFrame::updateWithMod(const Mod& m) QString name = ""; QString link = m.homepage(); if (m.name().isEmpty()) - name = m.internal_id(); + name = m.internalId(); else name = renderColorCodes(m.name()); diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 145e6b3d7..26fe5ab16 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -217,8 +217,8 @@ class ResourceFolderModelTest : public QObject { auto& res_1 = model.at(0).type() != ResourceType::FOLDER ? model.at(0) : model.at(1); auto& res_2 = model.at(0).type() == ResourceType::FOLDER ? model.at(0) : model.at(1); - auto id_1 = res_1.internal_id(); - auto id_2 = res_2.internal_id(); + auto id_1 = res_1.internalId(); + auto id_2 = res_2.internalId(); bool initial_enabled_res_2 = res_2.enabled(); bool initial_enabled_res_1 = res_1.enabled(); @@ -236,12 +236,12 @@ class ResourceFolderModelTest : public QObject { qDebug() << "res_1 got successfully toggled again."; QVERIFY(res_1.enabled() == initial_enabled_res_1); - QVERIFY(res_1.internal_id() == id_1); + QVERIFY(res_1.internalId() == id_1); qDebug() << "res_1 got back to its initial state."; QVERIFY(!res_2.enable(initial_enabled_res_2 ? EnableAction::ENABLE : EnableAction::DISABLE)); QVERIFY(res_2.enabled() == initial_enabled_res_2); - QVERIFY(res_2.internal_id() == id_2); + QVERIFY(res_2.internalId() == id_2); } }; From b7381d808852570373bcf42690d10be5e2eb7653 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 11:24:40 +0300 Subject: [PATCH 091/132] add size column for datapacks Signed-off-by: Trial97 --- .../minecraft/mod/DataPackFolderModel.cpp | 22 ++++++++++++++----- launcher/minecraft/mod/DataPackFolderModel.h | 11 +++++++++- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index 5f34ce6e1..350ae60e1 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -45,13 +45,14 @@ DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "File Name" }); + m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size", "File Name" }); m_columnNamesTranslated = - QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("File Name") }); - m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, SortType::Date, SortType::Filename }; - m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, + SortType::Date, SortType::Size, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true }; + m_columnsHideable = { false, true, false, true, true, true, true }; } QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const @@ -71,6 +72,10 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const const auto& resource = at(row); return resource.packFormatStr(); } + if (column == SizeColumn) { + const auto& resource = at(row); + return resource.sizeStr(); + } break; case Qt::DecorationRole: { if (column == ImageColumn) { @@ -112,7 +117,9 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const case FileNameColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); break; - // FIXME: there is no size column due to an oversight + case SizeColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); + break; default: break; } @@ -134,6 +141,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case PackFormatColumn: case DateColumn: case ImageColumn: + case SizeColumn: case FileNameColumn: return columnNames().at(section); default: @@ -151,6 +159,8 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); case DateColumn: return tr("The date and time this data pack was last changed (or added)."); + case SizeColumn: + return tr("The size of the data pack."); case FileNameColumn: return tr("The file name of the data pack."); default: diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h index 04153b893..24133354a 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.h +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -43,7 +43,16 @@ class DataPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns : std::uint8_t { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, FileNameColumn, NumColumns }; + enum Columns : std::uint8_t { + ActiveColumn = 0, + ImageColumn, + NameColumn, + PackFormatColumn, + DateColumn, + SizeColumn, + FileNameColumn, + NumColumns + }; explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); From 670f49309c96f1aff6f077d3d7cd0064eae8a29a Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 7 May 2026 15:47:58 +0300 Subject: [PATCH 092/132] chore(clang-tidy): fix clang tidy warnings Signed-off-by: Trial97 --- .../minecraft/VanillaInstanceCreationTask.cpp | 19 ++++++++----------- .../minecraft/VanillaInstanceCreationTask.h | 4 ++-- launcher/minecraft/mod/ModFolderModel.cpp | 1 + 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index 017f85027..e646e2c52 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -7,30 +7,27 @@ #include "minecraft/PackProfile.h" #include "settings/INISettingsObject.h" -VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version) - : InstanceCreationTask() - , m_version(std::move(version)) - , m_using_loader(true) - , m_loader(std::move(loader)) - , m_loader_version(std::move(loader_version)) +VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion) + : m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loaderVersion)) {} std::unique_ptr VanillaCreationTask::createInstance() { setStatus(tr("Creating instance from version %1").arg(m_version->name())); - auto inst = std::make_unique(m_globalSettings, std::make_unique(FS::PathCombine(m_stagingPath, "instance.cfg")), - m_stagingPath); + auto inst = std::make_unique( + m_globalSettings, std::make_unique(FS::PathCombine(m_stagingPath, "instance.cfg")), m_stagingPath); SettingsObject::Lock lock(inst->settings()); - auto components = inst->getPackProfile(); + auto* components = inst->getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - if (m_using_loader) + if (m_using_loader) { components->setComponentVersion(m_loader, m_loader_version->descriptor()); + } inst->setName(name()); inst->setIconKey(m_instIcon); - + components->saveNow(); return inst; } diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h index 7015a4fe5..c1a69ab62 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.h +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -7,8 +7,8 @@ class VanillaCreationTask final : public InstanceCreationTask { Q_OBJECT public: - VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {} - VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version); + explicit VanillaCreationTask(BaseVersion::Ptr version) : m_version(std::move(version)) {} + VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion); std::unique_ptr createInstance() override; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index b5e5b2e87..d5f609df3 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include "minecraft/mod/Resource.h" #include "minecraft/mod/ResourceFolderModel.h" From 993eb40481d0da1248f4ccfde56f5a50a3d34ddf Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 10 May 2026 01:46:29 +0500 Subject: [PATCH 093/132] fix: suppress sfinae-incomplete warning Signed-off-by: Octol1ttle --- launcher/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 235ba2975..7d4430fd2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1491,8 +1491,10 @@ if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") target_compile_options(Launcher_logic PRIVATE /wd4100) # C4100 - unused parameter target_compile_options(${Launcher_Name} PRIVATE /wd4100) # C4100 - unused parameter else() - target_compile_options(Launcher_logic PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers) - target_compile_options(${Launcher_Name} PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers) + # sfinae-incomplete is a new GCC warning and triggers in Qt headers + # no-unknown-warning-option so that compilers that don't have sfinae-incomplete don't error + target_compile_options(Launcher_logic PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-warning-option -Wno-sfinae-incomplete) + target_compile_options(${Launcher_Name} PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-warning-option -Wno-sfinae-incomplete) endif() #### The bundle mess! #### From d741b403a991e85b6f166bc734ca5a34e90bd3c9 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 11 May 2026 15:43:26 +0500 Subject: [PATCH 094/132] change: enable automatically enabling automatic merging on backport PRs Signed-off-by: Octol1ttle --- .github/workflows/backport.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 5d4fa35c3..716afc234 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -27,6 +27,7 @@ jobs: uses: korthout/backport-action@v4.4 with: # Config README: https://github.com/korthout/backport-action#backport-action + auto_merge_enabled: true pull_description: |- Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}. From d6db7507976763ecafbd92c9cc3fb3fd6081955d Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 12 May 2026 00:19:57 +0500 Subject: [PATCH 095/132] fix: do not open account select dialog if there are no valid accounts Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index d263cc50a..8892a5466 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -109,7 +109,7 @@ void LaunchController::decideAccount() } } - if (!m_accountToUse) { + if (!m_accountToUse && accounts->anyAccountIsValid()) { // If no default account is set, ask the user which one to use. ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox, m_parentWidget); From 40824a187d549bcdacb89ebfe0fa3ff95385e843 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 13 May 2026 02:15:27 -0400 Subject: [PATCH 096/132] revert: "change: enable automatically enabling automatic merging on backport PRs" Refs: d741b40 Signed-off-by: Seth Flynn --- .github/workflows/backport.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 716afc234..5d4fa35c3 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -27,7 +27,6 @@ jobs: uses: korthout/backport-action@v4.4 with: # Config README: https://github.com/korthout/backport-action#backport-action - auto_merge_enabled: true pull_description: |- Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}. From 6699d3eca0ea3450e83ae633e6754bda849ece84 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 25 Feb 2026 17:15:36 +0200 Subject: [PATCH 097/132] clang-tidy: clang-analyzer-* This commit aims to fix all clang-analyzer-* warnings from clang-tidy. Here is the list of the ones found in project: "clang-analyzer-core.uninitialized.UndefReturn", "clang-analyzer-deadcode.DeadStores", "clang-analyzer-optin.core.EnumCastOutOfRange", Some exceptions: clang-analyzer-cplusplus.NewDeleteLeaks -> may need to disable it as is a false positive clang-analyzer-optin.cplusplus.VirtualCall -> may need to disable it (or refactor a bunch of code to drop the virtual from those functions) Signed-off-by: Trial97 --- launcher/Application.cpp | 18 +++++----- launcher/MTPixmapCache.h | 36 +++++++++---------- launcher/modplatform/ModIndex.h | 1 + launcher/modplatform/flame/FlameAPI.h | 1 + .../modplatform/helpers/ExportToModList.h | 4 ++- .../import_ftb/PackInstallTask.cpp | 24 ++++--------- launcher/translations/POTranslator.cpp | 3 +- launcher/ui/dialogs/ExportToModListDialog.cpp | 2 ++ launcher/ui/instanceview/InstanceView.cpp | 4 +-- launcher/ui/widgets/PageContainer.h | 4 +-- launcher/ui/widgets/ProjectItem.cpp | 15 ++++---- 11 files changed, 50 insertions(+), 62 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index efd63714d..ddeb30588 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -578,16 +578,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { - bool migrated = false; - - if (!migrated) - migrated = handleDataMigration( - dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", - "polymc.cfg"); - if (!migrated) - migrated = handleDataMigration( - dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", - "multimc.cfg"); + auto migrated = handleDataMigration( + dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", + "polymc.cfg"); + if (!migrated) { + handleDataMigration(dataPath, + FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), + "MultiMC", "multimc.cfg"); + } } { diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h index 0ba9c5ac8..97db598de 100644 --- a/launcher/MTPixmapCache.h +++ b/launcher/MTPixmapCache.h @@ -14,26 +14,26 @@ else \ type = Qt::DirectConnection; -#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \ +#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE, RET_DEF) \ static RET_TYPE NAME() \ { \ - RET_TYPE ret; \ + RET_TYPE ret = RET_DEF; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \ return ret; \ } -#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \ +#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE) \ static RET_TYPE NAME(PARAM_1_TYPE p1) \ { \ - RET_TYPE ret; \ + RET_TYPE ret = RET_DEF; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \ return ret; \ } -#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \ +#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE, PARAM_2_TYPE) \ static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \ { \ - RET_TYPE ret; \ + RET_TYPE ret = RET_DEF; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \ Q_ARG(PARAM_2_TYPE, p2)); \ @@ -53,18 +53,18 @@ class PixmapCache final : public QObject { static void setInstance(PixmapCache* i) { s_instance = i; } public: - DEFINE_FUNC_NO_PARAM(cacheLimit, int) - DEFINE_FUNC_NO_PARAM(clear, bool) - DEFINE_FUNC_TWO_PARAM(find, bool, const QString&, QPixmap*) - DEFINE_FUNC_TWO_PARAM(find, bool, const QPixmapCache::Key&, QPixmap*) - DEFINE_FUNC_TWO_PARAM(insert, bool, const QString&, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(remove, bool, const QString&) - DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&) - DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int) - DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool) - DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int) + DEFINE_FUNC_NO_PARAM(cacheLimit, int, -1) + DEFINE_FUNC_NO_PARAM(clear, bool, false) + DEFINE_FUNC_TWO_PARAM(find, bool, false, const QString&, QPixmap*) + DEFINE_FUNC_TWO_PARAM(find, bool, false, const QPixmapCache::Key&, QPixmap*) + DEFINE_FUNC_TWO_PARAM(insert, bool, false, const QString&, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, {}, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(remove, bool, false, const QString&) + DEFINE_FUNC_ONE_PARAM(remove, bool, false, const QPixmapCache::Key&) + DEFINE_FUNC_TWO_PARAM(replace, bool, false, const QPixmapCache::Key&, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, false, int) + DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool, false) + DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, false, int) // NOTE: Every function returns something non-void to simplify the macros. private slots: diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index e1278ae84..8984e575e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -32,6 +32,7 @@ class QIODevice; namespace ModPlatform { enum class ModLoaderType : std::uint16_t { + None = 0U, NeoForge = 1U << 0U, Forge = 1U << 1U, Cauldron = 1U << 2U, diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 607b32cab..e77d53a4b 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -79,6 +79,7 @@ class FlameAPI : public ResourceAPI { case ModPlatform::LegacyFabric: case ModPlatform::Ornithe: case ModPlatform::Rift: + case ModPlatform::None: break; // not supported } return 0; diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h index ab7797fe6..7cbe730f2 100644 --- a/launcher/modplatform/helpers/ExportToModList.h +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -23,7 +23,9 @@ namespace ExportToModList { enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; -enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; +enum OptionalDataValue { None = 0, Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; +Q_DECLARE_FLAGS(OptionalData, OptionalDataValue) + QString exportToModList(QList mods, Formats format, OptionalData extraData); QString exportToModList(QList mods, QString lineTemplate); } // namespace ExportToModList diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp index 878ef26fa..659c5d2ed 100644 --- a/launcher/modplatform/import_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -63,12 +63,12 @@ void PackInstallTask::copySettings() instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString()); } - auto components = instance.getPackProfile(); + auto* components = instance.getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); auto modloader = m_pack.loaderType; - if (modloader.has_value()) + if (modloader.has_value()) { switch (modloader.value()) { case ModPlatform::NeoForge: { components->setComponentVersion("net.neoforged", m_pack.loaderVersion, true); @@ -86,28 +86,16 @@ void PackInstallTask::copySettings() components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true); break; } - case ModPlatform::Cauldron: - break; - case ModPlatform::LiteLoader: - break; - case ModPlatform::DataPack: - break; - case ModPlatform::Babric: - break; - case ModPlatform::BTA: - break; - case ModPlatform::LegacyFabric: - break; - case ModPlatform::Ornithe: - break; - case ModPlatform::Rift: + default: break; } + } components->saveNow(); instance.setName(name()); - if (m_instIcon == "default") + if (m_instIcon == "default") { m_instIcon = "ftb_logo"; + } instance.setIconKey(m_instIcon); } emitSucceeded(); diff --git a/launcher/translations/POTranslator.cpp b/launcher/translations/POTranslator.cpp index 458ebd230..0bd3638e6 100644 --- a/launcher/translations/POTranslator.cpp +++ b/launcher/translations/POTranslator.cpp @@ -183,8 +183,7 @@ void POTranslatorPrivate::reload() nextFuzzy = true; } } else if (line.startsWith('"')) { - QByteArray temp; - QByteArray* out = &temp; + QByteArray* out = nullptr; switch (mode) { case Mode::First: diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index e8873f9b4..a782a9190 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -214,6 +214,8 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) case ExportToModList::FileName: ui->templateText->insertPlainText("{filename}"); break; + case ExportToModList::None: + break; } } void ExportToModListDialog::enableCustom(bool enabled) diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index aff61cb3b..9a24b7990 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -511,8 +511,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) int wpWidth = viewport()->width(); option.rect.setWidth(wpWidth); - for (int i = 0; i < m_groups.size(); ++i) { - VisualGroup* category = m_groups.at(i); + for (auto* category : m_groups) { int y = category->verticalPosition(); y -= verticalOffset(); QRect backup = option.rect; @@ -522,7 +521,6 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) option.rect.setLeft(m_leftMargin); option.rect.setRight(wpWidth - m_rightMargin); category->drawHeader(&painter, option); - y += category->totalHeight() + m_categoryMargin; option.rect = backup; } diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index b73326c02..2ca5e6f08 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -56,9 +56,7 @@ class QGridLayout; class PageContainer : public QWidget, public BasePageContainer { Q_OBJECT public: - explicit PageContainer(BasePageProvider* pageProvider, - QString defaultId = QString(), - QWidget* parent = nullptr); + explicit PageContainer(BasePageProvider* pageProvider, QString defaultId = QString(), QWidget* parent = nullptr); ~PageContainer() override = default; void addButtons(QWidget* buttons); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index dd38cafd1..1c4fa3596 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -47,30 +47,31 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->setOpacity(0.4); // Fade out the entire item } // The default icon size will be a square (and height is usually the lower value). - auto icon_width = rect.height(), icon_height = rect.height(); + auto icon_width = rect.height(); int icon_x_margin = (rect.height() - icon_width) / 2; - int icon_y_margin = (rect.height() - icon_height) / 2; if (!opt.icon.isNull()) { // Icon painting + auto icon_height = 0; { auto icon_size = opt.decorationSize; icon_width = icon_size.width(); icon_height = icon_size.height(); - icon_y_margin = (rect.height() - icon_height) / 2; - icon_x_margin = icon_y_margin; // use same margins for consistency + icon_x_margin = (rect.height() - icon_height) / 2; // use same margins for consistency } // Centralize icon with a margin to separate from the other elements int x = rect.x() + icon_x_margin; - int y = rect.y() + icon_y_margin; + int y = rect.y() + icon_x_margin; - if (opt.features & QStyleOptionViewItem::HasCheckIndicator) + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { rect.translate(icon_x_margin / 2, 0); + } // Prevent 'scaling null pixmap' warnings - if (icon_width > 0 && icon_height > 0) + if (icon_width > 0 && icon_height > 0) { opt.icon.paint(painter, x, y, icon_width, icon_height); + } } // Change the rect so that funther painting is easier From bc1f9db6539c0b70cff674165dc345a789f98b05 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 13 May 2026 17:59:03 +0500 Subject: [PATCH 098/132] fix offline accounts not being refreshed during launch Closes #5435 Closes #5537 Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 8892a5466..f7ea23eb3 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -133,14 +133,6 @@ LaunchDecision LaunchController::decideLaunchMode() return LaunchDecision::Continue; } - if (m_wantedLaunchMode == LaunchMode::Normal) { - if (m_accountToUse->shouldRefresh() || m_accountToUse->accountState() == AccountState::Offline) { - // Force account refresh on the account used to launch the instance updating the AccountState - // only on first try and if it is not meant to be offline - m_accountToUse->refresh(); - } - } - const auto* accounts = APPLICATION->accounts(); MinecraftAccountPtr accountToCheck = nullptr; @@ -163,7 +155,9 @@ LaunchDecision LaunchController::decideLaunchMode() } auto state = accountToCheck->accountState(); - if (state == AccountState::Unchecked || state == AccountState::Errored) { + const bool needsRefresh = + m_wantedLaunchMode == LaunchMode::Normal && (state == AccountState::Offline || accountToCheck->shouldRefresh()); + if (state == AccountState::Unchecked || state == AccountState::Errored || needsRefresh) { accountToCheck->refresh(); state = AccountState::Working; } From 558e5bc15554f28045de907e03acfef455e695d5 Mon Sep 17 00:00:00 2001 From: mctaylors <95250141+mctaylors@users.noreply.github.com> Date: Wed, 13 May 2026 22:09:55 +0300 Subject: [PATCH 099/132] change(bug_report.yml): use input type where short answer is preferred Signed-off-by: mctaylors <95250141+mctaylors@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4ea328301..3574bf20d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,14 +23,14 @@ body: - macOS - Linux - Other -- type: textarea +- type: input attributes: label: Version of Prism Launcher description: The version of Prism Launcher used in the bug report. placeholder: Prism Launcher 5.0 validations: required: true -- type: textarea +- type: input attributes: label: Version of Qt description: The version of Qt used in the bug report. You can find it in Help -> About Prism Launcher -> About Qt. From 8a7b17f9587369b1d3ca5b1c3436f78609f4c6c2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 13 May 2026 21:12:31 +0200 Subject: [PATCH 100/132] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'https://releases.nixos.org/nixos/unstable/nixos-26.05pre990025.15f4ee454b1d/nixexprs.tar.xz?narHash=sha256-fN6ynMvcdwPDB09LpWJNO5ogu%2BHFydrBWXJywoI/NNg%3D' (2026-04-30) → 'https://releases.nixos.org/nixos/unstable/nixos-26.05pre995699.da5ad661ba4e/nixexprs.tar.xz?narHash=sha256-rNDJzV2JTV5SUTwv1cgKZYMdyoUYU9/YfegSaUf3QfY%3D' (2026-05-10) Signed-off-by: Sefa Eyeoglu --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index d6917f5a6..640d2bcf1 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1777578337, - "narHash": "sha256-fN6ynMvcdwPDB09LpWJNO5ogu+HFydrBWXJywoI/NNg=", - "rev": "15f4ee454b1dce334612fa6843b3e05cf546efab", + "lastModified": 1778443072, + "narHash": "sha256-rNDJzV2JTV5SUTwv1cgKZYMdyoUYU9/YfegSaUf3QfY=", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre990025.15f4ee454b1d/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre995699.da5ad661ba4e/nixexprs.tar.xz" }, "original": { "type": "tarball", From 8ba5444c6b1acf8712d3a65bf6705955a584e26f Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 13 May 2026 21:13:12 +0200 Subject: [PATCH 101/132] fix(nix): switch to KF6 ECM The override can be removed after https://github.com/NixOS/nixpkgs/pull/518987 reaches nixos-unstable See https://github.com/NixOS/nixpkgs/pull/513691 Signed-off-by: Sefa Eyeoglu --- nix/unwrapped.nix | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 478eb7b3e..829a4843c 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,12 +3,12 @@ stdenv, cmake, cmark, - extra-cmake-modules, gamemode, jdk17, kdePackages, libnbtplusplus, ninja, + pkg-config, qrencode, self, stripJavaArchivesHook, @@ -35,6 +35,13 @@ let ] else "unknown"; + + # Remove once https://github.com/NixOS/nixpkgs/pull/518987 lands + extra-cmake-modules = kdePackages.extra-cmake-modules.overrideAttrs (prevAttrs: { + meta = prevAttrs.meta // { + platforms = lib.platforms.all; + }; + }); in stdenv.mkDerivation { @@ -65,6 +72,7 @@ stdenv.mkDerivation { cmake ninja extra-cmake-modules + pkg-config jdk17 stripJavaArchivesHook ]; From f4567bcc8c324a1b61c26c5c3f5b2eedfd44496a Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 15 May 2026 14:14:46 +0500 Subject: [PATCH 102/132] fix renovate using old label Signed-off-by: Octol1ttle --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 0a74c6de7..856b2e91c 100644 --- a/renovate.json +++ b/renovate.json @@ -4,7 +4,7 @@ "config:recommended" ], "labels": [ - "area: CI", + "area: actions", "complexity: low", "priority: low", "type: robot", From 28eba8ed430b94be39d5b00fe5afcebab7b8f795 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 15 May 2026 13:01:41 +0100 Subject: [PATCH 103/132] Fix memory leak and crash with data packs modal Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/WorldListPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index e56e9c731..24c880429 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -259,6 +259,7 @@ void WorldListPage::on_actionData_Packs_triggered() dialog->setLayout(layout); + dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->exec(); APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); From 547b4c0d3ac42921b575b9e9cba9b92ae001d0ff Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 17 May 2026 23:45:40 +0500 Subject: [PATCH 104/132] feat(instance logs): log components Signed-off-by: Octol1ttle --- launcher/minecraft/MinecraftInstance.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index ff6a46b11..8e98a2efe 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -893,6 +893,14 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr QStringList out; + out << "Components:"; + for (int i = 0; i < m_components->rowCount(); ++i) { + const auto& component = m_components->getComponent(i); + out << indent + + QString("%1) %2 (%3) %4").arg(QString::number(i + 1), component->getName(), component->getID(), component->getVersion()); + } + out << emptyLine; + out << "Launcher: " + getLauncher(); out << "Main class: " + getMainClass() << emptyLine; From 61cdabd40ec9e793aeccb46199f6802754218dbb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 18:59:51 +0000 Subject: [PATCH 105/132] chore(deps): update cachix/install-nix-action digest to 8aa0397 --- .github/workflows/update-flake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index bcbf0fad6..fa3e3b4d3 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@616559265b40713947b9c190a8ff4b507b5df49b # v31 + - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31 - uses: DeterminateSystems/update-flake-lock@v28 with: From d4d032afead6f188c8609c769cd355bd889f4223 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 18:59:55 +0000 Subject: [PATCH 106/132] chore(deps): update korthout/backport-action action to v4.5 --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 716afc234..b102763b4 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -24,7 +24,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v4.4 + uses: korthout/backport-action@v4.5 with: # Config README: https://github.com/korthout/backport-action#backport-action auto_merge_enabled: true From fa61e58cd976740e6c815f03bda9ea5ce5718ff8 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 28 May 2026 09:20:57 +0100 Subject: [PATCH 107/132] Replace exec with open and handle saving geometry in signal listener Signed-off-by: TheKodeToad --- launcher/ui/pages/instance/WorldListPage.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 24c880429..71c370ab2 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -121,7 +121,7 @@ void WorldListPage::openedImpl() ui->toolBar->removeAction(ui->actionJoin); } - auto const setting_name = QString("WideBarVisibility_%1").arg(id()); + const auto setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); @@ -260,9 +260,11 @@ void WorldListPage::on_actionData_Packs_triggered() dialog->setLayout(layout); dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->exec(); - APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); + connect(dialog, &QDialog::finished, this, + [dialog]() { APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); }); + + dialog->open(); } void WorldListPage::on_actionReset_Icon_triggered() From ccc23c8bc3319cc0e976596cbe177fcac6145b93 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 28 May 2026 13:43:12 +0500 Subject: [PATCH 108/132] change: use native APIs for GPU discovery Signed-off-by: Octol1ttle --- .../setup-dependencies/linux/action.yml | 2 +- Containerfile | 2 +- launcher/HardwareInfo.cpp | 231 +++++++++--------- vcpkg.json | 3 +- 4 files changed, 122 insertions(+), 116 deletions(-) diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index fe7ee2142..fa5af702b 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -13,7 +13,7 @@ runs: dpkg-dev \ ninja-build extra-cmake-modules pkg-config scdoc \ cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ - libxcb-cursor-dev libtomlplusplus-dev libvulkan-dev + libxcb-cursor-dev libtomlplusplus-dev - name: Setup AppImage tooling shell: bash diff --git a/Containerfile b/Containerfile index 8ee4330ed..59595fe55 100644 --- a/Containerfile +++ b/Containerfile @@ -28,7 +28,7 @@ RUN apt-get --assume-yes --no-install-recommends install \ # Build system cmake ninja-build extra-cmake-modules pkg-config \ # Dependencies - cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev libvulkan-dev scdoc zlib1g-dev \ + cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev scdoc zlib1g-dev \ # Tooling clang-format clang-tidy git diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp index 3e20cc19b..425f07a09 100644 --- a/launcher/HardwareInfo.cpp +++ b/launcher/HardwareInfo.cpp @@ -18,84 +18,45 @@ #include "HardwareInfo.h" -#include -#include -#include -#include -#include "BuildConfig.h" - -#ifndef Q_OS_MACOS -#include -#include -#endif +#include +#include +#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) namespace { -bool vulkanInfo(QStringList& out) +QString afterColon(QString str) { - if (!QProcessEnvironment::systemEnvironment() - .value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME)) - .isEmpty()) { - return false; - } -#ifndef Q_OS_MACOS - QVulkanInstance inst; - if (!inst.create()) { - qWarning() << "Vulkan instance creation failed, VkResult:" << inst.errorCode(); - out << "Couldn't get Vulkan device information"; - return false; - } - - QVulkanWindow window; - window.setVulkanInstance(&inst); - - for (auto device : window.availablePhysicalDevices()) { - const auto supportedVulkanVersion = QVersionNumber(VK_API_VERSION_MAJOR(device.apiVersion), VK_API_VERSION_MINOR(device.apiVersion), - VK_API_VERSION_PATCH(device.apiVersion)); - out << QString("Found Vulkan device: %1 (API version %2)").arg(device.deviceName).arg(supportedVulkanVersion.toString()); - } -#endif - - return true; + return str.remove(0, str.indexOf(':') + 2).trimmed(); } -bool openGlInfo(QStringList& out) +template +bool readFromOutput(const char* command, F function) { - if (!QProcessEnvironment::systemEnvironment() - .value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME)) - .isEmpty()) { - return false; - } - QOpenGLContext ctx; - if (!ctx.create()) { - qWarning() << "OpenGL context creation failed"; - out << "Couldn't get OpenGL device information"; + FILE* file = popen(command, "r"); // NOLINT(*-command-processor) + if (!file) { + qWarning().nospace() << "Could not execute command '" << command << "': " << strerror(errno); return false; } - QOffscreenSurface surface; - surface.create(); - ctx.makeCurrent(&surface); + constexpr size_t bufferSize = 512; + std::array buffer{}; + while (fgets(buffer.data(), bufferSize, file) != nullptr) { + function(buffer.data()); + } - auto* f = ctx.functions(); - f->initializeOpenGLFunctions(); + const int exitCode = pclose(file); + if (exitCode != 0) { + if (exitCode == -1) { + qWarning().nospace() << "Could not close stream for command '" << command << "': " << strerror(errno); + } else { + qWarning().nospace() << "Command '" << command << "' exited with code " << exitCode; + } - auto toQString = [](const GLubyte* str) { return QString(reinterpret_cast(str)); }; - out << "OpenGL driver vendor: " + toQString(f->glGetString(GL_VENDOR)); - out << "OpenGL renderer: " + toQString(f->glGetString(GL_RENDERER)); - out << "OpenGL driver version: " + toQString(f->glGetString(GL_VERSION)); + return false; + } return true; } } // namespace - -#ifndef Q_OS_LINUX -QStringList HardwareInfo::gpuInfo() -{ - QStringList info; - vulkanInfo(info); - openGlInfo(info); - return info; -} #endif #ifdef Q_OS_WINDOWS @@ -104,6 +65,7 @@ QStringList HardwareInfo::gpuInfo() #endif #include +#include "dxgi.h" #include "windows.h" QString HardwareInfo::cpuInfo() @@ -140,15 +102,43 @@ uint64_t HardwareInfo::availableRamMiB() return 0; } +QStringList HardwareInfo::gpuInfo() +{ + IDXGIFactory* factory = nullptr; + if (CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast(&factory)) != S_OK) { // NOLINT(*-pro-type-reinterpret-cast) + qWarning() << "Could not create DXGI factory"; + return { "GPU discovery failed: could not create DXGI factory" }; + } + + UINT i = 0; + IDXGIAdapter* adapter = nullptr; + QStringList out; + + while (factory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND) { + DXGI_ADAPTER_DESC desc; + if (adapter->GetDesc(&desc) != S_OK) { + qWarning() << "Could not get DXGI adapter description"; + } else { + out << "GPU: " + QString::fromWCharArray(desc.Description); // NOLINT(*-pro-bounds-array-to-pointer-decay, *-no-array-decay) + } + + adapter->Release(); + ++i; + } + + factory->Release(); + return out; +} + #elif defined(Q_OS_MACOS) #include "sys/sysctl.h" QString HardwareInfo::cpuInfo() { - std::array buffer; + std::array buffer{}; size_t bufferSize = buffer.size(); if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) { - return QString(buffer.data()); + return { buffer.data() }; } qWarning() << "Could not get CPU model: sysctlbyname"; @@ -157,7 +147,7 @@ QString HardwareInfo::cpuInfo() uint64_t HardwareInfo::totalRamMiB() { - uint64_t memsize; + uint64_t memsize = 0; size_t memsizeSize = sizeof memsize; if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) { // transforming bytes -> mib @@ -175,7 +165,7 @@ uint64_t HardwareInfo::availableRamMiB() MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel() { - uint32_t level; + uint32_t level = 0; size_t levelSize = sizeof level; if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) { return static_cast(level); @@ -195,25 +185,37 @@ QString MacOSHardwareInfo::memoryPressureLevelName() return "Yellow"; case MemoryPressureLevel::Critical: return "Red"; + default: + Q_ASSERT(false); + return ""; } } +QStringList HardwareInfo::gpuInfo() +{ + QStringList out; + const bool success = readFromOutput("system_profiler SPDisplaysDataType", [&](const QString& str) { + // Chipset Model: Intel HD Graphics 620 + if (str.contains("Chipset Model")) { + out << "GPU: " + afterColon(str); + } + }); + if (!success) { + return { "GPU discovery failed: could not read from system_profiler" }; + } + + return out; +} + #elif defined(Q_OS_LINUX) #include -namespace { -QString afterColon(QString& str) -{ - return str.remove(0, str.indexOf(':') + 2).trimmed(); -} -} // namespace - QString HardwareInfo::cpuInfo() { std::ifstream cpuin("/proc/cpuinfo"); for (std::string line; std::getline(cpuin, line);) { // model name : AMD Ryzen 7 5800X 8-Core Processor - if (QString str = QString::fromStdString(line); str.startsWith("model name")) { + if (const QString str = QString::fromStdString(line); str.startsWith("model name")) { return afterColon(str); } } @@ -222,12 +224,13 @@ QString HardwareInfo::cpuInfo() return "unknown"; } -uint64_t readMemInfo(QString searchTarget) +namespace { +uint64_t readMemInfo(const QString& searchTarget) { std::ifstream memin("/proc/meminfo"); for (std::string line; std::getline(memin, line);) { // MemTotal: 16287480 kB - if (QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { + if (const QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { bool ok = false; const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok); if (!ok) { @@ -243,6 +246,7 @@ uint64_t readMemInfo(QString searchTarget) qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget; return 0; } +} // namespace uint64_t HardwareInfo::totalRamMiB() { @@ -256,52 +260,50 @@ uint64_t HardwareInfo::availableRamMiB() QStringList HardwareInfo::gpuInfo() { - QStringList list; - const bool vulkanSuccess = vulkanInfo(list); - const bool openGlSuccess = openGlInfo(list); - if (vulkanSuccess || openGlSuccess) { - return list; - } - - std::array buffer; - FILE* lspci = popen("lspci -k", "r"); - - if (!lspci) { - return { "Could not detect GPUs: lspci is not present" }; - } - bool readingGpuInfo = false; - QString currentModel = ""; - while (fgets(buffer.data(), 512, lspci) != nullptr) { - QString str(buffer.data()); + QString gpu; + QString driverInUse = "NONE"; + QString driversAvailable = "NONE"; + QStringList out; + + const bool success = readFromOutput("lspci -k", [&](const QString& str) { // clang-format off // 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7) // Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB // Kernel driver in use: amdgpu // Kernel modules: amdgpu // clang-format on - if (str.contains("VGA compatible controller")) { + if (str.contains("VGA compatible controller") || str.contains("3D controller")) { readingGpuInfo = true; } else if (!str.startsWith('\t')) { + if (readingGpuInfo) { + out << QString("GPU: %1 (driver in use: %2; drivers available: %3)").arg(gpu, driverInUse, driversAvailable); + driverInUse = "NONE"; + driversAvailable = "NONE"; + } readingGpuInfo = false; } + if (!readingGpuInfo) { - continue; + return; } + const QString value = afterColon(str); if (str.contains("Subsystem")) { - currentModel = "Found GPU: " + afterColon(str); + gpu = value; } if (str.contains("Kernel driver in use")) { - currentModel += " (using driver " + afterColon(str); + driverInUse = value; } if (str.contains("Kernel modules")) { - currentModel += ", available drivers: " + afterColon(str) + ")"; - list.append(currentModel); + driversAvailable = value; } + }); + if (!success) { + return { "GPU discovery failed: could not read from lspci" }; } - pclose(lspci); - return list; + + return out; } #else @@ -316,19 +318,20 @@ QString HardwareInfo::cpuInfo() uint64_t HardwareInfo::totalRamMiB() { - char buff[512]; - FILE* fp = popen("sysctl hw.physmem", "r"); - if (fp != nullptr) { - if (fgets(buff, 512, fp) != nullptr) { - std::string str(buff); - uint64_t mem = std::stoull(str.substr(12, std::string::npos)); + uint64_t out = 0; - // transforming kib -> mib - return mem / 1024; - } + const bool success = readFromOutput("sysctl hw.physmem", [&](const QString& str) { + const uint64_t mem = str.mid(12).toULong(); + + // transforming kib -> mib + out = mem / 1024; + }); + if (!success) { + qWarning() << "Could not get total RAM: could not read from sysctl"; + return 0; } - return 0; + return out; } #else @@ -343,4 +346,8 @@ uint64_t HardwareInfo::availableRamMiB() return 0; } +QStringList HardwareInfo::gpuInfo() +{ + return { "GPU discovery failed: not implemented for this OS" }; +} #endif diff --git a/vcpkg.json b/vcpkg.json index 5fd336fff..942e6d9e4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,7 +26,6 @@ ] }, "tomlplusplus", - "zlib", - "vulkan-headers" + "zlib" ] } From 2bf64efaf27486bad656cb306d4310b0c73abf3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 18:34:22 +0000 Subject: [PATCH 109/132] chore(deps): update azure/artifact-signing-action action to v2 --- .github/actions/package/windows/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/package/windows/action.yml b/.github/actions/package/windows/action.yml index cd0eb7d91..532f3db44 100644 --- a/.github/actions/package/windows/action.yml +++ b/.github/actions/package/windows/action.yml @@ -69,7 +69,7 @@ runs: - name: Sign executables if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v1 + uses: azure/artifact-signing-action@v2 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher @@ -142,7 +142,7 @@ runs: - name: Sign installer if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v1 + uses: azure/artifact-signing-action@v2 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher From 99291186b24860e60a24626fa7c55858327e097c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 06:04:43 +0000 Subject: [PATCH 110/132] chore(deps): update determinatesystems/magic-nix-cache-action action to v14 --- .github/workflows/nix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0fea44f08..58f4d263a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -103,7 +103,7 @@ jobs: # For PRs - name: Setup Nix Magic Cache if: ${{ github.event_name == 'pull_request' }} - uses: DeterminateSystems/magic-nix-cache-action@v13 + uses: DeterminateSystems/magic-nix-cache-action@v14 with: diagnostic-endpoint: "" use-flakehub: false From f6d1b29b04140fa1e43ffa44a5b5b99216b50ebe Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 1 Jun 2026 13:27:53 +0500 Subject: [PATCH 111/132] fix(LaunchController): don't remove account unless we have a new one Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index f7ea23eb3..30ee448a2 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -333,11 +333,11 @@ bool LaunchController::reauthenticateAccount(const MinecraftAccountPtr& account, if (button == QMessageBox::StandardButton::Yes) { auto* accounts = APPLICATION->accounts(); const bool isDefault = accounts->defaultAccount() == account; - accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); if (account->accountType() == AccountType::MSA) { auto newAccount = MSALoginDialog::newAccount(m_parentWidget); if (newAccount != nullptr) { + accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); accounts->addAccount(newAccount); if (isDefault) { From ea2d0d06448f4337ff27f70fc96e08d743371eb4 Mon Sep 17 00:00:00 2001 From: cat Date: Sun, 31 May 2026 14:26:32 +0000 Subject: [PATCH 112/132] AccountList: Skip refresh when !shouldRefresh Signed-off-by: cat --- launcher/minecraft/auth/AccountList.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index ac27b6bbc..418ba98d2 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -648,9 +648,17 @@ void AccountList::tryNext() while (m_refreshQueue.length()) { auto accountId = m_refreshQueue.front(); m_refreshQueue.pop_front(); + bool found = false; for (int i = 0; i < count(); i++) { auto account = at(i); if (account->internalId() == accountId) { + found = true; + if (!account->shouldRefresh()) { + // Account no longer needs refreshing, skip it. + qDebug() << "RefreshSchedule: Skipping account" << account->profileName() << "with internal ID" + << accountId << "(no longer needs refresh)"; + break; + } m_currentTask = account->refresh(); if (m_currentTask) { connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded); @@ -660,9 +668,12 @@ void AccountList::tryNext() << accountId; return; } + break; } } - qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found."; + if (!found) { + qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found."; + } } // if we get here, no account needed refreshing. Schedule refresh in an hour. m_refreshTimer->start(1000 * 3600); From 5c1d7832936943410903ab1a005cf4d5d99e2b23 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 3 Jun 2026 21:04:01 +0200 Subject: [PATCH 113/132] fix(nix): add jdk25 to wrapper Signed-off-by: Sefa Eyeoglu --- nix/wrapper.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 00752a8c4..23fc04d9f 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -6,6 +6,7 @@ glfw3-minecraft, jdk17, jdk21, + jdk25, jdk8, kdePackages, lib, @@ -34,6 +35,7 @@ controllerSupport ? stdenv.hostPlatform.isLinux, gamemodeSupport ? stdenv.hostPlatform.isLinux, jdks ? [ + jdk25 jdk21 jdk17 jdk8 From 6c15077731100fe471b32da4fd845061279086e7 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 4 Jun 2026 20:27:53 +0500 Subject: [PATCH 114/132] fix(LoggedProcess): show process start error string Signed-off-by: Octol1ttle --- launcher/LoggedProcess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index bae45ad88..c5ef47d7b 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -114,7 +114,7 @@ void LoggedProcess::on_error(QProcess::ProcessError error) { switch (error) { case QProcess::FailedToStart: { - emit log({ tr("The process failed to start.") }, MessageLevel::Fatal); + emit log({ tr("The process failed to start: %1").arg(errorString()) }, MessageLevel::Fatal); changeState(LoggedProcess::FailedToStart); break; } From 502a5175e2fc6ca386e4f8e855a2413f6e10dbfe Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 4 Jun 2026 19:07:24 +0200 Subject: [PATCH 115/132] fix category filtering I have no idea how this worked at all when I made the commit originally, but it works now, just as well as it did on the prior commit. Further improvements, using subfolders and other metadata will be in another PR. Signed-off-by: Tayou --- launcher/ui/dialogs/IconPickerDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index 051d8495f..b2269cc70 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -122,7 +122,7 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui } } - proxyModel = new QSortFilterProxyModel(this); + proxyModel = new IconProxyModel(this); proxyModel->setSourceModel(APPLICATION->icons()); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); ui->iconView->setModel(proxyModel); From f99a0883af9201f2acc76673a15b4f3fdfef2c73 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 4 Jun 2026 22:31:15 +0500 Subject: [PATCH 116/132] change(HardwareInfo/Windows): sort GPUs by performance, use smart pointers, log errors better Signed-off-by: Octol1ttle --- launcher/HardwareInfo.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp index 425f07a09..36b6f7783 100644 --- a/launcher/HardwareInfo.cpp +++ b/launcher/HardwareInfo.cpp @@ -65,8 +65,11 @@ bool readFromOutput(const char* command, F function) #endif #include -#include "dxgi.h" -#include "windows.h" +#include +#include + +#include +using Microsoft::WRL::ComPtr; QString HardwareInfo::cpuInfo() { @@ -104,29 +107,28 @@ uint64_t HardwareInfo::availableRamMiB() QStringList HardwareInfo::gpuInfo() { - IDXGIFactory* factory = nullptr; - if (CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast(&factory)) != S_OK) { // NOLINT(*-pro-type-reinterpret-cast) - qWarning() << "Could not create DXGI factory"; + ComPtr factory; + HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory)); + if (FAILED(hr)) { + qWarning() << "Could not create DXGI factory:" << Qt::hex << hr; return { "GPU discovery failed: could not create DXGI factory" }; } UINT i = 0; - IDXGIAdapter* adapter = nullptr; + ComPtr adapter; QStringList out; - - while (factory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND) { + while (factory->EnumAdapterByGpuPreference(i, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND) { DXGI_ADAPTER_DESC desc; - if (adapter->GetDesc(&desc) != S_OK) { - qWarning() << "Could not get DXGI adapter description"; - } else { + hr = adapter->GetDesc(&desc); + if (SUCCEEDED(hr)) { out << "GPU: " + QString::fromWCharArray(desc.Description); // NOLINT(*-pro-bounds-array-to-pointer-decay, *-no-array-decay) + } else { + qWarning() << "Could not get DXGI adapter description:" << Qt::hex << hr; } - adapter->Release(); ++i; } - factory->Release(); return out; } From 81159fd9d72a1a8647f28f9a95931b51ecd384ce Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 4 Jun 2026 22:37:17 +0500 Subject: [PATCH 117/132] fix(MSADeviceCodeStep): show network request error message Signed-off-by: Octol1ttle --- launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp index 3feb6852c..78762a32a 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -113,6 +113,12 @@ DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& d void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response) { + if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) { + qWarning() << "Device authorization failed:" << m_request->error() << m_request->errorString(); + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: %1").arg(m_request->errorString())); + return; + } + auto rsp = parseDeviceAuthorizationResponse(*response); if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { qWarning() << "Device authorization failed:" << rsp.error; @@ -120,12 +126,6 @@ void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response) tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description)); return; } - if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) { - qWarning() << "Device authorization failed:" << *response; - emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization")); - return; - } - if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) { qWarning() << "Device authorization failed: required fields missing"; emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing")); From 022d2fe7cd1d5f03afc9c208805ae6f5a4101841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Paku=C5=82a?= Date: Tue, 9 Jun 2026 19:27:54 +0200 Subject: [PATCH 118/132] Added missing tab stops and/or fixed their order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dominik PakuĊ‚a --- launcher/ui/pages/global/LauncherPage.ui | 7 ++++ launcher/ui/widgets/JavaSettingsWidget.ui | 3 ++ .../ui/widgets/MinecraftSettingsWidget.ui | 41 ++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 252f54b17..2fb2dfa26 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -673,6 +673,11 @@
scrollArea + sortByNameBtn + sortLastLaunchedBtn + askToRenameDirBtn + alwaysRenameDirBtn + neverRenameDirBtn preferMenuBarCheckBox autoUpdateCheckBox updateIntervalSpinBox @@ -692,7 +697,9 @@ downloadsDirMoveCheckBox metadataEnableBtn dependenciesEnableBtn + showModIncompatCheckBox modpackUpdatePromptBtn + downloadGameFilesBtn lineLimitSpinBox checkStopLogging numberOfConcurrentTasksSpinBox diff --git a/launcher/ui/widgets/JavaSettingsWidget.ui b/launcher/ui/widgets/JavaSettingsWidget.ui index 14638cf4e..03d632ad9 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.ui +++ b/launcher/ui/widgets/JavaSettingsWidget.ui @@ -392,7 +392,10 @@ autodownloadJavaCheckBox javaTestBtn javaDownloadBtn + minMemSpinBox maxMemSpinBox + permGenSpinBox + lowMemWarningCheckBox jvmArgsTextBox diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.ui b/launcher/ui/widgets/MinecraftSettingsWidget.ui index 80fb8530d..a063f9660 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.ui +++ b/launcher/ui/widgets/MinecraftSettingsWidget.ui @@ -858,32 +858,71 @@ It is most likely you will need to change the path - please refer to the mod's w openGlobalSettingsButton settingsTabs scrollArea + + + windowSizeGroupBox maximizedCheckBox - windowHeightSpinBox windowWidthSpinBox + windowHeightSpinBox closeAfterLaunchCheck quitAfterGameStopCheck + + + consoleSettingsBox showConsoleCheck showConsoleErrorCheck autoCloseConsoleCheck + + + globalDataPacksGroupBox + dataPacksPathEdit + dataPacksPathBrowse + + --> + gameTimeGroupBox showGameTime recordGameTime showGlobalGameTime showGameTimeWithoutDays + + instanceAccountGroupBox instanceAccountSelector + + serverJoinGroupBox serverJoinAddressButton serverJoinAddress worldJoinButton worldsCb + + + loaderGroup + neoForge + forge + fabric + quilt + liteLoader + babric + btaBabric + legacyFabric + ornithe + rift + javaScrollArea scrollArea_2 + + + legacySettingsGroupBox onlineFixes + + + nativeWorkaroundsGroupBox useNativeGLFWCheck lineEditGLFWPath useNativeOpenALCheck lineEditOpenALPath + enableFeralGamemodeCheck enableMangoHud useDiscreteGpuCheck From 8de7aa2b1703baacefd3c2024af09483dd5c54a1 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 10 Jun 2026 17:10:33 +0500 Subject: [PATCH 119/132] fix(JavaChecker): show process start error string Signed-off-by: Octol1ttle --- launcher/java/JavaChecker.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 5c52c653d..3a04756fa 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -179,13 +179,20 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) void JavaChecker::error(QProcess::ProcessError err) { if (err == QProcess::FailedToStart) { - qDebug() << "Java checker has failed to start."; + qDebug() << "Java checker has failed to start:" << process->errorString(); qDebug() << "Process environment:"; qDebug() << process->environment(); qDebug() << "Native environment:"; qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); killTimer.stop(); - emit checkFinished({ m_path, m_id }); + + Result result = { + m_path, + m_id, + }; + result.errorLog = process->errorString(); + result.validity = Result::Validity::Errored; + emit checkFinished(result); } emitSucceeded(); } From a90e3d403d4c1de4bd0191f10624af1c95baf711 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 10 Jun 2026 17:19:55 +0500 Subject: [PATCH 120/132] fix(LauncherPartLaunch): show process start error string Signed-off-by: Octol1ttle --- launcher/minecraft/launch/LauncherPartLaunch.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index a2c400e75..e4d3ec1ef 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -164,9 +164,9 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) switch (state) { case LoggedProcess::FailedToStart: { //: Error message displayed if instace can't start - const char* reason = QT_TR_NOOP("Could not launch Minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(tr(reason)); + const char* reason = QT_TR_NOOP("Could not launch Minecraft: %1"); + emit logLine(QString(reason).arg(m_process.errorString()), MessageLevel::Fatal); + emitFailed(tr(reason).arg(m_process.errorString())); return; } case LoggedProcess::Aborted: From 0a3adb7912101e0ba29e97a998e97a3e08146f1b Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 4 Jun 2026 21:17:03 +0200 Subject: [PATCH 121/132] on server errors, treat account as offline Signed-off-by: Tayou --- .../minecraft/auth/steps/LauncherLoginStep.cpp | 2 +- .../minecraft/auth/steps/MinecraftProfileStep.cpp | 2 +- .../minecraft/auth/steps/XboxAuthorizationStep.cpp | 2 +- launcher/minecraft/auth/steps/XboxUserStep.cpp | 2 +- launcher/net/NetUtils.h | 14 ++++++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 89293c22e..701261b52 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -56,7 +56,7 @@ void LauncherLoginStep::onRequestDone(QByteArray* response) qCDebug(authCredentials()) << *response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } else { diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 418c46a0e..7e089cf86 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -52,7 +52,7 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response) qWarning() << " Response:"; qWarning() << QString::fromUtf8(*response); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } else { diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 84320c7c1..4890f4da1 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -60,7 +60,7 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) qCDebug(authCredentials()) << *response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { if (processSTSError(*response)) { return; } diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 97544d09b..e2971dcd9 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -56,7 +56,7 @@ void XboxUserStep::onRequestDone(QByteArray* response) { if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } else { emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); diff --git a/launcher/net/NetUtils.h b/launcher/net/NetUtils.h index cd517bcca..67ebc9685 100644 --- a/launcher/net/NetUtils.h +++ b/launcher/net/NetUtils.h @@ -40,4 +40,18 @@ inline bool isApplicationError(QNetworkReply::NetworkError x) QNetworkReply::UnknownContentError }; return errors.contains(x); } + +// 500 class errors, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/500 +// microsoft may send these error codes when services (auth) are down. +// We treat this as a reason to launch in offline mode. +inline bool isServerError(QNetworkReply::NetworkError x) +{ + static QSet errors = { QNetworkReply::InternalServerError, + QNetworkReply::OperationNotImplementedError, + QNetworkReply::ServiceUnavailableError, // 503 | seen in logs in 2026 + //QNetworkReply::GatewayTimeoutError, // 504 | seen in logs in 2024 + // Qt doesn't have it mapped. Unknown covers it + QNetworkReply::UnknownServerError }; + return errors.contains(x); +} } // namespace Net From 768d12259b8ee76f4b71d312b48821682a355ec5 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 11 Jun 2026 14:35:07 +0200 Subject: [PATCH 122/132] add info & retry dialog Signed-off-by: Tayou --- launcher/LaunchController.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 30ee448a2..f817fd278 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -182,6 +182,33 @@ LaunchDecision LaunchController::decideLaunchMode() QString reauthReason; switch (state) { + case AccountState::Offline: { + if (m_wantedLaunchMode == LaunchMode::Normal) { + QMessageBox msg(m_parentWidget); + msg.setWindowTitle(tr("Auth servers offline")); + msg.setText(tr("The Minecraft authentication servers are currently unavailable.")); + msg.setIcon(QMessageBox::Warning); + + auto* launchOfflineButton = msg.addButton(tr("Launch Offline"), QMessageBox::AcceptRole); + auto* retryButton = msg.addButton(tr("Retry"), QMessageBox::ActionRole); + msg.addButton(tr("Cancel"), QMessageBox::RejectRole); + msg.setDefaultButton(launchOfflineButton); + + msg.exec(); + + if (msg.clickedButton() == launchOfflineButton) { + m_actualLaunchMode = LaunchMode::Offline; + return LaunchDecision::Continue; + } + if (msg.clickedButton() == retryButton) { + return LaunchDecision::Undecided; + } + return LaunchDecision::Abort; + } + + m_actualLaunchMode = LaunchMode::Offline; + return LaunchDecision::Continue; + } case AccountState::Errored: reauthReason = tr("An error occurred while refreshing '%1'").arg(accountToCheck->profileName()); break; From d71cfc33a2daa9811d7047d0ef5dd451232baad6 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 11 Jun 2026 17:24:30 +0200 Subject: [PATCH 123/132] show error code in dialog, change text for no internet Signed-off-by: Tayou --- launcher/LaunchController.cpp | 13 +++++++++++-- launcher/minecraft/auth/AccountData.h | 2 ++ launcher/minecraft/auth/steps/LauncherLoginStep.cpp | 1 + .../minecraft/auth/steps/MinecraftProfileStep.cpp | 1 + .../minecraft/auth/steps/XboxAuthorizationStep.cpp | 1 + launcher/minecraft/auth/steps/XboxUserStep.cpp | 1 + 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index f817fd278..86a5a61e1 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -41,6 +41,7 @@ #include "minecraft/auth/AccountList.h" #include "ui/InstanceWindow.h" +#include "net/NetUtils.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" @@ -185,8 +186,16 @@ LaunchDecision LaunchController::decideLaunchMode() case AccountState::Offline: { if (m_wantedLaunchMode == LaunchMode::Normal) { QMessageBox msg(m_parentWidget); - msg.setWindowTitle(tr("Auth servers offline")); - msg.setText(tr("The Minecraft authentication servers are currently unavailable.")); + + auto netErr = accountToCheck->accountData()->networkError; + if (Net::isServerError(netErr)) { + msg.setWindowTitle(tr("Auth servers offline")); + msg.setText(tr("The Minecraft authentication servers are currently unavailable.\n\n%1").arg(accountToCheck->lastError())); + } else { + msg.setWindowTitle(tr("No internet connection")); + msg.setText(tr("Unable to connect to the internet.\n\n%1").arg(accountToCheck->lastError())); + } + msg.setIcon(QMessageBox::Warning); auto* launchOfflineButton = msg.addButton(tr("Launch Offline"), QMessageBox::AcceptRole); diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 5fbe3213b..96ef94f30 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -41,6 +41,7 @@ #include #include +#include #include enum class Validity { None, Assumed, Certain }; @@ -118,5 +119,6 @@ struct AccountData { // runtime only information (not saved with the account) QString internalId; QString errorString; + QNetworkReply::NetworkError networkError = QNetworkReply::NoError; AccountState accountState = AccountState::Unchecked; }; diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 701261b52..4ceed8586 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -60,6 +60,7 @@ void LauncherLoginStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } return; diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 7e089cf86..b95682b2b 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -56,6 +56,7 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 4890f4da1..422aa1a54 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -67,6 +67,7 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index e2971dcd9..382750218 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -59,6 +59,7 @@ void XboxUserStep::onRequestDone(QByteArray* response) if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } return; From b387a1f79314245274500beecf54df8e55a9c84e Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 12 Jun 2026 19:24:06 +0500 Subject: [PATCH 124/132] change(ApiHeaderProxy): include Flame API key for CDN downloads Signed-off-by: Octol1ttle --- buildconfig/BuildConfig.h | 2 ++ launcher/net/ApiHeaderProxy.h | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index d430622bc..a5851bfba 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -194,8 +194,10 @@ class Config { QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; QStringList MODRINTH_MRPACK_HOSTS{ "cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com" }; + QString MODRINTH_DOWNLOAD_HOST = "cdn.modrinth.com"; QString FLAME_BASE_URL = "https://api.curseforge.com/v1"; + QString FLAME_DOWNLOAD_HOST = "edge.forgecdn.net"; QString versionString() const; /** diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h index d48379617..d12287a4e 100644 --- a/launcher/net/ApiHeaderProxy.h +++ b/launcher/net/ApiHeaderProxy.h @@ -61,17 +61,19 @@ class ApiHeaderProxy : public HeaderProxy { QList headers(const QNetworkRequest& request) const override { QList hdrs; - if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { + const auto host = request.url().host(); + + if (APPLICATION->capabilities() & Application::SupportsFlame && + (host == QUrl(BuildConfig.FLAME_BASE_URL).host() || host == BuildConfig.FLAME_DOWNLOAD_HOST)) { hdrs.append({ .headerName = "x-api-key", .headerValue = APPLICATION->getFlameAPIKey().toUtf8() }); - } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || - request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { + } else if (host == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || host == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); if (!token.isNull()) { hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() }); } } - if (request.url().host() == "cdn.modrinth.com" && !m_meta.isEmpty()) { + if (host == BuildConfig.MODRINTH_DOWNLOAD_HOST && !m_meta.isEmpty()) { hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() }); } return hdrs; From 2425d2f8a3621acbe29cf191d47d56b5154ae028 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 11 Jun 2026 18:34:53 +0200 Subject: [PATCH 125/132] merge dialogs Signed-off-by: Tayou --- launcher/LaunchController.cpp | 53 +++++++++-------------------------- launcher/LaunchController.h | 2 +- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 86a5a61e1..2b882338c 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -40,8 +40,8 @@ #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountList.h" -#include "ui/InstanceWindow.h" #include "net/NetUtils.h" +#include "ui/InstanceWindow.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/ProfileSelectDialog.h" @@ -183,41 +183,6 @@ LaunchDecision LaunchController::decideLaunchMode() QString reauthReason; switch (state) { - case AccountState::Offline: { - if (m_wantedLaunchMode == LaunchMode::Normal) { - QMessageBox msg(m_parentWidget); - - auto netErr = accountToCheck->accountData()->networkError; - if (Net::isServerError(netErr)) { - msg.setWindowTitle(tr("Auth servers offline")); - msg.setText(tr("The Minecraft authentication servers are currently unavailable.\n\n%1").arg(accountToCheck->lastError())); - } else { - msg.setWindowTitle(tr("No internet connection")); - msg.setText(tr("Unable to connect to the internet.\n\n%1").arg(accountToCheck->lastError())); - } - - msg.setIcon(QMessageBox::Warning); - - auto* launchOfflineButton = msg.addButton(tr("Launch Offline"), QMessageBox::AcceptRole); - auto* retryButton = msg.addButton(tr("Retry"), QMessageBox::ActionRole); - msg.addButton(tr("Cancel"), QMessageBox::RejectRole); - msg.setDefaultButton(launchOfflineButton); - - msg.exec(); - - if (msg.clickedButton() == launchOfflineButton) { - m_actualLaunchMode = LaunchMode::Offline; - return LaunchDecision::Continue; - } - if (msg.clickedButton() == retryButton) { - return LaunchDecision::Undecided; - } - return LaunchDecision::Abort; - } - - m_actualLaunchMode = LaunchMode::Offline; - return LaunchDecision::Continue; - } case AccountState::Errored: reauthReason = tr("An error occurred while refreshing '%1'").arg(accountToCheck->profileName()); break; @@ -261,13 +226,14 @@ bool LaunchController::askPlayDemo() const return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(const QString& playerName, bool* ok) const +QString LaunchController::askOfflineName(const QString& playerName, bool* ok) { if (ok != nullptr) { *ok = false; } - QString message; + QString title, message; + title = tr("Player name"); switch (m_actualLaunchMode) { case LaunchMode::Normal: Q_ASSERT(false); @@ -277,7 +243,14 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co break; case LaunchMode::Offline: if (m_wantedLaunchMode == LaunchMode::Normal) { - message = tr("You are not connected to the Internet, launching in offline mode\n\n"); + auto netErr = m_accountToUse->accountData()->networkError; + if (Net::isServerError(netErr)) { + title = tr("Auth servers offline"); + message = tr("The Minecraft authentication servers are currently unavailable, launching in offline mode.\n\n"); + } else { + title = tr("No internet connection"); + message = tr("You are not connected to the Internet, launching in offline mode.\n\n"); + } } message += tr("Choose your offline mode player name"); break; @@ -287,7 +260,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; ChooseOfflineNameDialog dialog(message, m_parentWidget); - dialog.setWindowTitle(tr("Player name")); + dialog.setWindowTitle(title); dialog.setUsername(usedname); if (dialog.exec() != QDialog::Accepted) { return {}; diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 742f20586..bc0d14e0f 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -78,7 +78,7 @@ class LaunchController : public Task { void decideAccount(); LaunchDecision decideLaunchMode(); bool askPlayDemo() const; - QString askOfflineName(const QString& playerName, bool* ok = nullptr) const; + QString askOfflineName(const QString& playerName, bool* ok = nullptr); bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason); private slots: From 0175653881e740c717fbf9059d59d505e0bbcb5e Mon Sep 17 00:00:00 2001 From: Andrey Kurlin Date: Sun, 21 Jun 2026 19:08:08 +0500 Subject: [PATCH 126/132] fix: trim whitespace from environment variables Signed-off-by: Andrey Kurlin --- launcher/ui/widgets/EnvironmentVariables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/EnvironmentVariables.cpp b/launcher/ui/widgets/EnvironmentVariables.cpp index 9387ef2e2..33bb00c63 100644 --- a/launcher/ui/widgets/EnvironmentVariables.cpp +++ b/launcher/ui/widgets/EnvironmentVariables.cpp @@ -104,7 +104,7 @@ QMap EnvironmentVariables::value() const QMap result; QTreeWidgetItem* item = ui->list->topLevelItem(0); for (int i = 1; item != nullptr; item = ui->list->topLevelItem(i++)) - result[item->text(0)] = item->text(1); + result[item->text(0).trimmed()] = item->text(1).trimmed(); return result; } From 9752f9dfd74bbaa01725dfd778620e5c6d2e933f Mon Sep 17 00:00:00 2001 From: Andrey Kurlin Date: Sun, 21 Jun 2026 23:35:20 +0500 Subject: [PATCH 127/132] fix: sort mod Minecraft versions as version lists Signed-off-by: Andrey Kurlin --- launcher/minecraft/mod/Mod.cpp | 40 +++++++++++++++++++++-- launcher/minecraft/mod/Mod.h | 3 +- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index d9e9e901a..f0cdfaff6 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include "MTPixmapCache.h" #include "MetadataHandler.h" @@ -49,6 +50,34 @@ #include "minecraft/mod/tasks/LocalModParseTask.h" #include "modplatform/ModIndex.h" +namespace { + +int compareVersionLists(const QStringList& leftVersions, const QStringList& rightVersions) +{ + const qsizetype commonSize = std::min(leftVersions.size(), rightVersions.size()); + + for (qsizetype i = 0; i < commonSize; i++) { + const auto leftVersion = Version(leftVersions.at(i).trimmed()); + const auto rightVersion = Version(rightVersions.at(i).trimmed()); + + if (leftVersion > rightVersion) + return 1; + + if (leftVersion < rightVersion) + return -1; + } + + if (leftVersions.size() > rightVersions.size()) + return 1; + + if (leftVersions.size() < rightVersions.size()) + return -1; + + return 0; +} + +} // namespace + Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() { m_enabled = (file.suffix() != "disabled"); @@ -88,7 +117,7 @@ int Mod::compare(const Resource& other, SortType type) const break; } case SortType::McVersions: { - auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive); + auto compare_result = compareVersionLists(mcVersions(), cast_other->mcVersions()); if (compare_result != 0) return compare_result; break; @@ -197,14 +226,19 @@ auto Mod::side() const -> QString return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide); } -auto Mod::mcVersions() const -> QString +auto Mod::mcVersions() const -> QStringList { if (metadata()) - return metadata()->mcVersions.join(", "); + return metadata()->mcVersions; return {}; } +auto Mod::mcVersionsString() const -> QString +{ + return mcVersions().join(", "); +} + auto Mod::releaseType() const -> QString { if (metadata()) diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 0d24409bf..27768ae2c 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -68,7 +68,8 @@ class Mod : public Resource { auto issueTracker() const -> QString; auto side() const -> QString; auto loaders() const -> QString; - auto mcVersions() const -> QString; + auto mcVersions() const -> QStringList; + auto mcVersionsString() const -> QString; auto releaseType() const -> QString; QStringList dependencies() const; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index d5f609df3..2137173ae 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -110,7 +110,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return at(row).loaders(); } case McVersionsColumn: { - return at(row).mcVersions(); + return at(row).mcVersionsString(); } case ReleaseTypeColumn: { return at(row).releaseType(); From a607373ced73fc636760f5a51e0874b1c82f0e71 Mon Sep 17 00:00:00 2001 From: Anceph Date: Wed, 24 Jun 2026 00:23:31 +0300 Subject: [PATCH 128/132] Added the option to sort instances by total playtime (#5701) Signed-off-by: Anceph --- launcher/ui/instanceview/InstanceProxyModel.cpp | 6 ++++++ launcher/ui/pages/global/LauncherPage.cpp | 10 +++++++++- launcher/ui/pages/global/LauncherPage.ui | 11 +++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/launcher/ui/instanceview/InstanceProxyModel.cpp b/launcher/ui/instanceview/InstanceProxyModel.cpp index ab6bef696..ea3a2b0fd 100644 --- a/launcher/ui/instanceview/InstanceProxyModel.cpp +++ b/launcher/ui/instanceview/InstanceProxyModel.cpp @@ -62,6 +62,12 @@ bool InstanceProxyModel::subSortLessThan(const QModelIndex& left, const QModelIn QString sortMode = APPLICATION->settings()->get("InstSortMode").toString(); if (sortMode == "LastLaunch") { return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); + } else if (sortMode == "Playtime") { + if (pdataLeft->totalTimePlayed() == pdataRight->totalTimePlayed()) { + // fallback to name sorting if playtime is equal + return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; + } + return pdataLeft->totalTimePlayed() > pdataRight->totalTimePlayed(); } else { return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; } diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 6276d3be6..1e1fed8f8 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -62,7 +62,9 @@ enum InstSortMode { // Sort alphabetically by name. Sort_Name, // Sort by which instance was launched most recently. - Sort_LastLaunch + Sort_LastLaunch, + // Sort by which instance has the most playtime. + Sort_Playtime, }; LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) @@ -71,6 +73,7 @@ LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::Launch ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); + ui->sortingModeGroup->setId(ui->sortByPlaytimeBtn, Sort_Playtime); loadSettings(); @@ -227,6 +230,9 @@ void LauncherPage::applySettings() case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); break; + case Sort_Playtime: + s->set("InstSortMode", "Playtime"); + break; case Sort_Name: default: s->set("InstSortMode", "Name"); @@ -282,6 +288,8 @@ void LauncherPage::loadSettings() QString sortMode = s->get("InstSortMode").toString(); if (sortMode == "LastLaunch") { ui->sortLastLaunchedBtn->setChecked(true); + } else if (sortMode == "Playtime"){ + ui->sortByPlaytimeBtn->setChecked(true); } else { ui->sortByNameBtn->setChecked(true); } diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 2fb2dfa26..de3b1f029 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -83,6 +83,16 @@
+ + + + By &playtime + + + sortingModeGroup + + + @@ -675,6 +685,7 @@ scrollArea sortByNameBtn sortLastLaunchedBtn + sortByPlaytimeBtn askToRenameDirBtn alwaysRenameDirBtn neverRenameDirBtn From 34783c10fed69df4a0396eec4b574f44d147852a Mon Sep 17 00:00:00 2001 From: Anceph <41387237+Anceph@users.noreply.github.com> Date: Wed, 24 Jun 2026 00:49:43 +0300 Subject: [PATCH 129/132] Remove the comment Signed-off-by: Anceph <41387237+Anceph@users.noreply.github.com> --- launcher/ui/instanceview/InstanceProxyModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/ui/instanceview/InstanceProxyModel.cpp b/launcher/ui/instanceview/InstanceProxyModel.cpp index ea3a2b0fd..f28149a56 100644 --- a/launcher/ui/instanceview/InstanceProxyModel.cpp +++ b/launcher/ui/instanceview/InstanceProxyModel.cpp @@ -64,7 +64,6 @@ bool InstanceProxyModel::subSortLessThan(const QModelIndex& left, const QModelIn return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); } else if (sortMode == "Playtime") { if (pdataLeft->totalTimePlayed() == pdataRight->totalTimePlayed()) { - // fallback to name sorting if playtime is equal return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; } return pdataLeft->totalTimePlayed() > pdataRight->totalTimePlayed(); From 4dc107b1fafdbdcb2575db089f4411f5692977a1 Mon Sep 17 00:00:00 2001 From: Anceph <41387237+Anceph@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:12:16 +0300 Subject: [PATCH 130/132] Change "By playtime" to "By total time played" Co-authored-by: TheKodeToad Signed-off-by: Anceph <41387237+Anceph@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index de3b1f029..c98cb1032 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -86,7 +86,7 @@ - By &playtime + By total time &played sortingModeGroup From f181b5d0d7a9b872c79887900b2b6f730945ec00 Mon Sep 17 00:00:00 2001 From: James Zhou Date: Wed, 24 Jun 2026 10:18:21 -0400 Subject: [PATCH 131/132] fix: recursive mod dependencies Signed-off-by: James Zhou --- launcher/minecraft/mod/ModFolderModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index d5f609df3..7ce61e9b8 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -342,7 +342,6 @@ QSet collectMods(const QSet& mods, QHash> relati auto affectedId = affected->mod_id(); if (findById(mods, affectedId) == nullptr && !seen.contains(affectedId)) { - seen.insert(affectedId); if (shouldBeEnabled != affected->enabled()) { affectedList << affected; } From cb56c641d7007cb6951530ae2529e2166472694c Mon Sep 17 00:00:00 2001 From: DioEgizio Date: Thu, 25 Jun 2026 09:44:31 +0200 Subject: [PATCH 132/132] fix: remove remaining 16x16 new.png Signed-off-by: DioEgizio --- launcher/resources/multimc/16x16/new.png | Bin 625 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 launcher/resources/multimc/16x16/new.png diff --git a/launcher/resources/multimc/16x16/new.png b/launcher/resources/multimc/16x16/new.png deleted file mode 100644 index dfde06f61adb3b182e05fd43b285005622bb59b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmV-%0*?KOP)N_H1(c*BtKPo0*>g++0M4LIzF+Jpafm*3+^2_;CNi&k0~k0t8GHDMSm1 zj1n0{iRA9`B)|XtroHmkX1^YD{57>uXdn=rN5zrqh4cwP8L;G$Hlo6>e|0E8v4Q~3 zc_bwkQy>YHev@C-urxJmtRT4tYbJse(@;Q?=w?;`2tNGy*WZ6m^!E?0naYOjpMU-) z1pjNj-l!LVMx$}&U3cGcW9vIbvZ4bR#V