diff options
author | Trial97 <alexandru.tripon97@gmail.com> | 2023-05-31 20:12:12 +0300 |
---|---|---|
committer | Trial97 <alexandru.tripon97@gmail.com> | 2023-05-31 20:12:12 +0300 |
commit | 29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089 (patch) | |
tree | ce92f8a86d08531879105a16194a14391c0ae2ea /launcher/modplatform | |
parent | e8ee4497f77a571b305a48b70f84c8729b800859 (diff) | |
parent | 954d4d701a136e79c25b58f9680d26a555a6e6fe (diff) | |
download | PrismLauncher-29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089.tar.gz PrismLauncher-29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089.tar.bz2 PrismLauncher-29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089.zip |
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into logdir
Diffstat (limited to 'launcher/modplatform')
42 files changed, 1032 insertions, 1523 deletions
diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 91922034..f7582b8f 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -1,18 +1,18 @@ #pragma once #include "minecraft/mod/Mod.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "modplatform/ModIndex.h" #include "tasks/Task.h" -class ModDownloadTask; +class ResourceDownloadTask; class ModFolderModel; class CheckUpdateTask : public Task { Q_OBJECT public: - CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder) + CheckUpdateTask(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder) : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; struct UpdatableMod { @@ -21,11 +21,11 @@ class CheckUpdateTask : public Task { QString old_version; QString new_version; QString changelog; - ModPlatform::Provider provider; - ModDownloadTask* download; + ModPlatform::ResourceProvider provider; + shared_qobject_ptr<ResourceDownloadTask> download; public: - UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t) + UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr<ResourceDownloadTask> t) : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) {} }; @@ -44,7 +44,7 @@ class CheckUpdateTask : public Task { protected: QList<Mod*>& m_mods; std::list<Version>& m_game_versions; - ModAPI::ModLoaderTypes m_loaders; + std::optional<ResourceAPI::ModLoaderTypes> m_loaders; std::shared_ptr<ModFolderModel> m_mods_folder; std::vector<UpdatableMod> m_updatable; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 234330a7..34d969f0 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -13,14 +13,12 @@ #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" -#include "net/NetJob.h" - static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { auto hash_task = createNewHash(mod); @@ -31,10 +29,10 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider hash_task->start(); } -EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { - m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); + m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10)); for (auto* mod : mods) { auto hash_task = createNewHash(mod); if (!hash_task) @@ -107,13 +105,13 @@ void EnsureMetadataTask::executeTask() } } - NetJob::Ptr version_task; + Task::Ptr version_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): version_task = modrinthVersionsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): version_task = flameVersionsTask(); break; } @@ -127,13 +125,13 @@ void EnsureMetadataTask::executeTask() }; connect(version_task.get(), &Task::finished, this, [this, invalidade_leftover] { - NetJob::Ptr project_task; + Task::Ptr project_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): project_task = modrinthProjectsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): project_task = flameProjectsTask(); break; } @@ -149,7 +147,7 @@ void EnsureMetadataTask::executeTask() m_current_task = nullptr; }); - m_current_task = project_task.get(); + m_current_task = project_task; project_task->start(); }); @@ -164,7 +162,7 @@ void EnsureMetadataTask::executeTask() setStatus(tr("Requesting metadata information from %1 for '%2'...") .arg(ProviderCaps.readableName(m_provider), m_mods.begin().value()->name())); - m_current_task = version_task.get(); + m_current_task = version_task; version_task->start(); } @@ -210,18 +208,18 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) // Modrinth -NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() +Task::Ptr EnsureMetadataTask::modrinthVersionsTask() { - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto* response = new QByteArray(); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); // Prevents unfortunate timings when aborting the task if (!ver_task) - return {}; + return Task::Ptr{nullptr}; - connect(ver_task.get(), &NetJob::succeeded, this, [this, response] { + connect(ver_task.get(), &Task::succeeded, this, [this, response] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -260,14 +258,14 @@ NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() return ver_task; } -NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() +Task::Ptr EnsureMetadataTask::modrinthProjectsTask() { QHash<QString, QString> addonIds; for (auto const& data : m_temp_versions) addonIds.insert(data.addonId.toString(), data.hash); auto response = new QByteArray(); - NetJob::Ptr proj_task; + Task::Ptr proj_task; if (addonIds.isEmpty()) { qWarning() << "No addonId found!"; @@ -279,9 +277,9 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() // Prevents unfortunate timings when aborting the task if (!proj_task) - return {}; + return Task::Ptr{nullptr}; - connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] { + connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -291,43 +289,53 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() return; } + QJsonArray entries; + try { - QJsonArray entries; if (addonIds.size() == 1) entries = { doc.object() }; else entries = Json::requireArray(doc); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } - for (auto entry : entries) { + for (auto entry : entries) { + ModPlatform::IndexedPack pack; + + try { auto entry_obj = Json::requireObject(entry); - ModPlatform::IndexedPack pack; Modrinth::loadIndexedPack(pack, entry_obj); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; - auto hash = addonIds.find(pack.addonId.toString()).value(); + // Skip this entry, since it has problems + continue; + } - auto mod_iter = m_mods.find(hash); - if (mod_iter == m_mods.end()) { - qWarning() << "Invalid project id from the API response."; - continue; - } + auto hash = addonIds.find(pack.addonId.toString()).value(); - auto* mod = mod_iter.value(); + auto mod_iter = m_mods.find(hash); + if (mod_iter == m_mods.end()) { + qWarning() << "Invalid project id from the API response."; + continue; + } - try { - setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); + auto* mod = mod_iter.value(); - modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); - } catch (Json::JsonException& e) { - qDebug() << e.cause(); - qDebug() << entries; + try { + setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name())); - emitFail(mod); - } + modrinthCallback(pack, m_temp_versions.find(hash).value(), mod); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << entries; + + emitFail(mod); } - } catch (Json::JsonException& e) { - qDebug() << e.cause(); - qDebug() << doc; } }); @@ -335,7 +343,7 @@ NetJob::Ptr EnsureMetadataTask::modrinthProjectsTask() } // Flame -NetJob::Ptr EnsureMetadataTask::flameVersionsTask() +Task::Ptr EnsureMetadataTask::flameVersionsTask() { auto* response = new QByteArray(); @@ -400,7 +408,7 @@ NetJob::Ptr EnsureMetadataTask::flameVersionsTask() return ver_task; } -NetJob::Ptr EnsureMetadataTask::flameProjectsTask() +Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash<QString, QString> addonIds; for (auto const& hash : m_mods.keys()) { @@ -414,7 +422,7 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask() } auto response = new QByteArray(); - NetJob::Ptr proj_task; + Task::Ptr proj_task; if (addonIds.isEmpty()) { qWarning() << "No addonId found!"; @@ -426,9 +434,9 @@ NetJob::Ptr EnsureMetadataTask::flameProjectsTask() // Prevents unfortunate timings when aborting the task if (!proj_task) - return {}; + return Task::Ptr{nullptr}; - connect(proj_task.get(), &NetJob::succeeded, this, [this, response, addonIds] { + connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] { QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index a8b0851e..03cae4e4 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -14,8 +14,8 @@ class EnsureMetadataTask : public Task { Q_OBJECT public: - EnsureMetadataTask(Mod*, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); - EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); + EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); ~EnsureMetadataTask() = default; @@ -28,11 +28,11 @@ class EnsureMetadataTask : public Task { private: // FIXME: Move to their own namespace - auto modrinthVersionsTask() -> NetJob::Ptr; - auto modrinthProjectsTask() -> NetJob::Ptr; + auto modrinthVersionsTask() -> Task::Ptr; + auto modrinthProjectsTask() -> Task::Ptr; - auto flameVersionsTask() -> NetJob::Ptr; - auto flameProjectsTask() -> NetJob::Ptr; + auto flameVersionsTask() -> Task::Ptr; + auto flameProjectsTask() -> Task::Ptr; // Helpers enum class RemoveFromList { @@ -57,9 +57,9 @@ class EnsureMetadataTask : public Task { private: QHash<QString, Mod*> m_mods; QDir m_index_dir; - ModPlatform::Provider m_provider; + ModPlatform::ResourceProvider m_provider; QHash<QString, ModPlatform::IndexedVersion> m_temp_versions; - ConcurrentTask* m_hashing_task; - NetJob* m_current_task; + ConcurrentTask::Ptr m_hashing_task; + Task::Ptr m_current_task; }; diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h deleted file mode 100644 index 703de143..00000000 --- a/launcher/modplatform/ModAPI.h +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - * - * 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 <https://www.gnu.org/licenses/>. - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QString> -#include <QList> -#include <list> - -#include "../Version.h" -#include "net/NetJob.h" - -namespace ModPlatform { -class ListModel; -struct IndexedPack; -} - -class ModAPI { - protected: - using CallerType = ModPlatform::ListModel; - - public: - virtual ~ModAPI() = default; - - enum ModLoaderType { - Unspecified = 0, - Forge = 1 << 0, - Cauldron = 1 << 1, - LiteLoader = 1 << 2, - Fabric = 1 << 3, - Quilt = 1 << 4 - }; - Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) - - struct SearchArgs { - int offset; - QString search; - QString sorting; - ModLoaderTypes loaders; - std::list<Version> versions; - }; - - virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; - virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) = 0; - - virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0; - virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0; - - - struct VersionSearchArgs { - QString addonId; - std::list<Version> mcVersions; - ModLoaderTypes loaders; - }; - - virtual void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const = 0; - - static auto getModLoaderString(ModLoaderType type) -> const QString { - switch (type) { - case Unspecified: - break; - case Forge: - return "forge"; - case Cauldron: - return "cauldron"; - case LiteLoader: - return "liteloader"; - case Fabric: - return "fabric"; - case Quilt: - return "quilt"; - } - return ""; - } - - protected: - inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString - { - QString s; - for(auto& ver : mcVersions){ - s += QString("\"%1\",").arg(ver.toString()); - } - s.remove(s.length() - 1, 1); //remove last comma - return s; - } -}; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 34fd9f30..6a507caf 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -24,47 +24,47 @@ namespace ModPlatform { -auto ProviderCapabilities::name(Provider p) -> const char* +auto ProviderCapabilities::name(ResourceProvider p) -> const char* { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "curseforge"; } return {}; } -auto ProviderCapabilities::readableName(Provider p) -> QString +auto ProviderCapabilities::readableName(ResourceProvider p) -> QString { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "Modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "CurseForge"; } return {}; } -auto ProviderCapabilities::hashType(Provider p) -> QStringList +auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return { "sha512", "sha1" }; - case Provider::FLAME: + case ResourceProvider::FLAME: // Try newer formats first, fall back to old format return { "sha1", "md5", "murmur2" }; } return {}; } -auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString +auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString { QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; switch (p) { - case Provider::MODRINTH: { + case ResourceProvider::MODRINTH: { algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; break; } - case Provider::FLAME: + case ResourceProvider::FLAME: algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; break; } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 518fed7c..8d0223f9 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -23,22 +23,22 @@ #include <QString> #include <QVariant> #include <QVector> +#include <memory> class QIODevice; namespace ModPlatform { -enum class Provider { - MODRINTH, - FLAME -}; +enum class ResourceProvider { MODRINTH, FLAME }; + +enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; class ProviderCapabilities { public: - auto name(Provider) -> const char*; - auto readableName(Provider) -> QString; - auto hashType(Provider) -> QStringList; - auto hash(Provider, QIODevice*, QString type = "") -> QString; + auto name(ResourceProvider) -> const char*; + auto readableName(ResourceProvider) -> QString; + auto hashType(ResourceProvider) -> QStringList; + auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString; }; struct ModpackAuthor { @@ -66,6 +66,10 @@ struct IndexedVersion { QString hash; bool is_preferred = true; QString changelog; + + // For internal use, not provided by APIs + bool is_currently_selected = false; + QString custom_target_folder; }; struct ExtraPackData { @@ -80,8 +84,10 @@ struct ExtraPackData { }; struct IndexedPack { + using Ptr = std::shared_ptr<IndexedPack>; + QVariant addonId; - Provider provider; + ResourceProvider provider; QString name; QString slug; QString description; @@ -96,9 +102,26 @@ struct IndexedPack { // Don't load by default, since some modplatform don't have that info bool extraDataLoaded = true; ExtraPackData extraData; + + // For internal use, not provided by APIs + [[nodiscard]] bool isVersionSelected(size_t index) const + { + if (!versionsLoaded) + return false; + + return versions.at(index).is_currently_selected; + } + [[nodiscard]] bool isAnyVersionSelected() const + { + if (!versionsLoaded) + return false; + + return std::any_of(versions.constBegin(), versions.constEnd(), + [](auto const& v) { return v.is_currently_selected; }); + } }; } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) -Q_DECLARE_METATYPE(ModPlatform::Provider) +Q_DECLARE_METATYPE(ModPlatform::ResourceProvider) diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h new file mode 100644 index 00000000..34f33779 --- /dev/null +++ b/launcher/modplatform/ResourceAPI.h @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com> +// +// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0 +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + * 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 <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QDebug> +#include <QList> +#include <QString> + +#include <list> +#include <optional> + +#include "../Version.h" + +#include "modplatform/ModIndex.h" +#include "tasks/Task.h" + +/* Simple class with a common interface for interacting with APIs */ +class ResourceAPI { + public: + virtual ~ResourceAPI() = default; + + enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; + Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) + + struct SortingMethod { + // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. + // Used by Flame in the API request. + unsigned int index; + // The real name of the sorting, as used in the respective API specification. + // Used by Modrinth in the API request. + QString name; + // The human-readable name of the sorting, used for display in the UI. + QString readable_name; + }; + + struct SearchArgs { + ModPlatform::ResourceType type{}; + int offset = 0; + + std::optional<QString> search; + std::optional<SortingMethod> sorting; + std::optional<ModLoaderTypes> loaders; + std::optional<std::list<Version> > versions; + }; + struct SearchCallbacks { + std::function<void(QJsonDocument&)> on_succeed; + std::function<void(QString const& reason, int network_error_code)> on_fail; + std::function<void()> on_abort; + }; + + struct VersionSearchArgs { + ModPlatform::IndexedPack pack; + + std::optional<std::list<Version> > mcVersions; + std::optional<ModLoaderTypes> loaders; + + VersionSearchArgs(VersionSearchArgs const&) = default; + void operator=(VersionSearchArgs other) + { + pack = other.pack; + mcVersions = other.mcVersions; + loaders = other.loaders; + } + }; + struct VersionSearchCallbacks { + std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; + }; + + struct ProjectInfoArgs { + ModPlatform::IndexedPack pack; + + ProjectInfoArgs(ProjectInfoArgs const&) = default; + void operator=(ProjectInfoArgs other) { pack = other.pack; } + }; + struct ProjectInfoCallbacks { + std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; + }; + + public: + /** Gets a list of available sorting methods for this API. */ + [[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0; + + public slots: + [[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + + [[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + + static auto getModLoaderString(ModLoaderType type) -> const QString + { + switch (type) { + case Forge: + return "forge"; + case Cauldron: + return "cauldron"; + case LiteLoader: + return "liteloader"; + case Fabric: + return "fabric"; + case Quilt: + return "quilt"; + default: + break; + } + return ""; + } + + protected: + [[nodiscard]] inline QString debugName() const { return "External resource API"; } + + [[nodiscard]] inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString + { + QString s; + for (auto& ver : mcVersions) { + s += QString("\"%1\",").arg(ver.toString()); + } + s.remove(s.length() - 1, 1); // remove last comma + return s; + } +}; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 291ad916..96cea7b7 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -81,16 +81,17 @@ bool PackInstallTask::abort() void PackInstallTask::executeTask() { qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId(); - auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); + NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) }; auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json") .arg(m_pack_safe_name).arg(m_version_name); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + + QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); + QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + QObject::connect(netJob.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); + jobPtr = netJob; jobPtr->start(); - - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); - QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); } void PackInstallTask::onDownloadSucceeded() @@ -552,7 +553,7 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(new Component(profile.get(), target_id, f)); + profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) }); return true; } @@ -641,7 +642,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(new Component(profile.get(), target_id, f)); + profile->appendComponent(ComponentPtr{ new Component(profile.get(), target_id, f) }); return true; } @@ -649,7 +650,7 @@ void PackInstallTask::installConfigs() { qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId(); setStatus(tr("Downloading configs...")); - jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); + jobPtr.reset(new NetJob(tr("Config download"), APPLICATION->network())); auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip") @@ -682,6 +683,7 @@ void PackInstallTask::installConfigs() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(jobPtr.get(), &NetJob::aborted, [&]{ abortable = false; jobPtr.reset(); @@ -747,7 +749,7 @@ void PackInstallTask::downloadMods() setStatus(tr("Downloading mods...")); jarmods.clear(); - jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); + jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network())); for(const auto& mod : m_version.mods) { // skip non-client mods if(!mod.client) continue; @@ -845,9 +847,11 @@ void PackInstallTask::downloadMods() }); connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(jobPtr.get(), &NetJob::aborted, [&] { abortable = false; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 7f1beb1a..83db642e 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -23,7 +23,7 @@ void Flame::FileResolvingTask::executeTask() { setStatus(tr("Resolving mod IDs...")); setProgress(0, 3); - m_dljob = new NetJob("Mod id resolver", m_network); + m_dljob.reset(new NetJob("Mod id resolver", m_network)); result.reset(new QByteArray()); //build json data to send QJsonObject object; @@ -35,7 +35,29 @@ void Flame::FileResolvingTask::executeTask() QByteArray data = Json::toText(object); auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data); m_dljob->addNetAction(dl); - connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); + + auto step_progress = std::make_shared<TaskStepProgress>(); + connect(m_dljob.get(), &NetJob::finished, this, [this, step_progress]() { + step_progress->state = TaskStepState::Succeeded; + stepProgress(*step_progress); + netJobFinished(); + }); + connect(m_dljob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { + step_progress->state = TaskStepState::Failed; + stepProgress(*step_progress); + emitFailed(reason); + }); + connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); + connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { + qDebug() << "Resolve slug progress" << current << total; + step_progress->update(current, total); + stepProgress(*step_progress); + }); + connect(m_dljob.get(), &NetJob::status, this, [this, step_progress](QString status) { + step_progress->status = status; + stepProgress(*step_progress); + }); + m_dljob->start(); } @@ -43,8 +65,8 @@ void Flame::FileResolvingTask::netJobFinished() { setProgress(1, 3); // job to check modrinth for blocked projects - m_checkJob = new NetJob("Modrinth check", m_network); - blockedProjects = QMap<File *,QByteArray *>(); + m_checkJob.reset(new NetJob("Modrinth check", m_network)); + blockedProjects = QMap<File*, std::shared_ptr<QByteArray>>(); QJsonDocument doc; QJsonArray array; @@ -71,8 +93,8 @@ void Flame::FileResolvingTask::netJobFinished() auto hash = out.hash; if(!hash.isEmpty()) { auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); - auto output = new QByteArray(); - auto dl = Net::Download::makeByteArray(QUrl(url), output); + auto output = std::make_shared<QByteArray>(); + auto dl = Net::Download::makeByteArray(QUrl(url), output.get()); QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; }); @@ -82,7 +104,27 @@ void Flame::FileResolvingTask::netJobFinished() } } } - connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished); + auto step_progress = std::make_shared<TaskStepProgress>(); + connect(m_checkJob.get(), &NetJob::finished, this, [this, step_progress]() { + step_progress->state = TaskStepState::Succeeded; + stepProgress(*step_progress); + modrinthCheckFinished(); + }); + connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { + step_progress->state = TaskStepState::Failed; + stepProgress(*step_progress); + emitFailed(reason); + }); + connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); + connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { + qDebug() << "Resolve slug progress" << current << total; + step_progress->update(current, total); + stepProgress(*step_progress); + }); + connect(m_checkJob.get(), &NetJob::status, this, [this, step_progress](QString status) { + step_progress->status = status; + stepProgress(*step_progress); + }); m_checkJob->start(); } @@ -95,7 +137,6 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { auto &out = *it; auto bytes = blockedProjects[out]; if (!out->resolved) { - delete bytes; continue; } @@ -112,11 +153,9 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { } else { out->resolved = false; } - - delete bytes; } //copy to an output list and filter out projects found on modrinth - auto block = new QList<File *>(); + auto block = std::make_shared<QList<File*>>(); auto it = blockedProjects.keys(); std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { return !f->resolved; @@ -124,32 +163,48 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { //Display not found mods early if (!block->empty()) { //blocked mods found, we need the slug for displaying.... we need another job :D ! - auto slugJob = new NetJob("Slug Job", m_network); - auto slugs = QVector<QByteArray>(block->size()); - auto index = 0; - for (auto fileInfo: *block) { - auto projectId = fileInfo->projectId; - slugs[index] = QByteArray(); + m_slugJob.reset(new NetJob("Slug Job", m_network)); + int index = 0; + for (auto mod : *block) { + auto projectId = mod->projectId; + auto output = std::make_shared<QByteArray>(); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); - auto dl = Net::Download::makeByteArray(url, &slugs[index]); - slugJob->addNetAction(dl); - index++; - } - connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() { - slugJob->deleteLater(); - auto index = 0; - for (const auto &slugResult: slugs) { - auto json = QJsonDocument::fromJson(slugResult); + auto dl = Net::Download::makeByteArray(url, output.get()); + qDebug() << "Fetching url slug for file:" << mod->fileName; + QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { + auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done + auto json = QJsonDocument::fromJson(*output); auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), "websiteUrl"); - auto mod = block->at(index); auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); mod->websiteUrl = link; - index++; - } + }); + m_slugJob->addNetAction(dl); + index++; + } + auto step_progress = std::make_shared<TaskStepProgress>(); + connect(m_slugJob.get(), &NetJob::succeeded, this, [this, step_progress]() { + step_progress->state = TaskStepState::Succeeded; + stepProgress(*step_progress); emitSucceeded(); }); - slugJob->start(); + connect(m_slugJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) { + step_progress->state = TaskStepState::Failed; + stepProgress(*step_progress); + emitFailed(reason); + }); + connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); + connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) { + qDebug() << "Resolve slug progress" << current << total; + step_progress->update(current, total); + stepProgress(*step_progress); + }); + connect(m_slugJob.get(), &NetJob::status, this, [this, step_progress](QString status) { + step_progress->status = status; + stepProgress(*step_progress); + }); + + m_slugJob->start(); } else { emitSucceeded(); } diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 8fc17ea9..c280827a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -1,41 +1,37 @@ #pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" #include "PackManifest.h" +#include "net/NetJob.h" +#include "tasks/Task.h" -namespace Flame -{ -class FileResolvingTask : public Task -{ +namespace Flame { +class FileResolvingTask : public Task { Q_OBJECT -public: - explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess); - virtual ~FileResolvingTask() {}; + public: + explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess); + virtual ~FileResolvingTask(){}; bool canAbort() const override { return true; } bool abort() override; - const Flame::Manifest &getResults() const - { - return m_toProcess; - } + const Flame::Manifest& getResults() const { return m_toProcess; } -protected: + protected: virtual void executeTask() override; -protected slots: + protected slots: void netJobFinished(); -private: /* data */ + private: /* data */ shared_qobject_ptr<QNetworkAccessManager> m_network; Flame::Manifest m_toProcess; - std::shared_ptr<QByteArray> result; + std::shared_ptr<QByteArray> result; NetJob::Ptr m_dljob; - NetJob::Ptr m_checkJob; + NetJob::Ptr m_checkJob; + NetJob::Ptr m_slugJob; void modrinthCheckFinished(); - QMap<File *, QByteArray *> blockedProjects; + QMap<File*, std::shared_ptr<QByteArray>> blockedProjects; }; -} +} // namespace Flame diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 4d71da21..92590a08 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -1,15 +1,19 @@ +// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com> +// +// SPDX-License-Identifier: GPL-3.0-only + #include "FlameAPI.h" #include "FlameModIndex.h" #include "Application.h" #include "BuildConfig.h" #include "Json.h" - +#include "net/NetJob.h" #include "net/Upload.h" -auto FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr +Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) { - auto* netJob = new NetJob(QString("Flame::MatchFingerprints"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Flame::MatchFingerprints"), APPLICATION->network()); QJsonObject body_obj; QJsonArray fingerprints_arr; @@ -24,7 +28,7 @@ auto FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* re netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } @@ -34,14 +38,14 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString QEventLoop lock; QString changelog; - auto* netJob = new NetJob(QString("Flame::FileChangelog"), APPLICATION->network()); - auto* response = new QByteArray(); + auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network()); + auto response = std::make_shared<QByteArray>(); netJob->addNetAction(Net::Download::makeByteArray( QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog") .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))), - response)); + response.get())); - QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &changelog] { + QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -56,10 +60,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString changelog = Json::ensureString(doc.object(), "data"); }); - QObject::connect(netJob, &NetJob::finished, [response, &lock] { - delete response; - lock.quit(); - }); + QObject::connect(netJob.get(), &NetJob::finished, [&lock] { lock.quit(); }); netJob->start(); lock.exec(); @@ -72,13 +73,12 @@ auto FlameAPI::getModDescription(int modId) -> QString QEventLoop lock; QString description; - auto* netJob = new NetJob(QString("Flame::ModDescription"), APPLICATION->network()); - auto* response = new QByteArray(); + auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network()); + auto response = std::make_shared<QByteArray>(); netJob->addNetAction(Net::Download::makeByteArray( - QString("https://api.curseforge.com/v1/mods/%1/description") - .arg(QString::number(modId)), response)); + QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response.get())); - QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &description] { + QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -93,10 +93,7 @@ auto FlameAPI::getModDescription(int modId) -> QString description = Json::ensureString(doc.object(), "data"); }); - QObject::connect(netJob, &NetJob::finished, [response, &lock] { - delete response; - lock.quit(); - }); + QObject::connect(netJob.get(), &NetJob::finished, [&lock] { lock.quit(); }); netJob->start(); lock.exec(); @@ -106,15 +103,21 @@ auto FlameAPI::getModDescription(int modId) -> QString auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion { + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return {}; + + auto versions_url = versions_url_optional.value(); + QEventLoop loop; - auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); - auto response = new QByteArray(); + auto netJob = makeShared<NetJob>(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); + auto response = std::make_shared<QByteArray>(); ModPlatform::IndexedVersion ver; - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response.get())); - QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] { + QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { @@ -148,11 +151,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe } }); - QObject::connect(netJob, &NetJob::finished, [response, netJob, &loop] { - netJob->deleteLater(); - delete response; - loop.quit(); - }); + QObject::connect(netJob.get(), &NetJob::finished, [&loop] { loop.quit(); }); netJob->start(); @@ -161,9 +160,9 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe return ver; } -auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +Task::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const { - auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network()); QJsonObject body_obj; QJsonArray addons_arr; @@ -178,15 +177,15 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); - QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } -auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* +Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const { - auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Flame::GetFiles"), APPLICATION->network()); QJsonObject body_obj; QJsonArray files_arr; @@ -201,8 +200,23 @@ auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); - QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } + +// https://docs.curseforge.com/?python#tocS_ModsSearchSortField +static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") }, + { 2, "Popularity", QObject::tr("Sort by Popularity") }, + { 3, "LastUpdated", QObject::tr("Sort by Last Updated") }, + { 4, "Name", QObject::tr("Sort by Name") }, + { 5, "Author", QObject::tr("Sort by Author") }, + { 6, "TotalDownloads", QObject::tr("Sort by Downloads") }, + { 7, "Category", QObject::tr("Sort by Category") }, + { 8, "GameVersion", QObject::tr("Sort by Game Version") } }; + +QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const +{ + return s_sorts; +} diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4c6ca64c..5811d717 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,84 +1,86 @@ +// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com> +// +// SPDX-License-Identifier: GPL-3.0-only + #pragma once #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" -class FlameAPI : public NetworkModAPI { +class FlameAPI : public NetworkResourceAPI { public: - auto matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr; auto getModFileChangelog(int modId, int fileId) -> QString; auto getModDescription(int modId) -> QString; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; - auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; + Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; + Task::Ptr matchFingerprints(const QList<uint>& fingerprints, QByteArray* response); + Task::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; + + [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; private: - inline auto getSortFieldInt(QString sortString) const -> int + static int getClassId(ModPlatform::ResourceType type) { - return sortString == "Featured" ? 1 - : sortString == "Popularity" ? 2 - : sortString == "LastUpdated" ? 3 - : sortString == "Name" ? 4 - : sortString == "Author" ? 5 - : sortString == "TotalDownloads" ? 6 - : sortString == "Category" ? 7 - : sortString == "GameVersion" ? 8 - : 1; + switch (type) { + default: + case ModPlatform::ResourceType::MOD: + return 6; + case ModPlatform::ResourceType::RESOURCE_PACK: + return 12; + } + } + + static int getMappedModLoader(ModLoaderTypes loaders) + { + // https://docs.curseforge.com/?http#tocS_ModLoaderType + if (loaders & Forge) + return 1; + if (loaders & Fabric) + return 4; + // TODO: remove this once Quilt drops official Fabric support + if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently* + return 4; // Quilt would probably be 5 + return 0; } private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override + [[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override { - auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); + auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); - return QString( - "https://api.curseforge.com/v1/mods/search?" - "gameId=432&" - "classId=6&" + QStringList get_arguments; + get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); + get_arguments.append(QString("index=%1").arg(args.offset)); + get_arguments.append("pageSize=25"); + if (args.search.has_value()) + get_arguments.append(QString("searchFilter=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); + get_arguments.append("sortOrder=desc"); + if (args.loaders.has_value()) + get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + get_arguments.append(gameVersionStr); - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sortField=%3&" - "sortOrder=desc&" - "modLoaderType=%4&" - "%5") - .arg(args.offset) - .arg(args.search) - .arg(getSortFieldInt(args.sorting)) - .arg(getMappedModLoader(args.loaders)) - .arg(gameVersionStr); + return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); }; - inline auto getModInfoURL(QString& id) const -> QString override + [[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override { return QString("https://api.curseforge.com/v1/mods/%1").arg(id); }; - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override + [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override { - QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; - QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders)); + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.pack.addonId.toString())}; - return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") - .arg(args.addonId) - .arg(gameVersionQuery) - .arg(modLoaderQuery); - }; + QStringList get_parameters; + if (args.mcVersions.has_value()) + get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); + if (args.loaders.has_value()) + get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); - public: - static auto getMappedModLoader(const ModLoaderTypes loaders) -> int - { - // https://docs.curseforge.com/?http#tocS_ModLoaderType - if (loaders & Forge) - return 1; - if (loaders & Fabric) - return 4; - // TODO: remove this once Quilt drops official Fabric support - if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently* - return 4; // Quilt would probably be 5 - return 0; - } + return url + get_parameters.join('&'); + }; }; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 8dd3a846..06a89502 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -7,7 +7,10 @@ #include "FileSystem.h" #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" static FlameAPI api; @@ -126,7 +129,8 @@ 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 }); + ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() }; + auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders }); // Check if we were aborted while getting the latest version if (m_was_aborted) { @@ -160,7 +164,7 @@ void FlameCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; auto old_version = mod->version(); if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { @@ -168,10 +172,10 @@ void FlameCheckUpdate::executeTask() old_version = current_ver.version; } - auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder); + auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder); m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), - ModPlatform::Provider::FLAME, download_task); + ModPlatform::ResourceProvider::FLAME, download_task); } } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 163c706c..4a98d684 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -8,7 +8,7 @@ class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder) + FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 1d441f09..dae93d1c 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -35,6 +35,7 @@ #include "FlameInstanceCreationTask.h" +#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/PackManifest.h" @@ -53,6 +54,13 @@ #include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/CustomMessageBox.h" +#include <QDebug> +#include <QFileInfo> + +#include "minecraft/World.h" +#include "minecraft/mod/tasks/LocalResourceParse.h" + + const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, { "1.4.7", "6.6.2.534" }, @@ -176,7 +184,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -218,7 +226,7 @@ bool FlameCreationTask::updateInstance() m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); - connect(job, &NetJob::finished, &loop, &QEventLoop::quit); + connect(job.get(), &Task::finished, &loop, &QEventLoop::quit); m_process_update_file_info_job = job; job->start(); @@ -366,7 +374,7 @@ bool FlameCreationTask::createInstance() instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version); instance.setName(name()); - m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); + m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack)); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { m_mod_id_resolver.reset(); @@ -375,7 +383,8 @@ bool FlameCreationTask::createInstance() }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); - + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails); m_mod_id_resolver->start(); loop.exec(); @@ -401,6 +410,10 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) QList<BlockedMod> blocked_mods; auto anyBlocked = false; for (const auto& result : results.files.values()) { + if (result.fileName.endsWith(".zip")) { + m_ZIP_resources.append(std::make_pair(result.fileName, result.targetFolder)); + } + if (!result.resolved || result.url.isEmpty()) { BlockedMod blocked_mod; blocked_mod.name = result.fileName; @@ -439,41 +452,9 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } } -/// @brief copy the matched blocked mods to the instance staging area -/// @param blocked_mods list of the blocked mods and their matched paths -void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods) -{ - setStatus(tr("Copying Blocked Mods...")); - setAbortable(false); - int i = 0; - int total = blocked_mods.length(); - setProgress(i, total); - for (auto const& mod : blocked_mods) { - if (!mod.matched) { - qDebug() << mod.name << "was not matched to a local file, skipping copy"; - continue; - } - - auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name); - - setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - - qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - - if (!FS::copy(mod.localPath, dest_path)()) { - qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; - } - - i++; - setProgress(i, total); - } - - setAbortable(true); -} - void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network())); for (const auto& result : m_mod_id_resolver->getResults().files) { QString filename = result.fileName; if (!result.required) { @@ -509,14 +490,121 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) } m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); }); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + validateZIPResouces(); + }); connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { m_files_job.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); + connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){ + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); + setProgress(current, total); + }); + connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); setStatus(tr("Downloading mods...")); m_files_job->start(); } + +/// @brief copy the matched blocked mods to the instance staging area +/// @param blocked_mods list of the blocked mods and their matched paths +void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods) +{ + setStatus(tr("Copying Blocked Mods...")); + setAbortable(false); + int i = 0; + int total = blocked_mods.length(); + setProgress(i, total); + for (auto const& mod : blocked_mods) { + if (!mod.matched) { + qDebug() << mod.name << "was not matched to a local file, skipping copy"; + continue; + } + + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name); + + setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); + + qDebug() << "Will try to copy" << mod.localPath << "to" << destPath; + + if (!FS::copy(mod.localPath, destPath)()) { + qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed"; + } + + i++; + setProgress(i, total); + } + + setAbortable(true); +} + + +void FlameCreationTask::validateZIPResouces() +{ + qDebug() << "Validating whether resources stored as .zip are in the right place"; + for (auto [fileName, targetFolder] : m_ZIP_resources) { + qDebug() << "Checking" << fileName << "..."; + auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName); + + /// @brief check the target and move the the file + /// @return path where file can now be found + auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) { + if (targetFolder != realTarget) { + qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget; + auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName); + qDebug() << "Moving" << localPath << "to" << destPath; + if (FS::move(localPath, destPath)) { + return destPath; + } + } + return localPath; + }; + + auto installWorld = [this](QString worldPath){ + qDebug() << "Installing World from" << worldPath; + QFileInfo worldFileInfo(worldPath); + World w(worldFileInfo); + if (!w.isValid()) { + qDebug() << "World at" << worldPath << "is not valid, skipping install."; + } else { + w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves")); + } + }; + + QFileInfo localFileInfo(localPath); + auto type = ResourceUtils::identify(localFileInfo); + + QString worldPath; + + switch (type) { + case PackedResourceType::ResourcePack : + validatePath(fileName, targetFolder, "resourcepacks"); + break; + case PackedResourceType::TexturePack : + validatePath(fileName, targetFolder, "texturepacks"); + break; + case PackedResourceType::DataPack : + validatePath(fileName, targetFolder, "datapacks"); + break; + case PackedResourceType::Mod : + validatePath(fileName, targetFolder, "mods"); + break; + case PackedResourceType::ShaderPack : + // in theroy flame API can't do this but who knows, that *may* change ? + // better to handle it if it *does* occure in the future + validatePath(fileName, targetFolder, "shaderpacks"); + break; + case PackedResourceType::WorldSave : + worldPath = validatePath(fileName, targetFolder, "saves"); + installWorld(worldPath); + break; + case PackedResourceType::UNKNOWN : + default : + qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is."; + break; + } + } +} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 3a1c729f..0ae4735b 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -77,6 +77,7 @@ class FlameCreationTask final : public InstanceCreationTask { void idResolverSucceeded(QEventLoop&); void setupDownloadJob(QEventLoop&); void copyBlockedMods(QList<BlockedMod> const& blocked_mods); + void validateZIPResouces(); private: QWidget* m_parent = nullptr; @@ -85,10 +86,12 @@ class FlameCreationTask final : public InstanceCreationTask { Flame::Manifest m_pack; // Handle to allow aborting - NetJob* m_process_update_file_info_job = nullptr; + Task::Ptr m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; QString m_managed_id, m_managed_version_id; + QList<std::pair<QString, QString>> m_ZIP_resources; + std::optional<InstancePtr> m_instance; }; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 32aa4bdb..7498e830 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -11,7 +11,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; pack.name = Json::requireString(obj, "name"); pack.slug = Json::requireString(obj, "slug"); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); @@ -76,10 +76,10 @@ static QString enumToString(int hash_algorithm) void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr<QNetworkAccessManager>& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector<ModPlatform::IndexedVersion> unsortedVersions; - auto profile = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile(); + auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { @@ -127,7 +127,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> auto hash_list = Json::ensureArray(obj, "hashes"); for (auto h : hash_list) { auto hash_entry = Json::ensureObject(h); - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); if (hash_types.contains(hash_algo)) { file.hash = Json::requireString(hash_entry, "value"); diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index db63cdbb..33c4a529 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -17,7 +17,7 @@ void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr<QNetworkAccessManager>& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; } // namespace FlameMod diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index f1e4759e..81c94e1b 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -12,12 +12,12 @@ namespace Hashing { static ModPlatform::ProviderCapabilities ProviderCaps; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider) { switch (provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: return createModrinthHasher(file_path); - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: return createFlameHasher(file_path); default: qCritical() << "[Hashing]" @@ -28,22 +28,22 @@ Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider) Hasher::Ptr createModrinthHasher(QString file_path) { - return new ModrinthHasher(file_path); + return makeShared<ModrinthHasher>(file_path); } Hasher::Ptr createFlameHasher(QString file_path) { - return new FlameHasher(file_path); + return makeShared<FlameHasher>(file_path); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) { - return new BlockedModHasher(file_path, provider); + return makeShared<BlockedModHasher>(file_path, provider); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type) { - auto hasher = new BlockedModHasher(file_path, provider); + auto hasher = makeShared<BlockedModHasher>(file_path, provider); hasher->useHashType(type); return hasher; } @@ -62,8 +62,8 @@ void ModrinthHasher::executeTask() return; } - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); - m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); file.close(); @@ -79,7 +79,7 @@ void FlameHasher::executeTask() // CF-specific auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); }; - std::ifstream file_stream(StringUtils::toStdString(m_path), std::ifstream::binary); + std::ifstream file_stream(StringUtils::toStdString(m_path).c_str(), std::ifstream::binary); // TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread. // How do we make this non-blocking then? m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out)); @@ -92,7 +92,7 @@ void FlameHasher::executeTask() } -BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider) +BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider) { setObjectName(QString("BlockedModHasher: %1").arg(file_path)); hash_type = ProviderCaps.hashType(provider).first(); diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h index fa3244f6..91146a52 100644 --- a/launcher/modplatform/helpers/HashUtils.h +++ b/launcher/modplatform/helpers/HashUtils.h @@ -42,21 +42,21 @@ class ModrinthHasher : public Hasher { class BlockedModHasher : public Hasher { public: - BlockedModHasher(QString file_path, ModPlatform::Provider provider); + BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); void executeTask() override; QStringList getHashTypes(); bool useHashType(QString type); private: - ModPlatform::Provider provider; + ModPlatform::ResourceProvider provider; QString hash_type; }; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider); Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createModrinthHasher(QString file_path); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type); } // namespace Hashing diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp deleted file mode 100644 index 7633030e..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "NetworkModAPI.h" - -#include "ui/pages/modplatform/ModModel.h" - -#include "Application.h" -#include "net/NetJob.h" - -void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const -{ - auto netJob = new NetJob(QString("%1::Search").arg(caller->debugName()), APPLICATION->network()); - auto searchUrl = getModSearchURL(args); - - auto response = new QByteArray(); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); }); - QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); - QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted); - QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - caller->searchRequestFinished(doc); - }); - - netJob->start(); -} - -void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) -{ - auto response = new QByteArray(); - auto job = getProject(pack.addonId.toString(), response); - - QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &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(); - qWarning() << *response; - return; - } - - callback(doc, pack); - }); - - job->start(); -} - -void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const -{ - auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network()); - auto response = new QByteArray(); - - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); - - QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, args.addonId); - }); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - netJob->start(); -} - -auto NetworkModAPI::getProject(QString addonId, QByteArray* response) const -> NetJob* -{ - auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); - auto searchUrl = getModInfoURL(addonId); - - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - return netJob; -} diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h deleted file mode 100644 index b8af22c7..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "modplatform/ModAPI.h" - -class NetworkModAPI : public ModAPI { - public: - void searchMods(CallerType* caller, SearchArgs&& args) const override; - void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) override; - void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const override; - - auto getProject(QString addonId, QByteArray* response) const -> NetJob* override; - - protected: - virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; - virtual auto getModInfoURL(QString& id) const -> QString = 0; - virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0; -}; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp new file mode 100644 index 00000000..a3c592fd --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +#include "NetworkResourceAPI.h" + +#include "Application.h" +#include "net/NetJob.h" + +#include "modplatform/ModIndex.h" + +Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const +{ + auto search_url_optional = getSearchURL(args); + if (!search_url_optional.has_value()) { + callbacks.on_fail("Failed to create search URL", -1); + return nullptr; + } + + auto search_url = search_url_optional.value(); + + auto response = new QByteArray(); + auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); + + QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks]{ + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + callbacks.on_fail(parse_error.errorString(), -1); + + return; + } + + callbacks.on_succeed(doc); + }); + + QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason){ + int network_error_code = -1; + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) + network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + callbacks.on_fail(reason, network_error_code); + }); + QObject::connect(netJob.get(), &NetJob::aborted, [callbacks]{ + callbacks.on_abort(); + }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { + delete response; + }); + + + return netJob; +} + +Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const +{ + auto response = new QByteArray(); + auto job = getProject(args.pack.addonId.toString(), response); + + QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &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(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.pack); + }); + + return job; +} + +Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const +{ + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return nullptr; + + auto versions_url = versions_url_optional.value(); + + auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); + auto response = new QByteArray(); + + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); + + QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.pack); + }); + + QObject::connect(netJob.get(), &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} + +Task::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const +{ + auto project_url_optional = getInfoURL(addonId); + if (!project_url_optional.has_value()) + return nullptr; + + auto project_url = project_url_optional.value(); + + auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); + + QObject::connect(netJob.get(), &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h new file mode 100644 index 00000000..94813bec --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com> +// +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include "modplatform/ResourceAPI.h" + +class NetworkResourceAPI : public ResourceAPI { + public: + Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; + + Task::Ptr getProject(QString addonId, QByteArray* response) const override; + + Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; + Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; + + protected: + [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> = 0; + [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional<QString> = 0; + [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> = 0; +}; diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 36aa60c7..e8768c5c 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -47,7 +47,7 @@ void PackFetchTask::fetch() publicPacks.clear(); thirdPartyPacks.clear(); - jobPtr = new NetJob("LegacyFTB::ModpackFetch", m_network); + jobPtr.reset(new NetJob("LegacyFTB::ModpackFetch", m_network)); QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml"); qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 06b3788b..36c142ac 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -69,7 +69,7 @@ void PackInstallTask::downloadPack() archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); - netJobContainer = new NetJob("Download FTB Pack", m_network); + netJobContainer.reset(new NetJob("Download FTB Pack", m_network)); QString url; if (m_pack.type == PackType::Private) { url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(archivePath); @@ -81,6 +81,7 @@ void PackInstallTask::downloadPack() connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); + connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); netJobContainer->start(); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp deleted file mode 100644 index 2979663d..00000000 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 flowln <flowlnlnln@gmail.com> - * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - * - * 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 <https://www.gnu.org/licenses/>. - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FTBPackInstallTask.h" - -#include "FileSystem.h" -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" -#include "modplatform/flame/PackManifest.h" -#include "net/ChecksumValidator.h" -#include "settings/INISettingsObject.h" - -#include "Application.h" -#include "BuildConfig.h" -#include "ui/dialogs/BlockedModsDialog.h" - -namespace ModpacksCH { - -PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) - : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent) -{} - -bool PackInstallTask::abort() -{ - if (!canAbort()) - return false; - - bool aborted = true; - - if (m_net_job) - aborted &= m_net_job->abort(); - if (m_mod_id_resolver_task) - aborted &= m_mod_id_resolver_task->abort(); - - return aborted ? InstanceTask::abort() : false; -} - -void PackInstallTask::executeTask() -{ - setStatus(tr("Getting the manifest...")); - setAbortable(false); - - // Find pack version - auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(), - [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; }); - - if (version_it == m_pack.versions.constEnd()) { - emitFailed(tr("Failed to find pack version %1").arg(m_version_name)); - return; - } - - auto version = *version_it; - - auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); - - auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response)); - - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); - QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::abort); - QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress); - - m_net_job = netJob; - - setAbortable(true); - netJob->start(); -} - -void PackInstallTask::onManifestDownloadSucceeded() -{ - m_net_job.reset(); - - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << m_response; - return; - } - - ModpacksCH::Version version; - try { - auto obj = Json::requireObject(doc); - ModpacksCH::loadVersion(version, obj); - } catch (const JSONValidationError& e) { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - - m_version = version; - - resolveMods(); -} - -void PackInstallTask::resolveMods() -{ - setStatus(tr("Resolving mods...")); - setAbortable(false); - setProgress(0, 100); - - m_file_id_map.clear(); - - Flame::Manifest manifest; - int index = 0; - - for (auto const& file : m_version.files) { - if (!file.serverOnly && file.url.isEmpty()) { - if (file.curseforge.file_id <= 0) { - emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); - return; - } - - Flame::File flame_file; - flame_file.projectId = file.curseforge.project_id; - flame_file.fileId = file.curseforge.file_id; - flame_file.hash = file.sha1; - - manifest.files.insert(flame_file.fileId, flame_file); - m_file_id_map.append(flame_file.fileId); - } else { - m_file_id_map.append(-1); - } - - index++; - } - - m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest); - - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::aborted, this, &PackInstallTask::abort); - connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); - - setAbortable(true); - - m_mod_id_resolver_task->start(); -} - -void PackInstallTask::onResolveModsSucceeded() -{ - auto anyBlocked = false; - - Flame::Manifest results = m_mod_id_resolver_task->getResults(); - for (int index = 0; index < m_file_id_map.size(); index++) { - auto const file_id = m_file_id_map.at(index); - if (file_id < 0) - continue; - - Flame::File results_file = results.files[file_id]; - VersionFile& local_file = m_version.files[index]; - - // First check for blocked mods - if (!results_file.resolved || results_file.url.isEmpty()) { - BlockedMod blocked_mod; - blocked_mod.name = local_file.name; - blocked_mod.websiteUrl = results_file.websiteUrl; - blocked_mod.hash = results_file.hash; - blocked_mod.matched = false; - blocked_mod.localPath = ""; - blocked_mod.targetFolder = results_file.targetFolder; - - m_blocked_mods.append(blocked_mod); - - anyBlocked = true; - } else { - local_file.url = results_file.url.toString(); - } - } - - m_mod_id_resolver_task.reset(); - - if (anyBlocked) { - qDebug() << "Blocked files found, displaying file list"; - - BlockedModsDialog message_dialog(m_parent, tr("Blocked files found"), - tr("The following files are not available for download in third party launchers.<br/>" - "You will need to manually download them and add them to the instance."), - m_blocked_mods); - - message_dialog.setModal(true); - - if (message_dialog.exec() == QDialog::Accepted) { - qDebug() << "Post dialog blocked mods list: " << m_blocked_mods; - createInstance(); - } else { - abort(); - } - - } else { - createInstance(); - } -} - -void PackInstallTask::createInstance() -{ - setAbortable(false); - - setStatus(tr("Creating the instance...")); - QCoreApplication::processEvents(); - - auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - - for (auto target : m_version.targets) { - if (target.type == "game" && target.name == "minecraft") { - components->setComponentVersion("net.minecraft", target.version, true); - break; - } - } - - for (auto target : m_version.targets) { - if (target.type != "modloader") - continue; - - if (target.name == "forge") { - components->setComponentVersion("net.minecraftforge", target.version); - } else if (target.name == "fabric") { - components->setComponentVersion("net.fabricmc.fabric-loader", target.version); - } - } - - // install any jar mods - QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods")); - if (jarModsDir.exists()) { - QStringList jarMods; - - for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { - jarMods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarMods); - } - - components->saveNow(); - - instance.setName(name()); - instance.setIconKey(m_instIcon); - instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); - - instance.saveNow(); - - onCreateInstanceSucceeded(); -} - -void PackInstallTask::onCreateInstanceSucceeded() -{ - downloadPack(); -} - -void PackInstallTask::downloadPack() -{ - setStatus(tr("Downloading mods...")); - setAbortable(false); - - auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); - for (auto const& file : m_version.files) { - if (file.serverOnly || file.url.isEmpty()) - continue; - - auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path, file.name); - qDebug() << "Will try to download" << file.url << "to" << path; - - QFileInfo file_info(file.name); - - auto dl = Net::Download::makeFile(file.url, path); - if (!file.sha1.isEmpty()) { - auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - } - - jobPtr->addNetAction(dl); - } - - connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); - connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); - connect(jobPtr, &NetJob::aborted, this, &PackInstallTask::abort); - connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress); - - m_net_job = jobPtr; - - setAbortable(true); - jobPtr->start(); -} - -void PackInstallTask::onModDownloadSucceeded() -{ - m_net_job.reset(); - if (!m_blocked_mods.isEmpty()) { - copyBlockedMods(); - } - emitSucceeded(); -} - -void PackInstallTask::onManifestDownloadFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} -void PackInstallTask::onResolveModsFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} -void PackInstallTask::onCreateInstanceFailed(QString reason) -{ - emitFailed(reason); -} -void PackInstallTask::onModDownloadFailed(QString reason) -{ - m_net_job.reset(); - emitFailed(reason); -} - -/// @brief copy the matched blocked mods to the instance staging area -void PackInstallTask::copyBlockedMods() -{ - setStatus(tr("Copying Blocked Mods...")); - setAbortable(false); - int i = 0; - int total = m_blocked_mods.length(); - setProgress(i, total); - for (auto const& mod : m_blocked_mods) { - if (!mod.matched) { - qDebug() << mod.name << "was not matched to a local file, skipping copy"; - continue; - } - - auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", mod.targetFolder, mod.name); - - setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - - qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - - if (!FS::copy(mod.localPath, dest_path)()) { - qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; - } - - i++; - setProgress(i, total); - } - - setAbortable(true); -} - -} // namespace ModpacksCH diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h deleted file mode 100644 index 97b1eb0b..00000000 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 flowln <flowlnlnln@gmail.com> - * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - * - * 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 <https://www.gnu.org/licenses/>. - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "FTBPackManifest.h" - -#include "InstanceTask.h" -#include "QObjectPtr.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "net/NetJob.h" -#include "ui/dialogs/BlockedModsDialog.h" - -#include <QWidget> - -namespace ModpacksCH { - -class PackInstallTask final : public InstanceTask -{ - Q_OBJECT - -public: - explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); - ~PackInstallTask() override = default; - - bool abort() override; - -protected: - void executeTask() override; - -private slots: - void onManifestDownloadSucceeded(); - void onResolveModsSucceeded(); - void onCreateInstanceSucceeded(); - void onModDownloadSucceeded(); - - void onManifestDownloadFailed(QString reason); - void onResolveModsFailed(QString reason); - void onCreateInstanceFailed(QString reason); - void onModDownloadFailed(QString reason); - -private: - void resolveMods(); - void createInstance(); - void downloadPack(); - void copyBlockedMods(); - -private: - NetJob::Ptr m_net_job = nullptr; - shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver_task = nullptr; - - QList<int> m_file_id_map; - - QByteArray m_response; - - Modpack m_pack; - QString m_version_name; - Version m_version; - - QMap<QString, QString> m_files_to_copy; - QList<BlockedMod> m_blocked_mods; - - //FIXME: nuke - QWidget* m_parent; -}; - -} diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp deleted file mode 100644 index 421527ae..00000000 --- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - * - * 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 <https://www.gnu.org/licenses/>. - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FTBPackManifest.h" - -#include "Json.h" - -static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj) -{ - s.id = Json::requireInteger(obj, "id"); - s.minimum = Json::requireInteger(obj, "minimum"); - s.recommended = Json::requireInteger(obj, "recommended"); -} - -static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj) -{ - t.id = Json::requireInteger(obj, "id"); - t.name = Json::requireString(obj, "name"); -} - -static void loadArt(ModpacksCH::Art & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.url = Json::requireString(obj, "url"); - a.type = Json::requireString(obj, "type"); - a.width = Json::requireInteger(obj, "width"); - a.height = Json::requireInteger(obj, "height"); - a.compressed = Json::requireBoolean(obj, "compressed"); - a.sha1 = Json::requireString(obj, "sha1"); - a.size = Json::requireInteger(obj, "size"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.website = Json::requireString(obj, "website"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj) -{ - v.id = Json::requireInteger(obj, "id"); - v.name = Json::requireString(obj, "name"); - v.type = Json::requireString(obj, "type"); - v.updated = Json::requireInteger(obj, "updated"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(v.specs, specs); -} - -void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.name = Json::requireString(obj, "name"); - m.synopsis = Json::requireString(obj, "synopsis"); - m.description = Json::requireString(obj, "description"); - m.type = Json::requireString(obj, "type"); - m.featured = Json::requireBoolean(obj, "featured"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = Json::requireInteger(obj, "refreshed"); - auto artArr = Json::requireArray(obj, "art"); - for (QJsonValueRef artRaw : artArr) - { - auto artObj = Json::requireObject(artRaw); - ModpacksCH::Art art; - loadArt(art, artObj); - m.art.append(art); - } - auto authorArr = Json::requireArray(obj, "authors"); - for (QJsonValueRef authorRaw : authorArr) - { - auto authorObj = Json::requireObject(authorRaw); - ModpacksCH::Author author; - loadAuthor(author, authorObj); - m.authors.append(author); - } - auto versionArr = Json::requireArray(obj, "versions"); - for (QJsonValueRef versionRaw : versionArr) - { - auto versionObj = Json::requireObject(versionRaw); - ModpacksCH::VersionInfo version; - loadVersionInfo(version, versionObj); - m.versions.append(version); - } - auto tagArr = Json::requireArray(obj, "tags"); - for (QJsonValueRef tagRaw : tagArr) - { - auto tagObj = Json::requireObject(tagRaw); - ModpacksCH::Tag tag; - loadTag(tag, tagObj); - m.tags.append(tag); - } - m.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.name = Json::requireString(obj, "name"); - a.type = Json::requireString(obj, "type"); - a.version = Json::requireString(obj, "version"); - a.updated = Json::requireInteger(obj, "updated"); -} - -static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj) -{ - a.id = Json::requireInteger(obj, "id"); - a.type = Json::requireString(obj, "type"); - a.path = Json::requireString(obj, "path"); - a.name = Json::requireString(obj, "name"); - a.version = Json::requireString(obj, "version"); - a.url = Json::ensureString(obj, "url"); // optional - a.sha1 = Json::requireString(obj, "sha1"); - a.size = Json::requireInteger(obj, "size"); - a.clientOnly = Json::requireBoolean(obj, "clientonly"); - a.serverOnly = Json::requireBoolean(obj, "serveronly"); - a.optional = Json::requireBoolean(obj, "optional"); - a.updated = Json::requireInteger(obj, "updated"); - auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional - a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project"); - a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file"); -} - -void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) -{ - m.id = Json::requireInteger(obj, "id"); - m.parent = Json::requireInteger(obj, "parent"); - m.name = Json::requireString(obj, "name"); - m.type = Json::requireString(obj, "type"); - m.installs = Json::requireInteger(obj, "installs"); - m.plays = Json::requireInteger(obj, "plays"); - m.updated = Json::requireInteger(obj, "updated"); - m.refreshed = Json::requireInteger(obj, "refreshed"); - auto specs = Json::requireObject(obj, "specs"); - loadSpecs(m.specs, specs); - auto targetArr = Json::requireArray(obj, "targets"); - for (QJsonValueRef targetRaw : targetArr) - { - auto versionObj = Json::requireObject(targetRaw); - ModpacksCH::VersionTarget target; - loadVersionTarget(target, versionObj); - m.targets.append(target); - } - auto fileArr = Json::requireArray(obj, "files"); - for (QJsonValueRef fileRaw : fileArr) - { - auto fileObj = Json::requireObject(fileRaw); - ModpacksCH::VersionFile file; - loadVersionFile(file, fileObj); - m.files.append(file); - } -} - -//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj) -//{ -// m.content = Json::requireString(obj, "content"); -// m.updated = Json::requireInteger(obj, "updated"); -//} diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h deleted file mode 100644 index a8b6f35e..00000000 --- a/launcher/modplatform/modpacksch/FTBPackManifest.h +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - * - * 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 <https://www.gnu.org/licenses/>. - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> - * Copyright 2020 Petr Mrazek <peterix@gmail.com> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QString> -#include <QVector> -#include <QUrl> -#include <QJsonObject> -#include <QMetaType> - -namespace ModpacksCH -{ - -struct Specs -{ - int id; - int minimum; - int recommended; -}; - -struct Tag -{ - int id; - QString name; -}; - -struct Art -{ - int id; - QString url; - QString type; - int width; - int height; - bool compressed; - QString sha1; - int size; - int64_t updated; -}; - -struct Author -{ - int id; - QString name; - QString type; - QString website; - int64_t updated; -}; - -struct VersionInfo -{ - int id; - QString name; - QString type; - int64_t updated; - Specs specs; -}; - -struct Modpack -{ - int id; - QString name; - QString synopsis; - QString description; - QString type; - bool featured; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - QVector<Art> art; - QVector<Author> authors; - QVector<VersionInfo> versions; - QVector<Tag> tags; -}; - -struct VersionTarget -{ - int id; - QString type; - QString name; - QString version; - int64_t updated; -}; - -struct VersionFileCurseForge -{ - int project_id; - int file_id; -}; - -struct VersionFile -{ - int id; - QString type; - QString path; - QString name; - QString version; - QString url; - QString sha1; - int size; - bool clientOnly; - bool serverOnly; - bool optional; - int64_t updated; - VersionFileCurseForge curseforge; -}; - -struct Version -{ - int id; - int parent; - QString name; - QString type; - int installs; - int plays; - int64_t updated; - int64_t refreshed; - Specs specs; - QVector<VersionTarget> targets; - QVector<VersionFile> files; -}; - -struct VersionChangelog -{ - QString content; - int64_t updated; -}; - -void loadModpack(Modpack & m, QJsonObject & obj); - -void loadVersion(Version & m, QJsonObject & obj); -} - -Q_DECLARE_METATYPE(ModpacksCH::Modpack) diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 747cf4c3..29e3d129 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -1,24 +1,29 @@ +// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com> +// +// SPDX-License-Identifier: GPL-3.0-only + #include "ModrinthAPI.h" #include "Application.h" #include "Json.h" +#include "net/NetJob.h" #include "net/Upload.h" -auto ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); netJob->addNetAction(Net::Download::makeByteArray( QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } -auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format, QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersions"), APPLICATION->network()); QJsonObject body_obj; @@ -30,28 +35,31 @@ auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } -auto ModrinthAPI::latestVersion(QString hash, - QString hash_format, - std::list<Version> mcVersions, - ModLoaderTypes loaders, - QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::latestVersion(QString hash, + QString hash_format, + std::optional<std::list<Version>> mcVersions, + std::optional<ModLoaderTypes> loaders, + QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -59,50 +67,67 @@ auto ModrinthAPI::latestVersion(QString hash, netJob->addNetAction(Net::Upload::makeByteArray( QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } -auto ModrinthAPI::latestVersions(const QStringList& hashes, - QString hash_format, - std::list<Version> mcVersions, - ModLoaderTypes loaders, - QByteArray* response) -> NetJob::Ptr +Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, + QString hash_format, + std::optional<std::list<Version>> mcVersions, + std::optional<ModLoaderTypes> loaders, + QByteArray* response) { - auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); QJsonObject body_obj; Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); + QObject::connect(netJob.get(), &NetJob::finished, [response] { delete response; }); return netJob; } -auto ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const { - auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network()); + auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob.get(), &NetJob::finished, [response, netJob] { + delete response; + }); return netJob; } + +// https://docs.modrinth.com/api-spec/#tag/projects/operation/searchProjects +static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "relevance", QObject::tr("Sort by Relevance") }, + { 2, "downloads", QObject::tr("Sort by Downloads") }, + { 3, "follows", QObject::tr("Sort by Follows") }, + { 4, "newest", QObject::tr("Sort by Last Updated") }, + { 5, "updated", QObject::tr("Sort by Newest") } }; + +QList<ResourceAPI::SortingMethod> ModrinthAPI::getSortingMethods() const +{ + return s_sorts; +} diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index e1a18681..b91ac5c1 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -1,69 +1,54 @@ +// SPDX-FileCopyrightText: 2022-2023 flowln <flowlnlnln@gmail.com> +// // SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> - * - * 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 <https://www.gnu.org/licenses/>. - */ #pragma once #include "BuildConfig.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" #include <QDebug> -class ModrinthAPI : public NetworkModAPI { +class ModrinthAPI : public NetworkResourceAPI { public: auto currentVersion(QString hash, QString hash_format, - QByteArray* response) -> NetJob::Ptr; + QByteArray* response) -> Task::Ptr; auto currentVersions(const QStringList& hashes, QString hash_format, - QByteArray* response) -> NetJob::Ptr; + QByteArray* response) -> Task::Ptr; auto latestVersion(QString hash, QString hash_format, - std::list<Version> mcVersions, - ModLoaderTypes loaders, - QByteArray* response) -> NetJob::Ptr; + std::optional<std::list<Version>> mcVersions, + std::optional<ModLoaderTypes> loaders, + QByteArray* response) -> Task::Ptr; auto latestVersions(const QStringList& hashes, QString hash_format, - std::list<Version> mcVersions, - ModLoaderTypes loaders, - QByteArray* response) -> NetJob::Ptr; + std::optional<std::list<Version>> mcVersions, + std::optional<ModLoaderTypes> loaders, + QByteArray* response) -> Task::Ptr; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; + Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; public: + [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; + inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList { QStringList l; - for (auto loader : {Forge, Fabric, Quilt}) - { - if ((types & loader) || types == Unspecified) - { - l << ModAPI::getModLoaderString(loader); + for (auto loader : {Forge, Fabric, Quilt}) { + if (types & loader) { + l << getModLoaderString(loader); } } if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there - l << ModAPI::getModLoaderString(Fabric); + l << getModLoaderString(Fabric); return l; } @@ -78,28 +63,58 @@ class ModrinthAPI : public NetworkModAPI { } private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override + [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) + { + switch (type) { + case ModPlatform::ResourceType::MOD: + return "mod"; + case ModPlatform::ResourceType::RESOURCE_PACK: + return "resourcepack"; + case ModPlatform::ResourceType::SHADER_PACK: + return "shader"; + default: + qWarning() << "Invalid resource type for Modrinth API!"; + break; + } + + return ""; + } + [[nodiscard]] QString createFacets(SearchArgs const& args) const + { + QStringList facets_list; + + if (args.loaders.has_value()) + facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); + if (args.versions.has_value()) + facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); + + return QString("[%1]").arg(facets_list.join(',')); + } + + public: + [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> override { - if (!validateModLoaders(args.loaders)) { - qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; - return ""; + if (args.loaders.has_value()) { + if (!validateModLoaders(args.loaders.value())) { + qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; + return {}; + } } - return QString(BuildConfig.MODRINTH_PROD_URL + - "/search?" - "offset=%1&" - "limit=25&" - "query=%2&" - "index=%3&" - "facets=[[%4],%5[\"project_type:mod\"]]") - .arg(args.offset) - .arg(args.search) - .arg(args.sorting) - .arg(getModLoaderFilters(args.loaders)) - .arg(getGameVersionsArray(args.versions)); + QStringList get_arguments; + get_arguments.append(QString("offset=%1").arg(args.offset)); + get_arguments.append(QString("limit=25")); + if (args.search.has_value()) + get_arguments.append(QString("query=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("index=%1").arg(args.sorting.value().name)); + get_arguments.append(QString("facets=%1").arg(createFacets(args))); + + return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; - inline auto getModInfoURL(QString& id) const -> QString override + inline auto getInfoURL(QString const& id) const -> std::optional<QString> override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; }; @@ -109,15 +124,16 @@ class ModrinthAPI : public NetworkModAPI { return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\"")); }; - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override + inline auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional<QString> override { - return QString(BuildConfig.MODRINTH_PROD_URL + - "/project/%1/version?" - "game_versions=[%2]&" - "loaders=[\"%3\"]") - .arg(args.addonId, - getGameVersionsString(args.mcVersions), - getModLoaderStrings(args.loaders).join("\",\"")); + QStringList get_arguments; + if (args.mcVersions.has_value()) + get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value()))); + if (args.loaders.has_value()) + get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); + + return QString("%1/project/%2/version%3%4") + .arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString @@ -127,12 +143,12 @@ class ModrinthAPI : public NetworkModAPI { s += QString("\"versions:%1\",").arg(ver.toString()); } s.remove(s.length() - 1, 1); //remove last comma - return s.isEmpty() ? QString() : QString("[%1],").arg(s); + return s.isEmpty() ? QString() : s; } inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { - return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt)); + return loaders & (Forge | Fabric | Quilt); } }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index e2d27547..d1be7209 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -4,12 +4,15 @@ #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/ConcurrentTask.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; @@ -34,7 +37,7 @@ void ModrinthCheckUpdate::executeTask() // Create all hashes QStringList hashes; - auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); for (auto* mod : m_mods) { @@ -108,11 +111,13 @@ void ModrinthCheckUpdate::executeTask() // 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 = { ModAPI::ModLoaderType::Forge, ModAPI::ModLoaderType::Fabric, ModAPI::ModLoaderType::Quilt }; - for (auto flag : flags) { - if (m_loaders.testFlag(flag)) { - loader_filter = api.getModLoaderString(flag); - break; + if (m_loaders.has_value()) { + static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt }; + for (auto flag : flags) { + if (m_loaders.value().testFlag(flag)) { + loader_filter = api.getModLoaderString(flag); + break; + } } } @@ -152,12 +157,12 @@ void ModrinthCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; - auto download_task = new ModDownloadTask(pack, project_ver, m_mods_folder); + auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder); m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog, - ModPlatform::Provider::MODRINTH, download_task); + ModPlatform::ResourceProvider::MODRINTH, download_task); } } } catch (Json::JsonException& e) { @@ -170,7 +175,7 @@ void ModrinthCheckUpdate::executeTask() setStatus(tr("Waiting for the API response from Modrinth...")); setProgress(1, 3); - m_net_job = job.get(); + m_net_job = qSharedPointerObjectCast<NetJob, Task>(job); job->start(); lock.exec(); diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index abf8ada1..88e1a675 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -8,7 +8,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - ModrinthCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder) + ModrinthCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} @@ -19,5 +19,5 @@ class ModrinthCheckUpdate : public CheckUpdateTask { void executeTask() override; private: - NetJob* m_net_job = nullptr; + NetJob::Ptr m_net_job = nullptr; }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index c5a27c9d..bb8227aa 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -11,6 +11,7 @@ #include "net/ChecksumValidator.h" +#include "net/NetJob.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -223,12 +224,21 @@ bool ModrinthCreationTask::createInstance() instance.setName(name()); instance.saveNow(); - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network())); + + auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft"); + auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); for (auto file : m_files) { - auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); - qDebug() << "Will try to download" << file.downloads.front() << "to" << path; - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); + auto file_path = FS::PathCombine(root_modpack_path, file.path); + if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) { + // This means we somehow got out of the root folder, so abort here to prevent exploits + setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.").arg(file.path)); + return false; + } + + qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; + auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_files_job->addNetAction(dl); @@ -236,8 +246,8 @@ bool ModrinthCreationTask::createInstance() // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &NetAction::failed, [this, &file, path, param] { - auto ndl = Net::Download::makeFile(file.downloads.dequeue(), path); + connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] { + auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_files_job->addNetAction(ndl); if (auto shared = param.lock()) shared->succeeded(); @@ -253,7 +263,11 @@ bool ModrinthCreationTask::createInstance() setError(reason); }); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); + setProgress(current, total); + }); + connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); setStatus(tr("Downloading mods...")); m_files_job->start(); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index ae45e096..7ade131e 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -27,13 +27,14 @@ static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; +// https://docs.modrinth.com/api-spec/#tag/projects/operation/getProject void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::ensureString(obj, "project_id"); if (pack.addonId.toString().isEmpty()) pack.addonId = Json::requireString(obj, "id"); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; pack.name = Json::requireString(obj, "title"); pack.slug = Json::ensureString(obj, "slug", ""); @@ -44,7 +45,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.description = Json::ensureString(obj, "description", ""); - pack.logoUrl = Json::requireString(obj, "icon_url"); + pack.logoUrl = Json::ensureString(obj, "icon_url", ""); pack.logoName = pack.addonId.toString(); ModPlatform::ModpackAuthor modAuthor; @@ -87,7 +88,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraData.donate.append(donate); } - pack.extraData.body = Json::ensureString(obj, "body"); + pack.extraData.body = Json::ensureString(obj, "body").remove("<br>"); pack.extraDataLoaded = true; } @@ -95,10 +96,10 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr<QNetworkAccessManager>& network, - BaseInstance* inst) + const BaseInstance* inst) { QVector<ModPlatform::IndexedVersion> unsortedVersions; - QString mcVersion = (static_cast<MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft"); + QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); @@ -179,7 +180,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; } else { - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH); for (auto& hash_type : hash_types) { if (hash_list.contains(hash_type)) { file.hash = Json::requireString(hash_list, hash_type); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 31881414..e73e4b18 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -29,7 +29,7 @@ void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr<QNetworkAccessManager>& network, - BaseInstance* inst); + const BaseInstance* inst); auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; } // namespace Modrinth diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0ed29311..510c7309 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -97,7 +97,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.name = mod_pack.name; mod.filename = mod_version.fileName; - if (mod_pack.provider == ModPlatform::Provider::FLAME) { + if (mod_pack.provider == ModPlatform::ResourceProvider::FLAME) { mod.mode = "metadata:curseforge"; } else { mod.mode = "url"; @@ -176,11 +176,11 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) in_stream << QString("\n[update]\n"); in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); switch (mod.provider) { - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); break; - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): addToStream("mod-id", mod.mod_id().toString()); addToStream("version", mod.version().toString()); break; @@ -273,7 +273,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod } { // [update] info - using Provider = ModPlatform::Provider; + using Provider = ModPlatform::ResourceProvider; auto update_table = table["update"]; if (!update_table || !update_table.is_table()) { diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 9754e5c4..4b096eec 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -49,7 +49,7 @@ class V1 { QString hash {}; // [update] - ModPlatform::Provider provider {}; + ModPlatform::ResourceProvider provider {}; QVariant file_id {}; QVariant project_id {}; diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index 6438d9ef..f07ca24a 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -44,12 +44,13 @@ void Technic::SingleZipPackInstallTask::executeTask() const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); auto entry = APPLICATION->metacache()->resolveEntry("general", path); entry->setStale(true); - m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network()); + m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); m_archivePath = entry->getFullPath(); auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); + connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress); connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed); m_filesNetJob->start(); } @@ -130,7 +131,7 @@ void Technic::SingleZipPackInstallTask::extractFinished() } } - shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor(); + auto packProcessor = makeShared<Technic::TechnicPackProcessor>(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed); packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion); diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 19731b38..c26d6a5a 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -70,7 +70,7 @@ void Technic::SolderPackInstallTask::executeTask() { setStatus(tr("Resolving modpack files")); - m_filesNetJob = new NetJob(tr("Resolving modpack files"), m_network); + m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network)); auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, &m_response)); @@ -107,7 +107,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() if (!build.minecraft.isEmpty()) m_minecraftVersion = build.minecraft; - m_filesNetJob = new NetJob(tr("Downloading modpack"), m_network); + m_filesNetJob.reset(new NetJob(tr("Downloading modpack"), m_network)); int i = 0; for (const auto &mod : build.mods) { @@ -127,6 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress); connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); @@ -219,7 +220,7 @@ void Technic::SolderPackInstallTask::extractFinished() } } - shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor(); + auto packProcessor = makeShared<Technic::TechnicPackProcessor>(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed); packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion, true); diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp index 95feb4b2..df713a72 100644 --- a/launcher/modplatform/technic/TechnicPackProcessor.cpp +++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp @@ -172,7 +172,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const auto libraryObject = Json::ensureObject(library, {}, ""); auto libraryName = Json::ensureString(libraryObject, "name", "", ""); - if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-')) + if ((libraryName.startsWith("net.minecraftforge:forge:") || libraryName.startsWith("net.minecraftforge:fmlloader:")) && libraryName.contains('-')) { QString libraryVersion = libraryName.section(':', 2); if (!libraryVersion.startsWith("1.7.10-")) |