// SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher * Copyright (c) 2023 Trial97 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "GetModDependenciesTask.h" #include #include #include #include "Json.h" #include "QObjectPtr.h" #include "minecraft/mod/MetadataHandler.h" #include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "tasks/ConcurrentTask.h" #include "tasks/SequentialTask.h" #include "ui/pages/modplatform/ModModel.h" #include "ui/pages/modplatform/flame/FlameResourceModels.h" #include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h" static Version mcVersion(BaseInstance* inst) { return static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion(); } static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) { return static_cast(inst)->getPackProfile()->getModLoaders().value(); } GetModDependenciesTask::GetModDependenciesTask(QObject* parent, BaseInstance* instance, ModFolderModel* folder, QList> selected) : SequentialTask(parent, tr("Get dependencies")) , m_selected(selected) , m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared(*instance), std::make_shared() } , m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared(*instance), std::make_shared() } , m_version(mcVersion(instance)) , m_loaderType(mcLoaders(instance)) { for (auto mod : folder->allMods()) if (auto meta = mod->metadata(); meta) m_mods.append(meta); prepare(); } void GetModDependenciesTask::prepare() { for (auto sel : m_selected) { for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) { addTask(prepareDependencyTask(dep, sel->pack->provider, 20)); } } } ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, const ModPlatform::ResourceProvider providerName) { if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) { auto overide = ModPlatform::getOverrideDeps(); auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) { return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt); }); if (over != overide.cend()) { return { isQuilt ? over->quilt : over->fabric, dep.type }; } } return dep; } QList GetModDependenciesTask::getDependenciesForVersion(const ModPlatform::IndexedVersion& version, const ModPlatform::ResourceProvider providerName) { QList c_dependencies; for (auto ver_dep : version.dependencies) { if (ver_dep.type != ModPlatform::DependencyType::REQUIRED) continue; auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty(); if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(), [&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) { return isOnlyVersion ? i.version == ver_dep.version : i.addonId == ver_dep.addonId; }); dep != c_dependencies.end()) continue; // check the current dependency list if (auto dep = std::find_if(m_selected.begin(), m_selected.end(), [&ver_dep, providerName, isOnlyVersion](std::shared_ptr i) { return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.version : i->pack->addonId == ver_dep.addonId); }); dep != m_selected.end()) continue; // check the selected versions if (auto dep = std::find_if(m_mods.begin(), m_mods.end(), [&ver_dep, providerName, isOnlyVersion](std::shared_ptr i) { return i->provider == providerName && (isOnlyVersion ? i->file_id == ver_dep.version : i->project_id == ver_dep.addonId); }); dep != m_mods.end()) continue; // check the existing mods if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), [&ver_dep, providerName, isOnlyVersion](std::shared_ptr i) { return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.addonId : i->pack->addonId == ver_dep.addonId); }); dep != m_pack_dependencies.end()) // check loaded dependencies continue; c_dependencies.append(getOverride(ver_dep, providerName)); } return c_dependencies; } Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr pDep) { auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; auto responseInfo = std::make_shared(); auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo); QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error); if (parse_error.error != QJsonParseError::NoError) { qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset << " reason: " << parse_error.errorString(); qDebug() << *responseInfo; return; } try { auto obj = provider.name == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data") : Json::requireObject(doc); provider.mod->loadIndexedPack(*pDep->pack, obj); } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading mod info: " << e.cause(); } }); return info; } Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Dependency& dep, const ModPlatform::ResourceProvider providerName, int level) { auto pDep = std::make_shared(); pDep->dependency = dep; pDep->pack = std::make_shared(); pDep->pack->addonId = dep.addonId; pDep->pack->provider = providerName; m_pack_dependencies.append(pDep); auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider; auto tasks = makeShared( this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString())); if (!dep.addonId.toString().isEmpty()) { tasks->addTask(getProjectInfoTask(pDep)); } ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType }; ResourceAPI::DependencySearchCallbacks callbacks; callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) { try { QJsonArray arr; if (dep.version.length() != 0 && doc.isObject()) { arr.append(doc.object()); } else { arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); } pDep->version = provider.mod->loadDependencyVersions(dep, arr); if (!pDep->version.addonId.isValid()) { if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt auto overide = ModPlatform::getOverrideDeps(); auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; }); if (over != overide.cend()) { removePack(dep.addonId); addTask(prepareDependencyTask({ over->fabric, dep.type }, provider.name, level)); return; } } qWarning() << "Error while reading mod version empty "; qDebug() << doc; return; } pDep->version.is_currently_selected = true; pDep->pack->versions = { pDep->version }; pDep->pack->versionsLoaded = true; } catch (const JSONValidationError& e) { qDebug() << doc; qWarning() << "Error while reading mod version: " << e.cause(); return; } if (level == 0) { qWarning() << "Dependency cycle exceeded"; return; } if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) { pDep->pack->addonId = pDep->version.addonId; auto dep_ = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name); if (dep_.addonId != pDep->version.addonId) { removePack(pDep->version.addonId); addTask(prepareDependencyTask(dep_, provider.name, level)); } else addTask(getProjectInfoTask(pDep)); } for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) { addTask(prepareDependencyTask(dep_, provider.name, level - 1)); } }; auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks)); tasks->addTask(version); return tasks; } void GetModDependenciesTask::removePack(const QVariant addonId) { auto pred = [addonId](const std::shared_ptr& v) { return v->pack->addonId == addonId; }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) m_pack_dependencies.removeIf(pred); #else for (auto it = m_pack_dependencies.begin(); it != m_pack_dependencies.end();) if (pred(*it)) it = m_pack_dependencies.erase(it); else ++it; #endif } QHash GetModDependenciesTask::getRequiredBy() { QHash rby; auto fullList = m_selected + m_pack_dependencies; for (auto mod : fullList) { auto addonId = mod->pack->addonId; auto provider = mod->pack->provider; auto version = mod->version.fileId; auto req = QStringList(); for (auto& smod : fullList) { if (provider != smod->pack->provider) continue; auto deps = smod->version.dependencies; if (auto dep = std::find_if(deps.begin(), deps.end(), [addonId, provider, version](const ModPlatform::Dependency& d) { return d.type == ModPlatform::DependencyType::REQUIRED && (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty() ? version == d.version : d.addonId == addonId); }); dep != deps.end()) { req.append(smod->pack->name); } } rby[addonId.toString()] = req; } return rby; }