From df943c93897af225710be5c08bd4090a9587cf25 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Wed, 15 Jul 2026 01:24:08 +0300 Subject: [PATCH 01/32] chore: bump patch version to 11.0.4 Signed-off-by: Trial97 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b3ed95df..464f022bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,7 +181,7 @@ set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 11) set(Launcher_VERSION_MINOR 0) -set(Launcher_VERSION_PATCH 3) +set(Launcher_VERSION_PATCH 4) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") From cbab259865c343a46b29b04d5df123e3aef696e8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 12 Jul 2026 12:12:10 +0300 Subject: [PATCH 02/32] fix: crash when trying to install a disabled modloader Signed-off-by: Trial97 (cherry picked from commit 6211625a1e3272d561efe04e518eb6b4ae5ebbb0) --- .../mod/tasks/GetModDependenciesTask.cpp | 2 +- .../ui/dialogs/ResourceDownloadDialog.cpp | 2 +- launcher/ui/pages/instance/ModFolderPage.cpp | 35 ++++++++++--------- launcher/ui/widgets/ModFilterWidget.cpp | 2 +- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp index 0859c9880..0b8fe2171 100644 --- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp +++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp @@ -37,7 +37,7 @@ static Version mcVersion(BaseInstance* inst) static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst) { - return static_cast(inst)->getPackProfile()->getSupportedModLoaders().value(); + return static_cast(inst)->getPackProfile()->getSupportedModLoaders().value_or(ModPlatform::ModLoaderTypes(0)); } static bool checkDependencies(std::shared_ptr sel, diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index bcb30c761..c222217d4 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -305,7 +305,7 @@ QList ModDownloadDialog::getPages() { QList pages; - auto loaders = static_cast(m_instance)->getPackProfile()->getSupportedModLoaders().value(); + auto loaders = static_cast(m_instance)->getPackProfile()->getSupportedModLoaders().value_or(ModPlatform::ModLoaderTypes(0)); if (ModrinthAPI::validateModLoaders(loaders)) { auto* page = ModrinthModPage::create(this, *m_instance); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 99c78647c..8bd082db5 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -167,10 +167,8 @@ void ModFolderPage::downloadMods() } auto* profile = static_cast(m_instance)->getPackProfile(); - if (!profile->getModLoaders().has_value()) { - if (handleNoModLoader()) { - return; - } + if (!profile->getModLoaders().has_value() && handleNoModLoader()) { + return; } m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); @@ -227,10 +225,8 @@ void ModFolderPage::updateMods(bool includeDeps) } auto* profile = static_cast(m_instance)->getPackProfile(); - if (!profile->getModLoaders().has_value()) { - if (handleNoModLoader()) { - return; - } + if (!profile->getModLoaders().has_value() && handleNoModLoader()) { + return; } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); @@ -337,10 +333,8 @@ void ModFolderPage::changeModVersion() } auto* profile = static_cast(m_instance)->getPackProfile(); - if (!profile->getModLoaders().has_value()) { - if (handleNoModLoader()) { - return; - } + if (!profile->getModLoaders().has_value() && handleNoModLoader()) { + return; } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); @@ -434,12 +428,21 @@ inline bool ModFolderPage::handleNoModLoader() // Should be safe auto* profile = static_cast(this->m_instance)->getPackProfile(); InstallLoaderDialog dialog(profile, QString(), this); - bool ret = dialog.exec() != 0; + // true if the user went through the install loader dialog + // false if the dialog got canceled/closed + bool dialogAccepted = dialog.exec() != 0; this->m_container->refreshContainer(); - // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed - // and false if the user went through and installed a loader - return !ret; + if (!dialogAccepted) { + return true; + } + if (!profile->getModLoaders().has_value()) { + CustomMessageBox::selectable( + this, tr("Error"), tr("No mod loader was installed. Please try again."), QMessageBox::Warning) + ->show(); + return true; + } + return false; } // Nothing happens the dialog is already closing // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 6fab2b2a5..7ad21ebdb 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -234,7 +234,7 @@ void ModFilterWidget::prepareBasicFilter() loaders |= ModPlatform::getModLoaderFromString(loader); } } else { - loaders = m_instance->getPackProfile()->getSupportedModLoaders().value(); + loaders = m_instance->getPackProfile()->getSupportedModLoaders().value_or(ModPlatform::ModLoaderTypes(0)); } ui->neoForge->setChecked(loaders & ModPlatform::NeoForge); ui->forge->setChecked(loaders & ModPlatform::Forge); From bbadd30ba10fe30374da7f40dd1169e8f91c6ae5 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 12 Jul 2026 12:23:48 +0300 Subject: [PATCH 03/32] fix: enable the installed modloader Signed-off-by: Trial97 (cherry picked from commit 7f277bb1fd35553be9bb25113ab20b427b5b4b3c) --- launcher/ui/dialogs/InstallLoaderDialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index deb5358fb..729b78fb7 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -163,6 +163,9 @@ void InstallLoaderDialog::done(int result) auto* page = pageCast(container->selectedPage()); if (page->selectedVersion()) { profile->setComponentVersion(page->id(), page->selectedVersion()->descriptor()); + if (auto component = profile->getComponent(page->id())) { + component->setEnabled(true); + } profile->resolve(Net::Mode::Online); } } From d406e005c95da3232ffbaf677328232dd49b8aea Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 22 Jul 2026 16:24:56 +0500 Subject: [PATCH 04/32] fix existing instance shortcuts not being detected Signed-off-by: Octol1ttle (cherry picked from commit ca3b86153d256dcaff19314da8c9fd46e57a843c) --- launcher/BaseInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0080cc516..77b444329 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -446,7 +446,7 @@ QList BaseInstance::shortcuts() const QString shortcutName = dict["name"].toString(); QString filePath = dict["filePath"].toString(); - if (!QDir(filePath).exists()) { + if (!QFileInfo::exists(filePath)) { qWarning() << "Shortcut" << shortcutName << "for instance" << name() << "have non-existent path" << filePath; continue; } From 828cbc6da08fac3497855530fe7020e6b96d09d1 Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Wed, 22 Jul 2026 12:40:20 +0530 Subject: [PATCH 05/32] Fix instance shortcuts breaking when the instance is renamed Signed-off-by: Vishrut Sachan (cherry picked from commit 78e9067972311691c48caa4572a24673249dc81d) --- launcher/BaseInstance.cpp | 9 +++++++++ launcher/BaseInstance.h | 1 + launcher/InstanceList.cpp | 2 +- launcher/minecraft/ShortcutUtils.cpp | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0080cc516..df96c80cb 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "Application.h" #include "Json.h" @@ -89,6 +90,9 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptrregisterSetting("linkedInstances", "[]"); m_settings->registerSetting("shortcuts", QString()); + m_settings->registerSetting("uuid", QString()); + if (m_settings->get("uuid").toString().isEmpty()) + m_settings->set("uuid", QUuid::createUuid().toString(QUuid::Id128)); // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); @@ -269,6 +273,11 @@ QString BaseInstance::id() const return QFileInfo(instanceRoot()).fileName(); } +QString BaseInstance::uuid() const +{ + return m_settings->get("uuid").toString(); +} + bool BaseInstance::isRunning() const { return m_isRunning; diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9280d2e1c..42657d7e5 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -115,6 +115,7 @@ class BaseInstance : public QObject { /// The instance's ID. The ID SHALL be determined by LAUNCHER internally. The ID IS guaranteed to /// be unique. virtual QString id() const; + virtual QString uuid() const; void setMinecraftRunning(bool running); void setRunning(bool running); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 1339499c7..41747fdc0 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -612,7 +612,7 @@ BaseInstance* InstanceList::getInstanceById(QString instId) const if (instId.isEmpty()) return nullptr; for (auto& inst : m_instances) { - if (inst->id() == instId) { + if (inst->id() == instId || inst->uuid() == instId) { return inst.get(); } } diff --git a/launcher/minecraft/ShortcutUtils.cpp b/launcher/minecraft/ShortcutUtils.cpp index b719e3142..7d662fe8b 100644 --- a/launcher/minecraft/ShortcutUtils.cpp +++ b/launcher/minecraft/ShortcutUtils.cpp @@ -146,7 +146,7 @@ bool createInstanceShortcut(const Shortcut& shortcut, const QString& filePath) QMessageBox::critical(shortcut.parent, QObject::tr("Create Shortcut"), QObject::tr("Not supported on your platform!")); return false; #endif - args.append({ "--launch", shortcut.instance->id() }); + args.append({ "--launch", shortcut.instance->uuid() }); args.append(shortcut.extraArgs); QString shortcutPath = FS::createShortcut(filePath, appPath, args, shortcut.name, iconPath); From 8962e6041a01d2b52b654a67f42f8ec1ee47bb3f Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Wed, 22 Jul 2026 16:24:19 +0530 Subject: [PATCH 06/32] Regenerate UUID on instance duplication Signed-off-by: Vishrut Sachan (cherry picked from commit 8c85c2f64b4d819c5109d217e4d9eb858009a5f9) --- launcher/BaseInstance.cpp | 8 +++++++- launcher/BaseInstance.h | 1 + launcher/InstanceCopyTask.cpp | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index df96c80cb..5189de871 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -91,8 +91,9 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptrregisterSetting("linkedInstances", "[]"); m_settings->registerSetting("shortcuts", QString()); m_settings->registerSetting("uuid", QString()); - if (m_settings->get("uuid").toString().isEmpty()) + if (m_settings->get("uuid").toString().isEmpty()) { m_settings->set("uuid", QUuid::createUuid().toString(QUuid::Id128)); + } // Game time override auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); @@ -278,6 +279,11 @@ QString BaseInstance::uuid() const return m_settings->get("uuid").toString(); } +void BaseInstance::regenerateUuid() +{ + m_settings->set("uuid", QUuid::createUuid().toString(QUuid::Id128)); +} + bool BaseInstance::isRunning() const { return m_isRunning; diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 42657d7e5..8bcb5fc4b 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -116,6 +116,7 @@ class BaseInstance : public QObject { /// be unique. virtual QString id() const; virtual QString uuid() const; + void regenerateUuid(); void setMinecraftRunning(bool running); void setRunning(bool running); diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index e32cdf095..802b548ef 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -151,6 +151,7 @@ void InstanceCopyTask::copyFinished() BaseInstance* inst(new NullInstance(m_globalSettings, std::move(instanceSettings), m_stagingPath)); inst->setName(name()); inst->setIconKey(m_instIcon); + inst->regenerateUuid(); if (!m_keepPlaytime) { inst->resetTimePlayed(); } From 7ea3dbb20b72bd6c81c58bab9fe768f7e1466cfa Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Wed, 22 Jul 2026 16:57:18 +0530 Subject: [PATCH 07/32] Use regenerateUuid() in the constructor to avioid duplicating code Signed-off-by: Vishrut Sachan (cherry picked from commit aeb01ef05403d8d296fb5a1e6b7aa4f2e4187629) --- launcher/BaseInstance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 5189de871..80885074c 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -92,7 +92,7 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptrregisterSetting("shortcuts", QString()); m_settings->registerSetting("uuid", QString()); if (m_settings->get("uuid").toString().isEmpty()) { - m_settings->set("uuid", QUuid::createUuid().toString(QUuid::Id128)); + regenerateUuid(); } // Game time override From ce60e888a880846395979fb807e46e0155ce8884 Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Thu, 23 Jul 2026 09:32:20 +0530 Subject: [PATCH 08/32] Prevent renaming instance folder while instance is running Signed-off-by: Vishrut Sachan (cherry picked from commit 944429ecd858ea937695ac68dd52c4d6baff61bd) --- launcher/InstanceDirUpdate.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp index 75fbdb6c6..bea4f1070 100644 --- a/launcher/InstanceDirUpdate.cpp +++ b/launcher/InstanceDirUpdate.cpp @@ -66,6 +66,13 @@ QString askToUpdateInstanceDirName(BaseInstance* instance, const QString& oldNam return QString(); } + if (instance->isRunning()) { + QMessageBox::warning(parent, QObject::tr("Cannot rename instance folder"), + QObject::tr("The instance folder cannot be renamed while the instance is running.\n\n" + "Only the instance name will be changed. The folder will keep its current name.")); + return QString(); + } + // Ask if we should rename if (renamingMode == "AskEverytime") { auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent); From 598fe62f77e522fe42ae9460782c813d5ffaabba Mon Sep 17 00:00:00 2001 From: Reuben Sonnenschein <137012810+frigtear@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:15:49 -0600 Subject: [PATCH 09/32] removed menu bar appearance change Signed-off-by: Reuben Sonnenschein <137012810+frigtear@users.noreply.github.com> (cherry picked from commit 1fc139117b1ffab028d1b3a2f88011502b90edfa) --- launcher/ui/themes/ThemeManager.mm | 9 --------- 1 file changed, 9 deletions(-) diff --git a/launcher/ui/themes/ThemeManager.mm b/launcher/ui/themes/ThemeManager.mm index d9fc291b6..78e8244a4 100644 --- a/launcher/ui/themes/ThemeManager.mm +++ b/launcher/ui/themes/ThemeManager.mm @@ -31,15 +31,6 @@ window.titlebarAppearsTransparent = YES; window.backgroundColor = [NSColor colorWithRed:color.redF() green:color.greenF() blue:color.blueF() alpha:color.alphaF()]; - // Unfortunately there seems to be no easy way to set the titlebar text color. - // The closest we can do without dubious hacks is set the dark/light mode state based on the brightness of the - // background color, which should at least make the text readable even if we can't use the theme's text color. - // It's a good idea to set this anyway since it also affects some other UI elements like text shadows (PrismLauncher#3825). - if (color.lightnessF() < 0.5) { - window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; - } else { - window.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; - } } void ThemeManager::setTitlebarColorOfAllWindowsOnMac(QColor color) From 54082b0e646da404537a2af5d84e87305776262d Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 30 Jul 2026 21:42:13 +0100 Subject: [PATCH 10/32] Fix building HardwareInfo.cpp on OpenBSD Signed-off-by: TheKodeToad (cherry picked from commit 229cc05300fd690b4c7d2ab47d1d6fbffcfe3215) --- launcher/HardwareInfo.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp index 36b6f7783..44941949f 100644 --- a/launcher/HardwareInfo.cpp +++ b/launcher/HardwareInfo.cpp @@ -21,12 +21,14 @@ #include #include -#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) +#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) namespace { +#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) QString afterColon(QString str) { return str.remove(0, str.indexOf(':') + 2).trimmed(); } +#endif template bool readFromOutput(const char* command, F function) From d861a8c9f2b4d04fb5267e9df1c6a372cf5977db Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 30 Jul 2026 23:15:20 +0100 Subject: [PATCH 11/32] Cache resource hard link count Signed-off-by: TheKodeToad (cherry picked from commit 4b041d8304cd63578e9e438753ca5d82f9b347c8) --- launcher/minecraft/mod/Resource.cpp | 3 ++- launcher/minecraft/mod/Resource.h | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 692622521..6f9e8c972 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -46,6 +46,7 @@ void Resource::parseFile() m_internal_id = file_name; std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); + m_hardLinkCount = FS::hardLinkCount(m_file_info.absoluteFilePath()); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; m_name = file_name; @@ -287,7 +288,7 @@ bool Resource::isSymLinkUnder(const QString& instPath) const bool Resource::isMoreThanOneHardLink() const { - return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1; + return m_hardLinkCount > 1; } auto Resource::getOriginalFileName() const -> QString diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 485405b24..2f72cc8d8 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -35,6 +35,8 @@ #pragma once +#include + #include #include #include @@ -210,4 +212,5 @@ class Resource : public QObject { int m_resolution_ticket = 0; QString m_size_str; qint64 m_size_info; + std::uintmax_t m_hardLinkCount = 0; }; From 84d5715b6c3bfd26ff50ce0b7d5240d27f4cb5c9 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 30 Jul 2026 20:28:18 +0100 Subject: [PATCH 12/32] Assert against double suspendSave/resumeSave call Signed-off-by: TheKodeToad (cherry picked from commit 89ebe1cdfa3ba40481313c0c7cd69c88cd8192e7) --- launcher/settings/INISettingsObject.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/settings/INISettingsObject.cpp b/launcher/settings/INISettingsObject.cpp index 519b8193e..ae5b9a5cb 100644 --- a/launcher/settings/INISettingsObject.cpp +++ b/launcher/settings/INISettingsObject.cpp @@ -56,11 +56,13 @@ bool INISettingsObject::reload() void INISettingsObject::suspendSave() { + Q_ASSERT(!m_suspendSave); m_suspendSave = true; } void INISettingsObject::resumeSave() { + Q_ASSERT(m_suspendSave); m_suspendSave = false; if (m_doSave) { m_ini.saveFile(m_filePath); From 1515584b576d335e5d5438756cb6895f536dcaf6 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Thu, 30 Jul 2026 20:58:36 +0100 Subject: [PATCH 13/32] Avoid suspendSave reentrance in MinecraftSettingsWidget Signed-off-by: TheKodeToad (cherry picked from commit 5021538e641ec391a24f9852388c60dcc2ed5bd9) --- .../ui/pages/instance/InstanceSettingsPage.h | 3 +- launcher/ui/widgets/JavaSettingsWidget.cpp | 2 - .../ui/widgets/MinecraftSettingsWidget.cpp | 266 +++++++++--------- launcher/ui/widgets/MinecraftSettingsWidget.h | 3 + 4 files changed, 136 insertions(+), 138 deletions(-) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 79d5944eb..cbb913889 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -46,7 +46,7 @@ class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage { public: explicit InstanceSettingsPage(MinecraftInstance* instance, QWidget* parent = nullptr) : MinecraftSettingsWidget(instance, parent) { - connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings); + connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::apply); connect(APPLICATION, &Application::globalSettingsApplied, this, &InstanceSettingsPage::loadSettings); } ~InstanceSettingsPage() override {} @@ -55,6 +55,7 @@ class InstanceSettingsPage : public MinecraftSettingsWidget, public BasePage { QString id() const override { return "settings"; } bool apply() override { + SettingsObject::Lock lock(m_instance->settings()); saveSettings(); return true; } diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index e13c847d0..869c5cb62 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -167,8 +167,6 @@ void JavaSettingsWidget::saveSettings() else settings = APPLICATION->settings(); - SettingsObject::Lock lock(settings); - // Java Install Settings bool javaInstall = m_instance == nullptr || m_ui->javaInstallationGroupBox->isChecked(); diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.cpp b/launcher/ui/widgets/MinecraftSettingsWidget.cpp index 460068bd3..80798a08f 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.cpp +++ b/launcher/ui/widgets/MinecraftSettingsWidget.cpp @@ -315,172 +315,168 @@ void MinecraftSettingsWidget::saveSettings() else settings = APPLICATION->settings(); - { - SettingsObject::Lock lock(settings); - - // Console - bool console = m_instance == nullptr || m_ui->consoleSettingsBox->isChecked(); + // Console + bool console = m_instance == nullptr || m_ui->consoleSettingsBox->isChecked(); - if (m_instance != nullptr) - settings->set("OverrideConsole", console); + if (m_instance != nullptr) + settings->set("OverrideConsole", console); - if (console) { - settings->set("ShowConsole", m_ui->showConsoleCheck->isChecked()); - settings->set("AutoCloseConsole", m_ui->autoCloseConsoleCheck->isChecked()); - settings->set("ShowConsoleOnError", m_ui->showConsoleErrorCheck->isChecked()); - } else { - settings->reset("ShowConsole"); - settings->reset("AutoCloseConsole"); - settings->reset("ShowConsoleOnError"); - } + if (console) { + settings->set("ShowConsole", m_ui->showConsoleCheck->isChecked()); + settings->set("AutoCloseConsole", m_ui->autoCloseConsoleCheck->isChecked()); + settings->set("ShowConsoleOnError", m_ui->showConsoleErrorCheck->isChecked()); + } else { + settings->reset("ShowConsole"); + settings->reset("AutoCloseConsole"); + settings->reset("ShowConsoleOnError"); + } - // Game Window - bool window = m_instance == nullptr || m_ui->windowSizeGroupBox->isChecked(); + // Game Window + bool window = m_instance == nullptr || m_ui->windowSizeGroupBox->isChecked(); - if (m_instance != nullptr) { - settings->set("OverrideWindow", window); - settings->set("OverrideMiscellaneous", window); - } + if (m_instance != nullptr) { + settings->set("OverrideWindow", window); + settings->set("OverrideMiscellaneous", window); + } - if (window) { - settings->set("LaunchMaximized", m_ui->maximizedCheckBox->isChecked()); - settings->set("MinecraftWinWidth", m_ui->windowWidthSpinBox->value()); - settings->set("MinecraftWinHeight", m_ui->windowHeightSpinBox->value()); - settings->set("CloseAfterLaunch", m_ui->closeAfterLaunchCheck->isChecked()); - settings->set("QuitAfterGameStop", m_ui->quitAfterGameStopCheck->isChecked()); - } else { - settings->reset("LaunchMaximized"); - settings->reset("MinecraftWinWidth"); - settings->reset("MinecraftWinHeight"); - settings->reset("CloseAfterLaunch"); - settings->reset("QuitAfterGameStop"); - } + if (window) { + settings->set("LaunchMaximized", m_ui->maximizedCheckBox->isChecked()); + settings->set("MinecraftWinWidth", m_ui->windowWidthSpinBox->value()); + settings->set("MinecraftWinHeight", m_ui->windowHeightSpinBox->value()); + settings->set("CloseAfterLaunch", m_ui->closeAfterLaunchCheck->isChecked()); + settings->set("QuitAfterGameStop", m_ui->quitAfterGameStopCheck->isChecked()); + } else { + settings->reset("LaunchMaximized"); + settings->reset("MinecraftWinWidth"); + settings->reset("MinecraftWinHeight"); + settings->reset("CloseAfterLaunch"); + settings->reset("QuitAfterGameStop"); + } - // Custom Commands - bool custcmd = m_instance == nullptr || m_ui->customCommands->checked(); + // Custom Commands + bool custcmd = m_instance == nullptr || m_ui->customCommands->checked(); - if (m_instance != nullptr) - settings->set("OverrideCommands", custcmd); + if (m_instance != nullptr) + settings->set("OverrideCommands", custcmd); - if (custcmd) { - settings->set("PreLaunchCommand", m_ui->customCommands->prelaunchCommand()); - settings->set("WrapperCommand", m_ui->customCommands->wrapperCommand()); - settings->set("PostExitCommand", m_ui->customCommands->postexitCommand()); - } else { - settings->reset("PreLaunchCommand"); - settings->reset("WrapperCommand"); - settings->reset("PostExitCommand"); - } + if (custcmd) { + settings->set("PreLaunchCommand", m_ui->customCommands->prelaunchCommand()); + settings->set("WrapperCommand", m_ui->customCommands->wrapperCommand()); + settings->set("PostExitCommand", m_ui->customCommands->postexitCommand()); + } else { + settings->reset("PreLaunchCommand"); + settings->reset("WrapperCommand"); + settings->reset("PostExitCommand"); + } - // Environment Variables - auto env = m_instance == nullptr || m_ui->environmentVariables->override(); + // Environment Variables + auto env = m_instance == nullptr || m_ui->environmentVariables->override(); - if (m_instance != nullptr) - settings->set("OverrideEnv", env); + if (m_instance != nullptr) + settings->set("OverrideEnv", env); - if (env) - settings->set("Env", Json::fromMap(m_ui->environmentVariables->value())); - else - settings->reset("Env"); + if (env) + settings->set("Env", Json::fromMap(m_ui->environmentVariables->value())); + else + settings->reset("Env"); - // Workarounds - bool workarounds = m_instance == nullptr || m_ui->nativeWorkaroundsGroupBox->isChecked(); + // Workarounds + bool workarounds = m_instance == nullptr || m_ui->nativeWorkaroundsGroupBox->isChecked(); - if (m_instance != nullptr) - settings->set("OverrideNativeWorkarounds", workarounds); + if (m_instance != nullptr) + settings->set("OverrideNativeWorkarounds", workarounds); - if (workarounds) { - settings->set("UseNativeGLFW", m_ui->useNativeGLFWCheck->isChecked()); - settings->set("CustomGLFWPath", m_ui->lineEditGLFWPath->text()); - settings->set("UseNativeOpenAL", m_ui->useNativeOpenALCheck->isChecked()); - settings->set("CustomOpenALPath", m_ui->lineEditOpenALPath->text()); - } else { - settings->reset("UseNativeGLFW"); - settings->reset("CustomGLFWPath"); - settings->reset("UseNativeOpenAL"); - settings->reset("CustomOpenALPath"); - } + if (workarounds) { + settings->set("UseNativeGLFW", m_ui->useNativeGLFWCheck->isChecked()); + settings->set("CustomGLFWPath", m_ui->lineEditGLFWPath->text()); + settings->set("UseNativeOpenAL", m_ui->useNativeOpenALCheck->isChecked()); + settings->set("CustomOpenALPath", m_ui->lineEditOpenALPath->text()); + } else { + settings->reset("UseNativeGLFW"); + settings->reset("CustomGLFWPath"); + settings->reset("UseNativeOpenAL"); + settings->reset("CustomOpenALPath"); + } - // Performance - bool performance = m_instance == nullptr || m_ui->perfomanceGroupBox->isChecked(); + // Performance + bool performance = m_instance == nullptr || m_ui->perfomanceGroupBox->isChecked(); - if (m_instance != nullptr) - settings->set("OverridePerformance", performance); + if (m_instance != nullptr) + settings->set("OverridePerformance", performance); - if (performance) { - settings->set("EnableFeralGamemode", m_ui->enableFeralGamemodeCheck->isChecked()); - settings->set("EnableMangoHud", m_ui->enableMangoHud->isChecked()); - settings->set("UseDiscreteGpu", m_ui->useDiscreteGpuCheck->isChecked()); - settings->set("UseZink", m_ui->useZink->isChecked()); - } else { - settings->reset("EnableFeralGamemode"); - settings->reset("EnableMangoHud"); - settings->reset("UseDiscreteGpu"); - settings->reset("UseZink"); - } + if (performance) { + settings->set("EnableFeralGamemode", m_ui->enableFeralGamemodeCheck->isChecked()); + settings->set("EnableMangoHud", m_ui->enableMangoHud->isChecked()); + settings->set("UseDiscreteGpu", m_ui->useDiscreteGpuCheck->isChecked()); + settings->set("UseZink", m_ui->useZink->isChecked()); + } else { + settings->reset("EnableFeralGamemode"); + settings->reset("EnableMangoHud"); + settings->reset("UseDiscreteGpu"); + settings->reset("UseZink"); + } - // Game time - bool gameTime = m_instance == nullptr || m_ui->gameTimeGroupBox->isChecked(); + // Game time + bool gameTime = m_instance == nullptr || m_ui->gameTimeGroupBox->isChecked(); - if (m_instance != nullptr) - settings->set("OverrideGameTime", gameTime); + if (m_instance != nullptr) + settings->set("OverrideGameTime", gameTime); - if (gameTime) { - settings->set("ShowGameTime", m_ui->showGameTime->isChecked()); - settings->set("RecordGameTime", m_ui->recordGameTime->isChecked()); - } else { - settings->reset("ShowGameTime"); - settings->reset("RecordGameTime"); - } + if (gameTime) { + settings->set("ShowGameTime", m_ui->showGameTime->isChecked()); + settings->set("RecordGameTime", m_ui->recordGameTime->isChecked()); + } else { + settings->reset("ShowGameTime"); + settings->reset("RecordGameTime"); + } - if (m_instance == nullptr) { - settings->set("ShowGlobalGameTime", m_ui->showGlobalGameTime->isChecked()); - settings->set("ShowGameTimeWithoutDays", m_ui->showGameTimeWithoutDays->isChecked()); - } + if (m_instance == nullptr) { + settings->set("ShowGlobalGameTime", m_ui->showGlobalGameTime->isChecked()); + settings->set("ShowGameTimeWithoutDays", m_ui->showGameTimeWithoutDays->isChecked()); + } - if (m_instance != nullptr) { - // Join server on launch - bool joinServerOnLaunch = m_ui->serverJoinGroupBox->isChecked(); - settings->set("JoinServerOnLaunch", joinServerOnLaunch); - if (joinServerOnLaunch) { - if (m_ui->serverJoinAddressButton->isChecked() || !m_quickPlaySingleplayer) { - settings->set("JoinServerOnLaunchAddress", m_ui->serverJoinAddress->text()); - settings->reset("JoinWorldOnLaunch"); - } else { - settings->set("JoinWorldOnLaunch", m_ui->worldsCb->currentText()); - settings->reset("JoinServerOnLaunchAddress"); - } + if (m_instance != nullptr) { + // Join server on launch + bool joinServerOnLaunch = m_ui->serverJoinGroupBox->isChecked(); + settings->set("JoinServerOnLaunch", joinServerOnLaunch); + if (joinServerOnLaunch) { + if (m_ui->serverJoinAddressButton->isChecked() || !m_quickPlaySingleplayer) { + settings->set("JoinServerOnLaunchAddress", m_ui->serverJoinAddress->text()); + settings->reset("JoinWorldOnLaunch"); } else { + settings->set("JoinWorldOnLaunch", m_ui->worldsCb->currentText()); settings->reset("JoinServerOnLaunchAddress"); - settings->reset("JoinWorldOnLaunch"); } + } else { + settings->reset("JoinServerOnLaunchAddress"); + settings->reset("JoinWorldOnLaunch"); + } - // Use an account for this instance - bool useAccountForInstance = m_ui->instanceAccountGroupBox->isChecked(); - settings->set("UseAccountForInstance", useAccountForInstance); - if (useAccountForInstance) { - int accountIndex = m_ui->instanceAccountSelector->currentIndex(); - - if (accountIndex != -1) { - const MinecraftAccountPtr account = APPLICATION->accounts()->at(accountIndex); - if (account != nullptr) - settings->set("InstanceAccountId", account->profileId()); - } - } else { - settings->reset("InstanceAccountId"); + // Use an account for this instance + bool useAccountForInstance = m_ui->instanceAccountGroupBox->isChecked(); + settings->set("UseAccountForInstance", useAccountForInstance); + if (useAccountForInstance) { + int accountIndex = m_ui->instanceAccountSelector->currentIndex(); + + if (accountIndex != -1) { + const MinecraftAccountPtr account = APPLICATION->accounts()->at(accountIndex); + if (account != nullptr) + settings->set("InstanceAccountId", account->profileId()); } + } else { + settings->reset("InstanceAccountId"); } + } - bool overrideLegacySettings = m_instance == nullptr || m_ui->legacySettingsGroupBox->isChecked(); + bool overrideLegacySettings = m_instance == nullptr || m_ui->legacySettingsGroupBox->isChecked(); - if (m_instance != nullptr) - settings->set("OverrideLegacySettings", overrideLegacySettings); + if (m_instance != nullptr) + settings->set("OverrideLegacySettings", overrideLegacySettings); - if (overrideLegacySettings) { - settings->set("OnlineFixes", m_ui->onlineFixes->isChecked()); - } else { - settings->reset("OnlineFixes"); - } + if (overrideLegacySettings) { + settings->set("OnlineFixes", m_ui->onlineFixes->isChecked()); + } else { + settings->reset("OnlineFixes"); } if (m_javaSettings != nullptr) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.h b/launcher/ui/widgets/MinecraftSettingsWidget.h index 847e05806..4a7604c64 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.h +++ b/launcher/ui/widgets/MinecraftSettingsWidget.h @@ -61,7 +61,10 @@ class MinecraftSettingsWidget : public QWidget { void saveDataPacksPath(); void selectDataPacksFolder(); + protected: MinecraftInstance* m_instance; + + public: Ui::MinecraftSettingsWidget* m_ui; JavaSettingsWidget* m_javaSettings = nullptr; bool m_quickPlaySingleplayer = false; From dfa2db0464771afe7394011b04c1f9d2286e4511 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 7 Aug 2026 15:19:53 +0100 Subject: [PATCH 14/32] Avoid triggering saves in INISettingsObject::reload Signed-off-by: TheKodeToad (cherry picked from commit 1ec58e1d1711f94093b1ae24d1e57699f98c2129) --- launcher/settings/INISettingsObject.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/launcher/settings/INISettingsObject.cpp b/launcher/settings/INISettingsObject.cpp index 519b8193e..128da352b 100644 --- a/launcher/settings/INISettingsObject.cpp +++ b/launcher/settings/INISettingsObject.cpp @@ -51,7 +51,19 @@ void INISettingsObject::setFilePath(const QString& filePath) bool INISettingsObject::reload() { - return m_ini.loadFile(m_filePath) && SettingsObject::reload(); + if (!m_ini.loadFile(m_filePath)) { + return false; + } + + bool suspendSavePrev = m_suspendSave; + bool doSavePrev = m_doSave; + + m_suspendSave = true; + bool result = SettingsObject::reload(); + + m_suspendSave = suspendSavePrev; + m_doSave = doSavePrev; + return result; } void INISettingsObject::suspendSave() From 2debb8cf8ebf70a7362fb35e624cd3cac8cbb815 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 7 Aug 2026 15:34:23 +0100 Subject: [PATCH 15/32] Avoid always saving when loading instance settings Signed-off-by: TheKodeToad (cherry picked from commit 9f8ffb4cf97b2a0e872a94f6cb161ec8ad262afd) --- launcher/minecraft/MinecraftInstance.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 8e98a2efe..c92e924c7 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -238,7 +238,9 @@ void MinecraftInstance::loadSpecificSettings() auto envSetting = m_settings->registerSetting("OverrideEnv", false); m_settings->registerOverride(global_settings->getSetting("Env"), envSetting); - m_settings->set("InstanceType", "OneSix"); + if (m_settings->get("InstanceType").toString() != "OneSix") { + m_settings->set("InstanceType", "OneSix"); + } } // Join server on launch, this does not have a global override From eaf2fa010b22565cbe6382d1a20d62bf4b762434 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Fri, 7 Aug 2026 15:48:14 +0100 Subject: [PATCH 16/32] Add Saving INI settings log message Signed-off-by: TheKodeToad (cherry picked from commit dc74e8acd7a1cd1bacfdeb3543b53207d44c2c2c) --- launcher/settings/INISettingsObject.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/settings/INISettingsObject.cpp b/launcher/settings/INISettingsObject.cpp index 128da352b..66522f696 100644 --- a/launcher/settings/INISettingsObject.cpp +++ b/launcher/settings/INISettingsObject.cpp @@ -75,7 +75,7 @@ void INISettingsObject::resumeSave() { m_suspendSave = false; if (m_doSave) { - m_ini.saveFile(m_filePath); + doSave(); } } @@ -103,6 +103,7 @@ void INISettingsObject::doSave() if (m_suspendSave) { m_doSave = true; } else { + qDebug() << "Saving INI settings to " << m_filePath; m_ini.saveFile(m_filePath); } } From 3cd518caa8f6bedb5e525110cea69e4f8b5f9816 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 14 Aug 2026 00:22:13 +0500 Subject: [PATCH 17/32] fix: remove unnecessary resets during startup Signed-off-by: Octol1ttle (cherry picked from commit 57d4cc720afe9df6d88303d5ffe69bcdab43c885) --- launcher/Application.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index d85526851..2b9433ae0 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -856,8 +856,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { auto resetIfInvalid = [this](const Setting* setting) { - if (const QUrl url(setting->get().toString()); !url.isValid() || (url.scheme() != "http" && url.scheme() != "https")) { - m_settings->reset(setting->id()); + if (const auto value = setting->get().toString(); !value.isEmpty()) { + if (const QUrl url(value); !url.isValid() || (url.scheme() != "http" && url.scheme() != "https")) { + m_settings->reset(setting->id()); + } } }; @@ -880,16 +882,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("MSAClientIDOverride", ""); // Custom Flame API Key - { - m_settings->registerSetting("CFKeyOverride", ""); - m_settings->registerSetting("FlameKeyOverride", ""); + m_settings->registerSetting({ "FlameKeyOverride", "CFKeyOverride" }, ""); - QString flameKey = m_settings->get("CFKeyOverride").toString(); - - if (!flameKey.isEmpty()) - m_settings->set("FlameKeyOverride", flameKey); - m_settings->reset("CFKeyOverride"); - } m_settings->registerSetting("FallbackMRBlockedMods", true); m_settings->registerSetting("ModrinthToken", ""); m_settings->registerSetting("UserAgentOverride", ""); From a35de941f1917285c702ffc03f345f12eb16b645 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Fri, 14 Aug 2026 00:24:07 +0500 Subject: [PATCH 18/32] fix: don't emit SettingChanged if the value didn't actually change Signed-off-by: Octol1ttle (cherry picked from commit b9b97f13476cc95628037d5f1c057f8a0cdd18f1) --- launcher/settings/INISettingsObject.cpp | 2 +- launcher/settings/Setting.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/settings/INISettingsObject.cpp b/launcher/settings/INISettingsObject.cpp index 08c00ad8f..a26519a48 100644 --- a/launcher/settings/INISettingsObject.cpp +++ b/launcher/settings/INISettingsObject.cpp @@ -105,7 +105,7 @@ void INISettingsObject::doSave() if (m_suspendSave) { m_doSave = true; } else { - qDebug() << "Saving INI settings to " << m_filePath; + qDebug() << "Saving INI settings to" << m_filePath; m_ini.saveFile(m_filePath); } } diff --git a/launcher/settings/Setting.cpp b/launcher/settings/Setting.cpp index 1e861e36b..209e16579 100644 --- a/launcher/settings/Setting.cpp +++ b/launcher/settings/Setting.cpp @@ -38,7 +38,9 @@ QVariant Setting::defValue() const void Setting::set(QVariant value) { - emit SettingChanged(*this, value); + if (const auto currentValue = get(); value != currentValue) { + emit SettingChanged(*this, value); + } } void Setting::reset() From a07a2639a6b517ce901e83dc847f5faa2271d4f7 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 4 Aug 2026 14:10:27 +0100 Subject: [PATCH 19/32] Pass flag to InstanceImportTask to indicate trusted source Signed-off-by: TheKodeToad (cherry picked from commit 9f406253e15b040b726443bae86790d0e149b955) --- launcher/InstanceImportTask.cpp | 4 ++-- launcher/InstanceImportTask.h | 3 ++- launcher/ui/pages/instance/ManagedPackPage.cpp | 16 ++++++++-------- launcher/ui/pages/instance/ManagedPackPage.h | 2 +- launcher/ui/pages/modplatform/ImportPage.cpp | 6 +++--- .../ui/pages/modplatform/flame/FlamePage.cpp | 2 +- .../pages/modplatform/modrinth/ModrinthPage.cpp | 3 ++- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 9b04f99b6..98c9d5f3f 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -59,8 +59,8 @@ #include #include -InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap&& extra_info) - : m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent) +InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, bool trustedSource, QWidget* parent, QMap&& extra_info) + : m_sourceUrl(sourceUrl), m_trustedSource(trustedSource), m_extra_info(extra_info), m_parent(parent) {} bool InstanceImportTask::abort() diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index c92e229a0..6d139d4d8 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -43,7 +43,7 @@ class InstanceImportTask : public InstanceTask { Q_OBJECT public: - explicit InstanceImportTask(const QUrl& sourceUrl, QWidget* parent = nullptr, QMap&& extra_info = {}); + explicit InstanceImportTask(const QUrl& sourceUrl, bool trustedSource, QWidget* parent = nullptr, QMap&& extra_info = {}); virtual ~InstanceImportTask() = default; bool abort() override; @@ -63,6 +63,7 @@ class InstanceImportTask : public InstanceTask { private: /* data */ QUrl m_sourceUrl; + bool m_trustedSource; QString m_archivePath; Task::Ptr m_task; enum class ModpackType { diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index d2683fa92..0dbf6df2f 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -359,7 +359,7 @@ void ModrinthManagedPackPage::update() { auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { - updatePack(customURL); + updatePack(customURL, false); return; } auto index = ui->versionsComboBox->currentIndex(); @@ -369,7 +369,7 @@ void ModrinthManagedPackPage::update() } auto version = m_pack.versions.at(index); - updatePack(version.downloadUrl, version.fileId.toString(), version.version); + updatePack(version.downloadUrl, true, version.fileId.toString(), version.version); } void ModrinthManagedPackPage::updateFromFile() @@ -378,7 +378,7 @@ void ModrinthManagedPackPage::updateFromFile() if (output.isEmpty()) return; - updatePack(output); + updatePack(output, false); } // FLAME @@ -488,7 +488,7 @@ void FlameManagedPackPage::update() { auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { - updatePack(customURL); + updatePack(customURL, false); return; } auto index = ui->versionsComboBox->currentIndex(); @@ -498,7 +498,7 @@ void FlameManagedPackPage::update() } auto version = m_pack.versions.at(index); - updatePack(version.downloadUrl, version.fileId.toString()); + updatePack(version.downloadUrl, true, version.fileId.toString()); } void FlameManagedPackPage::updateFromFile() @@ -507,10 +507,10 @@ void FlameManagedPackPage::updateFromFile() if (output.isEmpty()) return; - updatePack(output); + updatePack(output, false); } -void ManagedPackPage::updatePack(const QUrl& url, QString versionID, QString versionName) +void ManagedPackPage::updatePack(const QUrl& url, bool trusted, QString versionID, QString versionName) { QMap extra_info; // NOTE: Don't use 'm_pack.id' here, since we didn't completely parse all the metadata for the pack, including this field. @@ -518,7 +518,7 @@ void ManagedPackPage::updatePack(const QUrl& url, QString versionID, QString ver extra_info.insert("pack_version_id", versionID); extra_info.insert("original_instance_id", m_inst->id()); - auto extracted = new InstanceImportTask(url, this, std::move(extra_info)); + auto extracted = new InstanceImportTask(url, trusted, this, std::move(extra_info)); if (versionName.isEmpty()) { extracted->setName(m_inst->name()); diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index 4b7332896..40d1deee7 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -86,7 +86,7 @@ class ManagedPackPage : public QWidget, public BasePage { */ bool runUpdateTask(InstanceTask*); - void updatePack(const QUrl& url, QString versionID = {}, QString versionName = {}); + void updatePack(const QUrl& url, bool trusted, QString versionID = {}, QString versionName = {}); protected: InstanceWindow* m_instance_window = nullptr; diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 6e783014f..c93fcfa4a 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -118,7 +118,7 @@ void ImportPage::updateState() if (fi.exists() && (isZip || isMRPack)) { auto extra_info = QMap(m_extra_info); qDebug() << "Pack Extra Info" << extra_info << m_extra_info; - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info))); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, false, this, std::move(extra_info))); dialog->setSuggestedIcon("default"); } } else if (url.scheme() == "curseforge") { @@ -163,7 +163,7 @@ void ImportPage::updateState() extra_info.insert("pack_id", addonId); extra_info.insert("pack_version_id", fileId); - dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info))); + dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, false, this, std::move(extra_info))); dialog->setSuggestedIcon("default"); } else { @@ -183,7 +183,7 @@ void ImportPage::updateState() // hook, line and sinker. QFileInfo fi(url.fileName()); auto extra_info = QMap(m_extra_info); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info))); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, false, this, std::move(extra_info))); dialog->setSuggestedIcon("default"); } } else { diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 336133819..9ef1a768b 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -241,7 +241,7 @@ void FlamePage::suggestCurrent() extra_info.insert("pack_id", m_current->addonId.toString()); extra_info.insert("pack_version_id", version.fileId.toString()); - m_dialog->setSuggestedPack(m_current->name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info))); + m_dialog->setSuggestedPack(m_current->name, new InstanceImportTask(version.downloadUrl, true, this, std::move(extra_info))); QString editedLogoName = "curseforge_" + m_current->logoName; m_listModel->getLogo(m_current->logoName, m_current->logoUrl, [this, editedLogoName](QString logo) { m_dialog->setSuggestedIconFromFile(logo, editedLogoName); }); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 4798583bd..b9b6f0c0a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -318,7 +318,8 @@ void ModrinthPage::suggestCurrent() extra_info.insert("pack_id", m_current->addonId.toString()); extra_info.insert("pack_version_id", ver.fileId.toString()); - m_dialog->setSuggestedPack(m_current->name, ver.version, new InstanceImportTask(ver.downloadUrl, this, std::move(extra_info))); + m_dialog->setSuggestedPack(m_current->name, ver.version, + new InstanceImportTask(ver.downloadUrl, true, this, std::move(extra_info))); QString editedLogoName = "modrinth_" + m_current->logoName; m_model->getLogo(m_current->logoName, m_current->logoUrl, [this, editedLogoName](QString logo) { m_dialog->setSuggestedIconFromFile(logo, editedLogoName); }); From 961ba34ff079aa8a4302d96367f38c6cb3c27be0 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 5 Aug 2026 13:07:13 +0100 Subject: [PATCH 20/32] Add warning for untrusted mods in Modrinth/CurseForge pack Signed-off-by: TheKodeToad (cherry picked from commit 3f161cd5db615c84087652d3d671a96ff3cb042a) --- launcher/CMakeLists.txt | 2 + launcher/InstanceImportTask.cpp | 13 +-- .../flame/FlameInstanceCreationTask.cpp | 31 +++++++ .../flame/FlameInstanceCreationTask.h | 6 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 40 +++++++++ .../modrinth/ModrinthInstanceCreationTask.h | 6 +- launcher/ui/dialogs/UntrustedModsDialog.cpp | 26 ++++++ launcher/ui/dialogs/UntrustedModsDialog.h | 20 +++++ launcher/ui/dialogs/UntrustedModsDialog.ui | 88 +++++++++++++++++++ 9 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 launcher/ui/dialogs/UntrustedModsDialog.cpp create mode 100644 launcher/ui/dialogs/UntrustedModsDialog.h create mode 100644 launcher/ui/dialogs/UntrustedModsDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 375b4e589..385f90fd2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1097,6 +1097,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/InstallLoaderDialog.h ui/dialogs/ChooseOfflineNameDialog.cpp ui/dialogs/ChooseOfflineNameDialog.h + ui/dialogs/UntrustedModsDialog.cpp + ui/dialogs/UntrustedModsDialog.h ui/dialogs/skins/SkinManageDialog.cpp ui/dialogs/skins/SkinManageDialog.h diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 98c9d5f3f..6643ab10a 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -287,11 +287,12 @@ void InstanceImportTask::processFlame() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = - makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = makeShared(m_stagingPath, m_trustedSource, m_globalSettings, m_parent, pack_id, + pack_version_id, original_instance_id); } else { // FIXME: Find a way to get IDs in directly imported ZIPs - inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, QString(), QString()); + inst_creation_task = + makeShared(m_stagingPath, m_trustedSource, m_globalSettings, m_parent, QString(), QString()); } inst_creation_task->setName(*this); @@ -381,8 +382,8 @@ void InstanceImportTask::processModrinth() if (original_instance_id_it != m_extra_info.constEnd()) original_instance_id = original_instance_id_it.value(); - inst_creation_task = - makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id); + inst_creation_task = makeShared(m_stagingPath, m_trustedSource, m_globalSettings, m_parent, pack_id, + pack_version_id, original_instance_id); } else { QString pack_id; if (!m_sourceUrl.isEmpty()) { @@ -391,7 +392,7 @@ void InstanceImportTask::processModrinth() } // FIXME: Find a way to get the ID in directly imported ZIPs - inst_creation_task = makeShared(m_stagingPath, m_globalSettings, m_parent, pack_id); + inst_creation_task = makeShared(m_stagingPath, m_trustedSource, m_globalSettings, m_parent, pack_id); } inst_creation_task->setName(*this); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 534132a6e..c7d1b368a 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -68,6 +68,7 @@ #include "minecraft/World.h" #include "minecraft/mod/tasks/LocalResourceParse.h" #include "net/ApiDownload.h" +#include "ui/dialogs/UntrustedModsDialog.h" #include "ui/pages/modplatform/OptionalModDialog.h" static const FlameAPI api; @@ -307,6 +308,31 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, return loaderVersion; } +bool FlameCreationTask::promptForUntrustedMods() +{ + if (m_trustedSource) { + return true; + } + + QStringList untrustedMods; + + const QDir mcDir{ FS::PathCombine(m_stagingPath, "minecraft") }; + const QString modsPath{ FS::PathCombine(m_stagingPath, "minecraft/mods") }; + if (QDir(modsPath).exists()) { + QDirIterator iter{ modsPath, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks }; + while (iter.hasNext()) { + untrustedMods.append(mcDir.relativeFilePath(iter.next())); + } + } + + if (untrustedMods.empty()) { + return true; + } + + UntrustedModsDialog dialog{ untrustedMods, m_parent }; + return dialog.exec() == QDialog::Accepted; +} + std::unique_ptr FlameCreationTask::createInstance() { QEventLoop loop; @@ -345,6 +371,11 @@ std::unique_ptr FlameCreationTask::createInstance() } } + if (!promptForUntrustedMods()) { + emitAborted(); + return; + } + QString loaderType; QString loaderUid; QString loaderVersion; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 221ceaf22..8710bd367 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -52,12 +52,13 @@ class FlameCreationTask final : public InstanceCreationTask { public: FlameCreationTask(const QString& staging_path, + bool trustedSource, SettingsObject* global_settings, QWidget* parent, QString id, QString version_id, QString original_instance_id = {}) - : InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id)) + : InstanceCreationTask(), m_parent(parent), m_trustedSource(trustedSource), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id)) { setStagingPath(staging_path); setParentSettings(global_settings); @@ -77,8 +78,11 @@ class FlameCreationTask final : public InstanceCreationTask { void validateOtherResources(QEventLoop& loop); QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion); + [[nodiscard]] bool promptForUntrustedMods(); + private: QWidget* m_parent = nullptr; + bool m_trustedSource; shared_qobject_ptr m_modIdResolver; Flame::Manifest m_pack; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 0cb2c547d..d8ad6b91c 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -23,6 +23,7 @@ #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/UntrustedModsDialog.h" #include "ui/pages/modplatform/OptionalModDialog.h" #include @@ -205,6 +206,11 @@ std::unique_ptr ModrinthCreationTask::createInstance() } } + if (!promptForUntrustedMods()) { + emitAborted(); + return; + } + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_unique(configPath); auto instance = std::make_unique(m_globalSettings, std::move(instanceSettings), m_stagingPath); @@ -495,3 +501,37 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector&, bool setInternalData = true, bool showOptionalDialog = true); + [[nodiscard]] bool promptForUntrustedMods(); + private: QWidget* m_parent = nullptr; + bool m_trustedSource; QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version; QString m_managed_id, m_managed_version_id, m_managed_name; diff --git a/launcher/ui/dialogs/UntrustedModsDialog.cpp b/launcher/ui/dialogs/UntrustedModsDialog.cpp new file mode 100644 index 000000000..919af7f65 --- /dev/null +++ b/launcher/ui/dialogs/UntrustedModsDialog.cpp @@ -0,0 +1,26 @@ +#include "UntrustedModsDialog.h" +#include "ui_UntrustedModsDialog.h" + +#include +#include +#include +#include + +UntrustedModsDialog::UntrustedModsDialog(const QStringList& paths, QWidget* parent) : QDialog{ parent }, m_ui{ new Ui::UntrustedModsDialog } +{ + m_ui->setupUi(this); + m_ui->modList->addItems(paths); + + auto* ok = m_ui->buttonBox->button(QDialogButtonBox::Ok); + ok->setEnabled(false); + + connect(m_ui->confirmCheckbox, &QAbstractButton::clicked, ok, &QWidget::setEnabled); + + m_ui->confirmCheckbox->setEnabled(false); + QTimer::singleShot(3000, this, [this] { m_ui->confirmCheckbox->setEnabled(true); }); +} + +UntrustedModsDialog::~UntrustedModsDialog() +{ + delete m_ui; +} diff --git a/launcher/ui/dialogs/UntrustedModsDialog.h b/launcher/ui/dialogs/UntrustedModsDialog.h new file mode 100644 index 000000000..23b1249ef --- /dev/null +++ b/launcher/ui/dialogs/UntrustedModsDialog.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include + +namespace Ui { +class UntrustedModsDialog; +} + +class UntrustedModsDialog : public QDialog { + Q_OBJECT + public: + explicit UntrustedModsDialog(const QStringList& paths, QWidget* parent = nullptr); + ~UntrustedModsDialog() override; + + private: + Ui::UntrustedModsDialog* m_ui; +}; diff --git a/launcher/ui/dialogs/UntrustedModsDialog.ui b/launcher/ui/dialogs/UntrustedModsDialog.ui new file mode 100644 index 000000000..71bed8e26 --- /dev/null +++ b/launcher/ui/dialogs/UntrustedModsDialog.ui @@ -0,0 +1,88 @@ + + + UntrustedModsDialog + + + + 0 + 0 + 599 + 269 + + + + Easy There! + + + + + + <html><head/><body><p>The modpack you are installing includes mods which are not hosted on Modrinth or CurseForge:</p></body></html> + + + + + + + + + + <html><head/><body><p>Malicious mods are often distributed through links sent on platforms such as Discord. </p><p>We strongly recommend only importing modpacks from trusted sources.</p></body></html> + + + + + + + I trust this modpack and wish to proceed regardless + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + UntrustedModsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + UntrustedModsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From 4a2470988b3756824b9e873c04bd10774c0a35ac Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 5 Aug 2026 14:07:07 +0100 Subject: [PATCH 21/32] Bolden bottom warning text Signed-off-by: TheKodeToad (cherry picked from commit 09ffbf3f1f2889d11d28dc7ccfde66a1b1aeb145) --- launcher/ui/dialogs/UntrustedModsDialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/dialogs/UntrustedModsDialog.ui b/launcher/ui/dialogs/UntrustedModsDialog.ui index 71bed8e26..db5734bcf 100644 --- a/launcher/ui/dialogs/UntrustedModsDialog.ui +++ b/launcher/ui/dialogs/UntrustedModsDialog.ui @@ -6,7 +6,7 @@ 0 0 - 599 + 635 269 @@ -27,7 +27,7 @@ - <html><head/><body><p>Malicious mods are often distributed through links sent on platforms such as Discord. </p><p>We strongly recommend only importing modpacks from trusted sources.</p></body></html> + <html><head/><body><p><span style=" font-weight:600;">Malicious mods are often distributed through links sent on platforms such as Discord. </span></p><p>We strongly recommend only importing modpacks from trusted sources.</p></body></html> From 5ffe6a05cec356649aca88427ad4bdc2479820af Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 5 Aug 2026 16:29:53 +0100 Subject: [PATCH 22/32] Update launcher/ui/dialogs/UntrustedModsDialog.ui Co-authored-by: Octol1ttle Signed-off-by: TheKodeToad (cherry picked from commit 6fd54f2ac53b5cc80f43ec471de9d8bd000cf25f) --- launcher/ui/dialogs/UntrustedModsDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/UntrustedModsDialog.ui b/launcher/ui/dialogs/UntrustedModsDialog.ui index db5734bcf..d673aa456 100644 --- a/launcher/ui/dialogs/UntrustedModsDialog.ui +++ b/launcher/ui/dialogs/UntrustedModsDialog.ui @@ -27,7 +27,7 @@ - <html><head/><body><p><span style=" font-weight:600;">Malicious mods are often distributed through links sent on platforms such as Discord. </span></p><p>We strongly recommend only importing modpacks from trusted sources.</p></body></html> + <html><head/><body><p><b>Malicious mods are often distributed through links sent on platforms such as Discord.</b></p><p>We strongly recommend only importing modpacks from trusted sources.</p></body></html> From 7a847bb3386a910f3cc7a8ac9782a66dd03c0c4c Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Sun, 16 Aug 2026 18:36:00 +0500 Subject: [PATCH 23/32] backport: fix build Signed-off-by: Octol1ttle --- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 3 ++- .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index c7d1b368a..7f8985a09 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -372,8 +372,9 @@ std::unique_ptr FlameCreationTask::createInstance() } if (!promptForUntrustedMods()) { + m_abort = true; emitAborted(); - return; + return nullptr; } QString loaderType; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index d8ad6b91c..2ca6b082a 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -207,8 +207,9 @@ std::unique_ptr ModrinthCreationTask::createInstance() } if (!promptForUntrustedMods()) { + m_abort = true; emitAborted(); - return; + return nullptr; } QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); @@ -519,8 +520,8 @@ bool ModrinthCreationTask::promptForUntrustedMods() } } - const QDir mcDir{ FS::PathCombine(m_stagingPath, m_rootPath) }; - const QString modsPath{ FS::PathCombine(m_stagingPath, m_rootPath, "mods") }; + const QDir mcDir{ FS::PathCombine(m_stagingPath, m_root_path) }; + const QString modsPath{ FS::PathCombine(m_stagingPath, m_root_path, "mods") }; if (QDir(modsPath).exists()) { QDirIterator iter{ modsPath, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks }; while (iter.hasNext()) { From fb6cc777337f8965970b151481b796911e8b23d9 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 15 Aug 2026 22:07:24 +0300 Subject: [PATCH 24/32] fix: set entity state before emitting success Signed-off-by: Trial97 (cherry picked from commit 93feac2ed9ca2c8e638bf4e7616813b2cc6e6663) --- launcher/meta/BaseEntity.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index 1869e14d3..f2f3e13fb 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -143,7 +143,7 @@ void BaseEntityLoadTask::executeTask() } } catch (const Exception& e) { - qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); + qCritical() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); // just make sure it's gone and we never consider it again. FS::deletePath(fname); m_entity->m_load_status = BaseEntity::LoadStatus::NotLoaded; @@ -177,10 +177,10 @@ void BaseEntityLoadTask::executeTask() m_task->addNetAction(dl); m_task->setAskRetry(false); connect(m_task.get(), &Task::failed, this, &BaseEntityLoadTask::emitFailed); - connect(m_task.get(), &Task::succeeded, this, &BaseEntityLoadTask::emitSucceeded); connect(m_task.get(), &Task::succeeded, this, [this]() { m_entity->m_load_status = BaseEntity::LoadStatus::Remote; m_entity->m_file_sha256 = m_entity->m_sha256; + emitSucceeded(); }); connect(m_task.get(), &Task::progress, this, &Task::setProgress); From a8ed5aa0b0bbe69afdde9b76094b66ff248f9587 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Aug 2026 12:42:37 +0100 Subject: [PATCH 25/32] Bump to 11.1.0 Signed-off-by: TheKodeToad --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 464f022bd..0d69af7cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,8 +180,8 @@ set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 11) -set(Launcher_VERSION_MINOR 0) -set(Launcher_VERSION_PATCH 4) +set(Launcher_VERSION_MINOR 1) +set(Launcher_VERSION_PATCH 0) set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") From 77df2c2534b4929d5235155a8e064593d696cf50 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Mon, 17 Aug 2026 12:43:04 +0100 Subject: [PATCH 26/32] Fix formatting Signed-off-by: TheKodeToad --- launcher/InstanceImportTask.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 6d139d4d8..29d2eba4b 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -43,7 +43,10 @@ class InstanceImportTask : public InstanceTask { Q_OBJECT public: - explicit InstanceImportTask(const QUrl& sourceUrl, bool trustedSource, QWidget* parent = nullptr, QMap&& extra_info = {}); + explicit InstanceImportTask(const QUrl& sourceUrl, + bool trustedSource, + QWidget* parent = nullptr, + QMap&& extra_info = {}); virtual ~InstanceImportTask() = default; bool abort() override; From 811568e8753fa5e7941d1bcba3cb01fe51c0e508 Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Mon, 17 Aug 2026 17:17:34 +0500 Subject: [PATCH 27/32] backport: fix build (for real) Signed-off-by: Octol1ttle --- launcher/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 385f90fd2..fa710ab5f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1270,6 +1270,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ChooseProviderDialog.ui ui/dialogs/skins/SkinManageDialog.ui ui/dialogs/ChooseOfflineNameDialog.ui + ui/dialogs/UntrustedModsDialog.ui ) qt_wrap_ui(PRISM_UPDATE_UI From 8888accb3a922c6cc9d42c11094fdae831f78a52 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 4 Aug 2026 23:17:42 +0100 Subject: [PATCH 28/32] Improve performance of BaseInstance::uuid Signed-off-by: TheKodeToad (cherry picked from commit c5cc8321554bdcb67100b005d6d672ae0c16267f) --- launcher/BaseInstance.cpp | 15 ++++++++------- launcher/BaseInstance.h | 5 +++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index cd04573a5..ab847d39e 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -91,8 +91,12 @@ BaseInstance::BaseInstance(SettingsObject* globalSettings, std::unique_ptrregisterSetting("linkedInstances", "[]"); m_settings->registerSetting("shortcuts", QString()); m_settings->registerSetting("uuid", QString()); - if (m_settings->get("uuid").toString().isEmpty()) { + + const auto savedUUID = m_settings->get("uuid").toString(); + if (savedUUID.isEmpty()) { regenerateUuid(); + } else { + m_uuid = savedUUID; } // Game time override @@ -274,14 +278,11 @@ QString BaseInstance::id() const return QFileInfo(instanceRoot()).fileName(); } -QString BaseInstance::uuid() const -{ - return m_settings->get("uuid").toString(); -} - void BaseInstance::regenerateUuid() { - m_settings->set("uuid", QUuid::createUuid().toString(QUuid::Id128)); + const auto newUUID = QUuid::createUuid().toString(QUuid::Id128); + m_settings->set("uuid", newUUID); + m_uuid = newUUID; } bool BaseInstance::isRunning() const diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 8bcb5fc4b..8916375f8 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -114,8 +114,8 @@ class BaseInstance : public QObject { /// The instance's ID. The ID SHALL be determined by LAUNCHER internally. The ID IS guaranteed to /// be unique. - virtual QString id() const; - virtual QString uuid() const; + QString id() const; + QString uuid() const { return m_uuid; } void regenerateUuid(); void setMinecraftRunning(bool running); @@ -317,6 +317,7 @@ class BaseInstance : public QObject { RuntimeContext m_runtimeContext; private: /* data */ + QString m_uuid; Status m_status = Status::Present; bool m_crashed = false; bool m_hasUpdate = false; From d92e04bba48b5ede693e355acf3573616f40a424 Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Tue, 11 Aug 2026 18:38:53 +0530 Subject: [PATCH 29/32] fix: regenerate instance UUID on import Signed-off-by: Vishrut Sachan (cherry picked from commit a95d467fd90ac62f0cb542a7af318eccaa9ed2dd) --- launcher/InstanceImportTask.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 6643ab10a..e7c251168 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -350,6 +350,9 @@ void InstanceImportTask::processMultiMC() // reset time played on import... because packs. instance.resetTimePlayed(); + // UUID is carried over on export, but this is a distinct instance, so give it its own + instance.regenerateUuid(); + // set a new nice name instance.setName(name()); From df67e58d2286ea6d2a9c063b5f75e0bbd5c849ad Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 21 Jul 2026 23:30:38 +0500 Subject: [PATCH 30/32] fix(actions/winget): bump winget-releaser to fix failures Signed-off-by: Octol1ttle (cherry picked from commit 1407f4e82e487d076fadf75de2e142c3c44cb72d) --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1bb1c5b50..5660c097b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Publish on Winget - uses: vedantmgoyal2009/winget-releaser@v2 + uses: vedantmgoyal2009/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5 # docs: add description to inputs (#335) with: identifier: PrismLauncher.PrismLauncher version: ${{ github.event.release.tag_name }} From d9abdb754ae39c761c444bbd4d14b7c903560eed Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Wed, 22 Jul 2026 15:24:25 +0500 Subject: [PATCH 31/32] actions/winget: add commit pin explanation comment Signed-off-by: Octol1ttle (cherry picked from commit a1387ad27128d875a979dffdf2f83265a908b12d) --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5660c097b..3a658299f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,6 +17,7 @@ jobs: steps: - name: Publish on Winget + # @Octol1ttle: Pinned to a commit because no release has been published that works on ubuntu-slim uses: vedantmgoyal2009/winget-releaser@7bd472be23763def6e16bd06cc8b1cdfab0e2fd5 # docs: add description to inputs (#335) with: identifier: PrismLauncher.PrismLauncher From 0113982fbda7748e661f815bf78b0b681f63abca Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Tue, 28 Apr 2026 16:40:05 +0500 Subject: [PATCH 32/32] JavaSettingsWidget: available memory -> free memory Signed-off-by: Octol1ttle (cherry picked from commit b7e73db477f67f41e88a517a3cc4dccec7efb4f0) --- launcher/ui/widgets/JavaSettingsWidget.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/JavaSettingsWidget.ui b/launcher/ui/widgets/JavaSettingsWidget.ui index 03d632ad9..99ccd0414 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.ui +++ b/launcher/ui/widgets/JavaSettingsWidget.ui @@ -345,7 +345,7 @@ - Warn when there is not enough memory available + Warn when there is not enough free memory