diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 4b17cdf07..fb866ede1 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -58,7 +58,6 @@ #include "ComponentUpdateTask.h" #include "PackProfile.h" #include "PackProfile_p.h" -#include "minecraft/mod/Mod.h" #include "modplatform/ModIndex.h" static const QMap modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge }, @@ -1022,3 +1021,23 @@ std::optional PackProfile::getSupportedModLoaders() loaders |= ModPlatform::Forge; return loaders; } + +QList PackProfile::getModLoadersList() +{ + QList result; + for (auto c : d->components) { + if (c->isEnabled() && modloaderMapping.contains(c->getID())) { + result.append(modloaderMapping[c->getID()]); + } + } + + // TODO: remove this or add version condition once Quilt drops official Fabric support + if (result.contains(ModPlatform::Quilt) && !result.contains(ModPlatform::Fabric)) { + result.append(ModPlatform::Fabric); + } + if (getComponentVersion("net.minecraft") == "1.20.1" && result.contains(ModPlatform::NeoForge) && + !result.contains(ModPlatform::Forge)) { + result.append(ModPlatform::Forge); + } + return result; +} diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index e58e9ae9a..9b6710cc3 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -146,6 +146,7 @@ class PackProfile : public QAbstractListModel { std::optional getModLoaders(); // this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge) std::optional getSupportedModLoaders(); + QList getModLoadersList(); private: void scheduleSave(); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index b19b25484..fecb842fd 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -3,7 +3,6 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/tasks/GetModDependenciesTask.h" #include "modplatform/ModIndex.h" -#include "modplatform/ResourceAPI.h" #include "tasks/Task.h" class ResourceDownloadTask; @@ -16,8 +15,14 @@ class CheckUpdateTask : public Task { CheckUpdateTask(QList& mods, std::list& mcVersions, std::optional loaders, + QList loadersList, std::shared_ptr mods_folder) - : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){}; + : Task(nullptr) + , m_mods(mods) + , m_game_versions(mcVersions) + , m_loaders(loaders) + , m_loaders_list(loadersList) + , m_mods_folder(mods_folder){}; struct UpdatableMod { QString name; @@ -68,6 +73,7 @@ class CheckUpdateTask : public Task { QList& m_mods; std::list& m_game_versions; std::optional m_loaders; + QList m_loaders_list; std::shared_ptr m_mods_folder; std::vector m_updatable; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index a1cfe1a60..72437976d 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -4,6 +4,7 @@ #include "FlameAPI.h" #include +#include #include "FlameModIndex.h" #include "Application.h" @@ -12,7 +13,6 @@ #include "net/ApiDownload.h" #include "net/ApiUpload.h" #include "net/NetJob.h" -#include "net/Upload.h" Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, std::shared_ptr response) { @@ -34,7 +34,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList& fingerprints, std::shar return netJob; } -auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString +QString FlameAPI::getModFileChangelog(int modId, int fileId) { QEventLoop lock; QString changelog; @@ -69,7 +69,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString return changelog; } -auto FlameAPI::getModDescription(int modId) -> QString +QString FlameAPI::getModDescription(int modId) { QEventLoop lock; QString description; @@ -102,7 +102,7 @@ auto FlameAPI::getModDescription(int modId) -> QString return description; } -auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion +QList FlameAPI::getLatestVersions(VersionSearchArgs&& args) { auto versions_url_optional = getVersionsURL(args); if (!versions_url_optional.has_value()) @@ -114,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe auto netJob = makeShared(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); auto response = std::make_shared(); - ModPlatform::IndexedVersion ver; + QList ver; netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response)); @@ -134,9 +134,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe for (auto file : arr) { auto file_obj = Json::requireObject(file); - auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); - if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders)) - ver = file_tmp; + ver.append(FlameMod::loadIndexedPackVersion(file_obj)); } } catch (Json::JsonException& e) { @@ -146,7 +144,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe } }); - QObject::connect(netJob.get(), &NetJob::finished, [&loop] { loop.quit(); }); + QObject::connect(netJob.get(), &NetJob::finished, &loop, &QEventLoop::quit); netJob->start(); @@ -260,4 +258,27 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QList versions, + QList instanceLoaders, + ModPlatform::ModLoaderTypes modLoaders) +{ + // edge case: mod has installed for forge but the instance is fabric => fabric version will be prioritizated on update + auto bestVersion = [&versions](ModPlatform::ModLoaderTypes loader) { + std::optional ver; + for (auto file_tmp : versions) { + if (file_tmp.loaders & loader && (!ver.has_value() || file_tmp.date > ver->date)) { + ver = file_tmp; + } + } + return ver; + }; + for (auto l : instanceLoaders) { + auto ver = bestVersion(l); + if (ver.has_value()) { + return ver; + } + } + return bestVersion(modLoaders); +} diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index dfe76f9d5..1160151c5 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -5,7 +5,6 @@ #pragma once #include -#include #include #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" @@ -13,10 +12,13 @@ class FlameAPI : public NetworkResourceAPI { public: - auto getModFileChangelog(int modId, int fileId) -> QString; - auto getModDescription(int modId) -> QString; + QString getModFileChangelog(int modId, int fileId); + QString getModDescription(int modId); - auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; + QList getLatestVersions(VersionSearchArgs&& args); + std::optional getLatestVersion(QList versions, + QList instanceLoaders, + ModPlatform::ModLoaderTypes fallback); Task::Ptr getProjects(QStringList addonIds, std::shared_ptr response) const override; Task::Ptr matchFingerprints(const QList& fingerprints, std::shared_ptr response); @@ -26,9 +28,9 @@ class FlameAPI : public NetworkResourceAPI { static Task::Ptr getModCategories(std::shared_ptr response); static QList loadModCategories(std::shared_ptr response); - [[nodiscard]] auto getSortingMethods() const -> QList override; + [[nodiscard]] QList getSortingMethods() const override; - static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool + static inline bool validateModLoaders(ModPlatform::ModLoaderTypes loaders) { return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt); } @@ -67,7 +69,7 @@ class FlameAPI : public NetworkResourceAPI { return 0; } - static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList + static const QStringList getModLoaderStrings(const ModPlatform::ModLoaderTypes types) { QStringList l; for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) { @@ -78,10 +80,7 @@ class FlameAPI : public NetworkResourceAPI { return l; } - static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString - { - return "[" + getModLoaderStrings(types).join(',') + "]"; - } + static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; } private: [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index b4eb304f0..9deffce54 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -132,25 +132,26 @@ void FlameCheckUpdate::executeTask() setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name())); setProgress(i++, m_mods.size()); - auto latest_ver = api.getLatestVersion({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders }); + auto latest_vers = api.getLatestVersions({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { aborted(); return; } + auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, mod->loaders()); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name())); - if (!latest_ver.addonId.isValid()) { + if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game " "version / mod loader.")); continue; } - if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) { - auto pack = getProjectInfo(latest_ver); - auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString()); + if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != mod->metadata()->file_id) { + auto pack = getProjectInfo(latest_ver.value()); + auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString()); emit checkFailed(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url); continue; @@ -166,19 +167,19 @@ void FlameCheckUpdate::executeTask() pack->authors.append({ author }); pack->description = mod->description(); pack->provider = ModPlatform::ResourceProvider::FLAME; - if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) { + if (!latest_ver->hash.isEmpty() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) { auto old_version = mod->version(); if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { - auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt()); + auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt()); old_version = current_ver.version; } - auto download_task = makeShared(pack, latest_ver, m_mods_folder); - m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type, - api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), + auto download_task = makeShared(pack, latest_ver.value(), m_mods_folder); + m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, + api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), ModPlatform::ResourceProvider::FLAME, download_task); } - m_deps.append(std::make_shared(pack, latest_ver)); + m_deps.append(std::make_shared(pack, latest_ver.value())); } emitSucceeded(); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index f5bb1653d..bd6806c60 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -1,6 +1,5 @@ #pragma once -#include "Application.h" #include "modplatform/CheckUpdateTask.h" #include "net/NetJob.h" @@ -11,8 +10,9 @@ class FlameCheckUpdate : public CheckUpdateTask { FlameCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, + QList loadersList, std::shared_ptr mods_folder) - : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) + : CheckUpdateTask(mods, mcVersions, loaders, loadersList, mods_folder) {} public slots: diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 700bcd2e6..50fb11f05 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -1,23 +1,32 @@ #include "ModrinthCheckUpdate.h" +#include "Application.h" #include "ModrinthAPI.h" #include "ModrinthPackIndex.h" #include "Json.h" +#include "QObjectPtr.h" #include "ResourceDownloadTask.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/ConcurrentTask.h" -#include "minecraft/mod/ModFolderModel.h" - static ModrinthAPI api; +ModrinthCheckUpdate::ModrinthCheckUpdate(QList& mods, + std::list& mcVersions, + std::optional loaders, + QList loadersList, + std::shared_ptr mods_folder) + : CheckUpdateTask(mods, mcVersions, loaders, loadersList, mods_folder) + , m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first()) +{} + bool ModrinthCheckUpdate::abort() { - if (m_net_job) - return m_net_job->abort(); + if (m_job) + return m_job->abort(); return true; } @@ -29,15 +38,10 @@ bool ModrinthCheckUpdate::abort() void ModrinthCheckUpdate::executeTask() { setStatus(tr("Preparing mods for Modrinth...")); - setProgress(0, 3); + setProgress(0, 9); - QHash mappings; - - // Create all hashes - QStringList hashes; - auto best_hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); - - ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); + auto hashing_task = + makeShared(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); for (auto* mod : m_mods) { if (!mod->enabled()) { emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); @@ -49,132 +53,176 @@ void ModrinthCheckUpdate::executeTask() // Sadly the API can only handle one hash type per call, se we // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) - if (mod->metadata()->hash_format != best_hash_type) { + if (mod->metadata()->hash_format != m_hash_type) { auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [&hashes, &mappings, mod](QString hash) { - hashes.append(hash); - mappings.insert(hash, mod); - }); + connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); - hashing_task.addTask(hash_task); + hashing_task->addTask(hash_task); } else { - hashes.append(hash); - mappings.insert(hash, mod); + m_mappings.insert(hash, mod); } } - QEventLoop loop; - connect(&hashing_task, &Task::finished, [&loop] { loop.quit(); }); - hashing_task.start(); - loop.exec(); + connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); + m_job = hashing_task; + hashing_task->start(); +} - auto response = std::make_shared(); - auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); +void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr response, + ModPlatform::ModLoaderTypes loader, + bool forceModLoaderCheck) +{ + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; - connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; + emitFailed(parse_error.errorString()); + return; + } - emitFailed(parse_error.errorString()); - return; - } + setStatus(tr("Parsing the API response from Modrinth...")); + setProgress(m_next_loader_idx * 2, 9); - setStatus(tr("Parsing the API response from Modrinth...")); - setProgress(2, 3); - - try { - for (auto hash : mappings.keys()) { - auto project_obj = doc[hash].toObject(); - - // If the returned project is empty, but we have Modrinth metadata, - // it means this specific version is not available - if (project_obj.isEmpty()) { - qDebug() << "Mod " << mappings.find(hash).value()->name() << " got an empty response."; - qDebug() << "Hash: " << hash; - - emit checkFailed( - mappings.find(hash).value(), - tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader.")); - - continue; - } - - // Sometimes a version may have multiple files, one with "forge" and one with "fabric", - // so we may want to filter it - QString loader_filter; - if (m_loaders.has_value()) { - static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, - ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt }; - for (auto flag : flags) { - if (m_loaders.value().testFlag(flag)) { - loader_filter = ModPlatform::getModLoaderAsString(flag); - break; - } - } - } - - // Currently, we rely on a couple heuristics to determine whether an update is actually available or not: - // - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the - // loader_filter - // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) - // Such is the pain of having arbitrary files for a given version .-. - - auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, best_hash_type, loader_filter); - if (project_ver.downloadUrl.isEmpty()) { - qCritical() << "Modrinth mod without download url!"; - qCritical() << project_ver.fileName; - - emit checkFailed(mappings.find(hash).value(), tr("Mod has an empty download URL")); - - continue; - } - - auto mod_iter = mappings.find(hash); - if (mod_iter == mappings.end()) { - qCritical() << "Failed to remap mod from Modrinth!"; - continue; - } - auto mod = *mod_iter; - - auto key = project_ver.hash; - - // Fake pack with the necessary info to pass to the download task :) - auto pack = std::make_shared(); - pack->name = mod->name(); - pack->slug = mod->metadata()->slug; - pack->addonId = mod->metadata()->project_id; - pack->websiteUrl = mod->homeurl(); - for (auto& author : mod->authors()) - pack->authors.append({ author }); - pack->description = mod->description(); - pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { - if (mod->version() == project_ver.version_number) - continue; - - auto download_task = makeShared(pack, project_ver, m_mods_folder); - - m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type, - project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); - } - m_deps.append(std::make_shared(pack, project_ver)); + try { + for (auto hash : m_mappings.keys()) { + if (forceModLoaderCheck && !m_mappings[hash]->loaders().testAnyFlags(loader)) { + continue; } - } catch (Json::JsonException& e) { - emitFailed(e.cause() + " : " + e.what()); - return; - } - emitSucceeded(); - }); + auto project_obj = doc[hash].toObject(); - connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed); + // If the returned project is empty, but we have Modrinth metadata, + // it means this specific version is not available + if (project_obj.isEmpty()) { + qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." + << "Hash: " << hash; + + continue; + } + + // Sometimes a version may have multiple files, one with "forge" and one with "fabric", + // so we may want to filter it + QString loader_filter; + static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, + ModPlatform::ModLoaderType::Quilt, ModPlatform::ModLoaderType::Fabric }; + for (auto flag : flags) { + if (loader.testFlag(flag)) { + loader_filter = ModPlatform::getModLoaderAsString(flag); + break; + } + } + + // Currently, we rely on a couple heuristics to determine whether an update is actually available or not: + // - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the + // loader_filter + // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) + // Such is the pain of having arbitrary files for a given version .-. + + auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter); + if (project_ver.downloadUrl.isEmpty()) { + qCritical() << "Modrinth mod without download url!" << project_ver.fileName; + + continue; + } + + auto mod_iter = m_mappings.find(hash); + if (mod_iter == m_mappings.end()) { + qCritical() << "Failed to remap mod from Modrinth!"; + continue; + } + auto mod = *mod_iter; + m_mappings.remove(hash); + + auto key = project_ver.hash; + + // Fake pack with the necessary info to pass to the download task :) + auto pack = std::make_shared(); + pack->name = mod->name(); + pack->slug = mod->metadata()->slug; + pack->addonId = mod->metadata()->project_id; + pack->websiteUrl = mod->homeurl(); + for (auto& author : mod->authors()) + pack->authors.append({ author }); + pack->description = mod->description(); + pack->provider = ModPlatform::ResourceProvider::MODRINTH; + if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) { + if (mod->version() == project_ver.version_number) + continue; + + auto download_task = makeShared(pack, project_ver, m_mods_folder); + + m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type, + project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task); + } + m_deps.append(std::make_shared(pack, project_ver)); + } + } catch (Json::JsonException& e) { + emitFailed(e.cause() + " : " + e.what()); + return; + } + checkNextLoader(); +} + +void ModrinthCheckUpdate::getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck) +{ + auto response = std::make_shared(); + QStringList hashes; + if (forceModLoaderCheck) { + for (auto hash : m_mappings.keys()) { + if (m_mappings[hash]->loaders().testAnyFlags(loader)) { + hashes.append(hash); + } + } + } else { + hashes = m_mappings.keys(); + } + auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response); + + connect(job.get(), &Task::succeeded, this, + [this, response, loader, forceModLoaderCheck] { checkVersionsResponse(response, loader, forceModLoaderCheck); }); + + connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader); setStatus(tr("Waiting for the API response from Modrinth...")); - setProgress(1, 3); + setProgress(m_next_loader_idx * 2 - 1, 9); - m_net_job = qSharedPointerObjectCast(job); + m_job = job; job->start(); } + +void ModrinthCheckUpdate::checkNextLoader() +{ + if (m_mappings.isEmpty()) { + emitSucceeded(); + return; + } + if (m_next_loader_idx < m_loaders_list.size()) { + getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx)); + m_next_loader_idx++; + return; + } + static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt, + ModPlatform::ModLoaderType::Fabric }; + for (auto flag : flags) { + if (!m_loaders_list.contains(flag)) { + m_loaders_list.append(flag); + m_next_loader_idx++; + setProgress(m_next_loader_idx * 2 - 1, 9); + for (auto m : m_mappings) { + if (m->loaders().testAnyFlag(flag)) { + getUpdateModsForLoader(flag, true); + return; + } + } + setProgress(m_next_loader_idx * 2, 9); + } + } + for (auto m : m_mappings) { + emit checkFailed(m, + tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader.")); + } + emitSucceeded(); + return; +} diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index f2f2c7e92..2abd51970 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -1,8 +1,6 @@ #pragma once -#include "Application.h" #include "modplatform/CheckUpdateTask.h" -#include "net/NetJob.h" class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT @@ -11,16 +9,21 @@ class ModrinthCheckUpdate : public CheckUpdateTask { ModrinthCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, - std::shared_ptr mods_folder) - : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) - {} + QList loadersList, + std::shared_ptr mods_folder); public slots: bool abort() override; protected slots: void executeTask() override; + void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false); + void checkVersionsResponse(std::shared_ptr response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false); + void checkNextLoader(); private: - NetJob::Ptr m_net_job = nullptr; + Task::Ptr m_job = nullptr; + QHash m_mappings; + QString m_hash_type; + int m_next_loader_idx = 0; }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 1583b4f46..5cb25b218 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -1,4 +1,5 @@ #include "ModUpdateDialog.h" +#include "Application.h" #include "ChooseProviderDialog.h" #include "CustomMessageBox.h" #include "ProgressDialog.h" @@ -32,7 +33,12 @@ static std::list mcVersions(BaseInstance* inst) static std::optional mcLoaders(BaseInstance* inst) { - return { static_cast(inst)->getPackProfile()->getSupportedModLoaders() }; + return static_cast(inst)->getPackProfile()->getSupportedModLoaders(); +} + +static QList mcLoadersList(BaseInstance* inst) +{ + return static_cast(inst)->getPackProfile()->getModLoadersList(); } ModUpdateDialog::ModUpdateDialog(QWidget* parent, @@ -87,11 +93,12 @@ void ModUpdateDialog::checkCandidates() auto versions = mcVersions(m_instance); auto loaders = mcLoaders(m_instance); + auto loadersList = mcLoadersList(m_instance); SequentialTask check_task(m_parent, tr("Checking for updates")); if (!m_modrinth_to_update.empty()) { - m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model)); + m_modrinth_check_task.reset(new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, loadersList, m_mod_model)); connect(m_modrinth_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); }); @@ -99,7 +106,7 @@ void ModUpdateDialog::checkCandidates() } if (!m_flame_to_update.empty()) { - m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model)); + m_flame_check_task.reset(new FlameCheckUpdate(m_flame_to_update, versions, loaders, loadersList, m_mod_model)); connect(m_flame_check_task.get(), &CheckUpdateTask::checkFailed, this, [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({ mod, reason, recover_url }); });