This commit is contained in:
Dyredhead 2026-06-26 11:59:14 -04:00 committed by GitHub
commit 096de785ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 275 additions and 6 deletions

View file

@ -255,6 +255,8 @@ set(MINECRAFT_SOURCES
minecraft/update/FoldersTask.h
minecraft/update/LibrariesTask.cpp
minecraft/update/LibrariesTask.h
minecraft/update/ModUpdateTask.cpp
minecraft/update/ModUpdateTask.h
minecraft/launch/ClaimAccount.cpp
minecraft/launch/ClaimAccount.h

View file

@ -73,6 +73,7 @@
#include "minecraft/update/FoldersTask.h"
#include "minecraft/update/LegacyFMLLibrariesTask.h"
#include "minecraft/update/LibrariesTask.h"
#include "minecraft/update/ModUpdateTask.h"
#include "java/JavaUtils.h"
@ -263,10 +264,16 @@ void MinecraftInstance::loadSpecificSettings()
connect(dataPacksEnabled.get(), &Setting::SettingChanged, this, [this] { m_data_pack_list.reset(); });
connect(dataPacksPath.get(), &Setting::SettingChanged, this, [this] { m_data_pack_list.reset(); });
// Join server on launch, this does not have a global override
// Set mod downloaders, this does not have a global override
m_settings->registerSetting("OverrideModDownloadLoaders", false);
m_settings->registerSetting("ModDownloadLoaders", "[]");
// Enable automatic mod updates, this does not have a global override
m_settings->registerSetting("AutomaticallyUpdateMods", false);
m_settings->registerSetting("AutomaticallyUpdateModsAll", false);
m_settings->registerSetting("AutomaticallyUpdateModsEnabled", true);
// m_settings->registerSetting("AutomaticallyUpdateModsSpecified", "[]");
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);
@ -1202,6 +1209,13 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf
process->appendStep(makeShared<ScanModFolders>(pptr));
}
// Update mods if "AutomaticallyUpdateMods" is true.
// Must come after ScanModFolders to ensure mods are loaded.
if (settings()->get("AutomaticallyUpdateMods").toBool()) {
process->appendStep(
makeShared<TaskStepWrapper>(pptr, makeShared<ModUpdateTask>(this, settings()->get("AutomaticallyUpdateModsEnabled").toBool())));
}
// make sure we have enough RAM, warn the user if we don't
{
process->appendStep(makeShared<EnsureAvailableMemory>(pptr, this));

View file

@ -45,19 +45,19 @@ void ScanModFolders::executeTask()
auto m_inst = m_parent->instance();
auto loaders = m_inst->loaderModList();
connect(loaders, &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
connect(loaders, &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone, Qt::UniqueConnection);
if (!loaders->update()) {
m_modsDone = true;
}
auto cores = m_inst->coreModList();
connect(cores, &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone);
connect(cores, &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone, Qt::UniqueConnection);
if (!cores->update()) {
m_coreModsDone = true;
}
auto nils = m_inst->nilModList();
connect(nils, &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone);
connect(nils, &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone, Qt::UniqueConnection);
if (!nils->update()) {
m_nilModsDone = true;
}
@ -66,25 +66,63 @@ void ScanModFolders::executeTask()
void ScanModFolders::modsDone()
{
qDebug() << "Check done in ScanModFolders modsDone...";
if (!isRunning()) {
qDebug() << "ScanModFolders::modsDone called but step not running; ignoring.";
return;
}
m_modsDone = true;
checkDone();
}
void ScanModFolders::coreModsDone()
{
qDebug() << "Check done in ScanModFolders coreModsDone...";
if (!isRunning()) {
qDebug() << "ScanModFolders::coreModsDone called but step not running; "
"ignoring.";
return;
}
m_coreModsDone = true;
checkDone();
}
void ScanModFolders::nilModsDone()
{
qDebug() << "Check done in ScanModFolders nilModsDone...";
if (!isRunning()) {
qDebug() << "ScanModFolders::nilModsDone called but step not running; ignoring.";
return;
}
m_nilModsDone = true;
checkDone();
}
void ScanModFolders::checkDone()
{
qDebug() << "Check done in ScanModFolders...";
if (m_modsDone && m_coreModsDone && m_nilModsDone) {
// Disconnect model signals before finishing so the finished step doesn't
// receive later updateFinished() notifications and try to finish again.
auto m_inst = m_parent->instance();
if (m_inst) {
disconnect(m_inst->loaderModList(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
disconnect(m_inst->coreModList(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone);
disconnect(m_inst->nilModList(), &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone);
}
emitSucceeded();
}
}
void ScanModFolders::finalize()
{
auto m_inst = m_parent->instance();
if (!m_inst) {
return;
}
disconnect(m_inst->loaderModList(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
disconnect(m_inst->coreModList(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone);
disconnect(m_inst->nilModList(), &ModFolderModel::updateFinished, this, &ScanModFolders::nilModsDone);
}

View file

@ -22,10 +22,12 @@ class ScanModFolders : public LaunchStep {
Q_OBJECT
public:
explicit ScanModFolders(LaunchTask* parent) : LaunchStep(parent) {};
virtual ~ScanModFolders() {};
virtual ~ScanModFolders() = default;
virtual void executeTask() override;
virtual bool canAbort() const override { return false; }
virtual void finalize() override;
private slots:
void coreModsDone();
void modsDone();

View file

@ -0,0 +1,125 @@
#include "ModUpdateTask.h"
#include <ui/dialogs/ProgressDialog.h>
#include <ui/dialogs/ResourceUpdateDialog.h>
#include "Application.h"
#include "BuildConfig.h"
#include "launch/LaunchStep.h"
#include "minecraft/AssetsUtils.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModDetails.h"
#include "minecraft/mod/ModFolderModel.h"
#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
ModUpdateTask::ModUpdateTask(MinecraftInstance* inst, bool enabledModsOnly)
{
m_instance = inst;
m_enabledModsOnly = enabledModsOnly;
}
void ModUpdateTask::executeTask()
{
setStatus(tr("Updating mods..."));
qDebug() << "Updating mods...";
if (m_instance->typeName() != "Minecraft") {
return; // this is a null instance or a legacy instance
}
auto* profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (!profile->getModLoaders().has_value()) {
emitFailed(tr("Mod updates are unavailable when mod loader is missing!"));
return;
}
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
emitFailed(tr("Mod updates are unavailable when metadata is disabled!"));
return;
}
// ResourceUpdateDialog only accepts Resource,
// so we convert the Mod* list to a Resource* list with a simple static_cast
auto model = m_instance->loaderModList();
auto _modsList = model->allMods();
QList<Resource*> modsList;
modsList.reserve(_modsList.size());
for (auto mod : _modsList) {
modsList.append(static_cast<Resource*>(mod));
}
// Filter out disabled mods if "AutomaticallyUpdateModsEnabled" is true
if (m_enabledModsOnly) {
modsList.erase(std::remove_if(modsList.begin(), modsList.end(), [](Resource* resource) { return !resource->enabled(); }),
modsList.end());
}
// Spawn ResourceUpdateDialog to handle mod updates
// 99% copied from ModFolderPage.cpp
auto parent = QApplication::activeWindow();
ResourceUpdateDialog updateDialog =
ResourceUpdateDialog(parent, m_instance, model, modsList, m_includeDeps, profile->getModLoadersList());
updateDialog.checkCandidates();
if (updateDialog.aborted()) {
CustomMessageBox::selectable(parent, tr("Aborted"), tr("The mod updater was aborted!"), QMessageBox::Warning)->show();
emitAborted();
return;
}
if (updateDialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) };
if (modsList.size() > 1) {
if (!m_enabledModsOnly) {
message = tr("All mods are up-to-date! :)");
} else {
message = tr("All enabled mods are up-to-date! :)");
}
}
CustomMessageBox::selectable(parent, tr("Update checker"), message)->exec();
emitSucceeded();
return;
}
if (updateDialog.exec() != 0) {
auto* tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, parent, tasks](const QString& reason) {
CustomMessageBox::selectable(parent, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, parent, tasks]() {
CustomMessageBox::selectable(parent, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, parent, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(parent, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (const auto& task : updateDialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(parent);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
}
model->update();
qDebug() << "Finished updating mods...";
emitSucceeded();
}
bool ModUpdateTask::canAbort() const
{
return true;
}
bool ModUpdateTask::abort()
{
return true;
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "net/NetJob.h"
#include "tasks/Task.h"
class MinecraftInstance;
class ModUpdateTask : public Task {
Q_OBJECT
public:
ModUpdateTask(MinecraftInstance* inst, bool enabled);
virtual ~ModUpdateTask() = default;
void executeTask() override;
bool canAbort() const override;
public:
static QString resourceUrl();
public slots:
bool abort() override;
private:
MinecraftInstance* m_instance;
bool m_enabledModsOnly;
bool m_includeDeps = true;
};

View file

@ -61,6 +61,7 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstance* instance, QW
m_ui->serverJoinGroupBox->hide();
m_ui->globalDataPacksGroupBox->hide();
m_ui->loaderGroup->hide();
m_ui->autoModUpdateGroup->hide();
} else {
m_javaSettings = new JavaSettingsWidget(m_instance, this);
m_ui->javaScrollArea->setWidget(m_javaSettings);
@ -120,6 +121,18 @@ MinecraftSettingsWidget::MinecraftSettingsWidget(MinecraftInstance* instance, QW
m_ui->legacyFabric, m_ui->ornithe, m_ui->rift }) {
connect(c, &QCheckBox::stateChanged, this, &MinecraftSettingsWidget::saveSelectedLoaders);
}
connect(m_ui->autoModUpdateGroup, &QGroupBox::toggled, this, [this](bool value) {
m_instance->settings()->set("AutomaticallyUpdateMods", value);
// if (!value) {
// m_instance->settings()->reset("AutomaticallyUpdateModsAll");
// m_instance->settings()->reset("AutomaticallyUpdateModsEnabled");
// }
});
connect(m_ui->allMods, &QAbstractButton::toggled, this,
[this](bool value) { m_instance->settings()->set("AutomaticallyUpdateModsAll", value); });
connect(m_ui->enabledMods, &QAbstractButton::toggled, this,
[this](bool value) { m_instance->settings()->set("AutomaticallyUpdateModsEnabled", value); });
}
m_ui->maximizedWarning->hide();
@ -248,6 +261,10 @@ void MinecraftSettingsWidget::loadSettings()
m_ui->serverJoinGroupBox->setChecked(settings->get("JoinServerOnLaunch").toBool());
m_ui->autoModUpdateGroup->setChecked(settings->get("AutomaticallyUpdateMods").toBool());
m_ui->allMods->setChecked(settings->get("AutomaticallyUpdateModsAll").toBool());
m_ui->enabledMods->setChecked(settings->get("AutomaticallyUpdateModsEnabled").toBool());
m_ui->instanceAccountGroupBox->setChecked(settings->get("UseAccountForInstance").toBool());
updateAccountsMenu(*settings);
@ -455,6 +472,21 @@ void MinecraftSettingsWidget::saveSettings()
settings->reset("JoinWorldOnLaunch");
}
bool automaticallyUpdateMods = m_ui->autoModUpdateGroup->isChecked();
settings->set("AutomaticallyUpdateMods", automaticallyUpdateMods);
// if (automaticallyUpdateMods) {
if (m_ui->allMods->isChecked()) {
settings->set("AutomaticallyUpdateModsAll", true);
settings->set("AutomaticallyUpdateModsEnabled", false);
} else {
settings->set("AutomaticallyUpdateModsAll", false);
settings->set("AutomaticallyUpdateModsEnabled", true);
}
// } else {
// settings->reset("AutomaticallyUpdateModsAll");
// settings->reset("AutomaticallyUpdateModsEnabled");
// }
// Use an account for this instance
bool useAccountForInstance = m_ui->instanceAccountGroupBox->isChecked();
settings->set("UseAccountForInstance", useAccountForInstance);

View file

@ -268,7 +268,7 @@
<widget class="QLabel" name="label_3">
<property name="text">
<string>Allows installing data packs across all worlds if an applicable mod is installed.
It is most likely you will need to change the path - please refer to the mod's website.</string>
It is most likely you will need to change the path - please refer to the mod's website.</string>
</property>
</widget>
</item>
@ -554,6 +554,35 @@ It is most likely you will need to change the path - please refer to the mod's w
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="autoModUpdateGroup">
<property name="title">
<string>Automatically Update Mods</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
<item>
<widget class="QRadioButton" name="allMods">
<property name="text">
<string>All mods</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="enabledMods">
<property name="text">
<string>Enabled mods only</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">