diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7d4430fd2..c86990301 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -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 diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index bcc770992..b7f5b717d 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -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" @@ -1208,6 +1209,13 @@ LaunchTask* MinecraftInstance::createLaunchTask(AuthSessionPtr session, Minecraf process->appendStep(makeShared(pptr)); } + // Update mods if "AutomaticallyUpdateMods" is true. + // Must come after ScanModFolders to ensure mods are loaded. + if (settings()->get("AutomaticallyUpdateMods").toBool()) { + process->appendStep( + makeShared(pptr, makeShared(this, settings()->get("AutomaticallyUpdateModsEnabled").toBool()))); + } + // make sure we have enough RAM, warn the user if we don't { process->appendStep(makeShared(pptr, this)); diff --git a/launcher/minecraft/update/ModUpdateTask.cpp b/launcher/minecraft/update/ModUpdateTask.cpp new file mode 100644 index 000000000..7a0416e43 --- /dev/null +++ b/launcher/minecraft/update/ModUpdateTask.cpp @@ -0,0 +1,125 @@ +#include "ModUpdateTask.h" + +#include +#include +#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(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 modsList; + modsList.reserve(_modsList.size()); + for (auto mod : _modsList) { + modsList.append(static_cast(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; +} diff --git a/launcher/minecraft/update/ModUpdateTask.h b/launcher/minecraft/update/ModUpdateTask.h new file mode 100644 index 000000000..012c60bff --- /dev/null +++ b/launcher/minecraft/update/ModUpdateTask.h @@ -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; +};