From 9ce773891229babff6008d19e2e1a5ee0c08f6ea Mon Sep 17 00:00:00 2001 From: Pagwin Date: Wed, 19 Nov 2025 10:43:52 -0500 Subject: [PATCH 01/59] Implemented #4369 Signed-off-by: Pagwin --- launcher/ui/pages/instance/ModFolderPage.cpp | 39 ++++++++++++++++++-- launcher/ui/pages/instance/ModFolderPage.h | 2 + 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 198f336f9..54932c2ee 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -38,6 +38,7 @@ #include "ModFolderPage.h" #include "ui/dialogs/ExportToModListDialog.h" +#include "ui/dialogs/InstallLoaderDialog.h" #include "ui_ExternalResourcesPage.h" #include @@ -65,6 +66,7 @@ #include "tasks/Task.h" #include "ui/dialogs/ProgressDialog.h" +inline void HandleNoModLoader(ModFolderPage* self); ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(inst, model, parent), m_model(model) { @@ -145,7 +147,7 @@ void ModFolderPage::downloadMods() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + HandleNoModLoader(this); return; } @@ -201,7 +203,7 @@ void ModFolderPage::updateMods(bool includeDeps) auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + HandleNoModLoader(this); return; } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { @@ -305,7 +307,7 @@ void ModFolderPage::changeModVersion() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + HandleNoModLoader(this); return; } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { @@ -385,3 +387,34 @@ bool NilModFolderPage::shouldDisplay() const { return m_model->dir().exists(); } + +// Helper function so this doesn't need to be duplicated 3 times +inline void HandleNoModLoader(ModFolderPage* self) +{ + // QMessageBox::critical(self, tr("Error"), tr("Please install a mod loader first!")); + int resp = QMessageBox::question(self, self->tr("Missing ModLoader"), + self->tr("You need to install a mod loader before installing mods, would you like to do so?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + switch (resp) { + case QMessageBox::Yes: { + // now how do I get the values this all needs + if (self->m_instance->typeName() != "Minecraft") { + // not what we need + return; + } + auto profile = static_cast(self->m_instance)->getPackProfile(); + InstallLoaderDialog dialog(profile, QString(), self); + dialog.exec(); + self->m_container->refreshContainer(); + break; + } + case QMessageBox::No: { + // Nothing happens the dialog is already closing + break; + } + default: { + // Unreachable + break; + } + } +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index b33992470..6360c9739 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -45,6 +45,8 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT + friend void HandleNoModLoader(ModFolderPage* self); + public: explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ModFolderPage() = default; From 2f8a9fb6a29e35853ca4185fba56563c37db0467 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:32:56 +0000 Subject: [PATCH 02/59] chore(deps): update actions/checkout action to v6 --- .github/workflows/backport.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/flatpak.yml | 2 +- .github/workflows/nix.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/update-flake.yml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 2b5633c76..42806c8de 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,7 +21,7 @@ jobs: if: github.repository_owner == 'PrismLauncher' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name)) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 696311033..2723a2bd7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -139,7 +139,7 @@ jobs: ## - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3fdcc68ba..d70c4eb0f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -60,7 +60,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: "true" diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index c16917869..7a4a9b293 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index d6cacc665..23bb287dd 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -92,7 +92,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Nix uses: cachix/install-nix-action@v31 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4b9b56aa..9983fa6cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: upload_url: ${{ steps.create_release.outputs.upload_url }} steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: "true" path: "PrismLauncher-source" diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 64d299ef7..c52ec6040 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31 - uses: DeterminateSystems/update-flake-lock@v27 From 1dae1a210b6db38921d6205a7ee5d1fc7697d21a Mon Sep 17 00:00:00 2001 From: Pagwin Date: Thu, 20 Nov 2025 20:44:41 -0500 Subject: [PATCH 03/59] Made changes to address feedback from code review (#4374) Signed-off-by: Pagwin --- launcher/ui/pages/instance/ModFolderPage.cpp | 41 ++++++++++---------- launcher/ui/pages/instance/ModFolderPage.h | 2 +- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 54932c2ee..a619743b3 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -66,7 +66,6 @@ #include "tasks/Task.h" #include "ui/dialogs/ProgressDialog.h" -inline void HandleNoModLoader(ModFolderPage* self); ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent) : ExternalResourcesPage(inst, model, parent), m_model(model) { @@ -147,8 +146,9 @@ void ModFolderPage::downloadMods() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - HandleNoModLoader(this); - return; + if (HandleNoModLoader()) { + return; + } } m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); @@ -203,8 +203,9 @@ void ModFolderPage::updateMods(bool includeDeps) auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - HandleNoModLoader(this); - return; + if (HandleNoModLoader()) { + return; + } } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); @@ -307,8 +308,9 @@ void ModFolderPage::changeModVersion() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - HandleNoModLoader(this); - return; + if (HandleNoModLoader()) { + return; + } } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); @@ -389,28 +391,25 @@ bool NilModFolderPage::shouldDisplay() const } // Helper function so this doesn't need to be duplicated 3 times -inline void HandleNoModLoader(ModFolderPage* self) +inline bool ModFolderPage::HandleNoModLoader() { - // QMessageBox::critical(self, tr("Error"), tr("Please install a mod loader first!")); - int resp = QMessageBox::question(self, self->tr("Missing ModLoader"), - self->tr("You need to install a mod loader before installing mods, would you like to do so?"), + int resp = QMessageBox::question(this, this->tr("Missing ModLoader"), + this->tr("You need to install a mod loader before installing mods, would you like to do so?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); switch (resp) { case QMessageBox::Yes: { - // now how do I get the values this all needs - if (self->m_instance->typeName() != "Minecraft") { - // not what we need - return; - } - auto profile = static_cast(self->m_instance)->getPackProfile(); - InstallLoaderDialog dialog(profile, QString(), self); + // Should be safe + auto profile = static_cast(this->m_instance)->getPackProfile(); + InstallLoaderDialog dialog(profile, QString(), this); dialog.exec(); - self->m_container->refreshContainer(); - break; + this->m_container->refreshContainer(); + // returning false so the caller can go and open up the dialog it was originally going to + return false; } case QMessageBox::No: { // Nothing happens the dialog is already closing - break; + // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader + return true; } default: { // Unreachable diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 6360c9739..296f1dd4e 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -45,7 +45,7 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT - friend void HandleNoModLoader(ModFolderPage* self); + bool HandleNoModLoader(); public: explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); From fca8ac40fffac3ef31132f8c1c65474c638af8cf Mon Sep 17 00:00:00 2001 From: Pagwin Date: Fri, 21 Nov 2025 16:19:41 -0500 Subject: [PATCH 04/59] Minor remaining fixes for PR (#4374) - changed case of `handleNoModLoader` to match project style - made default case in switch statement for `handleNoModLoader` return true for safety Signed-off-by: Pagwin --- launcher/ui/pages/instance/ModFolderPage.cpp | 11 ++++++----- launcher/ui/pages/instance/ModFolderPage.h | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index a619743b3..0e56aeff8 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -146,7 +146,7 @@ void ModFolderPage::downloadMods() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - if (HandleNoModLoader()) { + if (handleNoModLoader()) { return; } } @@ -203,7 +203,7 @@ void ModFolderPage::updateMods(bool includeDeps) auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - if (HandleNoModLoader()) { + if (handleNoModLoader()) { return; } } @@ -308,7 +308,7 @@ void ModFolderPage::changeModVersion() auto profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { - if (HandleNoModLoader()) { + if (handleNoModLoader()) { return; } } @@ -391,7 +391,7 @@ bool NilModFolderPage::shouldDisplay() const } // Helper function so this doesn't need to be duplicated 3 times -inline bool ModFolderPage::HandleNoModLoader() +inline bool ModFolderPage::handleNoModLoader() { int resp = QMessageBox::question(this, this->tr("Missing ModLoader"), this->tr("You need to install a mod loader before installing mods, would you like to do so?"), @@ -413,7 +413,8 @@ inline bool ModFolderPage::HandleNoModLoader() } default: { // Unreachable - break; + // returning true as a safety measure + return true; } } } diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 296f1dd4e..aadeecb20 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -45,7 +45,7 @@ class ModFolderPage : public ExternalResourcesPage { Q_OBJECT - bool HandleNoModLoader(); + inline bool handleNoModLoader(); public: explicit ModFolderPage(BaseInstance* inst, std::shared_ptr model, QWidget* parent = nullptr); From 5e2c8bdcf7d356e829640f7da2bc36c3e40b5823 Mon Sep 17 00:00:00 2001 From: Pagwin Date: Sat, 22 Nov 2025 17:10:22 -0500 Subject: [PATCH 05/59] Fixed Loader Install Cancel crash Fixed a crash which occurred when the mod loader dialog was cancelled after being reached from the no loader dialog Signed-off-by: Pagwin --- launcher/ui/pages/instance/ModFolderPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 0e56aeff8..5e06085a5 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -401,10 +401,10 @@ inline bool ModFolderPage::handleNoModLoader() // Should be safe auto profile = static_cast(this->m_instance)->getPackProfile(); InstallLoaderDialog dialog(profile, QString(), this); - dialog.exec(); + bool ret = dialog.exec(); this->m_container->refreshContainer(); // returning false so the caller can go and open up the dialog it was originally going to - return false; + return !ret; } case QMessageBox::No: { // Nothing happens the dialog is already closing From c22592d74c1098811810df745d5da96179dc878f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 23 Nov 2025 00:31:37 +0000 Subject: [PATCH 06/59] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/c5ae371f1a6a7fd27823bc500d9390b38c05fa55?narHash=sha256-4PqRErxfe%2B2toFJFgcRKZ0UI9NSIOJa%2B7RXVtBhy4KE%3D' (2025-11-12) → 'github:NixOS/nixpkgs/117cc7f94e8072499b0a7aa4c52084fa4e11cc9b?narHash=sha256-%2BhBiJ%2BkG5IoffUOdlANKFflTT5nO3FrrR2CA3178Y5s%3D' (2025-11-20) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6cdc11a85..4676b8b69 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1762977756, - "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=", + "lastModified": 1763678758, + "narHash": "sha256-+hBiJ+kG5IoffUOdlANKFflTT5nO3FrrR2CA3178Y5s=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55", + "rev": "117cc7f94e8072499b0a7aa4c52084fa4e11cc9b", "type": "github" }, "original": { From 9de2f96341d45b7164fb8895b4309e9d11b4fda5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 22 Nov 2025 18:31:10 +0200 Subject: [PATCH 07/59] change packwiz extra warnings to debug Signed-off-by: Trial97 --- launcher/modplatform/packwiz/Packwiz.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0660d611c..072450c9a 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -71,7 +71,7 @@ auto stringEntry(toml::table table, QString entry_name) -> QString { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qWarning() << "Failed to read str property '" + entry_name + "' in mod metadata."; + qDebug() << "Failed to read str property '" + entry_name + "' in mod metadata."; return {}; } @@ -82,7 +82,7 @@ auto intEntry(toml::table table, QString entry_name) -> int { auto node = table[StringUtils::toStdString(entry_name)]; if (!node) { - qWarning() << "Failed to read int property '" + entry_name + "' in mod metadata."; + qDebug() << "Failed to read int property '" + entry_name + "' in mod metadata."; return {}; } From 17c4ad0f01546d536303e43ea96d47c190cb8c12 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 23 Nov 2025 16:15:43 +0000 Subject: [PATCH 08/59] Implement find wrapping in LogView Signed-off-by: TheKodeToad --- launcher/ui/widgets/LogView.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index df25a2434..73496a01a 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -180,5 +180,30 @@ void LogView::scrollToBottom() void LogView::findNext(const QString& what, bool reverse) { - find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0)); + if (what.isEmpty()) + return; + + const QTextDocument::FindFlags flags(reverse ? QTextDocument::FindBackward : 0); + + if (find(what, flags)) + return; + + QTextCursor cursor = textCursor(); + + if (reverse) { + if (cursor.atEnd()) + return; + + cursor.movePosition(QTextCursor::End); + } else { + if (cursor.atStart()) + return; + + cursor.movePosition(QTextCursor::Start); + } + + cursor = document()->find(what, cursor, flags); + + if (!cursor.isNull()) + setTextCursor(cursor); } From 55a212f3c4456fda43b0f76f9a354043d9531421 Mon Sep 17 00:00:00 2001 From: Pagwin Date: Sun, 23 Nov 2025 14:06:06 -0500 Subject: [PATCH 09/59] Grammar/Spelling nit fix for Missing Mod loader dialog Co-authored-by: Seth Flynn Signed-off-by: Pagwin --- launcher/ui/pages/instance/ModFolderPage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 5e06085a5..0ba2bba93 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -393,8 +393,8 @@ 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 ModLoader"), - this->tr("You need to install a mod loader before installing mods, would you like to do so?"), + 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: { From 9e614f94cdedef479e31528c5497c15692722f45 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Wed, 12 Nov 2025 21:57:25 +0100 Subject: [PATCH 10/59] fix FTBA instance loading Signed-off-by: Edvin Bryntesson --- .../modplatform/import_ftb/PackHelpers.cpp | 65 +++++++++++++------ launcher/modplatform/import_ftb/PackHelpers.h | 2 +- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp index 78f7e4ba6..ec2229138 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.cpp +++ b/launcher/modplatform/import_ftb/PackHelpers.cpp @@ -74,17 +74,49 @@ Modpack parseDirectory(QString path) modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion"); modpack.jvmArgs = root["jvmArgs"].toVariant(); modpack.totalPlayTime = Json::requireInteger(root, "totalPlayTime", "totalPlayTime"); + + auto modLoader = Json::requireString(root, "modLoader", "modLoader"); + if (!modLoader.isEmpty()) { + const auto parts = modLoader.split('-', Qt::KeepEmptyParts); + if (parts.size() >= 2) { + const auto loader = parts.first().toLower(); + modpack.version = parts.at(1).trimmed(); + if (loader == "neoforge") { + modpack.loaderType = ModPlatform::NeoForge; + } else if (loader == "forge") { + modpack.loaderType = ModPlatform::Forge; + } else if (loader == "fabric") { + modpack.loaderType = ModPlatform::Fabric; + } else if (loader == "quilt") { + modpack.loaderType = ModPlatform::Quilt; + } + } + } else { + legacyInstanceParsing(path, &modpack.loaderType, &modpack.loaderVersion); + } } catch (const Exception& e) { qDebug() << "Couldn't load ftb instance json: " << e.cause(); return {}; } + + auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg")); + if (iconFile.exists() && iconFile.isFile()) { + modpack.icon = QIcon(iconFile.absoluteFilePath()); + } else { // the logo is a file that the first bit denotes the image tipe followed by the actual image data + modpack.icon = loadFTBIcon(FS::PathCombine(path, ".ftbapp", "logo")); + } + return modpack; +} +void legacyInstanceParsing(QString path, std::optional* loaderType, QString* loaderVersion) +{ auto versionsFile = QFileInfo(FS::PathCombine(path, ".ftbapp", "version.json")); if (!versionsFile.exists() || !versionsFile.isFile()) { versionsFile = QFileInfo(FS::PathCombine(path, "version.json")); } if (!versionsFile.exists() || !versionsFile.isFile()) { - return {}; + qDebug() << "Couldn't find ftb version json"; + return; } try { auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file"); @@ -96,34 +128,29 @@ Modpack parseDirectory(QString path) auto name = Json::requireString(obj, "name", "name"); auto version = Json::requireString(obj, "version", "version"); if (name == "neoforge") { - modpack.loaderType = ModPlatform::NeoForge; - modpack.version = version; + *loaderType = ModPlatform::NeoForge; + *loaderVersion = version; break; } else if (name == "forge") { - modpack.loaderType = ModPlatform::Forge; - modpack.version = version; + *loaderType = ModPlatform::Forge; + *loaderVersion = version; break; } else if (name == "fabric") { - modpack.loaderType = ModPlatform::Fabric; - modpack.version = version; + *loaderType = ModPlatform::Fabric; + *loaderVersion = version; break; } else if (name == "quilt") { - modpack.loaderType = ModPlatform::Quilt; - modpack.version = version; + *loaderType = ModPlatform::Quilt; + *loaderVersion = version; break; } } - } catch (const Exception& e) { + } + catch (const Exception& e) + { qDebug() << "Couldn't load ftb version json: " << e.cause(); - return {}; + return; } - auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg")); - if (iconFile.exists() && iconFile.isFile()) { - modpack.icon = QIcon(iconFile.absoluteFilePath()); - } else { // the logo is a file that the first bit denotes the image tipe followed by the actual image data - modpack.icon = loadFTBIcon(FS::PathCombine(path, ".ftbapp", "logo")); - } - return modpack; } - } // namespace FTBImportAPP + diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h index 449ed2546..f010313ff 100644 --- a/launcher/modplatform/import_ftb/PackHelpers.h +++ b/launcher/modplatform/import_ftb/PackHelpers.h @@ -49,7 +49,7 @@ struct Modpack { using ModpackList = QList; Modpack parseDirectory(QString path); - +void legacyInstanceParsing(QString path, std::optional* loaderType, QString* loaderVersion); } // namespace FTBImportAPP // We need it for the proxy model From 117e0d7b24e37292fbc0f58ab6c8c393c1452f6d Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 24 Nov 2025 15:42:28 +0100 Subject: [PATCH 11/59] nix: remove explicit apple-sdk Signed-off-by: Tayou --- nix/unwrapped.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 2a00b5620..54e880818 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,7 +3,6 @@ stdenv, cmake, cmark, - apple-sdk_11, extra-cmake-modules, gamemode, jdk17, @@ -82,7 +81,6 @@ stdenv.mkDerivation { tomlplusplus zlib ] - ++ lib.optionals stdenv.hostPlatform.isDarwin [ apple-sdk_11 ] ++ lib.optional gamemodeSupport gamemode; cmakeFlags = [ From 331c1de9cd8cc536b24f51cac612fee14f9c047a Mon Sep 17 00:00:00 2001 From: Pagwin Date: Tue, 25 Nov 2025 13:34:31 -0500 Subject: [PATCH 12/59] Edited a comment for clarity Signed-off-by: Pagwin --- launcher/ui/pages/instance/ModFolderPage.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 0ba2bba93..7aee9e105 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -403,7 +403,9 @@ inline bool ModFolderPage::handleNoModLoader() InstallLoaderDialog dialog(profile, QString(), this); bool ret = dialog.exec(); this->m_container->refreshContainer(); - // returning false so the caller can go and open up the dialog it was originally going to + + // 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: { From fa930afe4ba14f056d5dee5ff148ffc55e3a87e1 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Jul 2025 02:45:52 +0300 Subject: [PATCH 13/59] move ExportToZipTask Signed-off-by: Trial97 --- CMakeLists.txt | 9 + launcher/CMakeLists.txt | 10 + launcher/MMCZip.cpp | 191 +++------------- launcher/MMCZip.h | 87 -------- launcher/archive/ArchiveWriter.cpp | 211 ++++++++++++++++++ launcher/archive/ArchiveWriter.h | 27 +++ launcher/archive/ExportToZipTask.cpp | 84 +++++++ launcher/archive/ExportToZipTask.h | 59 +++++ .../modplatform/flame/FlamePackExportTask.cpp | 3 +- .../modrinth/ModrinthPackExportTask.cpp | 3 +- launcher/ui/dialogs/ExportInstanceDialog.cpp | 3 +- vcpkg.json | 3 +- 12 files changed, 440 insertions(+), 250 deletions(-) create mode 100644 launcher/archive/ArchiveWriter.cpp create mode 100644 launcher/archive/ArchiveWriter.h create mode 100644 launcher/archive/ExportToZipTask.cpp create mode 100644 launcher/archive/ExportToZipTask.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ea2d4880..94ae651c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,6 +371,15 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find cmark find_package(cmark QUIET) + + find_package(LibArchive 3.7.8 QUIET) + # Fallback to pkg-config (if available) if CMake files aren't found + if(NOT LibArchive_FOUND) + find_package(PkgConfig) + if(PkgConfig_FOUND) + pkg_check_modules(LibArchive IMPORTED_TARGET LibArchive>=3.7.8) + endif() + endif() endif() include(ECMQtDeclareLoggingCategory) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 67ffa4dfb..d0a5ffe71 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -26,6 +26,10 @@ set(CORE_SOURCES NullInstance.h MMCZip.h MMCZip.cpp + archive/ArchiveWriter.cpp + archive/ArchiveWriter.h + archive/ExportToZipTask.cpp + archive/ExportToZipTask.h Untar.h Untar.cpp StringUtils.h @@ -1321,6 +1325,12 @@ else() target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus) endif() +if(TARGET PkgConfig::LibArchive) + target_link_libraries(Launcher_logic PkgConfig::LibArchive) +else() + target_link_libraries(Launcher_logic LibArchive::LibArchive) +endif() + if (UNIX AND NOT CYGWIN AND NOT APPLE) target_link_libraries(Launcher_logic gamemode diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index dfe397930..3a077e1e5 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -35,6 +35,7 @@ */ #include "MMCZip.h" +#include #include #include #include @@ -47,19 +48,32 @@ #if defined(LAUNCHER_APPLICATION) #include +#include "archive/ArchiveWriter.h" #endif namespace MMCZip { // ours -bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const Filter& filter) +using FilterFunction = std::function; +#if defined(LAUNCHER_APPLICATION) +bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet& contained, const FilterFunction& filter = nullptr) { - QuaZip modZip(from.filePath()); - modZip.open(QuaZip::mdUnzip); + std::unique_ptr modZip(archive_read_new(), archive_read_free); + if (!modZip) { + qCritical() << "Failed to create archive"; + } + archive_read_support_format_all(modZip.get()); - QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile(into); - for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) { - QString filename = modZip.getCurrentFileName(); + auto fromUtf8 = from.absoluteFilePath().toUtf8(); + if (archive_read_open_filename(modZip.get(), fromUtf8.constData(), 10240) != ARCHIVE_OK) { + qCritical() << "Failed to open file:" << from << ": " << archive_error_string(modZip.get()); + return false; + } + + archive_entry* entry; + + while (archive_read_next_header(modZip.get(), &entry) == ARCHIVE_OK) { + auto name = archive_entry_pathname(entry); + auto filename = QString::fromStdString(name); if (filter && !filter(filename)) { qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; continue; @@ -69,32 +83,16 @@ bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const continue; } contained.insert(filename); - - if (!fileInsideMod.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << filename << " from " << from.fileName(); - return false; - } - - QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); - - if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) { - qCritical() << "Failed to open " << filename << " in the jar"; - fileInsideMod.close(); - return false; - } - if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { - zipOutFile.close(); - fileInsideMod.close(); + if (!into.addFile(modZip.get(), entry)) { qCritical() << "Failed to copy data of " << filename << " into the jar"; return false; } - zipOutFile.close(); - fileInsideMod.close(); } + return true; } -bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks) +bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files) { QDir directory(dir); if (!directory.exists()) @@ -103,48 +101,18 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow for (auto e : files) { auto filePath = directory.relativeFilePath(e.absoluteFilePath()); auto srcPath = e.absoluteFilePath(); - if (followSymlinks) { - if (e.isSymLink()) { - srcPath = e.symLinkTarget(); - } else { - srcPath = e.canonicalFilePath(); - } - } - if (!JlCompress::compressFile(zip, srcPath, filePath)) + if (!zip.addFile(srcPath, filePath)) return false; } return true; } -bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks) -{ - QuaZip zip(fileCompressed); - zip.setUtf8Enabled(true); - QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); - if (!zip.open(QuaZip::mdCreate)) { - FS::deletePath(fileCompressed); - return false; - } - - auto result = compressDirFiles(&zip, dir, files, followSymlinks); - - zip.close(); - if (zip.getZipError() != 0) { - FS::deletePath(fileCompressed); - return false; - } - - return result; -} - -#if defined(LAUNCHER_APPLICATION) // ours bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods) { - QuaZip zipOut(targetJarPath); - zipOut.setUtf8Enabled(true); - if (!zipOut.open(QuaZip::mdCreate)) { + ArchiveWriter zipOut(targetJarPath); + if (!zipOut.open()) { FS::deletePath(targetJarPath); qCritical() << "Failed to open the minecraft.jar for modding"; return false; @@ -161,7 +129,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListenabled()) continue; if (mod->type() == ResourceType::ZIPFILE) { - if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { + if (!mergeZipFiles(zipOut, mod->fileinfo(), addedFiles)) { zipOut.close(); FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; @@ -170,7 +138,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListtype() == ResourceType::SINGLEFILE) { // FIXME: buggy - does not work with addedFiles auto filename = mod->fileinfo(); - if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) { + if (!zipOut.addFile(filename.absoluteFilePath(), filename.fileName())) { zipOut.close(); FS::deletePath(targetJarPath); qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar."; @@ -193,7 +161,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QListfileinfo().fileName() << "to the jar."; @@ -209,7 +177,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList extractSubDir(QuaZip* zip, const QString& subdir, const QString& target) { @@ -455,84 +406,6 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q } #if defined(LAUNCHER_APPLICATION) -void ExportToZipTask::executeTask() -{ - setStatus("Adding files..."); - setProgress(0, m_files.length()); - m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); }); - connect(&m_build_zip_watcher, &QFutureWatcher::finished, this, &ExportToZipTask::finish); - m_build_zip_watcher.setFuture(m_build_zip_future); -} - -auto ExportToZipTask::exportZip() -> ZipResult -{ - if (!m_dir.exists()) { - return ZipResult(tr("Folder doesn't exist")); - } - if (!m_output.isOpen() && !m_output.open(QuaZip::mdCreate)) { - return ZipResult(tr("Could not create file")); - } - - for (auto fileName : m_extra_files.keys()) { - if (m_build_zip_future.isCanceled()) - return ZipResult(); - QuaZipFile indexFile(&m_output); - if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileName))) { - return ZipResult(tr("Could not create:") + fileName); - } - indexFile.write(m_extra_files[fileName]); - } - - for (const QFileInfo& file : m_files) { - if (m_build_zip_future.isCanceled()) - return ZipResult(); - - auto absolute = file.absoluteFilePath(); - auto relative = m_dir.relativeFilePath(absolute); - setStatus("Compressing: " + relative); - setProgress(m_progress + 1, m_progressTotal); - if (m_follow_symlinks) { - if (file.isSymLink()) - absolute = file.symLinkTarget(); - else - absolute = file.canonicalFilePath(); - } - - if (!m_exclude_files.contains(relative) && !JlCompress::compressFile(&m_output, absolute, m_destination_prefix + relative)) { - return ZipResult(tr("Could not read and compress %1").arg(relative)); - } - } - - m_output.close(); - if (m_output.getZipError() != 0) { - return ZipResult(tr("A zip error occurred")); - } - return ZipResult(); -} - -void ExportToZipTask::finish() -{ - if (m_build_zip_future.isCanceled()) { - FS::deletePath(m_output_path); - emitAborted(); - } else if (auto result = m_build_zip_future.result(); result.has_value()) { - FS::deletePath(m_output_path); - emitFailed(result.value()); - } else { - emitSucceeded(); - } -} - -bool ExportToZipTask::abort() -{ - if (m_build_zip_future.isRunning()) { - m_build_zip_future.cancel(); - // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur - // immediately. - return true; - } - return false; -} void ExtractZipTask::executeTask() { diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index e23d29d65..2a5331522 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -58,31 +58,6 @@ namespace MMCZip { using FilterFileFunction = std::function; -/** - * Merge two zip files, using a filter function - */ -bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet& contained, const Filter& filter = nullptr); - -/** - * Compress directory, by providing a list of files to compress - * \param zip target archive - * \param dir directory that will be compressed (to compress with relative paths) - * \param files list of files to compress - * \param followSymlinks should follow symlinks when compressing file data - * \return true for success or false for failure - */ -bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks = false); - -/** - * Compress directory, by providing a list of files to compress - * \param fileCompressed target archive file - * \param dir directory that will be compressed (to compress with relative paths) - * \param files list of files to compress - * \param followSymlinks should follow symlinks when compressing file data - * \return true for success or false for failure - */ -bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false); - #if defined(LAUNCHER_APPLICATION) /** * take a source jar, add mods to it, resulting in target jar @@ -98,14 +73,6 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList; - - protected: - virtual void executeTask() override; - bool abort() override; - - ZipResult exportZip(); - void finish(); - - private: - QString m_output_path; - QuaZip m_output; - QDir m_dir; - QFileInfoList m_files; - QString m_destination_prefix; - bool m_follow_symlinks; - QStringList m_exclude_files; - QHash m_extra_files; - - QFuture m_build_zip_future; - QFutureWatcher m_build_zip_watcher; -}; class ExtractZipTask : public Task { Q_OBJECT diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp new file mode 100644 index 000000000..cfae8a8ff --- /dev/null +++ b/launcher/archive/ArchiveWriter.cpp @@ -0,0 +1,211 @@ + +#include "ArchiveWriter.h" +#include + +#include +#include + +#include + +namespace MMCZip { + +ArchiveWriter::ArchiveWriter(const QString& archiveName) : m_filename(archiveName) {} + +ArchiveWriter::~ArchiveWriter() +{ + close(); +} + +bool ArchiveWriter::open() +{ + if (m_filename.isEmpty()) { + qCritical() << "Archive m_filename not set."; + return false; + } + + m_archive = archive_write_new(); + if (!m_archive) { + qCritical() << "Archive not initialized."; + return false; + } + + QString lowerName = m_filename.toLower(); + if (lowerName.endsWith(".tar.gz") || lowerName.endsWith(".tgz")) { + archive_write_set_format_pax_restricted(m_archive); + archive_write_add_filter_gzip(m_archive); + } else if (lowerName.endsWith(".tar.bz2") || lowerName.endsWith(".tbz")) { + archive_write_set_format_pax_restricted(m_archive); + archive_write_add_filter_bzip2(m_archive); + } else if (lowerName.endsWith(".tar.xz") || lowerName.endsWith(".txz")) { + archive_write_set_format_pax_restricted(m_archive); + archive_write_add_filter_xz(m_archive); + } else if (lowerName.endsWith(".zip") || lowerName.endsWith(".jar")) { + archive_write_set_format_zip(m_archive); + } else if (lowerName.endsWith(".tar")) { + archive_write_set_format_pax_restricted(m_archive); + } else { + qCritical() << "Unknown archive format:" << m_filename; + return false; + } + + auto archiveNameUtf8 = m_filename.toUtf8(); + if (archive_write_open_filename(m_archive, archiveNameUtf8.constData()) != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); + return false; + } + + return true; +} + +bool ArchiveWriter::close() +{ + bool success = true; + if (m_archive) { + if (archive_write_close(m_archive) != ARCHIVE_OK) { + qCritical() << "Failed to close archive" << m_filename << "-" << archive_error_string(m_archive); + success = false; + } + if (archive_write_free(m_archive) != ARCHIVE_OK) { + qCritical() << "Failed to free archive" << m_filename << "-" << archive_error_string(m_archive); + success = false; + } + m_archive = nullptr; + } + return success; +} + +bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) +{ + QFileInfo fileInfo(fileName); + if (!fileInfo.exists()) { + qCritical() << "File does not exists:" << fileInfo.filePath(); + return false; + } + + std::unique_ptr entry_ptr(archive_entry_new(), archive_entry_free); + auto entry = entry_ptr.get(); + if (!entry) { + qCritical() << "Failed to create archive entry"; + return false; + } + + auto fileDestUtf8 = fileDest.toUtf8(); + archive_entry_set_pathname(entry, fileDestUtf8.constData()); + archive_entry_set_perm(entry, fileInfo.permissions() & 0777); + + if (fileInfo.isSymLink()) { + auto target = fileInfo.symLinkTarget().toUtf8(); + archive_entry_set_filetype(entry, AE_IFLNK); + archive_entry_set_symlink(entry, target.constData()); + archive_entry_set_size(entry, 0); + + if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { + qCritical() << "Failed to write symlink header for:" << fileDest << "-" << archive_error_string(m_archive); + return false; + } + return true; + } + + if (!fileInfo.isFile()) { + qCritical() << "Unsupported file type:" << fileInfo.filePath(); + return false; + } + + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file: " << fileInfo.filePath(); + return false; + } + + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_size(entry, file.size()); + + if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { + qCritical() << "Failed to write header for: " << fileDest; + return false; + } + + constexpr qint64 chunkSize = 8192; + QByteArray buffer; + buffer.resize(chunkSize); + + while (!file.atEnd()) { + auto bytesRead = file.read(buffer.data(), chunkSize); + if (bytesRead < 0) { + qCritical() << "Read error in file: " << fileInfo.filePath(); + return false; + } + + if (archive_write_data(m_archive, buffer.constData(), bytesRead) < 0) { + qCritical() << "Write error in archive for: " << fileDest; + return false; + } + } + return true; +} + +bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data) +{ + std::unique_ptr entry_ptr(archive_entry_new(), archive_entry_free); + auto entry = entry_ptr.get(); + if (!entry) { + qCritical() << "Failed to create archive entry"; + return false; + } + + auto fileDestUtf8 = fileDest.toUtf8(); + archive_entry_set_pathname(entry, fileDestUtf8.constData()); + archive_entry_set_perm(entry, 0644); + + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_size(entry, data.size()); + + if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { + qCritical() << "Failed to write header for: " << fileDest; + return false; + } + + if (archive_write_data(m_archive, data.constData(), data.size()) < 0) { + qCritical() << "Write error in archive for: " << fileDest; + return false; + } + return true; +} + +bool ArchiveWriter::addFile(archive* src, archive_entry* entry) +{ + if (!src) { + qCritical() << "Invalid source archive"; + return false; + } + if (!entry) { // if is empty read next header + if (auto r = archive_read_next_header(src, &entry); r == ARCHIVE_EOF) { + return false; + } else if (r != ARCHIVE_OK) { + qCritical() << "Failed to read entry from source archive:" << archive_error_string(src); + return false; + } + } + + if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { + qCritical() << "Failed to write header to entry:" << archive_entry_pathname(entry) << "-" << archive_error_string(m_archive); + return false; + } + const void* buff; + size_t size; + la_int64_t offset; + + int status; + while ((status = archive_read_data_block(src, &buff, &size, &offset)) == ARCHIVE_OK) { + if (archive_write_data(m_archive, buff, size) < 0) { + qCritical() << "Failed writing data block:" << archive_error_string(m_archive); + return false; + } + } + if (status != ARCHIVE_EOF) { + qCritical() << "Failed reading data block:" << archive_error_string(m_archive); + return false; + } + return true; +} +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h new file mode 100644 index 000000000..e8b5ca348 --- /dev/null +++ b/launcher/archive/ArchiveWriter.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include +#include + +namespace MMCZip { + +class ArchiveWriter { + public: + ArchiveWriter(const QString& archiveName); + ~ArchiveWriter(); + + bool open(); + bool close(); + + bool addFile(const QString& fileName, const QString& fileDest); + bool addFile(const QString& fileDest, const QByteArray& data); + bool addFile(archive* src, archive_entry* entry = nullptr); + + private: + struct archive* m_archive = nullptr; + QString m_filename; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExportToZipTask.cpp b/launcher/archive/ExportToZipTask.cpp new file mode 100644 index 000000000..b85bfd896 --- /dev/null +++ b/launcher/archive/ExportToZipTask.cpp @@ -0,0 +1,84 @@ + +#include "ExportToZipTask.h" + +#include + +#include "FileSystem.h" + +namespace MMCZip { +void ExportToZipTask::executeTask() +{ + setStatus("Adding files..."); + setProgress(0, m_files.length()); + m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); }); + connect(&m_build_zip_watcher, &QFutureWatcher::finished, this, &ExportToZipTask::finish); + m_build_zip_watcher.setFuture(m_build_zip_future); +} + +auto ExportToZipTask::exportZip() -> ZipResult +{ + if (!m_dir.exists()) { + return ZipResult(tr("Folder doesn't exist")); + } + if (!m_output.open()) { + return ZipResult(tr("Could not create file")); + } + + for (auto fileName : m_extra_files.keys()) { + if (m_build_zip_future.isCanceled()) + return ZipResult(); + if (!m_output.addFile(fileName, m_extra_files[fileName])) { + return ZipResult(tr("Could not add:") + fileName); + } + } + + for (const QFileInfo& file : m_files) { + if (m_build_zip_future.isCanceled()) + return ZipResult(); + + auto absolute = file.absoluteFilePath(); + auto relative = m_dir.relativeFilePath(absolute); + setStatus("Compressing: " + relative); + setProgress(m_progress + 1, m_progressTotal); + if (m_follow_symlinks) { + if (file.isSymLink()) + absolute = file.symLinkTarget(); + else + absolute = file.canonicalFilePath(); + } + + if (!m_exclude_files.contains(relative) && !m_output.addFile(absolute, m_destination_prefix + relative)) { + return ZipResult(tr("Could not read and compress %1").arg(relative)); + } + } + + if (!m_output.close()) { + return ZipResult(tr("A zip error occurred")); + } + return ZipResult(); +} + +void ExportToZipTask::finish() +{ + if (m_build_zip_future.isCanceled()) { + FS::deletePath(m_output_path); + emitAborted(); + } else if (auto result = m_build_zip_future.result(); result.has_value()) { + FS::deletePath(m_output_path); + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExportToZipTask::abort() +{ + if (m_build_zip_future.isRunning()) { + m_build_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h new file mode 100644 index 000000000..12fbdcd2b --- /dev/null +++ b/launcher/archive/ExportToZipTask.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#include "ArchiveWriter.h" +#include "tasks/Task.h" + +namespace MMCZip { +class ExportToZipTask : public Task { + Q_OBJECT + public: + ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) + : m_output_path(outputPath) + , m_output(outputPath) + , m_dir(dir) + , m_files(files) + , m_destination_prefix(destinationPrefix) + , m_follow_symlinks(followSymlinks) + { + setAbortable(true); + // m_output.setUtf8Enabled(utf8Enabled); // ignore for now + // need to test: + // - https://github.com/PrismLauncher/PrismLauncher/pull/2225 + // - https://github.com/PrismLauncher/PrismLauncher/pull/2353 + }; + ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) + : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks) {}; + + virtual ~ExportToZipTask() = default; + + void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; } + void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); } + + using ZipResult = std::optional; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult exportZip(); + void finish(); + + private: + QString m_output_path; + ArchiveWriter m_output; + QDir m_dir; + QFileInfoList m_files; + QString m_destination_prefix; + bool m_follow_symlinks; + QStringList m_exclude_files; + QHash m_extra_files; + + QFuture m_build_zip_future; + QFutureWatcher m_build_zip_watcher; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 98a61d0d1..b3ad4b606 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -31,6 +31,7 @@ #include "Application.h" #include "Json.h" #include "MMCZip.h" +#include "archive/ExportToZipTask.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" #include "modplatform/ModIndex.h" @@ -318,7 +319,7 @@ void FlamePackExportTask::buildZip() setStatus(tr("Adding files...")); setProgress(4, 5); - auto zipTask = makeShared(m_options.output, m_gameRoot, files, "overrides/", true, false); + auto zipTask = makeShared(m_options.output, m_gameRoot, files, "overrides/", true); zipTask->addExtraFile("manifest.json", generateIndex()); zipTask->addExtraFile("modlist.html", generateHTML()); diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 9ee4101e6..c638e8db0 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -25,6 +25,7 @@ #include #include "Json.h" #include "MMCZip.h" +#include "archive/ExportToZipTask.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/ModFolderModel.h" @@ -200,7 +201,7 @@ void ModrinthPackExportTask::buildZip() { setStatus(tr("Adding files...")); - auto zipTask = makeShared(output, gameRoot, files, "overrides/", true, true); + auto zipTask = makeShared(output, gameRoot, files, "overrides/", true); zipTask->addExtraFile("modrinth.index.json", generateIndex()); zipTask->setExcludeFiles(resolvedFiles.keys()); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 8d98b0513..21c16f01a 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -43,6 +43,7 @@ #include #include "FileIgnoreProxy.h" #include "QObjectPtr.h" +#include "archive/ExportToZipTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" #include "ui_ExportInstanceDialog.h" @@ -150,7 +151,7 @@ void ExportInstanceDialog::doExport() return; } - auto task = makeShared(output, m_instance->instanceRoot(), files, "", true, true); + auto task = makeShared(output, m_instance->instanceRoot(), files, "", true); connect(task.get(), &Task::failed, this, [this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); diff --git a/vcpkg.json b/vcpkg.json index 0399cdf27..d7ed27197 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -15,6 +15,7 @@ "host": true }, "tomlplusplus", - "zlib" + "zlib", + "libarchive" ] } From 60b2585711cd64d9ec9ea1da68e3f5cedff288cf Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Jul 2025 13:57:14 +0300 Subject: [PATCH 14/59] move some functions from MMCZip to use libarchive Signed-off-by: Trial97 --- CMakeLists.txt | 16 +- launcher/CMakeLists.txt | 9 +- launcher/MMCZip.cpp | 267 ++++++++---------- launcher/MMCZip.h | 13 +- launcher/archive/ArchiveReader.cpp | 170 +++++++++++ launcher/archive/ArchiveReader.h | 53 ++++ launcher/archive/ArchiveWriter.cpp | 36 +-- launcher/archive/ArchiveWriter.h | 5 +- launcher/archive/ExportToZipTask.h | 4 - launcher/minecraft/World.cpp | 53 ++-- .../technic/SingleZipPackInstallTask.cpp | 6 +- .../technic/SingleZipPackInstallTask.h | 3 +- 12 files changed, 383 insertions(+), 252 deletions(-) create mode 100644 launcher/archive/ArchiveReader.cpp create mode 100644 launcher/archive/ArchiveReader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 94ae651c2..86fa72ff6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,14 +358,13 @@ else() endif() if(NOT Launcher_FORCE_BUNDLED_LIBS) + find_package(PkgConfig REQUIRED) + pkg_check_modules(libarchive IMPORTED_TARGET libarchive) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) # Fallback to pkg-config (if available) if CMake files aren't found if(NOT tomlplusplus_FOUND) - find_package(PkgConfig QUIET) - if(PkgConfig_FOUND) - pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) - endif() + pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) endif() @@ -375,11 +374,12 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) find_package(LibArchive 3.7.8 QUIET) # Fallback to pkg-config (if available) if CMake files aren't found if(NOT LibArchive_FOUND) - find_package(PkgConfig) - if(PkgConfig_FOUND) - pkg_check_modules(LibArchive IMPORTED_TARGET LibArchive>=3.7.8) - endif() + pkg_check_modules(LibArchive IMPORTED_TARGET LibArchive>=3.7.8) endif() + + # Find qrcodegencpp-cmake + find_package(qrcodegencpp QUIET) + endif() include(ECMQtDeclareLoggingCategory) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index d0a5ffe71..ea209d12f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -26,6 +26,8 @@ set(CORE_SOURCES NullInstance.h MMCZip.h MMCZip.cpp + archive/ArchiveReader.cpp + archive/ArchiveReader.h archive/ArchiveWriter.cpp archive/ArchiveWriter.h archive/ExportToZipTask.cpp @@ -1310,6 +1312,7 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets + PkgConfig::libarchive ) if(TARGET PkgConfig::libqrencode) @@ -1325,12 +1328,6 @@ else() target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus) endif() -if(TARGET PkgConfig::LibArchive) - target_link_libraries(Launcher_logic PkgConfig::LibArchive) -else() - target_link_libraries(Launcher_logic LibArchive::LibArchive) -endif() - if (UNIX AND NOT CYGWIN AND NOT APPLE) target_link_libraries(Launcher_logic gamemode diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 3a077e1e5..8d496fbb9 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -36,15 +36,18 @@ #include "MMCZip.h" #include +#include #include #include #include #include "FileSystem.h" +#include "archive/ArchiveReader.h" #include #include #include #include +#include #if defined(LAUNCHER_APPLICATION) #include @@ -57,39 +60,26 @@ using FilterFunction = std::function; #if defined(LAUNCHER_APPLICATION) bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet& contained, const FilterFunction& filter = nullptr) { - std::unique_ptr modZip(archive_read_new(), archive_read_free); - if (!modZip) { - qCritical() << "Failed to create archive"; - } - archive_read_support_format_all(modZip.get()); - - auto fromUtf8 = from.absoluteFilePath().toUtf8(); - if (archive_read_open_filename(modZip.get(), fromUtf8.constData(), 10240) != ARCHIVE_OK) { - qCritical() << "Failed to open file:" << from << ": " << archive_error_string(modZip.get()); - return false; - } - - archive_entry* entry; - - while (archive_read_next_header(modZip.get(), &entry) == ARCHIVE_OK) { - auto name = archive_entry_pathname(entry); - auto filename = QString::fromStdString(name); + ArchiveReader r(from.absoluteFilePath()); + return r.parse([&into, &contained, &filter, from](ArchiveReader::File* f) { + auto filename = f->filename(); if (filter && !filter(filename)) { qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered"; - continue; + f->skip(); + return true; } if (contained.contains(filename)) { qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); - continue; + f->skip(); + return true; } contained.insert(filename); - if (!into.addFile(modZip.get(), entry)) { + if (!into.addFile(f)) { qCritical() << "Failed to copy data of " << filename << " into the jar"; return false; } - } - - return true; + return true; + }); } bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files) @@ -195,178 +185,147 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList extractSubDir(QuaZip* zip, const QString& subdir, const QString& target) +std::optional extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target) { auto target_top_dir = QUrl::fromLocalFile(target); QStringList extracted; qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; - auto numEntries = zip->getEntriesCount(); - if (numEntries < 0) { + if (!zip->collectFiles()) { qWarning() << "Failed to enumerate files in archive"; return std::nullopt; - } else if (numEntries == 0) { + } + if (zip->getFiles().isEmpty()) { qDebug() << "Extracting empty archives seems odd..."; return extracted; - } else if (!zip->goToFirstFile()) { - qWarning() << "Failed to seek to first file in zip"; - return std::nullopt; } - do { - QString file_name = zip->getCurrentFileName(); - file_name = FS::RemoveInvalidPathChars(file_name); - if (!file_name.startsWith(subdir)) - continue; + int flags; - auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size())); - auto original_name = relative_file_name; + /* Select which attributes we want to restore. */ + flags = ARCHIVE_EXTRACT_TIME; + flags |= ARCHIVE_EXTRACT_PERM; + flags |= ARCHIVE_EXTRACT_ACL; + flags |= ARCHIVE_EXTRACT_FFLAGS; - // Fix subdirs/files ending with a / getting transformed into absolute paths - if (relative_file_name.startsWith('/')) - relative_file_name = relative_file_name.mid(1); + std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { + archive_write_close(a); + archive_write_free(a); + }); + auto ext = extPtr.get(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); - // Fix weird "folders with a single file get squashed" thing - QString sub_path; - if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { - sub_path = relative_file_name.section('/', 0, -2) + '/'; - FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); - - relative_file_name = relative_file_name.split('/').last(); - } - - QString target_file_path; - if (relative_file_name.isEmpty()) { - target_file_path = target + '/'; - } else { - target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); - if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) - target_file_path += '/'; - } - - if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { - qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" - << target; - return std::nullopt; - } - - if (!JlCompress::extractFile(zip, "", target_file_path)) { - qWarning() << "Failed to extract file" << original_name << "to" << target_file_path; - JlCompress::removeFile(extracted); - return std::nullopt; - } - - extracted.append(target_file_path); - auto fileInfo = QFileInfo(target_file_path); - if (fileInfo.isFile()) { - auto permissions = fileInfo.permissions(); - auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser | - QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther; - auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - - auto newPermisions = (permissions & maxPermisions) | minPermisions; - if (newPermisions != permissions) { - if (!QFile::setPermissions(target_file_path, newPermisions)) { - qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); - } + if (!zip->parse([&subdir, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) { + QString file_name = f->filename(); + file_name = FS::RemoveInvalidPathChars(file_name); + if (!file_name.startsWith(subdir)) { + f->skip(); + return true; } - } else if (fileInfo.isDir()) { - // Ensure the folder has the minimal required permissions - QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup | - QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther; - QFile::Permissions currentPermissions = fileInfo.permissions(); - if ((currentPermissions & minimalPermissions) != minimalPermissions) { - if (!QFile::setPermissions(target_file_path, minimalPermissions)) { - qWarning() << (QObject::tr("Could not fix permissions for %1").arg(target_file_path)); - } + auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(subdir.size())); + auto original_name = relative_file_name; + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); } - } - qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; - } while (zip->goToNextFile()); + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + qWarning() << "Extracting" << relative_file_name << "was cancelled, because it was effectively outside of the target path" + << target; + return false; + } + if (!f->writeFile(ext, target_file_path)) { + qWarning() << "Failed to extract file" << original_name << "to" << target_file_path; + return false; + } + + extracted.append(target_file_path); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + return true; + })) { + qWarning() << "Failed to parse file" << zip->getZipName(); + JlCompress::removeFile(extracted); + return std::nullopt; + } return extracted; } -// ours -bool extractRelFile(QuaZip* zip, const QString& file, const QString& target) -{ - return JlCompress::extractFile(zip, file, target); -} - // ours std::optional extractDir(QString fileCompressed, QString dir) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return QStringList(); - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); - ; - return std::nullopt; + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return QStringList(); } + ArchiveReader zip(fileCompressed); return extractSubDir(&zip, "", dir); } // ours std::optional extractDir(QString fileCompressed, QString subdir, QString dir) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return QStringList(); - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); - ; - return std::nullopt; + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return QStringList(); } + ArchiveReader zip(fileCompressed); return extractSubDir(&zip, subdir, dir); } // ours bool extractFile(QString fileCompressed, QString file, QString target) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) { - // check if this is a minimum size empty zip file... - QFileInfo fileInfo(fileCompressed); - if (fileInfo.size() == 22) { - return true; - } - qWarning() << "Could not open archive for unpacking:" << fileCompressed << "Error:" << zip.getZipError(); + // check if this is a minimum size empty zip file... + QFileInfo fileInfo(fileCompressed); + if (fileInfo.size() == 22) { + return true; + } + ArchiveReader zip(fileCompressed); + auto f = zip.goToFile(file); + if (!f) { return false; } - return extractRelFile(&zip, file, target); + int flags; + + /* Select which attributes we want to restore. */ + flags = ARCHIVE_EXTRACT_TIME; + flags |= ARCHIVE_EXTRACT_PERM; + flags |= ARCHIVE_EXTRACT_ACL; + flags |= ARCHIVE_EXTRACT_FFLAGS; + + std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { + archive_write_close(a); + archive_write_free(a); + }); + auto ext = extPtr.get(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); + + return f->writeFile(ext, target); } bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter) diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 2a5331522..31265e0b8 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -48,6 +48,7 @@ #include #include #include +#include "archive/ArchiveReader.h" #if defined(LAUNCHER_APPLICATION) #include "minecraft/mod/Mod.h" @@ -64,21 +65,11 @@ using FilterFileFunction = std::function; */ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); #endif -/** - * Find a single file in archive by file name (not path) - * - * \param ignore_paths paths to skip when recursing the search - * - * \return the path prefix where the file is - */ -QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths = {}, const QString& root = QString("")); /** * Extract a subdirectory from an archive */ -std::optional extractSubDir(QuaZip* zip, const QString& subdir, const QString& target); - -bool extractRelFile(QuaZip* zip, const QString& file, const QString& target); +std::optional extractSubDir(ArchiveReader* zip, const QString& subdir, const QString& target); /** * Extract a whole archive. diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp new file mode 100644 index 000000000..5bb8054d4 --- /dev/null +++ b/launcher/archive/ArchiveReader.cpp @@ -0,0 +1,170 @@ +#include "ArchiveReader.h" +#include +namespace MMCZip { +QStringList ArchiveReader::getFiles() +{ + return m_fileNames; +} + +bool ArchiveReader::collectFiles(bool onlyFiles) +{ + return parse([this, onlyFiles](File* f) { + if (!onlyFiles || f->isFile()) + m_fileNames << f->filename(); + return f->skip(); + }); +} + +QString ArchiveReader::File::filename() +{ + return QString::fromUtf8(archive_entry_pathname(m_entry)); +} + +QByteArray ArchiveReader::File::readAll(int* outStatus) +{ + QByteArray data; + const void* buff; + size_t size; + la_int64_t offset; + + int status; + while ((status = archive_read_data_block(m_archive.get(), &buff, &size, &offset)) == ARCHIVE_OK) { + data.append(static_cast(buff), static_cast(size)); + } + if (status != ARCHIVE_EOF && status != ARCHIVE_OK) { + qWarning() << "libarchive read error: " << archive_error_string(m_archive.get()); + } + if (outStatus) { + *outStatus = status; + } + archive_read_close(m_archive.get()); + return data; +} + +QDateTime ArchiveReader::File::dateTime() +{ + auto mtime = archive_entry_mtime(m_entry); + auto mtime_nsec = archive_entry_mtime_nsec(m_entry); + auto dt = QDateTime::fromSecsSinceEpoch(mtime); + return dt.addMSecs(mtime_nsec / 1e6); +} + +int ArchiveReader::File::readNextHeader() +{ + return archive_read_next_header(m_archive.get(), &m_entry); +} + +auto ArchiveReader::goToFile(QString filename) -> std::unique_ptr +{ + auto f = std::make_unique(); + auto a = f->m_archive.get(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + auto fileName = m_archivePath.toUtf8(); + if (archive_read_open_filename(a, fileName.constData(), 10240) != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_archivePath << "-" << archive_error_string(a); + return nullptr; + } + + while (f->readNextHeader() == ARCHIVE_OK) { + if (f->filename() == filename) { + return f; + } + f->skip(); + } + + archive_read_close(a); + return nullptr; +} + +static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = false) +{ + int r; + const void* buff; + size_t size; + la_int64_t offset; + + for (;;) { + r = archive_read_data_block(ar, &buff, &size, &offset); + if (r == ARCHIVE_EOF) + return (ARCHIVE_OK); + if (r < ARCHIVE_OK) { + qCritical() << "Failed reading data block:" << archive_error_string(ar); + return (r); + } + if (notBlock) { + r = archive_write_data(aw, buff, size); + } else { + r = archive_write_data_block(aw, buff, size, offset); + } + if (r < ARCHIVE_OK) { + qCritical() << "Failed writing data block:" << archive_error_string(aw); + return (r); + } + } +} + +bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool notBlock) +{ + auto entry = m_entry; + if (!targetFileName.isEmpty()) { + entry = archive_entry_clone(m_entry); + auto nameUtf8 = targetFileName.toUtf8(); + archive_entry_set_pathname(entry, nameUtf8.constData()); + } + if (archive_write_header(out, entry) < ARCHIVE_OK) { + qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out); + return false; + } else if (archive_entry_size(m_entry) > 0) { + auto r = copy_data(m_archive.get(), out, notBlock); + if (r < ARCHIVE_OK) + qCritical() << "Failed reading data block:" << archive_error_string(out); + if (r < ARCHIVE_WARN) + return false; + } + auto r = archive_write_finish_entry(out); + if (r < ARCHIVE_OK) + qCritical() << "Failed dinish entry:" << archive_error_string(out); + return (r > ARCHIVE_WARN); +} + +bool ArchiveReader::parse(std::function doStuff) +{ + auto f = std::make_unique(); + auto a = f->m_archive.get(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + auto fileName = m_archivePath.toUtf8(); + if (archive_read_open_filename(a, fileName.constData(), 10240) != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_archivePath << "-" << f->error(); + return false; + } + + while (f->readNextHeader() == ARCHIVE_OK) { + if (!doStuff(f.get())) { + qCritical() << "Failed to parse file:" << f->filename() << "-" << f->error(); + return false; + } + } + + archive_read_close(a); + return true; +} + +bool ArchiveReader::File::isFile() +{ + return (archive_entry_filetype(m_entry) & AE_IFMT) == AE_IFREG; +} +bool ArchiveReader::File::skip() +{ + return archive_read_data_skip(m_archive.get()) == ARCHIVE_OK; +} +const char* ArchiveReader::File::error() +{ + return archive_error_string(m_archive.get()); +} +QString ArchiveReader::getZipName() +{ + return m_archivePath; +} +} // namespace MMCZip diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h new file mode 100644 index 000000000..9831c2cb7 --- /dev/null +++ b/launcher/archive/ArchiveReader.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace MMCZip { +class ArchiveReader { + public: + using ArchivePtr = std::unique_ptr; + ArchiveReader(QString fileName) : m_archivePath(fileName) {} + virtual ~ArchiveReader() = default; + + QStringList getFiles(); + QString getZipName(); + bool collectFiles(bool onlyFiles = true); + + class File { + public: + File() : m_archive(ArchivePtr(archive_read_new(), archive_read_free)) {} + virtual ~File() {} + + QString filename(); + bool isFile(); + QDateTime dateTime(); + const char* error(); + + QByteArray readAll(int* outStatus = nullptr); + bool skip(); + bool writeFile(archive* out, QString targetFileName = "", bool notBlock = false); + + private: + int readNextHeader(); + + private: + friend ArchiveReader; + ArchivePtr m_archive; + archive_entry* m_entry; + }; + + std::unique_ptr goToFile(QString filename); + bool parse(std::function); + + private: + QString m_archivePath; + + QStringList m_fileNames = {}; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index cfae8a8ff..a994f06aa 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -172,40 +172,8 @@ bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data) return true; } -bool ArchiveWriter::addFile(archive* src, archive_entry* entry) +bool ArchiveWriter::addFile(ArchiveReader::File* f) { - if (!src) { - qCritical() << "Invalid source archive"; - return false; - } - if (!entry) { // if is empty read next header - if (auto r = archive_read_next_header(src, &entry); r == ARCHIVE_EOF) { - return false; - } else if (r != ARCHIVE_OK) { - qCritical() << "Failed to read entry from source archive:" << archive_error_string(src); - return false; - } - } - - if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { - qCritical() << "Failed to write header to entry:" << archive_entry_pathname(entry) << "-" << archive_error_string(m_archive); - return false; - } - const void* buff; - size_t size; - la_int64_t offset; - - int status; - while ((status = archive_read_data_block(src, &buff, &size, &offset)) == ARCHIVE_OK) { - if (archive_write_data(m_archive, buff, size) < 0) { - qCritical() << "Failed writing data block:" << archive_error_string(m_archive); - return false; - } - } - if (status != ARCHIVE_EOF) { - qCritical() << "Failed reading data block:" << archive_error_string(m_archive); - return false; - } - return true; + return f->writeFile(m_archive, "", true); } } // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index e8b5ca348..2e26e4b5e 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -5,20 +5,21 @@ #include #include +#include "archive/ArchiveReader.h" namespace MMCZip { class ArchiveWriter { public: ArchiveWriter(const QString& archiveName); - ~ArchiveWriter(); + virtual ~ArchiveWriter(); bool open(); bool close(); bool addFile(const QString& fileName, const QString& fileDest); bool addFile(const QString& fileDest, const QByteArray& data); - bool addFile(archive* src, archive_entry* entry = nullptr); + bool addFile(ArchiveReader::File* f); private: struct archive* m_archive = nullptr; diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h index 12fbdcd2b..674053791 100644 --- a/launcher/archive/ExportToZipTask.h +++ b/launcher/archive/ExportToZipTask.h @@ -21,10 +21,6 @@ class ExportToZipTask : public Task { , m_follow_symlinks(followSymlinks) { setAbortable(true); - // m_output.setUtf8Enabled(utf8Enabled); // ignore for now - // need to test: - // - https://github.com/PrismLauncher/PrismLauncher/pull/2225 - // - https://github.com/PrismLauncher/PrismLauncher/pull/2353 }; ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false) : ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks) {}; diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 8ae097bad..854bb2ee8 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -57,6 +57,7 @@ #include "FileSystem.h" #include "PSaveFile.h" +#include "archive/ArchiveReader.h" using std::nullopt; using std::optional; @@ -244,36 +245,37 @@ void World::readFromFS(const QFileInfo& file) void World::readFromZip(const QFileInfo& file) { - QuaZip zip(file.absoluteFilePath()); - m_isValid = zip.open(QuaZip::mdUnzip); - if (!m_isValid) { + MMCZip::ArchiveReader r(file.absoluteFilePath()); + + if (m_isValid = r.collectFiles(); !m_isValid) { return; } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - m_isValid = !location.isEmpty(); - if (!m_isValid) { + + QString path; + const QString levelDat = "level.dat"; + for (auto filePath : r.getFiles()) { + QFileInfo fi(filePath); + if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) { + m_containerOffsetPath = filePath.chopped(levelDat.length()); + path = filePath; + break; + } + } + + if (m_isValid = !m_containerOffsetPath.isEmpty(); !m_isValid) { + return; + } + + auto zippedFile = r.goToFile(path); + if (m_isValid = !!zippedFile; !m_isValid) { return; } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); // read the install profile - m_isValid = zip.setCurrentFile(location + "level.dat"); + m_levelDatTime = zippedFile->dateTime(); if (!m_isValid) { return; } - m_isValid = zippedFile.open(QIODevice::ReadOnly); - QuaZipFileInfo64 levelDatInfo; - zippedFile.getFileInfo(&levelDatInfo); - auto modTime = levelDatInfo.getNTFSmTime(); - if (!modTime.isValid()) { - modTime = levelDatInfo.dateTime; - } - m_levelDatTime = modTime; - if (!m_isValid) { - return; - } - loadFromLevelDat(zippedFile.readAll()); - zippedFile.close(); + loadFromLevelDat(zippedFile->readAll()); } bool World::install(const QString& to, const QString& name) @@ -284,10 +286,7 @@ bool World::install(const QString& to, const QString& name) } bool ok = false; if (m_containerFile.isFile()) { - QuaZip zip(m_containerFile.absoluteFilePath()); - if (!zip.open(QuaZip::mdUnzip)) { - return false; - } + MMCZip::ArchiveReader zip(m_containerFile.absoluteFilePath()); ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath); } else if (m_containerFile.isDir()) { QString from = m_containerFile.filePath(); @@ -350,7 +349,7 @@ optional read_string(nbt::value& parent, const char* name) return nullopt; } auto& tag_str = namedValue.as(); - return QString::fromStdString(tag_str.get()); + return QString::fromUtf8(tag_str.get()); } catch ([[maybe_unused]] const std::out_of_range& e) { // fallback for old world formats qWarning() << "String NBT tag" << name << "could not be found."; diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index cc9ced10b..09428c31d 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -66,11 +66,7 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded() qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } + m_packZip.reset(new MMCZip::ArchiveReader(m_archivePath)); m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath()); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &Technic::SingleZipPackInstallTask::extractFinished); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.h b/launcher/modplatform/technic/SingleZipPackInstallTask.h index d49d008b9..09fc7b658 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.h +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.h @@ -16,6 +16,7 @@ #pragma once #include "InstanceTask.h" +#include "archive/ArchiveReader.h" #include "net/NetJob.h" #include @@ -54,7 +55,7 @@ class SingleZipPackInstallTask : public InstanceTask { QString m_minecraftVersion; QString m_archivePath; NetJob::Ptr m_filesNetJob; - std::unique_ptr m_packZip; + std::unique_ptr m_packZip; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; }; From 8c36be048c72fb91be4625e5384f48ea51054e1c Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 10 Jul 2025 20:13:06 +0300 Subject: [PATCH 15/59] move ExtractZipTask Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 4 +- launcher/FileSystem.cpp | 10 + launcher/FileSystem.h | 2 + launcher/InstanceImportTask.cpp | 44 ++- launcher/InstanceImportTask.h | 2 +- launcher/MMCZip.cpp | 138 +--------- launcher/MMCZip.h | 32 --- launcher/Untar.cpp | 260 ------------------ launcher/Untar.h | 46 ---- launcher/archive/ArchiveReader.cpp | 31 +++ launcher/archive/ArchiveReader.h | 1 + launcher/archive/ExtractZipTask.cpp | 130 +++++++++ launcher/archive/ExtractZipTask.h | 36 +++ .../java/download/ArchiveDownloadTask.cpp | 97 +++---- .../atlauncher/ATLPackInstallTask.cpp | 7 - .../legacy_ftb/PackInstallTask.cpp | 6 - .../modplatform/legacy_ftb/PackInstallTask.h | 1 - 17 files changed, 267 insertions(+), 580 deletions(-) delete mode 100644 launcher/Untar.cpp delete mode 100644 launcher/Untar.h create mode 100644 launcher/archive/ExtractZipTask.cpp create mode 100644 launcher/archive/ExtractZipTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ea209d12f..341a944dc 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -32,8 +32,8 @@ set(CORE_SOURCES archive/ArchiveWriter.h archive/ExportToZipTask.cpp archive/ExportToZipTask.h - Untar.h - Untar.cpp + archive/ExtractZipTask.cpp + archive/ExtractZipTask.h StringUtils.h StringUtils.cpp QVariantUtils.h diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 5b44dbd48..30d0a9c4c 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -1702,4 +1702,14 @@ QString getUniqueResourceName(const QString& filePath) return newFileName; } +bool removeFiles(QStringList listFile) +{ + bool ret = true; + // For each file + for (int i = 0; i < listFile.count(); i++) { + // Remove + ret = ret && QFile::remove(listFile.at(i)); + } + return ret; +} } // namespace FS diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b0d9ae2e8..f2676b147 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -291,6 +291,8 @@ bool move(const QString& source, const QString& dest); */ bool deletePath(QString path); +bool removeFiles(QStringList listFile); + /** * Trash a folder / file */ diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 77298e2ce..92cc89510 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -38,10 +38,11 @@ #include "Application.h" #include "FileSystem.h" -#include "MMCZip.h" #include "NullInstance.h" #include "QObjectPtr.h" +#include "archive/ArchiveReader.h" +#include "archive/ExtractZipTask.h" #include "icons/IconList.h" #include "icons/IconUtils.h" @@ -54,8 +55,8 @@ #include "net/ApiDownload.h" +#include #include -#include #include #include @@ -109,38 +110,26 @@ void InstanceImportTask::downloadFromUrl() filesNetJob->start(); } -QString InstanceImportTask::getRootFromZip(QuaZip* zip, const QString& root) +QString InstanceImportTask::getRootFromZip(QStringList files) { if (!isRunning()) { return {}; } - QuaZipDir rootDir(zip, root); - for (auto&& fileName : rootDir.entryList(QDir::Files)) { + for (auto&& fileName : files) { setDetails(fileName); - if (fileName == "instance.cfg") { + QFileInfo fileInfo(fileName); + if (fileInfo.fileName() == "instance.cfg") { qDebug() << "MultiMC:" << true; m_modpackType = ModpackType::MultiMC; - return root; + return fileInfo.path(); } - if (fileName == "manifest.json") { + if (fileInfo.fileName() == "manifest.json") { qDebug() << "Flame:" << true; m_modpackType = ModpackType::Flame; - return root; + return fileInfo.path(); } - QCoreApplication::processEvents(); } - - // Recurse the search to non-ignored subfolders - for (auto&& fileName : rootDir.entryList(QDir::Dirs)) { - if ("overrides/" == fileName) - continue; - - QString result = getRootFromZip(zip, root + fileName); - if (!result.isEmpty()) - return result; - } - return {}; } @@ -151,13 +140,12 @@ void InstanceImportTask::processZipPack() qDebug() << "Attempting to create instance from" << m_archivePath; // open the zip and find relevant files in it - auto packZip = std::make_shared(m_archivePath); - if (!packZip->open(QuaZip::mdUnzip)) { + MMCZip::ArchiveReader packZip(m_archivePath); + if (!packZip.collectFiles()) { emitFailed(tr("Unable to open supplied modpack zip file.")); return; } - QuaZipDir packZipDir(packZip.get()); qDebug() << "Attempting to determine instance type"; QString root; @@ -165,18 +153,18 @@ void InstanceImportTask::processZipPack() // NOTE: Prioritize modpack platforms that aren't searched for recursively. // Especially Flame has a very common filename for its manifest, which may appear inside overrides for example // https://docs.modrinth.com/docs/modpacks/format_definition/#storage - if (packZipDir.exists("/modrinth.index.json")) { + if (packZip.exists("/modrinth.index.json")) { // process as Modrinth pack qDebug() << "Modrinth:" << true; m_modpackType = ModpackType::Modrinth; - } else if (packZipDir.exists("/bin/modpack.jar") || packZipDir.exists("/bin/version.json")) { + } else if (packZip.exists("/bin/modpack.jar") || packZip.exists("/bin/version.json")) { // process as Technic pack qDebug() << "Technic:" << true; extractDir.mkpath("minecraft"); extractDir.cd("minecraft"); m_modpackType = ModpackType::Technic; } else { - root = getRootFromZip(packZip.get()); + root = getRootFromZip(packZip.getFiles()); setDetails(""); } if (m_modpackType == ModpackType::Unknown) { @@ -186,7 +174,7 @@ void InstanceImportTask::processZipPack() setStatus(tr("Extracting modpack")); // make sure we extract just the pack - auto zipTask = makeShared(packZip, extractDir, root); + auto zipTask = makeShared(m_archivePath, extractDir, root); auto progressStep = std::make_shared(); connect(zipTask.get(), &Task::finished, this, [this, progressStep] { diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 8884e0801..c3c833926 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -58,7 +58,7 @@ class InstanceImportTask : public InstanceTask { void processTechnic(); void processFlame(); void processModrinth(); - QString getRootFromZip(QuaZip* zip, const QString& root = ""); + QString getRootFromZip(QStringList files); private slots: void processZipPack(); diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 8d496fbb9..6a051ba63 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -265,7 +265,7 @@ std::optional extractSubDir(ArchiveReader* zip, const QString& subd return true; })) { qWarning() << "Failed to parse file" << zip->getZipName(); - JlCompress::removeFile(extracted); + FS::removeFiles(extracted); return std::nullopt; } @@ -363,140 +363,4 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q } return true; } - -#if defined(LAUNCHER_APPLICATION) - -void ExtractZipTask::executeTask() -{ - if (!m_input->isOpen() && !m_input->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied zip file.")); - return; - } - m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); - connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); - m_zip_watcher.setFuture(m_zip_future); -} - -auto ExtractZipTask::extractZip() -> ZipResult -{ - auto target = m_output_dir.absolutePath(); - auto target_top_dir = QUrl::fromLocalFile(target); - - QStringList extracted; - - qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target; - auto numEntries = m_input->getEntriesCount(); - if (numEntries < 0) { - return ZipResult(tr("Failed to enumerate files in archive")); - } - if (numEntries == 0) { - logWarning(tr("Extracting empty archives seems odd...")); - return ZipResult(); - } - if (!m_input->goToFirstFile()) { - return ZipResult(tr("Failed to seek to first file in zip")); - } - - setStatus("Extracting files..."); - setProgress(0, numEntries); - do { - if (m_zip_future.isCanceled()) - return ZipResult(); - setProgress(m_progress + 1, m_progressTotal); - QString file_name = m_input->getCurrentFileName(); - if (!file_name.startsWith(m_subdirectory)) - continue; - - auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); - auto original_name = relative_file_name; - setStatus("Unpacking: " + relative_file_name); - - // Fix subdirs/files ending with a / getting transformed into absolute paths - if (relative_file_name.startsWith('/')) - relative_file_name = relative_file_name.mid(1); - - // Fix weird "folders with a single file get squashed" thing - QString sub_path; - if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { - sub_path = relative_file_name.section('/', 0, -2) + '/'; - FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); - - relative_file_name = relative_file_name.split('/').last(); - } - - QString target_file_path; - if (relative_file_name.isEmpty()) { - target_file_path = target + '/'; - } else { - target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); - if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) - target_file_path += '/'; - } - - if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { - return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") - .arg(relative_file_name, target)); - } - - if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) { - JlCompress::removeFile(extracted); - return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); - } - - extracted.append(target_file_path); - auto fileInfo = QFileInfo(target_file_path); - if (fileInfo.isFile()) { - auto permissions = fileInfo.permissions(); - auto maxPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser | - QFileDevice::Permission::ReadGroup | QFileDevice::Permission::ReadOther; - auto minPermisions = QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - - auto newPermisions = (permissions & maxPermisions) | minPermisions; - if (newPermisions != permissions) { - if (!QFile::setPermissions(target_file_path, newPermisions)) { - logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); - } - } - } else if (fileInfo.isDir()) { - // Ensure the folder has the minimal required permissions - QFile::Permissions minimalPermissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadGroup | - QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther; - - QFile::Permissions currentPermissions = fileInfo.permissions(); - if ((currentPermissions & minimalPermissions) != minimalPermissions) { - if (!QFile::setPermissions(target_file_path, minimalPermissions)) { - logWarning(tr("Could not fix permissions for %1").arg(target_file_path)); - } - } - } - - qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; - } while (m_input->goToNextFile()); - - return ZipResult(); -} - -void ExtractZipTask::finish() -{ - if (m_zip_future.isCanceled()) { - emitAborted(); - } else if (auto result = m_zip_future.result(); result.has_value()) { - emitFailed(result.value()); - } else { - emitSucceeded(); - } -} - -bool ExtractZipTask::abort() -{ - if (m_zip_future.isRunning()) { - m_zip_future.cancel(); - // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur - // immediately. - return true; - } - return false; -} - -#endif } // namespace MMCZip diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 31265e0b8..8cd8e7345 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -109,36 +109,4 @@ bool extractFile(QString fileCompressed, QString file, QString dir); * \return true for success or false for failure */ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter); - -#if defined(LAUNCHER_APPLICATION) - -class ExtractZipTask : public Task { - Q_OBJECT - public: - ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "") - : ExtractZipTask(std::make_shared(input), outputDir, subdirectory) - {} - ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "") - : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) - {} - virtual ~ExtractZipTask() = default; - - using ZipResult = std::optional; - - protected: - virtual void executeTask() override; - bool abort() override; - - ZipResult extractZip(); - void finish(); - - private: - std::shared_ptr m_input; - QDir m_output_dir; - QString m_subdirectory; - - QFuture m_zip_future; - QFutureWatcher m_zip_watcher; -}; -#endif } // namespace MMCZip diff --git a/launcher/Untar.cpp b/launcher/Untar.cpp deleted file mode 100644 index f1963e7aa..000000000 --- a/launcher/Untar.cpp +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023-2024 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "Untar.h" -#include -#include -#include -#include -#include -#include "FileSystem.h" - -// adaptation of the: -// - https://github.com/madler/zlib/blob/develop/contrib/untgz/untgz.c -// - https://en.wikipedia.org/wiki/Tar_(computing) -// - https://github.com/euroelessar/cutereader/blob/master/karchive/src/ktar.cpp - -#define BLOCKSIZE 512 -#define SHORTNAMESIZE 100 - -enum class TypeFlag : char { - Regular = '0', // regular file - ARegular = 0, // regular file - Link = '1', // link - Symlink = '2', // reserved - Character = '3', // character special - Block = '4', // block special - Directory = '5', // directory - FIFO = '6', // FIFO special - Contiguous = '7', // reserved - // Posix stuff - GlobalPosixHeader = 'g', - ExtendedPosixHeader = 'x', - // 'A'– 'Z' Vendor specific extensions(POSIX .1 - 1988) - // GNU - GNULongLink = 'K', /* long link name */ - GNULongName = 'L', /* long file name */ -}; - -// struct Header { /* byte offset */ -// char name[100]; /* 0 */ -// char mode[8]; /* 100 */ -// char uid[8]; /* 108 */ -// char gid[8]; /* 116 */ -// char size[12]; /* 124 */ -// char mtime[12]; /* 136 */ -// char chksum[8]; /* 148 */ -// TypeFlag typeflag; /* 156 */ -// char linkname[100]; /* 157 */ -// char magic[6]; /* 257 */ -// char version[2]; /* 263 */ -// char uname[32]; /* 265 */ -// char gname[32]; /* 297 */ -// char devmajor[8]; /* 329 */ -// char devminor[8]; /* 337 */ -// char prefix[155]; /* 345 */ -// /* 500 */ -// }; - -bool readLonglink(QIODevice* in, qint64 size, QByteArray& longlink) -{ - qint64 n = 0; - size--; // ignore trailing null - if (size < 0) { - qCritical() << "The filename size is negative"; - return false; - } - longlink.resize(size + (BLOCKSIZE - size % BLOCKSIZE)); // make the size divisible by BLOCKSIZE - for (qint64 offset = 0; offset < longlink.size(); offset += BLOCKSIZE) { - n = in->read(longlink.data() + offset, BLOCKSIZE); - if (n != BLOCKSIZE) { - qCritical() << "The expected blocksize was not respected for the name"; - return false; - } - } - longlink.truncate(qstrlen(longlink.constData())); - return true; -} - -int getOctal(char* buffer, int maxlenght, bool* ok) -{ - return QByteArray(buffer, qstrnlen(buffer, maxlenght)).toInt(ok, 8); -} - -QString decodeName(char* name) -{ - return QFile::decodeName(QByteArray(name, qstrnlen(name, 100))); -} -bool Tar::extract(QIODevice* in, QString dst) -{ - char buffer[BLOCKSIZE]; - QString name, symlink, firstFolderName; - bool doNotReset = false, ok; - while (true) { - auto n = in->read(buffer, BLOCKSIZE); - if (n != BLOCKSIZE) { // allways expect complete blocks - qCritical() << "The expected blocksize was not respected"; - return false; - } - if (buffer[0] == 0) { // end of archive - return true; - } - int mode = getOctal(buffer + 100, 8, &ok) | QFile::ReadUser | QFile::WriteUser; // hack to ensure write and read permisions - if (!ok) { - qCritical() << "The file mode can't be read"; - return false; - } - // there are names that are exactly 100 bytes long - // and neither longlink nor \0 terminated (bug:101472) - - if (name.isEmpty()) { - name = decodeName(buffer); - if (!firstFolderName.isEmpty() && name.startsWith(firstFolderName)) { - name = name.mid(firstFolderName.size()); - } - } - if (symlink.isEmpty()) - symlink = decodeName(buffer); - qint64 size = getOctal(buffer + 124, 12, &ok); - if (!ok) { - qCritical() << "The file size can't be read"; - return false; - } - switch (TypeFlag(buffer[156])) { - case TypeFlag::Regular: - /* fallthrough */ - case TypeFlag::ARegular: { - auto fileName = FS::PathCombine(dst, name); - if (!FS::ensureFilePathExists(fileName)) { - qCritical() << "Can't ensure the file path to exist: " << fileName; - return false; - } - QFile out(fileName); - if (!out.open(QFile::WriteOnly)) { - qCritical() << "Can't open file:" << fileName; - return false; - } - out.setPermissions(QFile::Permissions(mode)); - while (size > 0) { - QByteArray tmp(BLOCKSIZE, 0); - n = in->read(tmp.data(), BLOCKSIZE); - if (n != BLOCKSIZE) { - qCritical() << "The expected blocksize was not respected when reading file"; - return false; - } - tmp.truncate(qMin(qint64(BLOCKSIZE), size)); - out.write(tmp); - size -= BLOCKSIZE; - } - break; - } - case TypeFlag::Directory: { - if (firstFolderName.isEmpty()) { - firstFolderName = name; - break; - } - auto folderPath = FS::PathCombine(dst, name); - if (!FS::ensureFolderPathExists(folderPath)) { - qCritical() << "Can't ensure that folder exists: " << folderPath; - return false; - } - break; - } - case TypeFlag::GNULongLink: { - doNotReset = true; - QByteArray longlink; - if (readLonglink(in, size, longlink)) { - symlink = QFile::decodeName(longlink.constData()); - } else { - qCritical() << "Failed to read long link"; - return false; - } - break; - } - case TypeFlag::GNULongName: { - doNotReset = true; - QByteArray longlink; - if (readLonglink(in, size, longlink)) { - name = QFile::decodeName(longlink.constData()); - } else { - qCritical() << "Failed to read long name"; - return false; - } - break; - } - case TypeFlag::Link: - /* fallthrough */ - case TypeFlag::Symlink: { - auto fileName = FS::PathCombine(dst, name); - if (!FS::create_link(FS::PathCombine(QFileInfo(fileName).path(), symlink), fileName)()) { // do not use symlinks - qCritical() << "Can't create link for:" << fileName << " to:" << FS::PathCombine(QFileInfo(fileName).path(), symlink); - return false; - } - FS::ensureFilePathExists(fileName); - QFile::setPermissions(fileName, QFile::Permissions(mode)); - break; - } - case TypeFlag::Character: - /* fallthrough */ - case TypeFlag::Block: - /* fallthrough */ - case TypeFlag::FIFO: - /* fallthrough */ - case TypeFlag::Contiguous: - /* fallthrough */ - case TypeFlag::GlobalPosixHeader: - /* fallthrough */ - case TypeFlag::ExtendedPosixHeader: - /* fallthrough */ - default: - break; - } - if (!doNotReset) { - name.truncate(0); - symlink.truncate(0); - } - doNotReset = false; - } - return true; -} - -bool GZTar::extract(QString src, QString dst) -{ - QuaGzipFile a(src); - if (!a.open(QIODevice::ReadOnly)) { - qCritical() << "Can't open tar file:" << src; - return false; - } - return Tar::extract(&a, dst); -} \ No newline at end of file diff --git a/launcher/Untar.h b/launcher/Untar.h deleted file mode 100644 index 50e3a16e3..000000000 --- a/launcher/Untar.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (c) 2023-2024 Trial97 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once -#include - -// this is a hack used for the java downloader (feel free to remove it in favor of a library) -// both extract functions will extract the first folder inside dest(disregarding the prefix) -namespace Tar { -bool extract(QIODevice* in, QString dst); -} - -namespace GZTar { -bool extract(QString src, QString dst); -} \ No newline at end of file diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index 5bb8054d4..e48d168ae 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -1,5 +1,8 @@ #include "ArchiveReader.h" #include +#include +#include + namespace MMCZip { QStringList ArchiveReader::getFiles() { @@ -167,4 +170,32 @@ QString ArchiveReader::getZipName() { return m_archivePath; } + +bool ArchiveReader::exists(const QString& filePath) const +{ + if (filePath == QLatin1String("/") || filePath.isEmpty()) + return true; + // Normalize input path (remove trailing slash, if any) + QString normalizedPath = QDir::cleanPath(filePath); + if (normalizedPath.startsWith('/')) + normalizedPath.remove(0, 1); + if (normalizedPath == QLatin1String(".")) + return true; + if (normalizedPath == QLatin1String("..")) + return false; // root only + + // Check for exact file match + if (m_fileNames.contains(normalizedPath, Qt::CaseInsensitive)) + return true; + + // Check for directory existence by seeing if any file starts with that path + QString dirPath = normalizedPath + QLatin1Char('/'); + for (const QString& f : m_fileNames) { + if (f.startsWith(dirPath, Qt::CaseInsensitive)) + return true; + } + + return false; +} + } // namespace MMCZip diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index 9831c2cb7..5cbf75308 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -18,6 +18,7 @@ class ArchiveReader { QStringList getFiles(); QString getZipName(); bool collectFiles(bool onlyFiles = true); + bool exists(const QString& filePath) const; class File { public: diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp new file mode 100644 index 000000000..69069d550 --- /dev/null +++ b/launcher/archive/ExtractZipTask.cpp @@ -0,0 +1,130 @@ +#include "ExtractZipTask.h" +#include +#include "FileSystem.h" +#include "archive/ArchiveReader.h" + +namespace MMCZip { + +void ExtractZipTask::executeTask() +{ + m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); + connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); + m_zip_watcher.setFuture(m_zip_future); +} + +auto ExtractZipTask::extractZip() -> ZipResult +{ + auto target = m_output_dir.absolutePath(); + auto target_top_dir = QUrl::fromLocalFile(target); + + QStringList extracted; + + qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input.getZipName() << "to" << target; + if (!m_input.collectFiles()) { + return ZipResult(tr("Failed to enumerate files in archive")); + } + if (m_input.getFiles().isEmpty()) { + logWarning(tr("Extracting empty archives seems odd...")); + return ZipResult(); + } + + int flags; + + /* Select which attributes we want to restore. */ + flags = ARCHIVE_EXTRACT_TIME; + flags |= ARCHIVE_EXTRACT_PERM; + flags |= ARCHIVE_EXTRACT_ACL; + flags |= ARCHIVE_EXTRACT_FFLAGS; + + std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { + archive_write_close(a); + archive_write_free(a); + }); + auto ext = extPtr.get(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); + + setStatus("Extracting files..."); + setProgress(0, m_input.getFiles().count()); + ZipResult result; + if (!m_input.parse([this, &result, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) { + if (m_zip_future.isCanceled()) + return false; + setProgress(m_progress + 1, m_progressTotal); + QString file_name = f->filename(); + if (!file_name.startsWith(m_subdirectory)) { + f->skip(); + return true; + } + + auto relative_file_name = QDir::fromNativeSeparators(file_name.mid(m_subdirectory.size())); + auto original_name = relative_file_name; + setStatus("Unpacking: " + relative_file_name); + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); + } + + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + result = ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") + .arg(relative_file_name, target)); + return false; + } + + if (!f->writeFile(ext, target_file_path)) { + result = ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); + return false; + } + extracted.append(target_file_path); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + return true; + })) { + FS::removeFiles(extracted); + return result.has_value() || m_zip_future.isCanceled() ? result + : ZipResult(tr("Failed to parse file %1").arg(m_input.getZipName())); + } + return ZipResult(); +} + +void ExtractZipTask::finish() +{ + if (m_zip_future.isCanceled()) { + emitAborted(); + } else if (auto result = m_zip_future.result(); result.has_value()) { + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExtractZipTask::abort() +{ + if (m_zip_future.isRunning()) { + m_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} + +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ExtractZipTask.h b/launcher/archive/ExtractZipTask.h new file mode 100644 index 000000000..4d9a4ccc4 --- /dev/null +++ b/launcher/archive/ExtractZipTask.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include "archive/ArchiveReader.h" +#include "tasks/Task.h" + +namespace MMCZip { + +class ExtractZipTask : public Task { + Q_OBJECT + public: + ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "") + : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) + {} + virtual ~ExtractZipTask() = default; + + using ZipResult = std::optional; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult extractZip(); + void finish(); + + private: + ArchiveReader m_input; + QDir m_output_dir; + QString m_subdirectory; + + QFuture m_zip_future; + QFutureWatcher m_zip_watcher; +}; +} // namespace MMCZip \ No newline at end of file diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index bb31ca1e2..e632225b0 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -18,10 +18,10 @@ #include "java/download/ArchiveDownloadTask.h" #include #include -#include "MMCZip.h" #include "Application.h" -#include "Untar.h" +#include "archive/ArchiveReader.h" +#include "archive/ExtractZipTask.h" #include "net/ChecksumValidator.h" #include "net/NetJob.h" #include "tasks/Task.h" @@ -67,68 +67,45 @@ void ArchiveDownloadTask::executeTask() void ArchiveDownloadTask::extractJava(QString input) { setStatus(tr("Extracting Java")); - if (input.endsWith("tar")) { - setStatus(tr("Extracting Java (Progress is not reported for tar archives)")); - QFile in(input); - if (!in.open(QFile::ReadOnly)) { - emitFailed(tr("Unable to open supplied tar file.")); - return; - } - if (!Tar::extract(&in, QDir(m_final_path).absolutePath())) { - emitFailed(tr("Unable to extract supplied tar file.")); - return; - } - emitSucceeded(); - return; - } else if (input.endsWith("tar.gz") || input.endsWith("taz") || input.endsWith("tgz")) { - setStatus(tr("Extracting Java (Progress is not reported for tar archives)")); - if (!GZTar::extract(input, QDir(m_final_path).absolutePath())) { - emitFailed(tr("Unable to extract supplied tar file.")); - return; - } - emitSucceeded(); - return; - } else if (input.endsWith("zip")) { - auto zip = std::make_shared(input); - if (!zip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Unable to open supplied zip file.")); - return; - } - auto files = zip->getFileNameList(); - if (files.isEmpty()) { - emitFailed(tr("No files were found in the supplied zip file.")); - return; - } - m_task = makeShared(zip, m_final_path, files[0]); - auto progressStep = std::make_shared(); - connect(m_task.get(), &Task::finished, this, [this, progressStep] { - progressStep->state = TaskStepState::Succeeded; - stepProgress(*progressStep); - }); - - connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded); - connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted); - connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) { - progressStep->state = TaskStepState::Failed; - stepProgress(*progressStep); - emitFailed(reason); - }); - connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); - - connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { - progressStep->update(current, total); - stepProgress(*progressStep); - }); - connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) { - progressStep->status = status; - stepProgress(*progressStep); - }); - m_task->start(); + MMCZip::ArchiveReader zip(input); + if (!zip.collectFiles()) { + emitFailed(tr("Unable to open supplied zip file.")); return; } + auto files = zip.getFiles(); + if (files.isEmpty()) { + emitFailed(tr("No files were found in the supplied zip file.")); + return; + } + auto firstFolderParts = files[0].split('/', Qt::SkipEmptyParts); + m_task = makeShared(input, m_final_path, firstFolderParts.value(0)); - emitFailed(tr("Could not determine archive type!")); + auto progressStep = std::make_shared(); + connect(m_task.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(m_task.get(), &Task::succeeded, this, &ArchiveDownloadTask::emitSucceeded); + connect(m_task.get(), &Task::aborted, this, &ArchiveDownloadTask::emitAborted); + connect(m_task.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(m_task.get(), &Task::stepProgress, this, &ArchiveDownloadTask::propagateStepProgress); + + connect(m_task.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(m_task.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_task->start(); + return; } bool ArchiveDownloadTask::abort() diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index ba3a25aa3..a3f045d02 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -675,13 +675,6 @@ void PackInstallTask::extractConfigs() setStatus(tr("Extracting configs...")); QDir extractDir(m_stagingPath); - - QuaZip packZip(archivePath); - if (!packZip.open(QuaZip::mdUnzip)) { - emitFailed(tr("Failed to open pack configs %1!").arg(archivePath)); - return; - } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft"); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, [this]() { downloadMods(); }); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 034ee3eae..33c0c38b6 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -102,12 +102,6 @@ void PackInstallTask::unzip() QDir extractDir(m_stagingPath); - m_packZip.reset(new QuaZip(archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) { - emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); - return; - } - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &PackInstallTask::onUnzipFinished); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 3459ee902..1df809f09 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -39,7 +39,6 @@ class PackInstallTask : public InstanceTask { private: /* data */ shared_qobject_ptr m_network; bool abortable = false; - std::unique_ptr m_packZip; QFuture> m_extractFuture; QFutureWatcher> m_extractFutureWatcher; NetJob::Ptr netJobContainer; From f38a0c8f986cd613f822cd6799a8c0e0e26e2f29 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Jul 2025 00:43:28 +0300 Subject: [PATCH 16/59] move more zip parsings Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 2 - launcher/InstanceImportTask.h | 2 - launcher/MMCZip.cpp | 33 +---- launcher/MMCZip.h | 5 - launcher/archive/ArchiveReader.cpp | 13 +- launcher/archive/ArchiveReader.h | 1 + launcher/archive/ArchiveWriter.cpp | 17 +++ launcher/archive/ArchiveWriter.h | 2 + launcher/archive/ExtractZipTask.cpp | 16 +- .../java/download/ArchiveDownloadTask.cpp | 1 - launcher/minecraft/World.cpp | 3 - launcher/minecraft/launch/ExtractNatives.cpp | 32 ++-- .../mod/tasks/LocalDataPackParseTask.cpp | 137 ++++++++---------- .../mod/tasks/LocalShaderPackParseTask.cpp | 17 +-- .../mod/tasks/LocalTexturePackParseTask.cpp | 70 +++------ .../mod/tasks/LocalWorldSaveParseTask.cpp | 59 ++++---- .../atlauncher/ATLPackInstallTask.cpp | 2 - .../modplatform/legacy_ftb/PackInstallTask.h | 2 - .../technic/SingleZipPackInstallTask.h | 2 - nix/unwrapped.nix | 2 + 20 files changed, 167 insertions(+), 251 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 92cc89510..04600daa9 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -59,8 +59,6 @@ #include #include -#include - InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap&& extra_info) : m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent) {} diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index c3c833926..4c9e6feb5 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -40,8 +40,6 @@ #include #include "InstanceTask.h" -class QuaZip; - class InstanceImportTask : public InstanceTask { Q_OBJECT public: diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 6a051ba63..349c6a809 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -37,9 +37,6 @@ #include "MMCZip.h" #include #include -#include -#include -#include #include "FileSystem.h" #include "archive/ArchiveReader.h" @@ -201,21 +198,8 @@ std::optional extractSubDir(ArchiveReader* zip, const QString& subd return extracted; } - int flags; - - /* Select which attributes we want to restore. */ - flags = ARCHIVE_EXTRACT_TIME; - flags |= ARCHIVE_EXTRACT_PERM; - flags |= ARCHIVE_EXTRACT_ACL; - flags |= ARCHIVE_EXTRACT_FFLAGS; - - std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { - archive_write_close(a); - archive_write_free(a); - }); + auto extPtr = ArchiveWriter::createDiskWriter(); auto ext = extPtr.get(); - archive_write_disk_set_options(ext, flags); - archive_write_disk_set_standard_lookup(ext); if (!zip->parse([&subdir, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) { QString file_name = f->filename(); @@ -309,21 +293,8 @@ bool extractFile(QString fileCompressed, QString file, QString target) if (!f) { return false; } - int flags; - - /* Select which attributes we want to restore. */ - flags = ARCHIVE_EXTRACT_TIME; - flags |= ARCHIVE_EXTRACT_PERM; - flags |= ARCHIVE_EXTRACT_ACL; - flags |= ARCHIVE_EXTRACT_FFLAGS; - - std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { - archive_write_close(a); - archive_write_free(a); - }); + auto extPtr = ArchiveWriter::createDiskWriter(); auto ext = extPtr.get(); - archive_write_disk_set_options(ext, flags); - archive_write_disk_set_standard_lookup(ext); return f->writeFile(ext, target); } diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 8cd8e7345..04fe90379 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -36,8 +36,6 @@ #pragma once -#include -#include #include #include #include @@ -46,15 +44,12 @@ #include #include #include -#include #include #include "archive/ArchiveReader.h" #if defined(LAUNCHER_APPLICATION) #include "minecraft/mod/Mod.h" #endif -#include "Filter.h" -#include "tasks/Task.h" namespace MMCZip { using FilterFileFunction = std::function; diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index e48d168ae..b2894b797 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -40,7 +40,6 @@ QByteArray ArchiveReader::File::readAll(int* outStatus) if (outStatus) { *outStatus = status; } - archive_read_close(m_archive.get()); return data; } @@ -131,7 +130,7 @@ bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool n return (r > ARCHIVE_WARN); } -bool ArchiveReader::parse(std::function doStuff) +bool ArchiveReader::parse(std::function doStuff) { auto f = std::make_unique(); auto a = f->m_archive.get(); @@ -143,16 +142,24 @@ bool ArchiveReader::parse(std::function doStuff) return false; } + bool breakControl = false; while (f->readNextHeader() == ARCHIVE_OK) { - if (!doStuff(f.get())) { + if (!doStuff(f.get(), breakControl)) { qCritical() << "Failed to parse file:" << f->filename() << "-" << f->error(); return false; } + if (breakControl) { + break; + } } archive_read_close(a); return true; } +bool ArchiveReader::parse(std::function doStuff) +{ + return parse([doStuff](File* f, bool&) { return doStuff(f); }); +} bool ArchiveReader::File::isFile() { diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index 5cbf75308..383dfc760 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -45,6 +45,7 @@ class ArchiveReader { std::unique_ptr goToFile(QString filename); bool parse(std::function); + bool parse(std::function); private: QString m_archivePath; diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index a994f06aa..cc3b4ab27 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -176,4 +176,21 @@ bool ArchiveWriter::addFile(ArchiveReader::File* f) { return f->writeFile(m_archive, "", true); } +std::unique_ptr ArchiveWriter::createDiskWriter() +{ + int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS; + + std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { + if (a) { + archive_write_close(a); + archive_write_free(a); + } + }); + + archive* ext = extPtr.get(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); + + return extPtr; +} } // namespace MMCZip \ No newline at end of file diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index 2e26e4b5e..3dac29405 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -21,6 +21,8 @@ class ArchiveWriter { bool addFile(const QString& fileDest, const QByteArray& data); bool addFile(ArchiveReader::File* f); + static std::unique_ptr createDiskWriter(); + private: struct archive* m_archive = nullptr; QString m_filename; diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp index 69069d550..15839ca45 100644 --- a/launcher/archive/ExtractZipTask.cpp +++ b/launcher/archive/ExtractZipTask.cpp @@ -2,6 +2,7 @@ #include #include "FileSystem.h" #include "archive/ArchiveReader.h" +#include "archive/ArchiveWriter.h" namespace MMCZip { @@ -28,21 +29,8 @@ auto ExtractZipTask::extractZip() -> ZipResult return ZipResult(); } - int flags; - - /* Select which attributes we want to restore. */ - flags = ARCHIVE_EXTRACT_TIME; - flags |= ARCHIVE_EXTRACT_PERM; - flags |= ARCHIVE_EXTRACT_ACL; - flags |= ARCHIVE_EXTRACT_FFLAGS; - - std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { - archive_write_close(a); - archive_write_free(a); - }); + auto extPtr = ArchiveWriter::createDiskWriter(); auto ext = extPtr.get(); - archive_write_disk_set_options(ext, flags); - archive_write_disk_set_standard_lookup(ext); setStatus("Extracting files..."); setProgress(0, m_input.getFiles().count()); diff --git a/launcher/java/download/ArchiveDownloadTask.cpp b/launcher/java/download/ArchiveDownloadTask.cpp index e632225b0..92357930b 100644 --- a/launcher/java/download/ArchiveDownloadTask.cpp +++ b/launcher/java/download/ArchiveDownloadTask.cpp @@ -16,7 +16,6 @@ * along with this program. If not, see . */ #include "java/download/ArchiveDownloadTask.h" -#include #include #include "Application.h" diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 854bb2ee8..8f7174a07 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -43,9 +43,6 @@ #include #include #include -#include -#include -#include #include #include #include diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp index afe091758..3174e0e2a 100644 --- a/launcher/minecraft/launch/ExtractNatives.cpp +++ b/launcher/minecraft/launch/ExtractNatives.cpp @@ -17,11 +17,10 @@ #include #include -#include -#include #include #include "FileSystem.h" -#include "MMCZip.h" +#include "archive/ArchiveReader.h" +#include "archive/ArchiveWriter.h" #ifdef major #undef major @@ -41,30 +40,21 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack) { - QuaZip zip(source); - if (!zip.open(QuaZip::mdUnzip)) { - return false; - } + MMCZip::ArchiveReader zip(source); QDir directory(targetFolder); - if (!zip.goToFirstFile()) { - return false; - } - do { - QString name = zip.getCurrentFileName(); + + auto extPtr = MMCZip::ArchiveWriter::createDiskWriter(); + auto ext = extPtr.get(); + + return zip.parse([applyJnilibHack, directory, ext](MMCZip::ArchiveReader::File* f) { + QString name = f->filename(); auto lowercase = name.toLower(); if (applyJnilibHack) { name = replaceSuffix(name, ".jnilib", ".dylib"); } QString absFilePath = directory.absoluteFilePath(name); - if (!JlCompress::extractFile(&zip, "", absFilePath)) { - return false; - } - } while (zip.goToNextFile()); - zip.close(); - if (zip.getZipError() != 0) { - return false; - } - return true; + return f->writeFile(ext, absFilePath); + }); } void ExtractNatives::executeTask() diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 73e676dbd..6dbdf9714 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -23,12 +23,9 @@ #include "FileSystem.h" #include "Json.h" +#include "archive/ArchiveReader.h" #include "minecraft/mod/ResourcePack.h" -#include -#include -#include - #include namespace DataPackUtils { @@ -106,67 +103,72 @@ bool processZIP(DataPack* pack, ProcessingLevel level) { Q_ASSERT(pack->type() == ResourceType::ZIPFILE); - QuaZip zip(pack->fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) + MMCZip::ArchiveReader zip(pack->fileinfo().filePath()); + + bool metaParsed = false; + bool directoryFound = false; + bool iconParsed = false; + bool mcmeta_result = false; + bool pack_png_result = false; + if (!zip.parse([&metaParsed, &directoryFound, &iconParsed, &mcmeta_result, &pack_png_result, pack, level]( + MMCZip::ArchiveReader::File* f, bool& breakControl) { + bool skip = true; + if (!metaParsed && f->filename() == "pack.mcmeta") { + metaParsed = true; + skip = false; + auto data = f->readAll(); + + mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data)); + + if (!mcmeta_result) { + breakControl = true; + return true; // mcmeta invalid + } + } + if (!directoryFound) { + QString normalizedPath = QDir::cleanPath(pack->directory()) + QLatin1Char('/'); + if (normalizedPath.startsWith('/')) + normalizedPath.remove(0, 1); + directoryFound = f->filename().startsWith(normalizedPath, Qt::CaseInsensitive); + } + if (!iconParsed && level != ProcessingLevel::BasicInfoOnly && f->filename() == "pack.png") { + iconParsed = true; + skip = false; + auto data = f->readAll(); + + pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + if (!pack_png_result) { + breakControl = true; + return true; // pack.png invalid + } + } + if (skip) { + f->skip(); + } + if (metaParsed && directoryFound && (level == ProcessingLevel::BasicInfoOnly || iconParsed)) { + breakControl = true; + } + + return true; + })) { return false; // can't open zip file - - QuaZipFile file(&zip); - - auto mcmeta_invalid = [&pack]() { + } + if (!mcmeta_result) { qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional - }; - - if (zip.setCurrentFile("pack.mcmeta")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return mcmeta_invalid(); - } - - auto data = file.readAll(); - - bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data)); - - file.close(); - if (!mcmeta_result) { - return mcmeta_invalid(); // mcmeta invalid - } - } else { - return mcmeta_invalid(); // could not set pack.mcmeta as current file. + } + if (!directoryFound) { + return false; // data dir does not exists at zip root } if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; // only need basic info already checked } - auto png_invalid = [&pack]() { + if (!pack_png_result) { qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png"; return true; // the png is optional - }; - - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } - - auto data = file.readAll(); - - bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - zip.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - zip.close(); - return png_invalid(); // could not set pack.mcmeta as current file. } - zip.close(); return true; } @@ -311,28 +313,17 @@ bool processPackPNG(const DataPack* pack) return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 } case ResourceType::ZIPFILE: { - QuaZip zip(pack->fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + MMCZip::ArchiveReader zip(pack->fileinfo().filePath()); + auto f = zip.goToFile("pack.png"); + if (!f) { + return png_invalid(); + } + auto data = f->readAll(); - QuaZipFile file(&zip); - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } + bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - auto data = file.readAll(); - - bool pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - - file.close(); - if (!pack_png_result) { - return png_invalid(); // pack.png invalid - } - } else { - return png_invalid(); // could not set pack.mcmeta as current file. + if (!pack_png_result) { + return png_invalid(); // pack.png invalid } return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740 } diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp index a6ecc5353..3443966d5 100644 --- a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp @@ -22,10 +22,7 @@ #include "LocalShaderPackParseTask.h" #include "FileSystem.h" - -#include -#include -#include +#include "archive/ArchiveReader.h" namespace ShaderPackUtils { @@ -63,25 +60,19 @@ bool processZIP(ShaderPack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::ZIPFILE); - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) + MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); + if (!zip.collectFiles()) return false; // can't open zip file - QuaZipFile file(&zip); - - QuaZipDir zipDir(&zip); - if (!zipDir.exists("/shaders")) { + if (!zip.exists("/shaders")) { return false; // assets dir does not exists at zip root } pack.setPackFormat(ShaderPackFormat::VALID); if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; // only need basic info already checked } - zip.close(); - return true; } diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index 18020808a..921b6ce09 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -20,9 +20,7 @@ #include "LocalTexturePackParseTask.h" #include "FileSystem.h" - -#include -#include +#include "archive/ArchiveReader.h" #include @@ -91,54 +89,35 @@ bool processZIP(TexturePack& pack, ProcessingLevel level) { Q_ASSERT(pack.type() == ResourceType::ZIPFILE); - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; + MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); - QuaZipFile file(&zip); + { + auto file = zip.goToFile("pack.txt"); + if (file) { + auto data = file->readAll(); - if (zip.setCurrentFile("pack.txt")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return false; - } + bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data)); - auto data = file.readAll(); - - bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data)); - - file.close(); - if (!packTXT_result) { - return false; + if (!packTXT_result) { + return false; + } } } if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; } - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return false; - } - - auto data = file.readAll(); + auto file = zip.goToFile("pack.png"); + if (file) { + auto data = file->readAll(); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data)); - file.close(); - zip.close(); if (!packPNG_result) { return false; } } - - zip.close(); - return true; } @@ -189,32 +168,19 @@ bool processPackPNG(const TexturePack& pack) return false; } case ResourceType::ZIPFILE: { - QuaZip zip(pack.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file + MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); - QuaZipFile file(&zip); - if (zip.setCurrentFile("pack.png")) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid(); - } - - auto data = file.readAll(); + auto file = zip.goToFile("pack.png"); + if (file) { + auto data = file->readAll(); bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data)); - file.close(); if (!pack_png_result) { - zip.close(); return png_invalid(); // pack.png invalid } - } else { - zip.close(); - return png_invalid(); // could not set pack.mcmeta as current file. } - return false; + return png_invalid(); // could not set pack.mcmeta as current file. } default: qWarning() << "Invalid type for resource pack parse task!"; diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index d45f537fa..e763bf64b 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -23,13 +23,11 @@ #include "LocalWorldSaveParseTask.h" #include "FileSystem.h" - -#include -#include -#include +#include "archive/ArchiveReader.h" #include #include +#include namespace WorldSaveUtils { @@ -105,22 +103,41 @@ bool processFolder(WorldSave& save, ProcessingLevel level) /// QString , /// bool /// ) -static std::tuple contains_level_dat(QuaZip& zip) +static std::tuple contains_level_dat(QString fileName) { + MMCZip::ArchiveReader zip(fileName); + if (!zip.collectFiles()) { + return std::make_tuple(false, "", false); + } bool saves = false; - QuaZipDir zipDir(&zip); - if (zipDir.exists("/saves")) { + if (zip.exists("/saves")) { saves = true; - zipDir.cd("/saves"); } - for (auto const& entry : zipDir.entryList()) { - zipDir.cd(entry); - if (zipDir.exists("level.dat")) { - return std::make_tuple(true, entry, saves); + for (auto file : zip.getFiles()) { + QString relativePath = file; + if (saves) { + if (!relativePath.startsWith("saves/", Qt::CaseInsensitive)) + continue; + relativePath = relativePath.mid(QString("saves/").length()); + } + if (!relativePath.endsWith("/level.dat", Qt::CaseInsensitive)) + continue; + + int slashIndex = relativePath.indexOf('/'); + if (slashIndex == -1) + continue; // malformed: no slash between saves/ and level.dat + + QString worldName = relativePath.left(slashIndex); + QString remaining = relativePath.mid(slashIndex + 1); + + // Check that there's nothing between worldName/ and level.dat + if (remaining == "level.dat") { + QString worldDir = (saves ? "saves/" : "") + worldName; + return std::make_tuple(true, worldDir, saves); } - zipDir.cd(".."); } + return std::make_tuple(false, "", saves); } @@ -128,19 +145,14 @@ bool processZIP(WorldSave& save, ProcessingLevel level) { Q_ASSERT(save.type() == ResourceType::ZIPFILE); - QuaZip zip(save.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; // can't open zip file - - auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip); - - if (save_dir_name.endsWith("/")) { - save_dir_name.chop(1); - } + auto [found, save_dir_name, found_saves_dir] = contains_level_dat(save.fileinfo().filePath()); if (!found) { return false; } + if (save_dir_name.endsWith("/")) { + save_dir_name.chop(1); + } save.setSaveDirName(save_dir_name); @@ -151,14 +163,11 @@ bool processZIP(WorldSave& save, ProcessingLevel level) } if (level == ProcessingLevel::BasicInfoOnly) { - zip.close(); return true; // only need basic info already checked } // reserved for more intensive processing - zip.close(); - return true; } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index a3f045d02..f107e4700 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -39,8 +39,6 @@ #include #include -#include - #include "FileSystem.h" #include "Json.h" #include "MMCZip.h" diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 1df809f09..6db6cb712 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -1,6 +1,4 @@ #pragma once -#include -#include #include "InstanceTask.h" #include "PackHelpers.h" #include "meta/Index.h" diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.h b/launcher/modplatform/technic/SingleZipPackInstallTask.h index 09fc7b658..9dd54458d 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.h +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.h @@ -19,8 +19,6 @@ #include "archive/ArchiveReader.h" #include "net/NetJob.h" -#include - #include #include #include diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 54e880818..e6bef012a 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -16,6 +16,7 @@ zlib, msaClientID ? null, gamemodeSupport ? stdenv.hostPlatform.isLinux, + libarchive, }: assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux @@ -78,6 +79,7 @@ stdenv.mkDerivation { kdePackages.qtnetworkauth kdePackages.quazip qrencode + libarchive tomlplusplus zlib ] From 30ef1587161cfd8c7bf523e5d494f8d7aa609c54 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Jul 2025 21:54:33 +0300 Subject: [PATCH 17/59] finish moving all code to libarchive Signed-off-by: Trial97 --- .../setup-dependencies/windows/action.yml | 1 + .../minecraft/mod/tasks/LocalModParseTask.cpp | 133 +++++++----------- .../mod/tasks/LocalWorldSaveParseTask.cpp | 3 +- .../technic/TechnicPackProcessor.cpp | 39 ++--- 4 files changed, 67 insertions(+), 109 deletions(-) diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index c6f23a9ca..2e6615aa5 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -82,6 +82,7 @@ runs: qrencode:p tomlplusplus:p quazip-qt6:p + libarchive:p - name: List pacman packages (MinGW) if: ${{ inputs.msystem != '' }} diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 38280f5af..d29c72b26 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -1,8 +1,7 @@ #include "LocalModParseTask.h" #include -#include -#include +#include #include #include #include @@ -13,6 +12,7 @@ #include "FileSystem.h" #include "Json.h" +#include "archive/ArchiveReader.h" #include "minecraft/mod/ModDetails.h" #include "settings/INIFile.h" @@ -470,31 +470,35 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) { ModDetails details; - QuaZip zip(mod.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) + MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); + if (!zip.collectFiles()) return false; - QuaZipFile file(&zip); + auto isForge = zip.exists("META-INF/mods.toml"); + if (isForge || zip.exists("META-INF/neoforge.mods.toml")) { + { + std::unique_ptr file; + if (isForge) { + file = zip.goToFile("META-INF/mods.toml"); + } else { + file = zip.goToFile("META-INF/neoforge.mods.toml"); + } + if (!file) { + return false; + } - if (zip.setCurrentFile("META-INF/mods.toml") || zip.setCurrentFile("META-INF/neoforge.mods.toml")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; + details = ReadMCModTOML(file->readAll()); } - details = ReadMCModTOML(file.readAll()); - file.close(); - // to replace ${file.jarVersion} with the actual version, as needed if (details.version == "${file.jarVersion}") { - if (zip.setCurrentFile("META-INF/MANIFEST.MF")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); + if (zip.exists("META-INF/MANIFEST.MF")) { + auto file = zip.goToFile("META-INF/MANIFEST.MF"); + if (!file) { return false; } - // quick and dirty line-by-line parser - auto manifestLines = QString(file.readAll()).split(s_newlineRegex); + auto manifestLines = QString(file->readAll()).split(s_newlineRegex); QString manifestVersion = ""; for (auto& line : manifestLines) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { @@ -510,69 +514,58 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) } details.version = manifestVersion; - - file.close(); } } - zip.close(); mod.setDetails(details); return true; - } else if (zip.setCurrentFile("mcmod.info")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); + } else if (zip.exists("mcmod.info")) { + auto file = zip.goToFile("mcmod.info"); + if (!file) { return false; } - details = ReadMCModInfo(file.readAll()); - file.close(); - zip.close(); + details = ReadMCModInfo(file->readAll()); mod.setDetails(details); return true; - } else if (zip.setCurrentFile("quilt.mod.json")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); + } else if (zip.exists("quilt.mod.json")) { + auto file = zip.goToFile("quilt.mod.json"); + if (!file) { return false; } - details = ReadQuiltModInfo(file.readAll()); - file.close(); - zip.close(); + details = ReadQuiltModInfo(file->readAll()); mod.setDetails(details); return true; - } else if (zip.setCurrentFile("fabric.mod.json")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); + } else if (zip.exists("fabric.mod.json")) { + auto file = zip.goToFile("fabric.mod.json"); + if (!file) { return false; } - details = ReadFabricModInfo(file.readAll()); - file.close(); - zip.close(); + details = ReadFabricModInfo(file->readAll()); mod.setDetails(details); return true; - } else if (zip.setCurrentFile("forgeversion.properties")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); + } else if (zip.exists("forgeversion.properties")) { + auto file = zip.goToFile("forgeversion.properties"); + if (!file) { return false; } - details = ReadForgeInfo(file.readAll()); - file.close(); - zip.close(); + details = ReadForgeInfo(file->readAll()); mod.setDetails(details); return true; - } else if (zip.setCurrentFile("META-INF/nil/mappings.json")) { + } else if (zip.exists("META-INF/nil/mappings.json")) { // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time QString foundNilMeta; - for (auto& fname : zip.getFileNameList()) { + for (auto& fname : zip.getFiles()) { // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") { foundNilMeta = fname; @@ -580,22 +573,18 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) } } - if (zip.setCurrentFile(foundNilMeta)) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); + if (zip.exists(foundNilMeta)) { + auto file = zip.goToFile(foundNilMeta); + if (!file) { return false; } - - details = ReadNilModInfo(file.readAll(), foundNilMeta); - file.close(); - zip.close(); + details = ReadNilModInfo(file->readAll(), foundNilMeta); mod.setDetails(details); return true; } } - zip.close(); return false; // no valid mod found in archive } @@ -624,25 +613,14 @@ bool processLitemod(Mod& mod, [[maybe_unused]] ProcessingLevel level) { ModDetails details; - QuaZip zip(mod.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return false; + MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); - QuaZipFile file(&zip); - - if (zip.setCurrentFile("litemod.json")) { - if (!file.open(QIODevice::ReadOnly)) { - zip.close(); - return false; - } - - details = ReadLiteModInfo(file.readAll()); - file.close(); + if (auto file = zip.goToFile("litemod.json"); file) { + details = ReadLiteModInfo(file->readAll()); mod.setDetails(details); return true; } - zip.close(); return false; // no valid litemod.json found in archive } @@ -700,24 +678,13 @@ bool loadIconFile(const Mod& mod, QPixmap* pixmap) return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file"); } case ResourceType::ZIPFILE: { - QuaZip zip(mod.fileinfo().filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive"); - - QuaZipFile file(&zip); - - if (zip.setCurrentFile(mod.iconPath())) { - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file in zip."; - zip.close(); - return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive"); - } - - auto data = file.readAll(); + MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); + auto file = zip.goToFile(mod.iconPath()); + if (file) { + auto data = file->readAll(); bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap); - file.close(); if (!icon_result) { return png_invalid("invalid png image"); // icon png invalid } diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index e763bf64b..50f7bbfa1 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -133,8 +133,7 @@ static std::tuple contains_level_dat(QString fileName) // Check that there's nothing between worldName/ and level.dat if (remaining == "level.dat") { - QString worldDir = (saves ? "saves/" : "") + worldName; - return std::make_tuple(true, worldDir, saves); + return std::make_tuple(true, worldName, saves); } } diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index b762e8882..4c40ddf73 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -19,12 +19,10 @@ #include #include #include -#include -#include -#include #include #include +#include "archive/ArchiveReader.h" void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString& instName, @@ -53,35 +51,30 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, QString versionJson = FS::PathCombine(minecraftPath, "bin", "version.json"); QString fmlMinecraftVersion; if (QFile::exists(modpackJar)) { - QuaZip zipFile(modpackJar); - if (!zipFile.open(QuaZip::mdUnzip)) { + MMCZip::ArchiveReader zipFile(modpackJar); + if (!zipFile.collectFiles()) { emit failed(tr("Unable to open \"bin/modpack.jar\" file!")); return; } - QuaZipDir zipFileRoot(&zipFile, "/"); - if (zipFileRoot.exists("/version.json")) { - if (zipFileRoot.exists("/fmlversion.properties")) { - zipFile.setCurrentFile("fmlversion.properties"); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) { + if (zipFile.exists("/version.json")) { + if (zipFile.exists("/fmlversion.properties")) { + auto file = zipFile.goToFile("fmlversion.properties"); + if (!file) { emit failed(tr("Unable to open \"fmlversion.properties\"!")); return; } - QByteArray fmlVersionData = file.readAll(); - file.close(); + QByteArray fmlVersionData = file->readAll(); INIFile iniFile; iniFile.loadFile(fmlVersionData); // If not present, this evaluates to a null string fmlMinecraftVersion = iniFile["fmlbuild.mcversion"].toString(); } - zipFile.setCurrentFile("version.json", QuaZip::csSensitive); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) { + auto file = zipFile.goToFile("version.json"); + if (!file) { emit failed(tr("Unable to open \"version.json\"!")); return; } - data = file.readAll(); - file.close(); + data = file->readAll(); } else { if (minecraftVersion.isEmpty()) { emit failed(tr("Could not find \"version.json\" inside \"bin/modpack.jar\", but Minecraft version is unknown")); @@ -93,16 +86,14 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, // Forge for 1.4.7 and for 1.5.2 require extra libraries. // Figure out the forge version and add it as a component // (the code still comes from the jar mod installed above) - if (zipFileRoot.exists("/forgeversion.properties")) { - zipFile.setCurrentFile("forgeversion.properties", QuaZip::csSensitive); - QuaZipFile file(&zipFile); - if (!file.open(QIODevice::ReadOnly)) { + if (zipFile.exists("/forgeversion.properties")) { + auto file = zipFile.goToFile("forgeversion.properties"); + if (!file) { // Really shouldn't happen, but error handling shall not be forgotten emit failed(tr("Unable to open \"forgeversion.properties\"")); return; } - QByteArray forgeVersionData = file.readAll(); - file.close(); + auto forgeVersionData = file->readAll(); INIFile iniFile; iniFile.loadFile(forgeVersionData); QString major, minor, revision, build; From 84efa6814da084c7ead5fe1fd9d4acd04199fb92 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Jul 2025 22:01:40 +0300 Subject: [PATCH 18/59] remove quazip Signed-off-by: Trial97 --- .../setup-dependencies/windows/action.yml | 1 - .gitmodules | 3 --- .markdownlintignore | 1 - CMakeLists.txt | 16 ------------- COPYING.md | 24 ------------------- launcher/CMakeLists.txt | 2 -- libraries/quazip | 1 - nix/unwrapped.nix | 1 - 8 files changed, 49 deletions(-) delete mode 160000 libraries/quazip diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index 2e6615aa5..f2985fd1b 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -81,7 +81,6 @@ runs: cmark:p qrencode:p tomlplusplus:p - quazip-qt6:p libarchive:p - name: List pacman packages (MinGW) diff --git a/.gitmodules b/.gitmodules index 7ad40becb..246d9d791 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "libraries/quazip"] - path = libraries/quazip - url = https://github.com/stachenov/quazip.git [submodule "libraries/tomlplusplus"] path = libraries/tomlplusplus url = https://github.com/marzer/tomlplusplus.git diff --git a/.markdownlintignore b/.markdownlintignore index a8669d01d..96f627ad9 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -1,2 +1 @@ libraries/nbtplusplus -libraries/quazip diff --git a/CMakeLists.txt b/CMakeLists.txt index 86fa72ff6..705bb695b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,14 +327,6 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS DBus) list(APPEND Launcher_QT_DBUS Qt6::DBus) list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) - - if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(QuaZip-Qt6 1.3 QUIET) - endif() - if (NOT QuaZip-Qt6_FOUND) - set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) - set(FORCE_BUNDLED_QUAZIP 1) - endif() else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() @@ -514,14 +506,6 @@ if(FORCE_BUNDLED_ZLIB) else() message(STATUS "Using system zlib") endif() -if (FORCE_BUNDLED_QUAZIP) - message(STATUS "Using bundled QuaZip") - set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. - set(QUAZIP_INSTALL 0) - add_subdirectory(libraries/quazip) # zip manipulation library -else() - message(STATUS "Using system QuaZip") -endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions if(NOT tomlplusplus_FOUND) diff --git a/COPYING.md b/COPYING.md index 8588c8951..fb33844f7 100644 --- a/COPYING.md +++ b/COPYING.md @@ -212,30 +212,6 @@ This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL -## Quazip - - Copyright (C) 2005-2021 Sergey A. Tachenov - - The QuaZip library is licensed under the GNU Lesser General Public - License V2.1 plus a static linking exception. - - The original ZIP/UNZIP package (MiniZip) is copyrighted by Gilles - Vollant and contributors, see quazip/(un)zip.h files for details. - Basically it's the zlib license. - - STATIC LINKING EXCEPTION - - The copyright holders give you permission to link this library with - independent modules to produce an executable, regardless of the license - terms of these independent modules, and to copy and distribute the - resulting executable under terms of your choice, provided that you also - meet, for each linked independent module, the terms and conditions of - the license of that module. An independent module is a module which is - not derived from or based on this library. If you modify this library, - you must extend this exception to your version of the library. - - See COPYING file for the full LGPL text. - ## launcher (`libraries/launcher`) PolyMC - Minecraft Launcher diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 341a944dc..c144157ab 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1347,7 +1347,6 @@ target_link_libraries(Launcher_logic ${Launcher_QT_LIBS} ) target_link_libraries(Launcher_logic - QuaZip::QuaZip cmark::cmark LocalPeer Launcher_rainbow @@ -1414,7 +1413,6 @@ if(Launcher_BUILD_UPDATER) add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(prism_updater_logic - QuaZip::QuaZip ${ZLIB_LIBRARIES} systeminfo BuildConfig diff --git a/libraries/quazip b/libraries/quazip deleted file mode 160000 index 3fd3b299b..000000000 --- a/libraries/quazip +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3fd3b299b875fbd2beac4894b8a870d80022cad7 diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index e6bef012a..2aaba42a1 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -77,7 +77,6 @@ stdenv.mkDerivation { cmark kdePackages.qtbase kdePackages.qtnetworkauth - kdePackages.quazip qrencode libarchive tomlplusplus From 2a99bb534fd151e54358f2cadf03ed7405401921 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Jul 2025 22:40:29 +0300 Subject: [PATCH 19/59] add missing copyrights Signed-off-by: Trial97 --- launcher/MMCZip.cpp | 1 - launcher/archive/ArchiveReader.cpp | 34 ++++++++++++++++++ launcher/archive/ArchiveReader.h | 34 ++++++++++++++++++ launcher/archive/ArchiveWriter.cpp | 35 ++++++++++++++++++- launcher/archive/ArchiveWriter.h | 34 ++++++++++++++++++ launcher/archive/ExportToZipTask.cpp | 35 ++++++++++++++++++- launcher/archive/ExportToZipTask.h | 34 ++++++++++++++++++ launcher/archive/ExtractZipTask.cpp | 34 ++++++++++++++++++ launcher/archive/ExtractZipTask.h | 34 ++++++++++++++++++ .../minecraft/mod/tasks/LocalModParseTask.cpp | 1 - 10 files changed, 272 insertions(+), 4 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 349c6a809..1c7818301 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -36,7 +36,6 @@ #include "MMCZip.h" #include -#include #include "FileSystem.h" #include "archive/ArchiveReader.h" diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index b2894b797..c5a06c8a6 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "ArchiveReader.h" #include #include diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index 383dfc760..e05b16025 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #pragma once #include diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index cc3b4ab27..bbd16aba8 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -1,4 +1,37 @@ - +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "ArchiveWriter.h" #include diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index 3dac29405..14e1764ea 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #pragma once #include diff --git a/launcher/archive/ExportToZipTask.cpp b/launcher/archive/ExportToZipTask.cpp index b85bfd896..684de90c8 100644 --- a/launcher/archive/ExportToZipTask.cpp +++ b/launcher/archive/ExportToZipTask.cpp @@ -1,4 +1,37 @@ - +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "ExportToZipTask.h" #include diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h index 674053791..add6c2b15 100644 --- a/launcher/archive/ExportToZipTask.h +++ b/launcher/archive/ExportToZipTask.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #pragma once #include diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp index 15839ca45..1f3604b87 100644 --- a/launcher/archive/ExtractZipTask.cpp +++ b/launcher/archive/ExtractZipTask.cpp @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "ExtractZipTask.h" #include #include "FileSystem.h" diff --git a/launcher/archive/ExtractZipTask.h b/launcher/archive/ExtractZipTask.h index 4d9a4ccc4..5fe10b853 100644 --- a/launcher/archive/ExtractZipTask.h +++ b/launcher/archive/ExtractZipTask.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2025 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #pragma once #include diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index d29c72b26..9242685cd 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -1,7 +1,6 @@ #include "LocalModParseTask.h" #include -#include #include #include #include From d036bba34132be3f9f590b4afefcbb718fc42aa0 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 11 Jul 2025 22:51:12 +0300 Subject: [PATCH 20/59] add libarchive as actions dependency Signed-off-by: Trial97 --- .github/actions/setup-dependencies/linux/action.yml | 2 +- .github/actions/setup-dependencies/macos/action.yml | 2 +- launcher/CMakeLists.txt | 5 +++++ launcher/MMCZip.cpp | 6 +----- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index 21ba30dc7..610126442 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -9,7 +9,7 @@ runs: run: | sudo apt-get -y update sudo apt-get -y install \ - dpkg-dev \ + dpkg-dev libarchive-dev \ ninja-build extra-cmake-modules scdoc \ libqrencode-dev \ appstream libxcb-cursor-dev diff --git a/.github/actions/setup-dependencies/macos/action.yml b/.github/actions/setup-dependencies/macos/action.yml index 0e6d48621..6abda19c6 100644 --- a/.github/actions/setup-dependencies/macos/action.yml +++ b/.github/actions/setup-dependencies/macos/action.yml @@ -14,7 +14,7 @@ runs: shell: bash run: | brew update - brew install ninja extra-cmake-modules temurin@17 + brew install ninja extra-cmake-modules temurin@17 libarchive - name: Set JAVA_HOME shell: bash diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c144157ab..c8739c1cf 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -621,6 +621,10 @@ set(PRISMUPDATER_SOURCES # Zip MMCZip.h MMCZip.cpp + archive/ArchiveReader.cpp + archive/ArchiveReader.h + archive/ArchiveWriter.cpp + archive/ArchiveWriter.h # Time MMCTime.h @@ -1421,6 +1425,7 @@ if(Launcher_BUILD_UPDATER) Qt${QT_VERSION_MAJOR}::Network ${Launcher_QT_LIBS} cmark::cmark + PkgConfig::libarchive ) add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 1c7818301..8e4e433ed 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -38,6 +38,7 @@ #include #include "FileSystem.h" #include "archive/ArchiveReader.h" +#include "archive/ArchiveWriter.h" #include #include @@ -45,11 +46,6 @@ #include #include -#if defined(LAUNCHER_APPLICATION) -#include -#include "archive/ArchiveWriter.h" -#endif - namespace MMCZip { // ours using FilterFunction = std::function; From 8cace06df9e51f3d98c7c453fcf816678cf265f3 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Mon, 14 Jul 2025 14:44:28 +0300 Subject: [PATCH 21/59] move libarchive outside bundle libs Signed-off-by: Trial97 --- CMakeLists.txt | 26 +++++++++---------- launcher/minecraft/mod/Mod.h | 2 -- launcher/minecraft/mod/ModDetails.h | 4 --- .../modplatform/flame/FlamePackExportTask.cpp | 1 - 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 705bb695b..8dfe02f89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -349,29 +349,29 @@ else() pkg_check_modules(libqrencode REQUIRED IMPORTED_TARGET libqrencode) endif() -if(NOT Launcher_FORCE_BUNDLED_LIBS) +# Find libarchive through CMake packages, mainly for vcpkg +find_package(LibArchive QUIET) +# CMake packages aren't available in most distributions of libarchive, so fallback to pkg-config +if(NOT LibArchive_FOUND) find_package(PkgConfig REQUIRED) - pkg_check_modules(libarchive IMPORTED_TARGET libarchive) + pkg_check_modules(libarchive REQUIRED IMPORTED_TARGET libarchive) +endif() + + +if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) # Fallback to pkg-config (if available) if CMake files aren't found if(NOT tomlplusplus_FOUND) - pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) + endif() endif() # Find cmark find_package(cmark QUIET) - - find_package(LibArchive 3.7.8 QUIET) - # Fallback to pkg-config (if available) if CMake files aren't found - if(NOT LibArchive_FOUND) - pkg_check_modules(LibArchive IMPORTED_TARGET LibArchive>=3.7.8) - endif() - - # Find qrcodegencpp-cmake - find_package(qrcodegencpp QUIET) - endif() include(ECMQtDeclareLoggingCategory) diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index eceb8c256..c548f5350 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -44,8 +44,6 @@ #include #include -#include - #include "ModDetails.h" #include "Resource.h" diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 9195c0368..9b81f561f 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -35,14 +35,10 @@ #pragma once -#include - #include #include #include -#include "minecraft/mod/MetadataHandler.h" - struct ModLicense { QString name = {}; QString id = {}; diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index b3ad4b606..1166dc99c 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -30,7 +30,6 @@ #include #include "Application.h" #include "Json.h" -#include "MMCZip.h" #include "archive/ExportToZipTask.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" From 27259ff52d63b087db0ec2590c95f961a28820d9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Jul 2025 18:24:54 +0300 Subject: [PATCH 22/59] Improve mod parsing Signed-off-by: Trial97 --- launcher/minecraft/World.cpp | 38 ++-- .../minecraft/mod/tasks/LocalModParseTask.cpp | 166 ++++++++---------- .../mod/tasks/LocalTexturePackParseTask.cpp | 40 ++--- 3 files changed, 103 insertions(+), 141 deletions(-) diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index 8f7174a07..bdbe721e3 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -244,35 +244,23 @@ void World::readFromZip(const QFileInfo& file) { MMCZip::ArchiveReader r(file.absoluteFilePath()); - if (m_isValid = r.collectFiles(); !m_isValid) { - return; - } - - QString path; - const QString levelDat = "level.dat"; - for (auto filePath : r.getFiles()) { + m_isValid = false; + r.parse([this](MMCZip::ArchiveReader::File* file, bool& stop) { + const QString levelDat = "level.dat"; + auto filePath = file->filename(); QFileInfo fi(filePath); if (fi.fileName().compare(levelDat, Qt::CaseInsensitive) == 0) { m_containerOffsetPath = filePath.chopped(levelDat.length()); - path = filePath; - break; + if (!m_containerOffsetPath.isEmpty()) { + return false; + } + m_levelDatTime = file->dateTime(); + loadFromLevelDat(file->readAll()); + m_isValid = true; + stop = true; } - } - - if (m_isValid = !m_containerOffsetPath.isEmpty(); !m_isValid) { - return; - } - - auto zippedFile = r.goToFile(path); - if (m_isValid = !!zippedFile; !m_isValid) { - return; - } - // read the install profile - m_levelDatTime = zippedFile->dateTime(); - if (!m_isValid) { - return; - } - loadFromLevelDat(zippedFile->readAll()); + return true; + }); } bool World::install(const QString& to, const QString& name) diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 9242685cd..df22b97b2 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -1,6 +1,7 @@ #include "LocalModParseTask.h" #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "Json.h" #include "archive/ArchiveReader.h" #include "minecraft/mod/ModDetails.h" +#include "modplatform/ModIndex.h" #include "settings/INIFile.h" static const QRegularExpression s_newlineRegex("\r\n|\n|\r"); @@ -470,35 +472,32 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) ModDetails details; MMCZip::ArchiveReader zip(mod.fileinfo().filePath()); - if (!zip.collectFiles()) - return false; - auto isForge = zip.exists("META-INF/mods.toml"); - if (isForge || zip.exists("META-INF/neoforge.mods.toml")) { - { - std::unique_ptr file; - if (isForge) { - file = zip.goToFile("META-INF/mods.toml"); - } else { - file = zip.goToFile("META-INF/neoforge.mods.toml"); - } - if (!file) { - return false; - } + bool baseForgePopulated = false; + bool isNilMod = false; + bool isValid = false; + QString manifestVersion = {}; + QByteArray nilData = {}; + QString nilFilePath = {}; - details = ReadMCModTOML(file->readAll()); - } + if (!zip.parse([&details, &baseForgePopulated, &manifestVersion, &isValid, &nilData, &isNilMod, &nilFilePath]( + MMCZip::ArchiveReader::File* file, bool& stop) { + auto filePath = file->filename(); - // to replace ${file.jarVersion} with the actual version, as needed - if (details.version == "${file.jarVersion}") { - if (zip.exists("META-INF/MANIFEST.MF")) { - auto file = zip.goToFile("META-INF/MANIFEST.MF"); - if (!file) { - return false; + if (filePath == "META-INF/mods.toml" || filePath == "META-INF/neoforge.mods.toml") { + details = ReadMCModTOML(file->readAll()); + isValid = true; + if (details.version == "${file.jarVersion}" && !manifestVersion.isEmpty()) { + details.version = manifestVersion; } + stop = details.version != "${file.jarVersion}"; + baseForgePopulated = true; + return true; + } + if (filePath == "META-INF/MANIFEST.MF") { // quick and dirty line-by-line parser auto manifestLines = QString(file->readAll()).split(s_newlineRegex); - QString manifestVersion = ""; + manifestVersion = ""; for (auto& line : manifestLines) { if (line.startsWith("Implementation-Version: ", Qt::CaseInsensitive)) { manifestVersion = line.remove("Implementation-Version: ", Qt::CaseInsensitive); @@ -511,79 +510,64 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level) if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") { manifestVersion = "NONE"; } - - details.version = manifestVersion; + if (baseForgePopulated) { + details.version = manifestVersion; + stop = true; + } + return true; + } + if (filePath == "mcmod.info") { + details = ReadMCModInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "quilt.mod.json") { + details = ReadQuiltModInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "fabric.mod.json") { + details = ReadFabricModInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "forgeversion.properties") { + details = ReadForgeInfo(file->readAll()); + isValid = true; + stop = true; + return true; + } + if (filePath == "META-INF/nil/mappings.json") { + // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename + // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time + isNilMod = true; + stop = !nilFilePath.isEmpty(); + file->skip(); + return true; } - } - - mod.setDetails(details); - - return true; - } else if (zip.exists("mcmod.info")) { - auto file = zip.goToFile("mcmod.info"); - if (!file) { - return false; - } - - details = ReadMCModInfo(file->readAll()); - - mod.setDetails(details); - return true; - } else if (zip.exists("quilt.mod.json")) { - auto file = zip.goToFile("quilt.mod.json"); - if (!file) { - return false; - } - - details = ReadQuiltModInfo(file->readAll()); - - mod.setDetails(details); - return true; - } else if (zip.exists("fabric.mod.json")) { - auto file = zip.goToFile("fabric.mod.json"); - if (!file) { - return false; - } - - details = ReadFabricModInfo(file->readAll()); - - mod.setDetails(details); - return true; - } else if (zip.exists("forgeversion.properties")) { - auto file = zip.goToFile("forgeversion.properties"); - if (!file) { - return false; - } - - details = ReadForgeInfo(file->readAll()); - - mod.setDetails(details); - return true; - } else if (zip.exists("META-INF/nil/mappings.json")) { - // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename - // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time - - QString foundNilMeta; - for (auto& fname : zip.getFiles()) { // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file - if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") { - foundNilMeta = fname; - break; + if (filePath.endsWith(".nilmod.css") && filePath != "nilloader.nilmod.css") { + nilData = file->readAll(); + nilFilePath = filePath; + stop = isNilMod; + return true; } - } - - if (zip.exists(foundNilMeta)) { - auto file = zip.goToFile(foundNilMeta); - if (!file) { - return false; - } - details = ReadNilModInfo(file->readAll(), foundNilMeta); - - mod.setDetails(details); + file->skip(); return true; - } + })) { + return false; + } + if (isNilMod) { + details = ReadNilModInfo(nilData, nilFilePath); + isValid = true; + } + if (isValid) { + mod.setDetails(details); + return true; } - return false; // no valid mod found in archive } diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index 921b6ce09..106d7c323 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -90,35 +90,25 @@ bool processZIP(TexturePack& pack, ProcessingLevel level) Q_ASSERT(pack.type() == ResourceType::ZIPFILE); MMCZip::ArchiveReader zip(pack.fileinfo().filePath()); + bool packProcessed = false; + bool iconProcessed = false; - { - auto file = zip.goToFile("pack.txt"); - if (file) { + return zip.parse([&packProcessed, &iconProcessed, &pack, level](MMCZip::ArchiveReader::File* file, bool& stop) { + if (!packProcessed && file->filename() == "pack.txt") { + packProcessed = true; auto data = file->readAll(); - - bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data)); - - if (!packTXT_result) { - return false; - } + stop = packProcessed && (iconProcessed || level == ProcessingLevel::BasicInfoOnly); + return TexturePackUtils::processPackTXT(pack, std::move(data)); } - } - - if (level == ProcessingLevel::BasicInfoOnly) { + if (!iconProcessed && file->filename() == "pack.png") { + iconProcessed = true; + auto data = file->readAll(); + stop = packProcessed && iconProcessed; + return TexturePackUtils::processPackPNG(pack, std::move(data)); + } + file->skip(); return true; - } - - auto file = zip.goToFile("pack.png"); - if (file) { - auto data = file->readAll(); - - bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data)); - - if (!packPNG_result) { - return false; - } - } - return true; + }); } bool processPackTXT(TexturePack& pack, QByteArray&& raw_data) From aa265a45eecadcc7402fac6abfd37205c50be480 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Jul 2025 20:23:01 +0300 Subject: [PATCH 23/59] fix nix build Signed-off-by: Trial97 --- launcher/archive/ArchiveReader.cpp | 2 ++ launcher/archive/ArchiveReader.h | 9 ++++----- launcher/archive/ArchiveWriter.cpp | 1 + launcher/archive/ArchiveWriter.h | 4 +--- launcher/archive/ExportToZipTask.h | 2 +- launcher/minecraft/mod/tasks/LocalModParseTask.cpp | 2 -- launcher/modplatform/flame/FlamePackExportTask.cpp | 3 ++- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index c5a06c8a6..e47a4a720 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -33,6 +33,7 @@ * limitations under the License. */ #include "ArchiveReader.h" +#include #include #include #include @@ -239,4 +240,5 @@ bool ArchiveReader::exists(const QString& filePath) const return false; } +ArchiveReader::File::File() : m_archive(ArchivePtr(archive_read_new(), archive_read_free)) {} } // namespace MMCZip diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index e05b16025..36da1fea0 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -34,14 +34,13 @@ */ #pragma once -#include -#include - #include #include #include #include +struct archive; +struct archive_entry; namespace MMCZip { class ArchiveReader { public: @@ -56,8 +55,8 @@ class ArchiveReader { class File { public: - File() : m_archive(ArchivePtr(archive_read_new(), archive_read_free)) {} - virtual ~File() {} + File(); + virtual ~File() = default; QString filename(); bool isFile(); diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index bbd16aba8..ccd18bad6 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -34,6 +34,7 @@ */ #include "ArchiveWriter.h" #include +#include #include #include diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index 14e1764ea..2e2b31153 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -34,13 +34,11 @@ */ #pragma once -#include -#include - #include #include #include "archive/ArchiveReader.h" +struct archive; namespace MMCZip { class ArchiveWriter { diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h index add6c2b15..315c78f93 100644 --- a/launcher/archive/ExportToZipTask.h +++ b/launcher/archive/ExportToZipTask.h @@ -39,7 +39,7 @@ #include #include -#include "ArchiveWriter.h" +#include "archive/ArchiveWriter.h" #include "tasks/Task.h" namespace MMCZip { diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index df22b97b2..59d3876b3 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -1,7 +1,6 @@ #include "LocalModParseTask.h" #include -#include #include #include #include @@ -14,7 +13,6 @@ #include "Json.h" #include "archive/ArchiveReader.h" #include "minecraft/mod/ModDetails.h" -#include "modplatform/ModIndex.h" #include "settings/INIFile.h" static const QRegularExpression s_newlineRegex("\r\n|\n|\r"); diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index 1166dc99c..d63eb709f 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -30,7 +30,6 @@ #include #include "Application.h" #include "Json.h" -#include "archive/ExportToZipTask.h" #include "minecraft/PackProfile.h" #include "minecraft/mod/ModFolderModel.h" #include "modplatform/ModIndex.h" @@ -38,6 +37,8 @@ #include "modplatform/helpers/HashUtils.h" #include "tasks/Task.h" +#include "archive/ExportToZipTask.h" + const QString FlamePackExportTask::TEMPLATE = "
  • {name}{authors}
  • \n"; const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" }); From 7962e223abe9f90b861604d2690d4cf6d64de49f Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 24 Sep 2025 16:25:33 +0300 Subject: [PATCH 24/59] fix merge conflicts Signed-off-by: Trial97 --- .../mod/tasks/LocalDataPackParseTask.cpp | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 6dbdf9714..9b4bb4a50 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -106,60 +106,50 @@ bool processZIP(DataPack* pack, ProcessingLevel level) MMCZip::ArchiveReader zip(pack->fileinfo().filePath()); bool metaParsed = false; - bool directoryFound = false; bool iconParsed = false; bool mcmeta_result = false; bool pack_png_result = false; - if (!zip.parse([&metaParsed, &directoryFound, &iconParsed, &mcmeta_result, &pack_png_result, pack, level]( - MMCZip::ArchiveReader::File* f, bool& breakControl) { - bool skip = true; - if (!metaParsed && f->filename() == "pack.mcmeta") { - metaParsed = true; - skip = false; - auto data = f->readAll(); + if (!zip.parse( + [&metaParsed, &iconParsed, &mcmeta_result, &pack_png_result, pack, level](MMCZip::ArchiveReader::File* f, bool& breakControl) { + bool skip = true; + if (!metaParsed && f->filename() == "pack.mcmeta") { + metaParsed = true; + skip = false; + auto data = f->readAll(); - mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data)); + mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data)); - if (!mcmeta_result) { - breakControl = true; - return true; // mcmeta invalid + if (!mcmeta_result) { + breakControl = true; + return true; // mcmeta invalid + } } - } - if (!directoryFound) { - QString normalizedPath = QDir::cleanPath(pack->directory()) + QLatin1Char('/'); - if (normalizedPath.startsWith('/')) - normalizedPath.remove(0, 1); - directoryFound = f->filename().startsWith(normalizedPath, Qt::CaseInsensitive); - } - if (!iconParsed && level != ProcessingLevel::BasicInfoOnly && f->filename() == "pack.png") { - iconParsed = true; - skip = false; - auto data = f->readAll(); + if (!iconParsed && level != ProcessingLevel::BasicInfoOnly && f->filename() == "pack.png") { + iconParsed = true; + skip = false; + auto data = f->readAll(); - pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); - if (!pack_png_result) { - breakControl = true; - return true; // pack.png invalid + pack_png_result = DataPackUtils::processPackPNG(pack, std::move(data)); + if (!pack_png_result) { + breakControl = true; + return true; // pack.png invalid + } + } + if (skip) { + f->skip(); + } + if (metaParsed && (level == ProcessingLevel::BasicInfoOnly || iconParsed)) { + breakControl = true; } - } - if (skip) { - f->skip(); - } - if (metaParsed && directoryFound && (level == ProcessingLevel::BasicInfoOnly || iconParsed)) { - breakControl = true; - } - return true; - })) { + return true; + })) { return false; // can't open zip file } if (!mcmeta_result) { qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta"; return false; // the mcmeta is not optional } - if (!directoryFound) { - return false; // data dir does not exists at zip root - } if (level == ProcessingLevel::BasicInfoOnly) { return true; // only need basic info already checked From c456e35e41ae65b9db9b0b1b8cd80de2501333ec Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 18 Jul 2025 20:58:15 +0300 Subject: [PATCH 25/59] build(cmake): better detect libarchive Co-authored-by: Octol1ttle Co-authored-by: Seth Flynn Signed-off-by: Alexandru Ionut Tripon Signed-off-by: Octol1ttle Signed-off-by: Seth Flynn Signed-off-by: Trial97 --- .github/actions/setup-dependencies/macos/action.yml | 2 +- CMakeLists.txt | 1 - launcher/CMakeLists.txt | 11 ++++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-dependencies/macos/action.yml b/.github/actions/setup-dependencies/macos/action.yml index 6abda19c6..0e6d48621 100644 --- a/.github/actions/setup-dependencies/macos/action.yml +++ b/.github/actions/setup-dependencies/macos/action.yml @@ -14,7 +14,7 @@ runs: shell: bash run: | brew update - brew install ninja extra-cmake-modules temurin@17 libarchive + brew install ninja extra-cmake-modules temurin@17 - name: Set JAVA_HOME shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dfe02f89..27d5108ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,7 +357,6 @@ if(NOT LibArchive_FOUND) pkg_check_modules(libarchive REQUIRED IMPORTED_TARGET libarchive) endif() - if(NOT Launcher_FORCE_BUNDLED_LIBS) # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c8739c1cf..6caadfce7 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1331,6 +1331,11 @@ if(TARGET PkgConfig::tomlplusplus) else() target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus) endif() +if(TARGET PkgConfig::libarchive) + target_link_libraries(Launcher_logic PkgConfig::libarchive) +else() + target_link_libraries(Launcher_logic LibArchive::LibArchive) +endif() if (UNIX AND NOT CYGWIN AND NOT APPLE) target_link_libraries(Launcher_logic @@ -1425,8 +1430,12 @@ if(Launcher_BUILD_UPDATER) Qt${QT_VERSION_MAJOR}::Network ${Launcher_QT_LIBS} cmark::cmark - PkgConfig::libarchive ) + if(TARGET PkgConfig::libarchive) + target_link_libraries(prism_updater_logic PkgConfig::libarchive) + else() + target_link_libraries(prism_updater_logic LibArchive::LibArchive) + endif() add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp) target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest) From bc90327d4441a75b21ca2740303b81662a6d561e Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 24 Sep 2025 17:27:59 -0400 Subject: [PATCH 26/59] build(vcpkg): remove bzip2 dependency This was previously required by Quazip Signed-off-by: Seth Flynn Signed-off-by: Trial97 --- vcpkg.json | 1 - 1 file changed, 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index d7ed27197..30c170935 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,5 @@ { "dependencies": [ - "bzip2", "cmark", { "name": "ecm", From 085183d530dbadf64399469b56efcd09376c6f62 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 25 Sep 2025 09:34:49 +0300 Subject: [PATCH 27/59] fix license headers Signed-off-by: Trial97 --- launcher/archive/ArchiveReader.cpp | 17 ----------------- launcher/archive/ArchiveReader.h | 17 ----------------- launcher/archive/ArchiveWriter.cpp | 17 ----------------- launcher/archive/ArchiveWriter.h | 17 ----------------- launcher/archive/ExportToZipTask.cpp | 17 ----------------- launcher/archive/ExportToZipTask.h | 17 ----------------- launcher/archive/ExtractZipTask.cpp | 17 ----------------- launcher/archive/ExtractZipTask.h | 17 ----------------- 8 files changed, 136 deletions(-) diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index e47a4a720..c6602d50f 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #include "ArchiveReader.h" #include diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index 36da1fea0..56455de63 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #pragma once diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index ccd18bad6..c0028d365 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #include "ArchiveWriter.h" #include diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index 2e2b31153..c351d4bb8 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #pragma once diff --git a/launcher/archive/ExportToZipTask.cpp b/launcher/archive/ExportToZipTask.cpp index 684de90c8..4886dc963 100644 --- a/launcher/archive/ExportToZipTask.cpp +++ b/launcher/archive/ExportToZipTask.cpp @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #include "ExportToZipTask.h" diff --git a/launcher/archive/ExportToZipTask.h b/launcher/archive/ExportToZipTask.h index 315c78f93..2e0a8273e 100644 --- a/launcher/archive/ExportToZipTask.h +++ b/launcher/archive/ExportToZipTask.h @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #pragma once diff --git a/launcher/archive/ExtractZipTask.cpp b/launcher/archive/ExtractZipTask.cpp index 1f3604b87..7dd002017 100644 --- a/launcher/archive/ExtractZipTask.cpp +++ b/launcher/archive/ExtractZipTask.cpp @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #include "ExtractZipTask.h" #include diff --git a/launcher/archive/ExtractZipTask.h b/launcher/archive/ExtractZipTask.h index 5fe10b853..a9ad0a548 100644 --- a/launcher/archive/ExtractZipTask.h +++ b/launcher/archive/ExtractZipTask.h @@ -14,23 +14,6 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ #pragma once From b119cc04df7c9e60b1f99d701e6e2a68a25e44c3 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 26 Sep 2025 19:20:50 +0300 Subject: [PATCH 28/59] update license readme Signed-off-by: Trial97 --- libraries/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/README.md b/libraries/README.md index be41e549f..2ebb80515 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -107,11 +107,13 @@ See [github repo](https://github.com/nayuki/QR-Code-generator). MIT -## quazip +## libarchive -A zip manipulation library. +Multi-format archive and compression library. -LGPL 2.1 with linking exception. +See [github repo](https://github.com/libarchive/libarchive). + +BSD 2-Clause license with some exception. ## rainbow From 8acc52b1ab8221cc9bdde6b3b8d4d9f9e7fe27a8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 4 Oct 2025 22:37:56 +0300 Subject: [PATCH 29/59] fix: CF import and Modrinth export Signed-off-by: Trial97 --- launcher/InstanceImportTask.cpp | 12 ++++++++++-- launcher/archive/ArchiveWriter.cpp | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 04600daa9..d3cc3bd02 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -113,18 +113,26 @@ QString InstanceImportTask::getRootFromZip(QStringList files) if (!isRunning()) { return {}; } + auto cleanPath = [](QString path) { + if (path == ".") + return QString(); + QString result = path; + if (result.startsWith("./")) + result = result.mid(2); + return result; + }; for (auto&& fileName : files) { setDetails(fileName); QFileInfo fileInfo(fileName); if (fileInfo.fileName() == "instance.cfg") { qDebug() << "MultiMC:" << true; m_modpackType = ModpackType::MultiMC; - return fileInfo.path(); + return cleanPath(fileInfo.path()); } if (fileInfo.fileName() == "manifest.json") { qDebug() << "Flame:" << true; m_modpackType = ModpackType::Flame; - return fileInfo.path(); + return cleanPath(fileInfo.path()); } QCoreApplication::processEvents(); } diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index c0028d365..4f01e7cea 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -56,7 +56,7 @@ bool ArchiveWriter::open() } else if (lowerName.endsWith(".tar.xz") || lowerName.endsWith(".txz")) { archive_write_set_format_pax_restricted(m_archive); archive_write_add_filter_xz(m_archive); - } else if (lowerName.endsWith(".zip") || lowerName.endsWith(".jar")) { + } else if (lowerName.endsWith(".zip") || lowerName.endsWith(".jar") || lowerName.endsWith(".mrpack")) { archive_write_set_format_zip(m_archive); } else if (lowerName.endsWith(".tar")) { archive_write_set_format_pax_restricted(m_archive); From 561187b18af23b54bce670609e8fa1852dcfa6bb Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 7 Oct 2025 09:16:43 +0300 Subject: [PATCH 30/59] fix: utf8 pathname encoding Signed-off-by: Trial97 --- launcher/archive/ArchiveWriter.cpp | 12 +++++++++--- launcher/main.cpp | 7 +++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 4f01e7cea..0d678c152 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -65,6 +65,11 @@ bool ArchiveWriter::open() return false; } + if (archive_write_set_options(m_archive, "hdrcharset=UTF-8") != ARCHIVE_OK) { + qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); + return false; + } + auto archiveNameUtf8 = m_filename.toUtf8(); if (archive_write_open_filename(m_archive, archiveNameUtf8.constData()) != ARCHIVE_OK) { qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); @@ -138,7 +143,7 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) archive_entry_set_size(entry, file.size()); if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { - qCritical() << "Failed to write header for: " << fileDest; + qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); return false; } @@ -178,12 +183,12 @@ bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data) archive_entry_set_size(entry, data.size()); if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { - qCritical() << "Failed to write header for: " << fileDest; + qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); return false; } if (archive_write_data(m_archive, data.constData(), data.size()) < 0) { - qCritical() << "Write error in archive for: " << fileDest; + qCritical() << "Write error in archive for: " << fileDest << "-" << archive_error_string(m_archive); return false; } return true; @@ -193,6 +198,7 @@ bool ArchiveWriter::addFile(ArchiveReader::File* f) { return f->writeFile(m_archive, "", true); } + std::unique_ptr ArchiveWriter::createDiskWriter() { int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS; diff --git a/launcher/main.cpp b/launcher/main.cpp index 2bce655d2..46368e72e 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -37,6 +37,13 @@ int main(int argc, char* argv[]) { + // try to set the utf-8 locale for the libarchive + for (auto name : { ".UTF-8", "en_US.UTF-8", "C.UTF-8" }) { + if (std::setlocale(LC_CTYPE, name)) { + break; + } + } + // initialize Qt Application app(argc, argv); From 5100588c8b875dd1ef0405950da3bcc03ae75ee4 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 31 Oct 2025 12:52:55 +0200 Subject: [PATCH 31/59] ensure secure extraction Signed-off-by: Trial97 --- launcher/archive/ArchiveWriter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 0d678c152..4880e2cb0 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -201,7 +201,8 @@ bool ArchiveWriter::addFile(ArchiveReader::File* f) std::unique_ptr ArchiveWriter::createDiskWriter() { - int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS; + int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | + ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_SECURE_SYMLINKS; std::unique_ptr extPtr(archive_write_disk_new(), [](archive* a) { if (a) { From 44b36312beb812710973920f328a2f7370f312ff Mon Sep 17 00:00:00 2001 From: Trial97 Date: Fri, 31 Oct 2025 13:04:49 +0200 Subject: [PATCH 32/59] nothing but zip writes Signed-off-by: Trial97 --- launcher/archive/ArchiveWriter.cpp | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 4880e2cb0..f67868a16 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -46,24 +46,7 @@ bool ArchiveWriter::open() return false; } - QString lowerName = m_filename.toLower(); - if (lowerName.endsWith(".tar.gz") || lowerName.endsWith(".tgz")) { - archive_write_set_format_pax_restricted(m_archive); - archive_write_add_filter_gzip(m_archive); - } else if (lowerName.endsWith(".tar.bz2") || lowerName.endsWith(".tbz")) { - archive_write_set_format_pax_restricted(m_archive); - archive_write_add_filter_bzip2(m_archive); - } else if (lowerName.endsWith(".tar.xz") || lowerName.endsWith(".txz")) { - archive_write_set_format_pax_restricted(m_archive); - archive_write_add_filter_xz(m_archive); - } else if (lowerName.endsWith(".zip") || lowerName.endsWith(".jar") || lowerName.endsWith(".mrpack")) { - archive_write_set_format_zip(m_archive); - } else if (lowerName.endsWith(".tar")) { - archive_write_set_format_pax_restricted(m_archive); - } else { - qCritical() << "Unknown archive format:" << m_filename; - return false; - } + archive_write_set_format_zip(m_archive); if (archive_write_set_options(m_archive, "hdrcharset=UTF-8") != ARCHIVE_OK) { qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); From df3ff6075111b3a1589e62e235c112717c5fccb2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 5 Nov 2025 08:18:32 +0200 Subject: [PATCH 33/59] update the vcpkg baseline Signed-off-by: Trial97 --- vcpkg-configuration.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json index b1b0996e2..20811632c 100644 --- a/vcpkg-configuration.json +++ b/vcpkg-configuration.json @@ -1,7 +1,7 @@ { "default-registry": { "kind": "git", - "baseline": "1fddddc280dfed63956e15ef74f4321bc6a219c9", + "baseline": "2d6a6cf3ac9a7cc93942c3d289a2f9c661a6f4a7", "repository": "https://github.com/microsoft/vcpkg" }, "registries": [ From 06c3bb89ff3318831edab358c5eb70c752e0a653 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 5 Nov 2025 01:56:17 -0500 Subject: [PATCH 34/59] build(vcpkg): minimize libarchive build This avoids pulling in libxml2 for xar support, which we don't use and was giving us some trouble in CI Signed-off-by: Seth Flynn Signed-off-by: Trial97 --- vcpkg.json | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/vcpkg.json b/vcpkg.json index 30c170935..942e6d9e4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,5 @@ { "dependencies": [ - "cmark", { "name": "ecm", "host": true @@ -13,8 +12,20 @@ "name": "pkgconf", "host": true }, + + "cmark", + { + "name": "libarchive", + "default-features": false, + "features": [ + "bzip2", + "lz4", + "lzma", + "lzo", + "zstd" + ] + }, "tomlplusplus", - "zlib", - "libarchive" + "zlib" ] } From a17908e07862daf77518cb42e19b1aaf06c1bb78 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 9 Nov 2025 18:02:03 +0200 Subject: [PATCH 35/59] use file stat to get time information Signed-off-by: Trial97 --- launcher/archive/ArchiveWriter.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index f67868a16..0e4851b66 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -18,6 +18,7 @@ #include "ArchiveWriter.h" #include #include +#include #include #include @@ -96,6 +97,13 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) auto fileDestUtf8 = fileDest.toUtf8(); archive_entry_set_pathname(entry, fileDestUtf8.constData()); + + QByteArray utf8 = fileInfo.absoluteFilePath().toUtf8(); + const char* cpath = utf8.constData(); + struct stat st; + if (stat(cpath, &st) == 0) { + archive_entry_copy_stat(entry, &st); + } archive_entry_set_perm(entry, fileInfo.permissions() & 0777); if (fileInfo.isSymLink()) { From 67a5cc9b8d261e7541ec5fa88ded4728a5244a16 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Thu, 13 Nov 2025 12:52:00 +0200 Subject: [PATCH 36/59] Update launcher/archive/ArchiveWriter.cpp Co-authored-by: Seth Flynn Signed-off-by: Alexandru Ionut Tripon --- launcher/archive/ArchiveWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 0e4851b66..73f8b7da5 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -104,7 +104,7 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) if (stat(cpath, &st) == 0) { archive_entry_copy_stat(entry, &st); } - archive_entry_set_perm(entry, fileInfo.permissions() & 0777); + archive_entry_set_perm(entry, fileInfo.permissions()); if (fileInfo.isSymLink()) { auto target = fileInfo.symLinkTarget().toUtf8(); From c787f4e29160473753cf3c1e4449da0a561f40a3 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Thu, 13 Nov 2025 18:44:20 +0200 Subject: [PATCH 37/59] fix merge Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 6caadfce7..37e1ebbdb 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1316,7 +1316,6 @@ target_link_libraries(Launcher_logic qdcss BuildConfig Qt${QT_VERSION_MAJOR}::Widgets - PkgConfig::libarchive ) if(TARGET PkgConfig::libqrencode) From 792b1d6648fa8868298c076810e5280f132bf6b9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 26 Nov 2025 19:12:13 +0200 Subject: [PATCH 38/59] apply suggestions Signed-off-by: Trial97 --- launcher/archive/ArchiveReader.cpp | 10 ++++-- launcher/archive/ArchiveWriter.cpp | 53 +++++++++++++++--------------- launcher/archive/ArchiveWriter.h | 1 + 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index c6602d50f..638d4b6d5 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-3.0-only +// SPDX-License-Identifier: GPL-3.0-only AND LicenseRef-PublicDomain /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2025 Trial97 @@ -14,6 +14,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * + * Additional note: Portions of this file are released into the public domain + * under LicenseRef-PublicDomain. */ #include "ArchiveReader.h" #include @@ -144,7 +147,7 @@ bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool n } auto r = archive_write_finish_entry(out); if (r < ARCHIVE_OK) - qCritical() << "Failed dinish entry:" << archive_error_string(out); + qCritical() << "Failed to finish writing entry:" << archive_error_string(out); return (r > ARCHIVE_WARN); } @@ -155,7 +158,8 @@ bool ArchiveReader::parse(std::function doStuff) archive_read_support_format_all(a); archive_read_support_filter_all(a); auto fileName = m_archivePath.toUtf8(); - if (archive_read_open_filename(a, fileName.constData(), 10240) != ARCHIVE_OK) { + const auto blockSize = 10240; + if (archive_read_open_filename(a, fileName.constData(), blockSize) != ARCHIVE_OK) { qCritical() << "Failed to open archive file:" << m_archivePath << "-" << f->error(); return false; } diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index 73f8b7da5..f69ee3f74 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -47,7 +47,8 @@ bool ArchiveWriter::open() return false; } - archive_write_set_format_zip(m_archive); + auto format = m_format.toUtf8(); + archive_write_set_format_by_name(m_archive, format.constData()); if (archive_write_set_options(m_archive, "hdrcharset=UTF-8") != ARCHIVE_OK) { qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive); @@ -84,7 +85,7 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) { QFileInfo fileInfo(fileName); if (!fileInfo.exists()) { - qCritical() << "File does not exists:" << fileInfo.filePath(); + qCritical() << "File does not exist:" << fileInfo.filePath(); return false; } @@ -101,16 +102,15 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) QByteArray utf8 = fileInfo.absoluteFilePath().toUtf8(); const char* cpath = utf8.constData(); struct stat st; - if (stat(cpath, &st) == 0) { - archive_entry_copy_stat(entry, &st); + if (stat(cpath, &st) != 0) { + qCritical() << "Failed to stat file:" << fileInfo.filePath(); } + archive_entry_copy_stat(entry, &st); archive_entry_set_perm(entry, fileInfo.permissions()); if (fileInfo.isSymLink()) { auto target = fileInfo.symLinkTarget().toUtf8(); - archive_entry_set_filetype(entry, AE_IFLNK); archive_entry_set_symlink(entry, target.constData()); - archive_entry_set_size(entry, 0); if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { qCritical() << "Failed to write symlink header for:" << fileDest << "-" << archive_error_string(m_archive); @@ -119,39 +119,38 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) return true; } - if (!fileInfo.isFile()) { + if (!fileInfo.isFile() && !fileInfo.isDir()) { qCritical() << "Unsupported file type:" << fileInfo.filePath(); return false; } - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open file: " << fileInfo.filePath(); - return false; - } - - archive_entry_set_filetype(entry, AE_IFREG); - archive_entry_set_size(entry, file.size()); - if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { qCritical() << "Failed to write header for: " << fileDest << "-" << archive_error_string(m_archive); return false; } - constexpr qint64 chunkSize = 8192; - QByteArray buffer; - buffer.resize(chunkSize); - - while (!file.atEnd()) { - auto bytesRead = file.read(buffer.data(), chunkSize); - if (bytesRead < 0) { - qCritical() << "Read error in file: " << fileInfo.filePath(); + if (fileInfo.isFile()) { + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file: " << fileInfo.filePath(); return false; } - if (archive_write_data(m_archive, buffer.constData(), bytesRead) < 0) { - qCritical() << "Write error in archive for: " << fileDest; - return false; + constexpr qint64 chunkSize = 8192; + QByteArray buffer; + buffer.resize(chunkSize); + + while (!file.atEnd()) { + auto bytesRead = file.read(buffer.data(), chunkSize); + if (bytesRead < 0) { + qCritical() << "Read error in file: " << fileInfo.filePath(); + return false; + } + + if (archive_write_data(m_archive, buffer.constData(), bytesRead) < 0) { + qCritical() << "Write error in archive for: " << fileDest; + return false; + } } } return true; diff --git a/launcher/archive/ArchiveWriter.h b/launcher/archive/ArchiveWriter.h index c351d4bb8..807bb297c 100644 --- a/launcher/archive/ArchiveWriter.h +++ b/launcher/archive/ArchiveWriter.h @@ -41,5 +41,6 @@ class ArchiveWriter { private: struct archive* m_archive = nullptr; QString m_filename; + QString m_format = "zip"; }; } // namespace MMCZip \ No newline at end of file From ea05eb951a4aa76386f84d309203601e0c47a495 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 26 Nov 2025 19:16:31 +0200 Subject: [PATCH 39/59] add back kdePackages.qt5compat Signed-off-by: Trial97 --- nix/unwrapped.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 2aaba42a1..7d461a072 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -77,6 +77,7 @@ stdenv.mkDerivation { cmark kdePackages.qtbase kdePackages.qtnetworkauth + kdePackages.qt5compat qrencode libarchive tomlplusplus From ff40679d0f82edb420290d24b20db9ce0b7ddfad Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 26 Nov 2025 17:01:09 -0500 Subject: [PATCH 40/59] refactor(launcher/archive): ensure correct filetype for archived files We can rely on stat for most things but this Signed-off-by: Seth Flynn --- launcher/archive/ArchiveReader.cpp | 5 ++--- launcher/archive/ArchiveReader.h | 1 + launcher/archive/ArchiveWriter.cpp | 25 ++++++++++++++----------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/launcher/archive/ArchiveReader.cpp b/launcher/archive/ArchiveReader.cpp index 638d4b6d5..a866b49c5 100644 --- a/launcher/archive/ArchiveReader.cpp +++ b/launcher/archive/ArchiveReader.cpp @@ -84,7 +84,7 @@ auto ArchiveReader::goToFile(QString filename) -> std::unique_ptr archive_read_support_format_all(a); archive_read_support_filter_all(a); auto fileName = m_archivePath.toUtf8(); - if (archive_read_open_filename(a, fileName.constData(), 10240) != ARCHIVE_OK) { + if (archive_read_open_filename(a, fileName.constData(), m_blockSize) != ARCHIVE_OK) { qCritical() << "Failed to open archive file:" << m_archivePath << "-" << archive_error_string(a); return nullptr; } @@ -158,8 +158,7 @@ bool ArchiveReader::parse(std::function doStuff) archive_read_support_format_all(a); archive_read_support_filter_all(a); auto fileName = m_archivePath.toUtf8(); - const auto blockSize = 10240; - if (archive_read_open_filename(a, fileName.constData(), blockSize) != ARCHIVE_OK) { + if (archive_read_open_filename(a, fileName.constData(), m_blockSize) != ARCHIVE_OK) { qCritical() << "Failed to open archive file:" << m_archivePath << "-" << f->error(); return false; } diff --git a/launcher/archive/ArchiveReader.h b/launcher/archive/ArchiveReader.h index 56455de63..379006278 100644 --- a/launcher/archive/ArchiveReader.h +++ b/launcher/archive/ArchiveReader.h @@ -65,6 +65,7 @@ class ArchiveReader { private: QString m_archivePath; + size_t m_blockSize = 10240; QStringList m_fileNames = {}; }; diff --git a/launcher/archive/ArchiveWriter.cpp b/launcher/archive/ArchiveWriter.cpp index f69ee3f74..87cead69c 100644 --- a/launcher/archive/ArchiveWriter.cpp +++ b/launcher/archive/ArchiveWriter.cpp @@ -105,21 +105,23 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) if (stat(cpath, &st) != 0) { qCritical() << "Failed to stat file:" << fileInfo.filePath(); } + // This should handle the copying of most attributes archive_entry_copy_stat(entry, &st); - archive_entry_set_perm(entry, fileInfo.permissions()); + // However: + // "The [filetype] constants used by stat(2) may have different numeric values from the corresponding [libarchive constants]." + // - `archive_entry_stat(3)` if (fileInfo.isSymLink()) { + archive_entry_set_filetype(entry, AE_IFLNK); + + // We also need to manually copy some attributes from the link itself, as `stat` above operates on its target auto target = fileInfo.symLinkTarget().toUtf8(); archive_entry_set_symlink(entry, target.constData()); - - if (archive_write_header(m_archive, entry) != ARCHIVE_OK) { - qCritical() << "Failed to write symlink header for:" << fileDest << "-" << archive_error_string(m_archive); - return false; - } - return true; - } - - if (!fileInfo.isFile() && !fileInfo.isDir()) { + archive_entry_set_size(entry, 0); + archive_entry_set_perm(entry, fileInfo.permissions()); + } else if (fileInfo.isFile()) { + archive_entry_set_filetype(entry, AE_IFREG); + } else { qCritical() << "Unsupported file type:" << fileInfo.filePath(); return false; } @@ -129,7 +131,7 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) return false; } - if (fileInfo.isFile()) { + if (fileInfo.isFile() && !fileInfo.isSymLink()) { QFile file(fileInfo.absoluteFilePath()); if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Failed to open file: " << fileInfo.filePath(); @@ -153,6 +155,7 @@ bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest) } } } + return true; } From 2dcc89aa0037735a9bfffa776c0b92ed25b355f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:10:29 +0000 Subject: [PATCH 41/59] chore(deps): update hendrikmuhs/ccache-action action to v1.2.20 --- .github/actions/setup-dependencies/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index 2d2889d5f..686e3c63d 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -56,7 +56,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.19 + uses: hendrikmuhs/ccache-action@v1.2.20 with: variant: sccache create-symlink: ${{ runner.os != 'Windows' }} From 5b037793fff0d454631284d07dce0eb19cff4bfc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:10:33 +0000 Subject: [PATCH 42/59] chore(deps): update determinatesystems/update-flake-lock action to v28 --- .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 64d299ef7..b995f4dca 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v5 - uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31 - - uses: DeterminateSystems/update-flake-lock@v27 + - uses: DeterminateSystems/update-flake-lock@v28 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" From 074846d72aa6ce1b39f628695ba11aba40b7024e Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 24 Sep 2025 13:49:17 +0300 Subject: [PATCH 43/59] remove qt5compat Signed-off-by: Trial97 --- .github/actions/setup-dependencies/action.yml | 2 +- .github/actions/setup-dependencies/windows/action.yml | 1 - CMakeLists.txt | 3 +-- launcher/LoggedProcess.cpp | 8 ++++---- launcher/LoggedProcess.h | 10 +++++----- launcher/minecraft/launch/LauncherPartLaunch.cpp | 2 +- launcher/ui/dialogs/AboutDialog.cpp | 7 +++++-- libraries/LocalPeer/CMakeLists.txt | 3 +-- libraries/qdcss/CMakeLists.txt | 3 +-- libraries/systeminfo/CMakeLists.txt | 3 +-- nix/unwrapped.nix | 1 - 11 files changed, 20 insertions(+), 23 deletions(-) diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index 686e3c63d..4c19474b7 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -79,5 +79,5 @@ runs: aqtversion: "==3.1.*" version: ${{ inputs.qt-version }} arch: ${{ inputs.qt-architecture }} - modules: qt5compat qtimageformats qtnetworkauth + modules: qtimageformats qtnetworkauth cache: ${{ inputs.build-type == 'Debug' }} diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index f2985fd1b..ada060d66 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -76,7 +76,6 @@ runs: qt6-base:p qt6-svg:p qt6-imageformats:p - qt6-5compat:p qt6-networkauth:p cmark:p qrencode:p diff --git a/CMakeLists.txt b/CMakeLists.txt index 27d5108ba..f0ba31368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,10 +323,9 @@ endif() include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) - find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL) + find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml NetworkAuth OpenGL) find_package(Qt6 COMPONENTS DBus) list(APPEND Launcher_QT_DBUS Qt6::DBus) - list(APPEND Launcher_QT_LIBS Qt6::Core5Compat) else() message(FATAL_ERROR "Qt version ${Launcher_QT_VERSION_MAJOR} is not supported") endif() diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index b1efc8bd3..bae45ad88 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -36,10 +36,10 @@ #include "LoggedProcess.h" #include -#include +#include #include "MessageLevel.h" -LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent) +LoggedProcess::LoggedProcess(const QStringConverter::Encoding output_codec, QObject* parent) : QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec) { // QProcess has a strange interface... let's map a lot of those into a few. @@ -57,9 +57,9 @@ LoggedProcess::~LoggedProcess() } } -QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decoder) +QStringList LoggedProcess::reprocess(const QByteArray& data, QStringDecoder& decoder) { - auto str = decoder.toUnicode(data); + QString str = decoder(data); if (!m_leftover_line.isEmpty()) { str.prepend(m_leftover_line); diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 75ba15dfd..c01d464d1 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -36,7 +36,7 @@ #pragma once #include -#include +#include #include "MessageLevel.h" /* @@ -49,7 +49,7 @@ class LoggedProcess : public QProcess { enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted }; public: - explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0); + explicit LoggedProcess(QStringConverter::Encoding outputEncoding = QStringConverter::System, QObject* parent = nullptr); virtual ~LoggedProcess(); State state() const; @@ -77,11 +77,11 @@ class LoggedProcess : public QProcess { private: void changeState(LoggedProcess::State state); - QStringList reprocess(const QByteArray& data, QTextDecoder& decoder); + QStringList reprocess(const QByteArray& data, QStringDecoder& decoder); private: - QTextDecoder m_err_decoder; - QTextDecoder m_out_decoder; + QStringDecoder m_err_decoder; + QStringDecoder m_out_decoder; QString m_leftover_line; bool m_killed = false; State m_state = NotRunning; diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 388d55628..d824c904b 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -50,7 +50,7 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent) - , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale()) + , m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QStringConverter::Utf8 : QStringConverter::System) { if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) { static const QRegularExpression s_settingUser(".*Setting user.+", QRegularExpression::CaseInsensitiveOption); diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 9dcda1832..ca5706d21 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -48,9 +48,12 @@ namespace { QString getCreditsHtml() { QFile dataFile(":/documents/credits.html"); - dataFile.open(QIODevice::ReadOnly); - + if (!dataFile.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open file '" << dataFile.fileName() << "' for reading!"; + return {}; + } QString fileContent = QString::fromUtf8(dataFile.readAll()); + dataFile.close(); return fileContent.arg(QObject::tr("%1 Developers").arg(BuildConfig.LAUNCHER_DISPLAYNAME), QObject::tr("MultiMC Developers"), QObject::tr("With special thanks to")); diff --git a/libraries/LocalPeer/CMakeLists.txt b/libraries/LocalPeer/CMakeLists.txt index dd78647c0..539cea135 100644 --- a/libraries/LocalPeer/CMakeLists.txt +++ b/libraries/LocalPeer/CMakeLists.txt @@ -2,8 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(LocalPeer) if(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core Network Core5Compat REQUIRED) - list(APPEND LocalPeer_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) + find_package(Qt6 COMPONENTS Core Network REQUIRED) endif() set(SINGLE_SOURCES diff --git a/libraries/qdcss/CMakeLists.txt b/libraries/qdcss/CMakeLists.txt index 7e497feca..d1c1078cd 100644 --- a/libraries/qdcss/CMakeLists.txt +++ b/libraries/qdcss/CMakeLists.txt @@ -2,8 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(qdcss) if(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) - list(APPEND qdcss_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) + find_package(Qt6 COMPONENTS Core REQUIRED) endif() set(QDCSS_SOURCES diff --git a/libraries/systeminfo/CMakeLists.txt b/libraries/systeminfo/CMakeLists.txt index 80b6b8094..e091637cf 100644 --- a/libraries/systeminfo/CMakeLists.txt +++ b/libraries/systeminfo/CMakeLists.txt @@ -1,8 +1,7 @@ project(systeminfo) if(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core Core5Compat REQUIRED) - list(APPEND systeminfo_LIBS Qt${QT_VERSION_MAJOR}::Core5Compat) + find_package(Qt6 COMPONENTS Core REQUIRED) endif() set(systeminfo_SOURCES diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 7d461a072..2aaba42a1 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -77,7 +77,6 @@ stdenv.mkDerivation { cmark kdePackages.qtbase kdePackages.qtnetworkauth - kdePackages.qt5compat qrencode libarchive tomlplusplus From 3459e5bb527b89f9dc48e06d4cb7776a13946ded Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 27 Nov 2025 15:52:34 -0500 Subject: [PATCH 44/59] build(cmake): de-vendor ecm files These are now present in virtually all modern versions of ECM shipped by distributions, so we don't really need to keep them around Signed-off-by: Seth Flynn --- cmake/ECMQueryQt.cmake | 100 ------------------------------------ cmake/QtVersionOption.cmake | 38 -------------- 2 files changed, 138 deletions(-) delete mode 100644 cmake/ECMQueryQt.cmake delete mode 100644 cmake/QtVersionOption.cmake diff --git a/cmake/ECMQueryQt.cmake b/cmake/ECMQueryQt.cmake deleted file mode 100644 index 98eb50089..000000000 --- a/cmake/ECMQueryQt.cmake +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-FileCopyrightText: 2014 Rohan Garg -# SPDX-FileCopyrightText: 2014 Alex Merry -# SPDX-FileCopyrightText: 2014-2016 Aleix Pol -# SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau -# SPDX-FileCopyrightText: 2022 Ahmad Samir -# -# SPDX-License-Identifier: BSD-3-Clause -#[=======================================================================[.rst: -ECMQueryQt ---------------- -This module can be used to query the installation paths used by Qt. - -For Qt5 this uses ``qmake``, and for Qt6 this used ``qtpaths`` (the latter has built-in -support to query the paths of a target platform when cross-compiling). - -This module defines the following function: -:: - - ecm_query_qt( [TRY]) - -Passing ``TRY`` will result in the method not making the build fail if the executable -used for querying has not been found, but instead simply print a warning message and -return an empty string. - -Example usage: - -.. code-block:: cmake - - include(ECMQueryQt) - ecm_query_qt(bin_dir QT_INSTALL_BINS) - -If the call succeeds ``${bin_dir}`` will be set to ``/path/to/bin/dir`` (e.g. -``/usr/lib64/qt/bin/``). - -Since: 5.93 -#]=======================================================================] - -include(${CMAKE_CURRENT_LIST_DIR}/QtVersionOption.cmake) -include(CheckLanguage) -check_language(CXX) -if (CMAKE_CXX_COMPILER) - # Enable the CXX language to let CMake look for config files in library dirs. - # See: https://gitlab.kitware.com/cmake/cmake/-/issues/23266 - enable_language(CXX) -endif() - -if (QT_MAJOR_VERSION STREQUAL "5") - # QUIET to accommodate the TRY option - find_package(Qt${QT_MAJOR_VERSION}Core QUIET) - if(TARGET Qt5::qmake) - get_target_property(_qmake_executable_default Qt5::qmake LOCATION) - - set(QUERY_EXECUTABLE ${_qmake_executable_default} - CACHE FILEPATH "Location of the Qt5 qmake executable") - set(_exec_name_text "Qt5 qmake") - set(_cli_option "-query") - endif() -elseif(QT_MAJOR_VERSION STREQUAL "6") - # QUIET to accommodate the TRY option - find_package(Qt6 COMPONENTS CoreTools QUIET CONFIG) - if (TARGET Qt6::qtpaths) - get_target_property(_qtpaths_executable Qt6::qtpaths LOCATION) - - set(QUERY_EXECUTABLE ${_qtpaths_executable} - CACHE FILEPATH "Location of the Qt6 qtpaths executable") - set(_exec_name_text "Qt6 qtpaths") - set(_cli_option "--query") - endif() -endif() - -function(ecm_query_qt result_variable qt_variable) - set(options TRY) - set(oneValueArgs) - set(multiValueArgs) - - cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT QUERY_EXECUTABLE) - if(ARGS_TRY) - set(${result_variable} "" PARENT_SCOPE) - message(STATUS "No ${_exec_name_text} executable found. Can't check ${qt_variable}") - return() - else() - message(FATAL_ERROR "No ${_exec_name_text} executable found. Can't check ${qt_variable} as required") - endif() - endif() - execute_process( - COMMAND ${QUERY_EXECUTABLE} ${_cli_option} "${qt_variable}" - RESULT_VARIABLE return_code - OUTPUT_VARIABLE output - ) - if(return_code EQUAL 0) - string(STRIP "${output}" output) - file(TO_CMAKE_PATH "${output}" output_path) - set(${result_variable} "${output_path}" PARENT_SCOPE) - else() - message(WARNING "Failed call: ${_command} \"${qt_variable}\"") - message(FATAL_ERROR "${_exec_name_text} call failed: ${return_code}") - endif() -endfunction() diff --git a/cmake/QtVersionOption.cmake b/cmake/QtVersionOption.cmake deleted file mode 100644 index 1390f9db6..000000000 --- a/cmake/QtVersionOption.cmake +++ /dev/null @@ -1,38 +0,0 @@ -#.rst: -# QtVersionOption -# --------------- -# -# Adds a build option to select the major Qt version if necessary, -# that is, if the major Qt version has not yet been determined otherwise -# (e.g. by a corresponding find_package() call). -# -# This module is typically included by other modules requiring knowledge -# about the major Qt version. -# -# ``QT_MAJOR_VERSION`` is defined to either be "5" or "6". -# -# -# Since 5.82.0. - -#============================================================================= -# SPDX-FileCopyrightText: 2021 Volker Krause -# -# SPDX-License-Identifier: BSD-3-Clause - -if (DEFINED QT_MAJOR_VERSION) - return() -endif() - -if (TARGET Qt5::Core) - set(QT_MAJOR_VERSION 5) -elseif (TARGET Qt6::Core) - set(QT_MAJOR_VERSION 6) -else() - option(BUILD_WITH_QT6 "Build against Qt 6" OFF) - - if (BUILD_WITH_QT6) - set(QT_MAJOR_VERSION 6) - else() - set(QT_MAJOR_VERSION 5) - endif() -endif() From 690a61a9e05a7afe3b918b7ad35cd33e26b2654f Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 27 Nov 2025 15:53:15 -0500 Subject: [PATCH 45/59] build(cmake): de-vendor versionless qt commands These are already shipped in all versions of Qt 6 Signed-off-by: Seth Flynn --- CMakeLists.txt | 1 - cmake/QtVersionlessBackport.cmake | 97 ------------------------------- 2 files changed, 98 deletions(-) delete mode 100644 cmake/QtVersionlessBackport.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 27d5108ba..e9f1284d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,7 +320,6 @@ if(NOT ZLIB_FOUND) endif() # Find the required Qt parts -include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_VERSION_MAJOR 6) find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth OpenGL) diff --git a/cmake/QtVersionlessBackport.cmake b/cmake/QtVersionlessBackport.cmake deleted file mode 100644 index 46792db58..000000000 --- a/cmake/QtVersionlessBackport.cmake +++ /dev/null @@ -1,97 +0,0 @@ -#============================================================================= -# Copyright 2005-2011 Kitware, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of Kitware, Inc. nor the names of its -# contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#============================================================================= - -# From Qt5CoreMacros.cmake - -function(qt_generate_moc) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_generate_moc(${ARGV}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_generate_moc(${ARGV}) - endif() -endfunction() - -function(qt_wrap_cpp outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_wrap_cpp("${outfiles}" ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_wrap_cpp("${outfiles}" ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - -function(qt_add_binary_resources) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_add_binary_resources(${ARGV}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_add_binary_resources(${ARGV}) - endif() -endfunction() - -function(qt_add_resources outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_add_resources("${outfiles}" ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_add_resources("${outfiles}" ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - -function(qt_add_big_resources outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_add_big_resources(${outfiles} ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_add_big_resources(${outfiles} ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - -function(qt_import_plugins) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_import_plugins(${ARGV}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_import_plugins(${ARGV}) - endif() -endfunction() - - -# From Qt5WidgetsMacros.cmake - -function(qt_wrap_ui outfiles) - if(QT_VERSION_MAJOR EQUAL 5) - qt5_wrap_ui("${outfiles}" ${ARGN}) - elseif(QT_VERSION_MAJOR EQUAL 6) - qt6_wrap_ui("${outfiles}" ${ARGN}) - endif() - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) -endfunction() - From 030dc4e7b14d656cdcc4aa733f5be2434bea004f Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 27 Nov 2025 16:48:16 -0500 Subject: [PATCH 46/59] ci(setup-dependencies/linux): use deps from system Signed-off-by: Seth Flynn --- .github/actions/setup-dependencies/linux/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index 610126442..877b8aa6e 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -9,9 +9,9 @@ runs: run: | sudo apt-get -y update sudo apt-get -y install \ - dpkg-dev libarchive-dev \ - ninja-build extra-cmake-modules scdoc \ - libqrencode-dev \ + dpkg-dev \ + ninja-build extra-cmake-modules pkg-config scdoc \ + cmark-dev libarchive-dev libqrencode-dev tomlplusplus-dev zlib-dev \ appstream libxcb-cursor-dev - name: Setup AppImage tooling From dc774aa424033d692ff4f1cf3f80dc24f3e510e7 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 27 Nov 2025 16:04:39 -0500 Subject: [PATCH 47/59] build(cmake): remove `Launcher_FORCE_BUNDLED_LIBS` option As we won't be providing "bundled" libraries anymore, this option is basically pointless Signed-off-by: Seth Flynn --- CMakeLists.txt | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 27d5108ba..3fbf33554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,6 @@ set(Launcher_DISCORD_URL "https://prismlauncher.org/discord" CACHE STRING "URL f set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL for the subreddit.") # Builds -set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against") # Java downloader @@ -309,16 +308,6 @@ set(Launcher_BUILD_TIMESTAMP "${TODAY}") ################################ 3rd Party Libs ################################ -# Successive configurations of cmake without cleaning the build dir will cause zlib fallback to fail due to cached values -# Record when fallback triggered and skip this find_package -if(NOT Launcher_FORCE_BUNDLED_LIBS AND NOT FORCE_BUNDLED_ZLIB) - find_package(ZLIB QUIET) -endif() -if(NOT ZLIB_FOUND) - set(FORCE_BUNDLED_ZLIB TRUE CACHE BOOL "") - mark_as_advanced(FORCE_BUNDLED_ZLIB) -endif() - # Find the required Qt parts include(QtVersionlessBackport) if(Launcher_QT_VERSION_MAJOR EQUAL 6) @@ -357,22 +346,20 @@ if(NOT LibArchive_FOUND) pkg_check_modules(libarchive REQUIRED IMPORTED_TARGET libarchive) endif() -if(NOT Launcher_FORCE_BUNDLED_LIBS) - # Find toml++ - find_package(tomlplusplus 3.2.0 QUIET) - # Fallback to pkg-config (if available) if CMake files aren't found - if(NOT tomlplusplus_FOUND) - find_package(PkgConfig QUIET) - if(PkgConfig_FOUND) - pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) - endif() +# Find toml++ +find_package(tomlplusplus 3.2.0 QUIET) +# Fallback to pkg-config (if available) if CMake files aren't found +if(NOT tomlplusplus_FOUND) + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) endif() - - - # Find cmark - find_package(cmark QUIET) endif() + +# Find cmark +find_package(cmark QUIET) + include(ECMQtDeclareLoggingCategory) ####################################### Program Info ####################################### @@ -479,7 +466,9 @@ add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker -if(FORCE_BUNDLED_ZLIB) + +find_package(ZLIB QUIET) +if(NOT ZLIB_FOUND) message(STATUS "Using bundled zlib") set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib From e2c31569dc827247076c7c138ecbe43309522207 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 27 Nov 2025 16:09:52 -0500 Subject: [PATCH 48/59] build: remove third party submodules Most of these are extremely common in distributions now, so packagers don't have much need for our in-tree versions - most don't even use them as is With our move to vcpkg for Windows/macOS, we also don't have a need for them. So time to say goodbye! Signed-off-by: Seth Flynn --- .../setup-dependencies/linux/action.yml | 24 +++++- .gitmodules | 12 --- CMakeLists.txt | 82 ++----------------- flatpak/cmark.yml | 14 ++++ flatpak/org.prismlauncher.PrismLauncher.yml | 3 + flatpak/tomlplusplus.yml | 6 ++ libraries/README.md | 32 -------- libraries/cmark | 1 - libraries/extra-cmake-modules | 1 - libraries/tomlplusplus | 1 - libraries/zlib | 1 - 11 files changed, 54 insertions(+), 123 deletions(-) create mode 100644 flatpak/cmark.yml create mode 100644 flatpak/tomlplusplus.yml delete mode 160000 libraries/cmark delete mode 160000 libraries/extra-cmake-modules delete mode 160000 libraries/tomlplusplus delete mode 160000 libraries/zlib diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index 877b8aa6e..e47941ce3 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -11,9 +11,31 @@ runs: sudo apt-get -y install \ dpkg-dev \ ninja-build extra-cmake-modules pkg-config scdoc \ - cmark-dev libarchive-dev libqrencode-dev tomlplusplus-dev zlib-dev \ + cmark libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ appstream libxcb-cursor-dev + # TODO(@getchoo): Install with the above when all targets use Ubuntu 24.04 + - name: Install tomlplusplus + if: ${{ runner.arch == 'ARM64' }} + shell: bash + run: | + sudo apt-get -y install libtomlplusplus-dev + + # FIXME(@getchoo): THIS IS HORRIBLE TO DO! + # Install tomlplusplus from Ubuntu 24.04, since it never got backported to 22.04 + # I've done too much to continue keeping this as a submodule.... + - name: Install tomlplusplus from 24.04 + if: ${{ runner.arch != 'ARM64' }} + shell: bash + run: | + deb_arch="$(dpkg-architecture -q DEB_HOST_ARCH)" + curl -Lo libtomlplusplus-dev.deb http://mirrors.kernel.org/ubuntu/pool/universe/t/tomlplusplus/libtomlplusplus-dev_3.4.0+ds-0.2build1_"$deb_arch".deb + curl -Lo libtomlplusplus3t64.deb http://mirrors.kernel.org/ubuntu/pool/universe/t/tomlplusplus/libtomlplusplus3t64_3.4.0+ds-0.2build1_"$deb_arch".deb + sudo dpkg -i libtomlplusplus3t64.deb + sudo dpkg -i libtomlplusplus-dev.deb + rm *.deb + sudo apt-get install -f + - name: Setup AppImage tooling shell: bash run: | diff --git a/.gitmodules b/.gitmodules index 246d9d791..f2d71ef29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,18 +1,6 @@ -[submodule "libraries/tomlplusplus"] - path = libraries/tomlplusplus - url = https://github.com/marzer/tomlplusplus.git [submodule "libraries/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PrismLauncher/libnbtplusplus.git -[submodule "libraries/zlib"] - path = libraries/zlib - url = https://github.com/madler/zlib.git -[submodule "libraries/extra-cmake-modules"] - path = libraries/extra-cmake-modules - url = https://github.com/KDE/extra-cmake-modules -[submodule "libraries/cmark"] - path = libraries/cmark - url = https://github.com/commonmark/cmark.git [submodule "flatpak/shared-modules"] path = flatpak/shared-modules url = https://github.com/flathub/shared-modules.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fbf33554..0cefd727a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,9 +104,6 @@ endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_WARN_DEPRECATED_UP_TO=0x060200") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_UP_TO=0x060000") -# Fix aarch64 build for toml++ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0") - # set CXXFLAGS for build targets set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}") @@ -174,20 +171,9 @@ endif() option(BUILD_TESTING "Build the testing tree." ON) -find_package(ECM QUIET NO_MODULE) -if(NOT ECM_FOUND) - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/CMakeLists.txt") - message(STATUS "Using bundled ECM") - set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/extra-cmake-modules/modules;${CMAKE_MODULE_PATH}") - else() - message(FATAL_ERROR - " Could not find ECM\n \n" - " Either install ECM using the system package manager or clone submodules\n" - " Submodules can be cloned with 'git submodule update --init --recursive'") - endif() -else() - set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") -endif() +find_package(ECM NO_MODULE REQUIRED) +set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") + include(CTest) include(ECMAddTests) if(BUILD_TESTING) @@ -326,6 +312,8 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 6) set(QT_LIBEXECS_DIR ${QT${QT_VERSION_MAJOR}_INSTALL_PREFIX}/${QT${QT_VERSION_MAJOR}_INSTALL_LIBEXECS}) endif() +find_package(cmark REQUIRED) + # Find libqrencode ## NOTE(@getchoo): Never use pkg-config with MSVC since the vcpkg port makes our install bundle fail to find the dll if(MSVC) @@ -339,27 +327,17 @@ else() endif() # Find libarchive through CMake packages, mainly for vcpkg -find_package(LibArchive QUIET) +find_package(LibArchive) # CMake packages aren't available in most distributions of libarchive, so fallback to pkg-config if(NOT LibArchive_FOUND) find_package(PkgConfig REQUIRED) pkg_check_modules(libarchive REQUIRED IMPORTED_TARGET libarchive) endif() -# Find toml++ -find_package(tomlplusplus 3.2.0 QUIET) -# Fallback to pkg-config (if available) if CMake files aren't found -if(NOT tomlplusplus_FOUND) - find_package(PkgConfig QUIET) - if(PkgConfig_FOUND) - pkg_check_modules(tomlplusplus IMPORTED_TARGET tomlplusplus>=3.2.0) - endif() -endif() +find_package(tomlplusplus 3.2.0 REQUIRED) +find_package(ZLIB REQUIRED) -# Find cmark -find_package(cmark QUIET) - include(ECMQtDeclareLoggingCategory) ####################################### Program Info ####################################### @@ -467,52 +445,8 @@ add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker -find_package(ZLIB QUIET) -if(NOT ZLIB_FOUND) - message(STATUS "Using bundled zlib") - - set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Suppress cmake warnings and allow INTERPROCEDURAL_OPTIMIZATION for zlib - set(SKIP_INSTALL_ALL ON) - add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL) - - # On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not. - # We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway. - check_include_file(unistd.h NEED_GENERATED_ZCONF) - if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF) - # zlib's cmake script renames a file, dirtying the submodule, see https://github.com/madler/zlib/issues/162 - message(STATUS "Undoing Rename") - message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h") - file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h") - endif() - - set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/libraries/zlib" "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib" CACHE STRING "" FORCE) - set_target_properties(zlibstatic PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIR}") - add_library(ZLIB::ZLIB ALIAS zlibstatic) - set(ZLIB_LIBRARY ZLIB::ZLIB CACHE STRING "zlib library name") - - find_package(ZLIB REQUIRED) -else() - message(STATUS "Using system zlib") -endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions -if(NOT tomlplusplus_FOUND) - message(STATUS "Using bundled tomlplusplus") - add_subdirectory(libraries/tomlplusplus) # toml parser -else() - message(STATUS "Using system tomlplusplus") -endif() -if(NOT cmark_FOUND) - message(STATUS "Using bundled cmark") - set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING}) - set(BUILD_TESTING 0) - set(BUILD_SHARED_LIBS 0) - add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser - add_library(cmark::cmark ALIAS cmark) - set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING}) -else() - message(STATUS "Using system cmark") -endif() add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(libraries/qdcss) # css parser diff --git a/flatpak/cmark.yml b/flatpak/cmark.yml new file mode 100644 index 000000000..d5078baab --- /dev/null +++ b/flatpak/cmark.yml @@ -0,0 +1,14 @@ +name: cmark +buildsystem: cmake-ninja +builddir: true +config-opts: + - -DCMAKE_TESTS=OFF +sources: + - type: archive + url: https://github.com/commonmark/cmark/archive/0.31.1.tar.gz + sha256: 3da93db5469c30588cfeb283d9d62edfc6ded9eb0edc10a4f5bbfb7d722ea802 + x-checker-data: + type: anitya + project-id: 9159 + stable-only: true + url-template: https://github.com/commonmark/cmark/archive/$version.tar.gz diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml index 136aef91a..80ed8158d 100644 --- a/flatpak/org.prismlauncher.PrismLauncher.yml +++ b/flatpak/org.prismlauncher.PrismLauncher.yml @@ -27,6 +27,9 @@ finish-args: - --filesystem=/sys/kernel/mm/transparent_hugepage:ro modules: + - cmark.yml + - tomlplusplus.yml + # Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31) - shared-modules/libusb/libusb.json diff --git a/flatpak/tomlplusplus.yml b/flatpak/tomlplusplus.yml new file mode 100644 index 000000000..0afaf6678 --- /dev/null +++ b/flatpak/tomlplusplus.yml @@ -0,0 +1,6 @@ +name: tomlplusplus +buildsystem: cmake-ninja +sources: + - type: archive + url: https://github.com/marzer/tomlplusplus/archive/v3.4.0.tar.gz + sha256: 8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155 diff --git a/libraries/README.md b/libraries/README.md index 2ebb80515..8cd49d24b 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -10,14 +10,6 @@ See [github repo](https://github.com/FeralInteractive/gamemode). BSD-3-Clause licensed -## cmark - -The C reference implementation of CommonMark, a standardized Markdown spec. - -See [github_repo](https://github.com/commonmark/cmark). - -BSD2 licensed. - ## javacheck Simple Java tool that prints the JVM details - version and platform bitness. @@ -99,22 +91,6 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith Public domain (the author disclaimed the copyright). -## QR-Code-generator - -A simple library for generating QR codes - -See [github repo](https://github.com/nayuki/QR-Code-generator). - -MIT - -## libarchive - -Multi-format archive and compression library. - -See [github repo](https://github.com/libarchive/libarchive). - -BSD 2-Clause license with some exception. - ## rainbow Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiaddons.html). Used for adaptive text coloring. @@ -127,14 +103,6 @@ A Prism Launcher-specific library for probing system information. Apache 2.0 -## tomlplusplus - -A TOML language parser. Used by Forge 1.14+ to store mod metadata. - -See [github repo](https://github.com/marzer/tomlplusplus). - -Licenced under the MIT licence. - ## qdcss A quick and dirty css parser, used by NilLoader to store mod metadata. diff --git a/libraries/cmark b/libraries/cmark deleted file mode 160000 index 3460cd809..000000000 --- a/libraries/cmark +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3460cd809b6dd311b58e92733ece2fc956224fd2 diff --git a/libraries/extra-cmake-modules b/libraries/extra-cmake-modules deleted file mode 160000 index 1f820dc98..000000000 --- a/libraries/extra-cmake-modules +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1f820dc98d0a520c175433bcbb0098327d82aac6 diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus deleted file mode 160000 index c4369ae1d..000000000 --- a/libraries/tomlplusplus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c4369ae1d8955cae20c4ab40b9813ef4b60e48be diff --git a/libraries/zlib b/libraries/zlib deleted file mode 160000 index 51b7f2abd..000000000 --- a/libraries/zlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf From 38afa3a94c12a73de83b38d9cba948f7490e8e65 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Thu, 27 Nov 2025 16:19:51 -0500 Subject: [PATCH 49/59] build: remove vendored gamemode code Signed-off-by: Seth Flynn --- .../setup-dependencies/linux/action.yml | 2 +- CMakeLists.txt | 6 +- launcher/CMakeLists.txt | 2 +- libraries/README.md | 8 - libraries/gamemode/CMakeLists.txt | 7 - libraries/gamemode/include/gamemode_client.h | 337 ------------------ nix/unwrapped.nix | 6 +- nix/wrapper.nix | 2 +- 8 files changed, 9 insertions(+), 361 deletions(-) delete mode 100644 libraries/gamemode/CMakeLists.txt delete mode 100644 libraries/gamemode/include/gamemode_client.h diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index e47941ce3..c1cc517a3 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -11,7 +11,7 @@ runs: sudo apt-get -y install \ dpkg-dev \ ninja-build extra-cmake-modules pkg-config scdoc \ - cmark libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ + cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ appstream libxcb-cursor-dev # TODO(@getchoo): Install with the above when all targets use Ubuntu 24.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cefd727a..323ef58d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,6 +314,11 @@ endif() find_package(cmark REQUIRED) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig REQUIRED) + pkg_check_modules(gamemode REQUIRED IMPORTED_TARGET gamemode) +endif() + # Find libqrencode ## NOTE(@getchoo): Never use pkg-config with MSVC since the vcpkg port makes our install bundle fail to find the dll if(MSVC) @@ -447,7 +452,6 @@ add_subdirectory(libraries/javacheck) # java compatibility checker add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions -add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(libraries/qdcss) # css parser diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 37e1ebbdb..02fc1321a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1336,7 +1336,7 @@ else() target_link_libraries(Launcher_logic LibArchive::LibArchive) endif() -if (UNIX AND NOT CYGWIN AND NOT APPLE) +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(Launcher_logic gamemode ) diff --git a/libraries/README.md b/libraries/README.md index 8cd49d24b..df4d251f7 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -2,14 +2,6 @@ This folder has third-party or otherwise external libraries needed for other parts to work. -## gamemode - -A performance optimization daemon. - -See [github repo](https://github.com/FeralInteractive/gamemode). - -BSD-3-Clause licensed - ## javacheck Simple Java tool that prints the JVM details - version and platform bitness. diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt deleted file mode 100644 index 61195ac21..000000000 --- a/libraries/gamemode/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.15) -project(gamemode - VERSION 1.6.1) - -add_library(gamemode) -target_include_directories(gamemode PUBLIC include) -target_link_libraries(gamemode PUBLIC ${CMAKE_DL_LIBS}) diff --git a/libraries/gamemode/include/gamemode_client.h b/libraries/gamemode/include/gamemode_client.h deleted file mode 100644 index b186cd489..000000000 --- a/libraries/gamemode/include/gamemode_client.h +++ /dev/null @@ -1,337 +0,0 @@ -/* - -Copyright (c) 2017-2019, Feral Interactive -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Feral Interactive nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - */ -#ifndef CLIENT_GAMEMODE_H -#define CLIENT_GAMEMODE_H -/* - * GameMode supports the following client functions - * Requests are refcounted in the daemon - * - * int gamemode_request_start() - Request gamemode starts - * 0 if the request was sent successfully - * -1 if the request failed - * - * int gamemode_request_end() - Request gamemode ends - * 0 if the request was sent successfully - * -1 if the request failed - * - * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and - * destruction, as appropriate. In this configuration, errors will be printed to stderr - * - * int gamemode_query_status() - Query the current status of gamemode - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * const char* gamemode_error_string() - Get an error string - * returns a string describing any of the above errors - * - * Note: All the above requests can be blocking - dbus requests can and will block while the daemon - * handles the request. It is not recommended to make these calls in performance critical code - */ - -#include -#include - -#include -#include - -#include - -static char internal_gamemode_client_error_string[512] = { 0 }; - -/** - * Load libgamemode dynamically to dislodge us from most dependencies. - * This allows clients to link and/or use this regardless of runtime. - * See SDL2 for an example of the reasoning behind this in terms of - * dynamic versioning as well. - */ -static volatile int internal_libgamemode_loaded = 1; - -/* Typedefs for the functions to load */ -typedef int (*api_call_return_int)(void); -typedef const char* (*api_call_return_cstring)(void); -typedef int (*api_call_pid_return_int)(pid_t); - -/* Storage for functors */ -static api_call_return_int REAL_internal_gamemode_request_start = NULL; -static api_call_return_int REAL_internal_gamemode_request_end = NULL; -static api_call_return_int REAL_internal_gamemode_query_status = NULL; -static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; - -/** - * Internal helper to perform the symbol binding safely. - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol(void* handle, - const char* name, - void** out_func, - size_t func_size, - bool required) -{ - void* symbol_lookup = NULL; - char* dl_error = NULL; - - /* Safely look up the symbol */ - symbol_lookup = dlsym(handle, name); - dl_error = dlerror(); - if (required && (dl_error || !symbol_lookup)) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlsym failed - %s", dl_error); - return -1; - } - - /* Have the symbol correctly, copy it to make it usable */ - memcpy(out_func, &symbol_lookup, func_size); - return 0; -} - -/** - * Loads libgamemode and needed functions - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_load_libgamemode(void) -{ - /* We start at 1, 0 is a success and -1 is a fail */ - if (internal_libgamemode_loaded != 1) { - return internal_libgamemode_loaded; - } - - /* Anonymous struct type to define our bindings */ - struct binding { - const char* name; - void** functor; - size_t func_size; - bool required; - } bindings[] = { - { "real_gamemode_request_start", (void**)&REAL_internal_gamemode_request_start, sizeof(REAL_internal_gamemode_request_start), - true }, - { "real_gamemode_request_end", (void**)&REAL_internal_gamemode_request_end, sizeof(REAL_internal_gamemode_request_end), true }, - { "real_gamemode_query_status", (void**)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, - { "real_gamemode_error_string", (void**)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), true }, - { "real_gamemode_request_start_for", (void**)&REAL_internal_gamemode_request_start_for, - sizeof(REAL_internal_gamemode_request_start_for), false }, - { "real_gamemode_request_end_for", (void**)&REAL_internal_gamemode_request_end_for, sizeof(REAL_internal_gamemode_request_end_for), - false }, - { "real_gamemode_query_status_for", (void**)&REAL_internal_gamemode_query_status_for, - sizeof(REAL_internal_gamemode_query_status_for), false }, - }; - - void* libgamemode = NULL; - - /* Try and load libgamemode */ - libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); - if (!libgamemode) { - /* Attempt to load unversioned library for compatibility with older - * versions (as of writing, there are no ABI changes between the two - - * this may need to change if ever ABI-breaking changes are made) */ - libgamemode = dlopen("libgamemode.so", RTLD_NOW); - if (!libgamemode) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlopen failed - %s", dlerror()); - internal_libgamemode_loaded = -1; - return -1; - } - } - - /* Attempt to bind all symbols */ - for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { - struct binding* binder = &bindings[i]; - - if (internal_bind_libgamemode_symbol(libgamemode, binder->name, binder->functor, binder->func_size, binder->required)) { - internal_libgamemode_loaded = -1; - return -1; - }; - } - - /* Success */ - internal_libgamemode_loaded = 0; - return 0; -} - -/** - * Redirect to the real libgamemode - */ -__attribute__((always_inline)) static inline const char* gamemode_error_string(void) -{ - /* If we fail to load the system gamemode, or we have an error string already, return our error - * string instead of diverting to the system version */ - if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { - return internal_gamemode_client_error_string; - } - - return REAL_internal_gamemode_error_string(); -} - -/** - * Redirect to the real libgamemode - * Allow automatically requesting game mode - * Also prints errors as they happen. - */ -#ifdef GAMEMODE_AUTO -__attribute__((constructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_start(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - if (REAL_internal_gamemode_request_start() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -#ifdef GAMEMODE_AUTO -__attribute__((destructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_end(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - if (REAL_internal_gamemode_request_end() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_query_status missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status(); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_start_for == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_request_start_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_start_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_end_for == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_request_end_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_end_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status_for == NULL) { - snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), - "gamemode_query_status_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status_for(pid); -} - -#endif // CLIENT_GAMEMODE_H diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 7d461a072..e5fc0bad4 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -15,12 +15,8 @@ tomlplusplus, zlib, msaClientID ? null, - gamemodeSupport ? stdenv.hostPlatform.isLinux, libarchive, }: -assert lib.assertMsg ( - gamemodeSupport -> stdenv.hostPlatform.isLinux -) "gamemodeSupport is only available on Linux."; let date = @@ -83,7 +79,7 @@ stdenv.mkDerivation { tomlplusplus zlib ] - ++ lib.optional gamemodeSupport gamemode; + ++ lib.optional stdenv.hostPlatform.isLinux gamemode; cmakeFlags = [ # downstream branding diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 01edd0d9a..4ca2d7b3b 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -51,7 +51,7 @@ assert lib.assertMsg ( ) "textToSpeechSupport only has an effect on Linux."; let - prismlauncher' = prismlauncher-unwrapped.override { inherit msaClientID gamemodeSupport; }; + prismlauncher' = prismlauncher-unwrapped.override { inherit msaClientID; }; in symlinkJoin { From 0704027e6c1976b7ed675f861294a13e110c15de Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Fri, 7 Nov 2025 01:01:11 -0500 Subject: [PATCH 50/59] build: install 256x256 png icons This is required by `go-appimage` - and apparently(?) a requirement by XDG, with SVGs being optional Signed-off-by: Seth Flynn --- CMakeLists.txt | 1 + program_info/CMakeLists.txt | 1 + .../org.prismlauncher.PrismLauncher_256.png | Bin 0 -> 11387 bytes 3 files changed, 2 insertions(+) create mode 100755 program_info/org.prismlauncher.PrismLauncher_256.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d8484d60..16468f5d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -406,6 +406,7 @@ elseif(UNIX) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps") + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_PNG_256} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/256x256/apps" RENAME "${Launcher_AppID}.png") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "share/${Launcher_Name}") diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index db6920e20..23eb840b1 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -29,6 +29,7 @@ set(Launcher_SVGFileName "${Launcher_SVGFileName}" PARENT_SCOPE) set(Launcher_Desktop "program_info/${Launcher_AppID}.desktop" PARENT_SCOPE) set(Launcher_mrpack_MIMEInfo "program_info/modrinth-mrpack-mime.xml" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/${Launcher_AppID}.metainfo.xml" PARENT_SCOPE) +set(Launcher_PNG_256 "program_info/${Launcher_AppID}_256.png" PARENT_SCOPE) set(Launcher_SVG "program_info/${Launcher_SVGFileName}" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/prismlauncher.icns" PARENT_SCOPE) set(Launcher_Branding_ICO "program_info/prismlauncher.ico") diff --git a/program_info/org.prismlauncher.PrismLauncher_256.png b/program_info/org.prismlauncher.PrismLauncher_256.png new file mode 100755 index 0000000000000000000000000000000000000000..6f164febed2a6c57869e4eceb949bb1b405aac47 GIT binary patch literal 11387 zcmaiaby!qi)b5!fhLrAZl+?hugt=J(zE z_r1^gW1ln6+RxtSUGG_Ytu=4#YYin_EJ`c@0C1IG$!h}u2zd(vFwl{MiASX!azM9I zRgwpu{=161KcxczJ)kTv^Tsd#sK6Jd`|Ho&I>il z(YMIw`b66H_|HMsLw@tTnUf2Y9v=f09QOYjf-UW+=y{`8T)Mqcv8USFdA6SC)#&3s z^$|fvTa+zc$^z?=`sSwWB6zAFuocZAeYW((0GwrF$M;d02G)vk=qiT-c^cEJnp6|e zvmloRsK*=PjK}TeR{%E}*nRp_cRb+rv5+7z)8=79F7*0F@sBXps2m0i#V}yCCKAx7I8s1ful#tq2?EH8P0PE?8p9*r!1rMn3>Rsz?vnoCvK%5@Et77jK zrIdNmFA^XhzW6{&(qW_bY?Tyvmp%rvzy$5egVtD5%%m>yG-vZ%)Vk-G2@mdJfL1XG7UGS`OtA-PA-w(0ff~_h zXvOJ7z}-L!{QF9LnOq3crr))d`!?>`6|q%H?F11vHk6GdWkC|jrd`TLTz5uUz-qMN zrvej>sM@#S?5{d;mCNLsuKThCE%>FWVv3nInPzm=%{cA>-@GBdtrv=sK?GyOx&?*vXl7xq#O+c#!seR-4T(-OoNd zDr0ul{v{UINivA#7<{)IJezJ^RXwwNjo#t)neN`xtn|R9U**ls~*2pF^|rT(C9s zH$lwyFNSG@&X-x5jOI;zSvW(?Hu3V&834Aa`V2StRPZ9i!-wrSGekR(xqdksb*>FMQl2p~d!tf##A>A$sO`Xg$I z&SU^T%L}eDTV->sI@QsZ0f+om(C|Ci@4EnVZopldcSre?X0IvF@up)oO^g%B;M>*} z4*FlV{#^+068V1U8u+j(8JAZ-v~O?uNg~Nily9GGeYualqk2>{{qcUmgT;{P#oIB@8&W#J5zIub5l(L1e7gYmJ0Hq_B)C_6GF# zR$}EE@S^3oZ#fUA1A_+7oOP*BuJFUS5CIos4QZ#(F?{7XLzL*1BQHyH$iON%H?F_9 zj}c=>xrplb(eucS~JSVPM4+dMFnbHFg$l)rcx?v~j`D zxmwjAIxT!w$+E~Tq#05Rzdb{NQ6(&YO>5BczZw@)RDVmHbOxB!m}F3}T+w4Zp+K;x zcn-Hg%;j%~sooj2x-qV5YG4tHTa_S#Pu4-HIAVoh)3ZO2FdrS=hIB}9f5jeKJUp^< z_M@+fQw2kP?;I`MRpgHzl>11`%B?@!65Zz<(Do=Be!-^;XxL?{_7ymvH4xcRO`AL4 zHJN;~oswOyJ|c{lmP)aa4Jg^V4ds4q`nOqrx9~76wsv+w2aWyfO~r2I!R92!+Zw4gAhffWDw# zA5Rjv|6RHGsL(gOFX>tKGY&hR3&CE+YIJd@^f?7QqMI4`s?E`)S!GVz1%2XatWdJ9 zfh^snpz7l}9~sWaHslQ#9 zfj|NQmLg{M-1PW`iu5@zjPAQtix|n*0N}yl!q(dhNbbzT4jueCVm%F~Cp8#E zxjDK*sKievSD~L;#|+S2my<;nG~I+2^w8UKYrW3Re)RV!#{XK5XyjwV=Vh z8eIr-k|mUtRD5Qg1^nC#ReSCJF1k*LAn@UvY)4;fxih+%wCSuO??zi#%<9)k>0RYH z2(>vkST1BCryTlPGZTy69DFSL`>7@+qp{;kK&(4c^1<@xc zg^C!au8X+y`QBtVz1U7IPxx&3swfM0n=mZY+LB$JF8a*hQ!7gfMCcj3XRbVh|NHNA zFkMK(&(xlrmYf{H=##)!m3NfCp z4PjIvP~*;n`PuB1HbTx0#BhF!Qe>bP_i^}+?CSzqr-cY|5WVBG1u{+IW<#02gI}+x z^CJ)2|GDk3l3Oa8tfi*&zOWZ(B{hj7_SGvgJK!ExxWd(^-#A~ys9MB$>JfqBwG1pN zDl^*jBkIk`*x$=gu*pnFB#MkWaO2?3w=NbMg|zV zby6)fi9nw#(7)PcrzVQ2J;?(Z#iDvApzaU6zf{Y@)`{l&yx~ar#05LfTly-E?Q|M4 z34?y5GxR)95uEL+Y$|Y5#>`0(AtS=xVUp2U6DLzWrMG@mD<6Br0)G|hz21JiTk`0= zKgRiN=-0aI%8qJrlT??ZdJ)E|;T_KJ9KKeGGNo)v4oUP*>1a45Gf%B8(8%3)frl zZJvo`{eBFFL(A?iI$rB?e36z&Xi}ya?qVk!wT}I1J7Z}`Q#!U{mPhS?x}gkH~}BVfI=Ls_WQeCAr9NKcJ?Nc4=a+4d>7Cnk>Vy*?Ii3b|o8j~142g0S&9M3B#F5yqrT z31pjPh+n>^O`f{Lo*#;P=7Lds@*(4@X&wHIj^BQh!8?fZQ*MIot3ff*oaQ5;u@yF+v&7mklzb@|?v1RvQ{CZJ^ z)(IR;1evGQMPC7Yt#4XKkBmdImH$GOQu(ED7aPy%SrAOs3oQP;$S;$X#01^YBV-(f zg9hzi6r}jX1oE(v37w^tk?|0yEF*7{Nt>eKyzqNAF47>+IWdVBk5S!ukD-f}%Etw9 z+_5tfm*N$*CgkSqB=}rN-Kfq-svITgE?iEKUgC0NKKVxz2yw3o`wmnm$k4D{zX%q& zx*(cw84E&#$`(?re5IY6>S_UfFW?%^FG${83(~Q?v^K(I};$)E8&K?g|dpAuKr!UhdtwHbpioaoJv#-D@Dl#;T zvz@Q~6ibaW2QPNfI^GJk%7%H9Dg@kpzA`Coh*r5>fvKYsXk*UN5P9uj4O(>$a>b;3 zCpSiyM1PczW1}_^mSJ(f&Y?yVOA$fJAW%Pi+U^p@ESKkjxe)VXQuB&JCf6VKqH!pc8B5he(4*qFY=%n`cL>vOuK(G)~I>g$0?E%Vp8 z+dyeXixV?sgk$?E6g`O+ZalaZh?ic%9GEjKUr$VdZ!qXpl-;VEF-~mrZ{WonaBgYCge$a>R1@g#nnTLy;ffuW~7+1e&KqiN+o!@c1 zvBKOFKh(XhMwgh5Aw4D!$N9(JSmYRZvd zuk)`J@BF_Nx@cO?NS%dxJ4? z{zYAt?C|^p`EAtGd$WHG9TJYEsOaA1{bg$Nua`y#T)57NfL#hbakV23c_=b`_C_0} zhL~MFw;dw%v>aSjAkPdAi`5(CYrvjXm=7nxjkx+l_bvZrKj2c=->f$O%8R~Weql+9 z7G11>GYy3o-|M}`bBQb#5pJ4C$&B(u0Yq0@bYc#U*mK<92NR&TO=W7?VQDV+!_t^w z_Y0s+vJ@c+>`WT^m|VAR-2$_gapF!4O5 z=SjLjJWdUDNf=XaI)4qm-R(%)-nN_dJ1^Nb7Cy3p;)fxO)^|a0yJ)WNGMj!>0B7wr zMiGrDY@dj&_<9!07~eAnBMb@*CDTPu?4ZcVX5b1TIJtQTYftcd@G-R-8Y$t;>83Xd zs_z)XCk_`pBHLoS@Ex zbUMPXG!-L>`en>!260A4=Jzeqc2GSmX7%FVZ`0uYM-hclibPAzDDuNrwAFT3KI}nq zLY;tT!!6A%rEi}?%I)n++sc`1Tm>lEtE;OW-o17MZ+iul}0(m0*VJhVQ46W ztis8m9!4w4xq`Y1V`m!M$GRg9b zjiS9$ISPH3PMElUS|ayKSzW>1@wrL&CG$EOWBAGm5_YP|`WN+YVEfh%x&L@227#FG zg(6=0VdWuyfO$AZbr|sF>O@J;M;I$kJ`@T#I5|bk9~}LX>R}U9iCtF6APw^xJQ6l3 zOlBC8TqjJoPES$jc;;%&bT37R6&IIMxK>%UXGWB$h{T=X+0d~EzSVO*M>z@!@}4=C zd(!6-BaY^gBSglZ6m^O7MFWV{m6h4KIpk!+H}WfHN)N=1jEq*V@2sU;E(#@pvrfvq zjZXtMpOoFJko}zTruB>_L8O;t(hiLkgIUFAe2tBaZa!)Xc%Fo;h9jY#Hf=fXb)z(S z7ozFFC_8yF6xJP>n)xCAJ{tM#|s2F{}~JA zKz(0yYEFJa+O)QxGb!DqH;q&gUP#YV!d_>66tyop0J~Xc8WJEFW>wD)OdfH$pJ7>= zi;{ypqn8vQdOtAG5`%{tAtNXvA~%!ZilypS2mvGlfczn0wAL_MsJ{4297>4vITlt* zf%Vuye<#O`5TVDPiSY^{eyjV0g!tZ^4M)*1lcdvx7HIVeEj%ICC&wH4?5zN*c(d=9PWZd`ABjM zCs9OmUy0r;1QnN;&*Q3f^rc)b8(2l0RMIU>pum{8ZN)LM0|C>2FRMk#sb4R;b0X)5 z=g&ogfr(~j4bUx&dGUbjZ|k9f7BMn5M9MOZlMd2K_&Ej-MdiZ&1JP~<&~Y6}^3U3% zL=!vO{YLb!?E-VQ=v)aIeIE7DjU zw){laeN0R@!<5-E%Kl)>rGX97PWvN@8FjD8BXr+U;%%uj^no0O;ImK zh?M_LHA^AbqNs1fY#q)Pzu~rdzSEW!37LdMLM(#48f03mR_e6zVp#*Ch35b>eYHBe|Li4Q0To;BU@1NJwef1_3`QHL(C-{EjulVZoUQRTAIZ z4hJ4W3*>CJxytGy-@-ee90f)r39wawQ($NU`Dz_4~Os=_duO{!Z6V2`#W4uR{8#Y84tGGi z9TzVFM%l_=-{$Sa3WQk>X9ugVhye6ucVt)8<&m^e9p5X0ND}~H?n@dMFVVfqfhNIx zUIeO)h~WEzy@I{bBrq~e1AyQ>t-$~Nm7+ZJupP~<0^(NsHhbf^z!QwR>R0#OS)dIr zP&mkk2Mg~S6i&ZYI(?7OX&_Pfeo_=lSX5aZ6EYV^_NO|mv_;w({EaxM!-@P$GO_H8 z-(Xv!T$=8?DfM@>06}B$s}`2>64qkbUz&eyyiA9B9v_uv_2`Cd_092G5ia5H&*Do7 z%vP2nn*)PBHG$a*N=k&O#uE3~*&XHIg^^ioFHm5=DJX)#U9stZNHsWywxq~bUU4|+ zjGGv#$S>3#uT+ZH;jOgo`4DH~@DKOiRMfD3K7v{vbMz8WV57c%UimievhAi*+uRd8A^lIa%KYM+j=&UWU&R-CT66!> zp@Egm*G`OIu5iBF-$58C|GG6L;c+KhN#nuOQ#RwH4pXz$Q3YGRRJ!WAQ@Zx(v&V&J zKHXqn9W90(eDkN8o))ljvdJfk+rK%uMSnxR9|~PsNsC*(S{vqamX%RvV*WupCilft zr%kAIwL_re=@z>WAoLsOdkrSQ{S`u@#pR=U+;oX!;(e4TdXWE_k1Cx^+0oJN!z5E? z5!%`BLDrk!R`b?_M_RAn=p|QC7ya5maY;6D_r=X-@GqP>J1$TtH8)cq7&TNy7Afkp-EDho}2VYNd74BA^OQZ zVPyS8#h4dUw=;Tu_q$ak*8GlTj|Eh6;_TlyYeii8_#1sBY>t)KFCPBvTt~aHNB##} zi~hUbW^?n%`%=OU)>a1G5VTO^m6(TLgNlbAs07w7DwujIG5-xDTBHeuw~NDtdU9)l zU2T+{e{?pyU&;n+>8tYWM^fc<4bE>OaxQ+M40gmJ+@NV}S{r+E%`NS?v~idLO4w<+jx< zVs+WpI4b;ukh&6c?N>6D){JvvCmE9^UecS)($C*WSdc*FIGY*qF*Tf!!ony=Cp{7| zaH}aPjeqGh^jATx#YFyhO3(AYfEn*A@Os7iBFR#@-nC^NDO%ZMuPwz(aeJSirgZ!7uiJZhw zr_AX7Ec}j)u{t`5jVbvog)QYOj-^7GqBDtOx7w1W>_ZAfp_gHSjmyNppuFe}9$sw( z$Ca)|w3;goY$v<_YF>fooDq!)$8y|#mGg5c^Im%RQ~HHpgZjC=b+E7s`Y%SVnVh%2 zGzkaFl;)tx1x&Z3iWf$K&1Rm%h2D??KdOVX^9Fm~t19KT^*mR4HjxSEbSsrc>9~2} z!1{uVdRkXT2E1oSbdi^%^eR!az3FiMVdh@zH1ya_8Vy$TqZ=dV7CqtaJ=Fg9=NR-3QdU@Uv3q5OTcJ$tQmG22Z- zcrz1AO@5(uk6h|@=ULM~MAV^#-TSwPkIydBKppxA!*_(!T4B zrp=!oe3f-lbxq?3Wj>-C+u@YVF~2#Yh#Pg5j{CX4s!VPm#EJ53Xt3dUjT^u@bPEkc zAq1&I6)t1}0EhWMEdXoziBV=m(ec1E8*TsQ=beqO+2+={jAa(87<4`LkU%M^X=~}o z(Obov3lDsC75-uI2!`U1&uJdF1vklr7;D=Iw4YGcOs=0`AaF5C(G}a|YW!~HUk*O5 z8EGc>N)4xv)7S}&8GUIVYbq8>aH@>CJNeZT_ebNoF_s`zqa^FaB=`48-Z zJ115wn-luhsDr^X+PG|?Wt;wm)D7{J+sbw4k1#;nD9B;VD`X~PyusR4PEX0m-MogU z4C`H#H{29>iCM+}JHAX?7qV}xE7+Wu^3KsZ!-4oq%y;g7H#E;)Rd zH}?ZW&m5mp0oPXwrmrPju1#fDkc=D{ZNG5;o*;J0%zSlo-7SXxhVg|@l|sQY!+O%maP&xbd=O!`ZbIjvk#v*rA+(ZUKCf$uR<`>?o} zSlbSbvTC82R{a|I{o9fJ)|DB-) zSHet+H*gH9gfqbFpLbdln>TnrB5roFVqnYv8^vy3R6*v4`bG}{#UzSkZ2Qr^%6cM= zADpv7G)h;qmWcfb(T>{jI{y7jMV@>Ac8pI85?lWCUVrQ|KCC2!PoobvRD3SZR2+3N zu(~SHFMN7y`r6PTn&0$OdVX4XJrS$r{i06FHbqolr_<@;xKSix^z$V4r;62^oYaa@ z7a<$vc;aVo4bIkGR_ku^Ky_sJrjKC@LS4v_vyY%_zTvHI* z(bx2H^!g8CDlbF&o>K9K1=hB5kNI2qZwo!2#Yu~0w>+eeC+#|rtSTs|!_g&`YpQ7P zXReMN(J5IB5jdiL{5e#gQCV43IHYOO&Mi_1f7jUc!Q+pFov~G{NyGJbgYVki``oXe%mXiXL1iykc8Ek zh*_Tt^)Gk;;|FXm;&WF3P%OkmBg6!m)tO^9Kw(;{-(PEg;zy(jSSsTcb8yV--p_nw z-%uEb2BocEFb&v;Id$BD*Gv~bsyE!OL!4JZUSXzu8%xIU-FaYiFMLn5>I4M9(sE#B z8$+hTW!z#5#toArrNoNyrXMQl`_3M`i&R!QT;)>sMM3 zxEGDsujYDMJ^Nz)993F6ym-18BBbN>NAvijVhU|7E|6M~pu+=@mq*a(t4JLk^q0>- zA8eyezMV~Wjq&l02=^BNUJVx8n;hf|7r;-X^~*yZAT`5=no8m=z1%Fn;&n}c!ygVG zm9<0?_j)u@ny+(uT?ERn2l)qSQSr08D+{2qPE^x*d3P7unS7jy?1FZ`}?cR~Pcj9szvcTSG%WYalsivT?NKFQ^K zBY^(FC7w@I?myc~K%GU>E_|L>@p|BEXQ=qP54M}QUUjt}icG@60;zmJP#B!wW^S&J z=J<+3hrCaFnIzH4_sQ!&F%Icyj$j(!KMEi_*9BWTe**+Sc{xy;`=Uesg%awirg_a% z;f*c2Bi~*5E~-y; z?EtS2F?=GctJ{G3;4cxRt$w@yt;+zFf=q&Su>@ey?NXiYfL?YNR07{+ab00b`il-b z1+4nFW+r~7nG^<2{fS}z?i?5pjjR&igLpcfUyy!(Y24$|n+|COxqrqr6c`Fz{=d7_3tfmVePwDFiwV))6DxFkC` zXb>sWL#@e?vc(mSY>FSq7or*p25HyRHdVvqD0F@W(GEaEmHWN|@Xd>ox z{!#wW5=c+Xow5K~EU0uK(}xr8kfAnZG<&2x5v~30+^oO+Z5gWCnnbz`7+#0nA5y(NpmV15Ds6a9X*rfk5gGg}37jbt)p< zcwi$_wI?3MVECM>TIk_kYv|=5fKHGTgOqo=EmnO_x0wv4zvTgH{`54MgNwm_iORRu z6J38dT!Mq^;YJuBH~e`M!H=&p7ir`#X#ju8o>Cq#6qkOFMYm$*<;^sbkep$~#L0NWtv)=RH0gKr;}i z%Xqc(X~ncFwkH$Zh!4hR<1}e|og_?MSe@#Fg3^g|WCiU@d$)U!I}03gfgg?1^;#73 zSB4-+4Q Date: Fri, 7 Nov 2025 02:04:29 -0500 Subject: [PATCH 51/59] build: use `go-appimage` to create appimages Signed-off-by: Seth Flynn --- .github/actions/package/linux/action.yml | 40 +++++-------------- .../setup-dependencies/linux/action.yml | 35 ++++++++-------- 2 files changed, 28 insertions(+), 47 deletions(-) diff --git a/.github/actions/package/linux/action.yml b/.github/actions/package/linux/action.yml index 74e1c2a14..bc69b1155 100644 --- a/.github/actions/package/linux/action.yml +++ b/.github/actions/package/linux/action.yml @@ -60,43 +60,21 @@ runs: run: | cmake --install ${{ env.BUILD_DIR }} --config ${{ inputs.build-type }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr - mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml - export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated - - export OUTPUT="PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" - - chmod +x linuxdeploy-*.AppImage - - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib - mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - - cp -r ${{ runner.workspace }}/Qt/${{ inputs.qt-version }}/gcc_*64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines - - cp /usr/lib/"$DEB_HOST_MULTIARCH"/libcrypto.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/"$DEB_HOST_MULTIARCH"/libssl.so.* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - cp /usr/lib/"$DEB_HOST_MULTIARCH"/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ - - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" - export LD_LIBRARY_PATH - - chmod +x AppImageUpdate-"$APPIMAGE_ARCH".AppImage - cp AppImageUpdate-"$APPIMAGE_ARCH".AppImage ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin - - export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage.zsync" + cp ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.{metainfo,appdata}.xml if [ '${{ inputs.gpg-private-key-id }}' != '' ]; then - export SIGN=1 - export SIGN_KEY=${{ inputs.gpg-private-key-id }} - mkdir -p ~/.gnupg/ - echo "$GPG_PRIVATE_KEY" > ~/.gnupg/private.key - gpg --import ~/.gnupg/private.key + echo "$GPG_PRIVATE_KEY" > privkey.asc + gpg --import privkey.asc + gpg --export --armor 9C7A2C9B62603299 > pubkey.asc else echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY fi - ./linuxdeploy-"$APPIMAGE_ARCH".AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg + appimagetool -s deploy "$INSTALL_APPIMAGE_DIR"/usr/share/applications/*.desktop + cp ~/bin/AppImageUpdate.AppImage "$INSTALL_APPIMAGE_DIR"/usr/bin/ + appimagetool "$INSTALL_APPIMAGE_DIR" - mv "PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" + mv "Prism_Launcher-$VERSION-$APPIMAGE_ARCH.AppImage" "PrismLauncher-Linux-$VERSION-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" - name: Package portable tarball shell: bash @@ -128,4 +106,4 @@ runs: uses: actions/upload-artifact@v5 with: name: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync - path: PrismLauncher-${{ runner.os }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync + path: Prism_Launcher-${{ inputs.version }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index c1cc517a3..bebc6bb93 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -12,7 +12,7 @@ runs: dpkg-dev \ ninja-build extra-cmake-modules pkg-config scdoc \ cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ - appstream libxcb-cursor-dev + libxcb-cursor-dev # TODO(@getchoo): Install with the above when all targets use Ubuntu 24.04 - name: Install tomlplusplus @@ -38,20 +38,17 @@ runs: - name: Setup AppImage tooling shell: bash + env: + GH_TOKEN: ${{ github.token }} run: | - declare -A appimage_deps - - deb_arch="$(dpkg-architecture -q DEB_HOST_ARCH)" - case "$deb_arch" in + # Determinate AppImage architecture to use + dpkg_arch="$(dpkg-architecture -q DEB_HOST_ARCH_CPU)" + case "$dpkg_arch" in "amd64") - appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"]="4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage" - appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"]="15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage" - appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"]="f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage" + APPIMAGE_ARCH="x86_64" ;; "arm64") - appimage_deps["https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-aarch64.AppImage"]="06706ac8189797dccd36bd384105892cb5e6e71f784f4df526cc958adc223cd6 linuxdeploy-aarch64.AppImage" - appimage_deps["https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-aarch64.AppImage"]="bf1c24aff6d749b5cf423afad6f15abd4440f81dec1aab95706b25f6667cdcf1 linuxdeploy-plugin-qt-aarch64.AppImage" - appimage_deps["https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-aarch64.AppImage"]="cf27f810dfe5eda41f130769e4a4b562b9d93665371c15ebeffb84ee06a41550 AppImageUpdate-aarch64.AppImage" + APPIMAGE_ARCH="aarch64" ;; *) echo "# 🚨 The Debian architecture \"$deb_arch\" is not recognized!" >> "$GITHUB_STEP_SUMMARY" @@ -59,9 +56,15 @@ runs: ;; esac - for url in "${!appimage_deps[@]}"; do - curl -LO "$url" - sha256sum -c - <<< "${appimage_deps[$url]}" - done + gh release download continuous \ + --repo probonopd/go-appimage \ + --pattern "appimagetool-*-$APPIMAGE_ARCH.AppImage" \ + --output ~/bin/appimagetool + chmod +x ~/bin/appimagetool + echo "$HOME/bin" >> "$GITHUB_PATH" - sudo apt -y install libopengl0 + gh release download \ + --repo AppImageCommunity/AppImageUpdate \ + --pattern "AppImageUpdate-$APPIMAGE_ARCH.AppImage" \ + --output ~/bin/AppImageUpdate.AppImage + chmod +x ~/bin/AppImageUpdate.AppImage From be25d3a6a571bd290c905b21c4cc0ae3fea84a39 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 29 Nov 2025 11:34:27 -0500 Subject: [PATCH 52/59] fix(launcher/updater): call architecture-agnostic AppImageUpdate Signed-off-by: Seth Flynn --- launcher/updater/prismupdater/PrismUpdater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 61c94046c..90c3abe6f 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -811,7 +811,7 @@ bool PrismUpdaterApp::callAppImageUpdate() auto appimage_path = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); QProcess proc = QProcess(); qDebug() << "Calling: AppImageUpdate" << appimage_path; - proc.setProgram(FS::PathCombine(m_rootPath, "bin", "AppImageUpdate-x86_64.AppImage")); + proc.setProgram(FS::PathCombine(m_rootPath, "bin", "AppImageUpdate.AppImage")); proc.setArguments({ appimage_path }); auto result = proc.startDetached(); if (!result) From b1b4b5d38a3e2424785193f81b50941f32ca97ea Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 29 Nov 2025 14:13:51 -0500 Subject: [PATCH 53/59] fix(launcher): set correct bin path for self-contained appimages Signed-off-by: Seth Flynn --- launcher/Application.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 9576ae695..0ed1cc71d 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -371,7 +371,25 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } QString origcwdPath = QDir::currentPath(); +#if defined(Q_OS_LINUX) + const QString binFilePath = applicationFilePath(); + const bool isAppImage = binFilePath.startsWith("/tmp/.mount_"); + // Yes, this can technically trigger the logic below if someone makes an AppImage with an actual launcher exe named "ld-linux" + // Please don't :) + const bool executedFromLinker = QFileInfo(binFilePath).fileName().startsWith("ld-linux"); + + // NOTE(@getchoo): In order for `go-appimage` to generate self-contained AppImages, it executes apps from a bundled linker at + // /lib64 + // This is not the path to our actual binary, which we want + QString binPath; + if (isAppImage && executedFromLinker) { + binPath = FS::PathCombine(applicationDirPath(), "../usr/bin"); + } else { + binPath = applicationDirPath(); + } +#else QString binPath = applicationDirPath(); +#endif { // Root path is used for updates and portable data From 428f8d6d80630a82e4ff6d7e1af55f26fdad2b2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 30 Nov 2025 00:31:21 +0000 Subject: [PATCH 54/59] chore(nix): update lockfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/117cc7f94e8072499b0a7aa4c52084fa4e11cc9b?narHash=sha256-%2BhBiJ%2BkG5IoffUOdlANKFflTT5nO3FrrR2CA3178Y5s%3D' (2025-11-20) → 'github:NixOS/nixpkgs/2fad6eac6077f03fe109c4d4eb171cf96791faa4?narHash=sha256-sKoIWfnijJ0%2B9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI%3D' (2025-11-27) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4676b8b69..5f4817048 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1763678758, - "narHash": "sha256-+hBiJ+kG5IoffUOdlANKFflTT5nO3FrrR2CA3178Y5s=", + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "117cc7f94e8072499b0a7aa4c52084fa4e11cc9b", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", "type": "github" }, "original": { From 5532976ea7bc025eedef52a639d77b6ca6af01a3 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:36:19 +0100 Subject: [PATCH 55/59] fix: Add back fallback to pkgconfig for tomlplusplus fixes a regression in #4405 Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16468f5d3..197839893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,12 @@ if(NOT LibArchive_FOUND) endif() find_package(tomlplusplus 3.2.0 REQUIRED) +# fallback to pkgconfig, important especially as many distros package toml++ built with meson +if(NOT tomlplusplus_FOUND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(tomlplusplus REQUIRED IMPORTED_TARGET tomlplusplus>=3.2.0) +endif() + find_package(ZLIB REQUIRED) From e8d33e5af1036d7fe630dd61f59f6d03b4fbfffd Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Wed, 3 Dec 2025 19:29:51 -0500 Subject: [PATCH 56/59] build: manually specify appimage update information Previously this targeted the wrong filename from our releases Signed-off-by: Seth Flynn --- .github/actions/package/linux/action.yml | 11 +++++++---- .github/actions/setup-dependencies/linux/action.yml | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/actions/package/linux/action.yml b/.github/actions/package/linux/action.yml index bc69b1155..2df591579 100644 --- a/.github/actions/package/linux/action.yml +++ b/.github/actions/package/linux/action.yml @@ -72,9 +72,12 @@ runs: appimagetool -s deploy "$INSTALL_APPIMAGE_DIR"/usr/share/applications/*.desktop cp ~/bin/AppImageUpdate.AppImage "$INSTALL_APPIMAGE_DIR"/usr/bin/ - appimagetool "$INSTALL_APPIMAGE_DIR" - - mv "Prism_Launcher-$VERSION-$APPIMAGE_ARCH.AppImage" "PrismLauncher-Linux-$VERSION-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" + # FIXME(@getchoo): Validate AppStream information when https://github.com/probonopd/go-appimage/pull/379 is merged + mkappimage \ + --no-appstream \ + --updateinformation "gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-$APPIMAGE_ARCH.AppImage.zsync" \ + "$INSTALL_APPIMAGE_DIR" \ + "PrismLauncher-Linux-$VERSION-${{ inputs.build-type }}-$APPIMAGE_ARCH.AppImage" - name: Package portable tarball shell: bash @@ -106,4 +109,4 @@ runs: uses: actions/upload-artifact@v5 with: name: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync - path: Prism_Launcher-${{ inputs.version }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync + path: PrismLauncher-${{ runner.os }}-${{ inputs.version }}-${{ inputs.build-type }}-${{ env.APPIMAGE_ARCH }}.AppImage.zsync diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index bebc6bb93..67a1a9826 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -60,7 +60,11 @@ runs: --repo probonopd/go-appimage \ --pattern "appimagetool-*-$APPIMAGE_ARCH.AppImage" \ --output ~/bin/appimagetool - chmod +x ~/bin/appimagetool + gh release download continuous \ + --repo probonopd/go-appimage \ + --pattern "mkappimage-*-$APPIMAGE_ARCH.AppImage" \ + --output ~/bin/mkappimage + chmod +x ~/bin/appimagetool ~/bin/mkappimage echo "$HOME/bin" >> "$GITHUB_PATH" gh release download \ From 04a405067d80d716600c3bd51a25b6d7ad3161f8 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sat, 6 Dec 2025 14:37:39 +0500 Subject: [PATCH 57/59] fix(LaunchController): correctly communicate if asking for offline name was successful Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 19 +++++++++++++------ launcher/LaunchController.h | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 2273318ee..ea9694056 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -148,8 +148,12 @@ bool LaunchController::askPlayDemo() return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok) +QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok) { + if (ok) { + *ok = false; + } + // we ask the user for a player name QString message = tr("Choose your offline mode player name."); if (demo) { @@ -166,9 +170,12 @@ QString LaunchController::askOfflineName(QString playerName, bool demo, bool& ok return {}; } - if (const QString name = dialog.getUsername(); !name.isEmpty()) { - usedname = name; - APPLICATION->settings()->set("LastOfflinePlayerName", usedname); + const QString name = dialog.getUsername(); + usedname = name; + APPLICATION->settings()->set("LastOfflinePlayerName", usedname); + + if (ok) { + *ok = true; } return usedname; } @@ -185,7 +192,7 @@ void LaunchController::login() if (m_demo) { // we ask the user for a player name bool ok = false; - auto name = askOfflineName("Player", m_demo, ok); + auto name = askOfflineName("Player", m_demo, &ok); if (ok) { m_session = std::make_shared(); static const QRegularExpression s_removeChars("[{}-]"); @@ -264,7 +271,7 @@ void LaunchController::login() bool ok = false; QString name; if (m_offlineName.isEmpty()) { - name = askOfflineName(m_session->player_name, m_session->demo, ok); + name = askOfflineName(m_session->player_name, m_session->demo, &ok); if (!ok) { tryagain = false; break; diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index af57994f5..50b72eb18 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -77,7 +77,7 @@ class LaunchController : public Task { void launchInstance(); void decideAccount(); bool askPlayDemo(); - QString askOfflineName(QString playerName, bool demo, bool& ok); + QString askOfflineName(QString playerName, bool demo, bool* ok = nullptr); bool reauthenticateAccount(MinecraftAccountPtr account); private slots: From 86fd58e6cbe6683e0482646041c7695eb651d9fb Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sat, 6 Dec 2025 16:34:59 +0500 Subject: [PATCH 58/59] Apply suggestions from code review Co-authored-by: TheKodeToad Signed-off-by: Octol1ttle --- launcher/LaunchController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index ea9694056..cbea045fc 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -150,7 +150,7 @@ bool LaunchController::askPlayDemo() QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok) { - if (ok) { + if (ok != nullptr) { *ok = false; } @@ -174,7 +174,7 @@ QString LaunchController::askOfflineName(QString playerName, bool demo, bool* ok usedname = name; APPLICATION->settings()->set("LastOfflinePlayerName", usedname); - if (ok) { + if (ok != nullptr) { *ok = true; } return usedname; From 1344dcb27e12005212aeb9fbd36b6cb30ad6faec Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 7 Dec 2025 12:45:35 +0500 Subject: [PATCH 59/59] EditorConfig: set continuation indent size in CMakeLists for IntelliJ Signed-off-by: Octol1ttle --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 56166b207..52510c515 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,3 +6,6 @@ root = true # C++ Code Style settings [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] cpp_generate_documentation_comments = doxygen_slash_star + +[CMakeLists.txt] +ij_continuation_indent_size = 4 \ No newline at end of file