diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 194694d7f..3a20e458b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -514,6 +514,8 @@ set(API_SOURCES modplatform/helpers/HashUtils.cpp modplatform/helpers/OverrideUtils.h modplatform/helpers/OverrideUtils.cpp + modplatform/helpers/GetModPackExtraInfoTask.h + modplatform/helpers/GetModPackExtraInfoTask.cpp modplatform/helpers/ExportToModList.h modplatform/helpers/ExportToModList.cpp diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 77298e2ce..a603ae3d2 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -45,7 +45,9 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" +#include "modplatform/ModIndex.h" #include "modplatform/flame/FlameInstanceCreationTask.h" +#include "modplatform/helpers/GetModPackExtraInfoTask.h" #include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "modplatform/technic/TechnicPackProcessor.h" @@ -55,7 +57,6 @@ #include "net/ApiDownload.h" #include -#include #include #include @@ -81,7 +82,7 @@ void InstanceImportTask::executeTask() if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); - processZipPack(); + processExtraInfoPack(); } else { setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); @@ -440,3 +441,42 @@ void InstanceImportTask::processModrinth() setAbortable(true); m_task->start(); } + +void InstanceImportTask::processExtraInfoPack() +{ + if (!m_extra_info.isEmpty()) { + processZipPack(); + return; + } + auto populateExtraInfo = [this](GetModPackExtraInfoTask* task) { + m_extra_info.insert("pack_id", task->getVersion().addonId.toString()); + m_extra_info.insert("pack_version_id", task->getVersion().version); + setIcon(task->getLogoName()); + }; + auto modrinthTask = makeShared(m_archivePath, ModPlatform::ResourceProvider::MODRINTH); + connect(modrinthTask.get(), &Task::succeeded, [populateExtraInfo, modrinthTask] { populateExtraInfo(modrinthTask.get()); }); + auto progressStep = std::make_shared(); + connect(modrinthTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + processZipPack(); + }); + + connect(modrinthTask.get(), &Task::aborted, this, &InstanceImportTask::emitAborted); + connect(modrinthTask.get(), &Task::failed, this, [this, progressStep](QString) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + }); + connect(modrinthTask.get(), &Task::stepProgress, this, &InstanceImportTask::propagateStepProgress); + + connect(modrinthTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(modrinthTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_task.reset(modrinthTask); + modrinthTask->start(); +} diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 8884e0801..7b38a146f 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -62,6 +62,7 @@ class InstanceImportTask : public InstanceTask { private slots: void processZipPack(); + void processExtraInfoPack(); void extractFinished(); private: /* data */ diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index d1facfd23..258af73f9 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -261,3 +261,50 @@ std::optional FlameAPI::getLatestVersion(QList(); + auto ver_task = matchFingerprints({ hash.toUInt() }, response); + QObject::connect(ver_task.get(), &Task::succeeded, [response, &output, hash] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame::CurrentVersions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + try { + auto doc_obj = Json::requireObject(doc); + auto data_obj = Json::requireObject(doc_obj, "data"); + auto data_arr = Json::requireArray(data_obj, "exactMatches"); + + if (data_arr.isEmpty()) { + qWarning() << "No matches found for fingerprint search!"; + return; + } + + for (auto match : data_arr) { + auto match_obj = Json::ensureObject(match, {}); + auto file_obj = Json::ensureObject(match_obj, "file", {}); + + if (match_obj.isEmpty() || file_obj.isEmpty()) { + qWarning() << "Fingerprint match is empty!"; + continue; + } + + auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt()); + if (fingerprint != hash) + continue; + output = FlameMod::loadIndexedPackVersion(file_obj); + } + + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } + }); + return ver_task; +} diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index f72bdb624..51bfb4987 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -150,4 +150,6 @@ class FlameAPI : public NetworkResourceAPI { } return url; } + + virtual Task::Ptr getVersionFromHash(QString hash, ModPlatform::IndexedVersion&) override; }; diff --git a/launcher/modplatform/helpers/GetModPackExtraInfoTask.cpp b/launcher/modplatform/helpers/GetModPackExtraInfoTask.cpp new file mode 100644 index 000000000..4350d0a7d --- /dev/null +++ b/launcher/modplatform/helpers/GetModPackExtraInfoTask.cpp @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "modplatform/helpers/GetModPackExtraInfoTask.h" +#include +#include "Application.h" +#include "Json.h" +#include "QObjectPtr.h" +#include "icons/IconList.h" +#include "modplatform/ModIndex.h" +#include "modplatform/flame/FlameAPI.h" +#include "modplatform/flame/FlameModIndex.h" +#include "modplatform/helpers/HashUtils.h" +#include "modplatform/modrinth/ModrinthAPI.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" +#include "net/Download.h" +#include "net/HttpMetaCache.h" +#include "net/NetJob.h" +#include "tasks/Task.h" + +GetModPackExtraInfoTask::GetModPackExtraInfoTask(QString path, ModPlatform::ResourceProvider provider) : m_path(path), m_provider(provider) +{ + switch (m_provider) { + case ModPlatform::ResourceProvider::MODRINTH: + m_api = std::make_unique(); + break; + case ModPlatform::ResourceProvider::FLAME: + m_api = std::make_unique(); + break; + } +} + +bool GetModPackExtraInfoTask::abort() +{ + if (m_current_task) + m_current_task->abort(); + + emitAborted(); + return true; +} + +void GetModPackExtraInfoTask::executeTask() +{ + setStatus(tr("Generating file hash")); + setProgress(1, 4); + auto hashTask = Hashing::createHasher(m_path, m_provider); + + auto progressStep = std::make_shared(); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, this, &GetModPackExtraInfoTask::hashDone); + connect(hashTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(hashTask.get(), &Task::aborted, this, &GetModPackExtraInfoTask::emitAborted); + connect(hashTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(hashTask.get(), &Task::stepProgress, this, &GetModPackExtraInfoTask::propagateStepProgress); + + connect(hashTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(hashTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_current_task.reset(hashTask); + hashTask->start(); +} + +void GetModPackExtraInfoTask::hashDone(QString result) +{ + setStatus(tr("Matching hash with version")); + setProgress(2, 4); + auto verTask = m_api->getVersionFromHash(result, m_version); + (dynamic_cast(verTask.get()))->setAskRetry(false); + auto progressStep = std::make_shared(); + connect(verTask.get(), &Task::succeeded, this, &GetModPackExtraInfoTask::getProjectInfo); + connect(verTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(verTask.get(), &Task::aborted, this, &GetModPackExtraInfoTask::emitAborted); + connect(verTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(verTask.get(), &Task::stepProgress, this, &GetModPackExtraInfoTask::propagateStepProgress); + + connect(verTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(verTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_current_task.reset(verTask); + verTask->start(); +} + +void GetModPackExtraInfoTask::getProjectInfo() +{ + if (!m_version.addonId.isValid()) { + emitFailed(tr("Version not found")); + return; + } + setStatus(tr("Get project information")); + setProgress(3, 4); + auto responseInfo = std::make_shared(); + auto projectTask = m_api->getProject(m_version.addonId.toString(), responseInfo); + connect(projectTask.get(), &Task::succeeded, [responseInfo, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qDebug() << *responseInfo; + emitFailed(tr("Error parsing project info")); + return; + } + try { + switch (m_provider) { + case ModPlatform::ResourceProvider::MODRINTH: { + auto obj = Json::requireObject(doc); + Modrinth::loadIndexedPack(m_pack, obj); + break; + } + case ModPlatform::ResourceProvider::FLAME: { + auto obj = Json::requireObject(Json::requireObject(doc), "data"); + FlameMod::loadIndexedPack(m_pack, obj); + break; + } + } + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading mod info: " << e.cause(); + emitFailed(tr("Error parsing project info")); + return; + } + getLogo(); + }); + + auto progressStep = std::make_shared(); + connect(projectTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(projectTask.get(), &Task::aborted, this, &GetModPackExtraInfoTask::emitAborted); + connect(projectTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(projectTask.get(), &Task::stepProgress, this, &GetModPackExtraInfoTask::propagateStepProgress); + + connect(projectTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(projectTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_current_task.reset(projectTask); + projectTask->start(); +} + +void GetModPackExtraInfoTask::getLogo() +{ + m_logo_name = m_provider == ModPlatform::ResourceProvider::MODRINTH ? ("modrinth_" + m_pack.slug) + : ("curseforge_" + m_pack.logoName.section(".", 0, 0)); + QString providerName = m_provider == ModPlatform::ResourceProvider::MODRINTH ? "Modrinth" : "Flame"; + + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(providerName, QString("logos/%1").arg(m_logo_name.section(".", 0, 0))); + auto logoTask = makeShared(QString("%1 Icon Download %2").arg(providerName, m_logo_name), APPLICATION->network()); + logoTask->addNetAction(Net::Download::makeCached(QUrl(m_pack.logoUrl), entry)); + + m_logo_full_path = entry->getFullPath(); + QObject::connect(logoTask.get(), &NetJob::succeeded, this, [this] { + APPLICATION->icons()->installIcon(m_logo_full_path, m_logo_name); + emitSucceeded(); + }); + + auto progressStep = std::make_shared(); + connect(logoTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(logoTask.get(), &Task::aborted, this, &GetModPackExtraInfoTask::emitAborted); + connect(logoTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(logoTask.get(), &Task::stepProgress, this, &GetModPackExtraInfoTask::propagateStepProgress); + + connect(logoTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(logoTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + m_current_task.reset(logoTask); + + logoTask->start(); +} \ No newline at end of file diff --git a/launcher/modplatform/helpers/GetModPackExtraInfoTask.h b/launcher/modplatform/helpers/GetModPackExtraInfoTask.h new file mode 100644 index 000000000..3f38f4adb --- /dev/null +++ b/launcher/modplatform/helpers/GetModPackExtraInfoTask.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "modplatform/ModIndex.h" +#include "modplatform/helpers/NetworkResourceAPI.h" +#include "tasks/Task.h" + +class GetModPackExtraInfoTask : public Task { + Q_OBJECT + public: + GetModPackExtraInfoTask(QString path, ModPlatform::ResourceProvider provider); + virtual ~GetModPackExtraInfoTask() = default; + + bool canAbort() const override { return true; } + + ModPlatform::IndexedVersion getVersion() { return m_version; }; + ModPlatform::IndexedPack getPack() { return m_pack; }; + QString getLogoName() { return m_logo_name.isEmpty() ? "default" : m_logo_name; }; + QString getLogoFullPath() { return m_logo_full_path; }; + + public slots: + bool abort() override; + + protected slots: + void executeTask() override; + + private slots: + void hashDone(QString result); + void getProjectInfo(); + void getLogo(); + + private: + QString m_path; + Task::Ptr m_current_task; + ModPlatform::ResourceProvider m_provider; + std::unique_ptr m_api; + + ModPlatform::IndexedVersion m_version; + ModPlatform::IndexedPack m_pack; + QString m_logo_name; + QString m_logo_full_path; +}; \ No newline at end of file diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h index d89014a38..089fb302d 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.h +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -5,7 +5,9 @@ #pragma once #include +#include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" +#include "tasks/Task.h" class NetworkResourceAPI : public ResourceAPI { public: @@ -17,6 +19,8 @@ class NetworkResourceAPI : public ResourceAPI { Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const override; + virtual Task::Ptr getVersionFromHash(QString hash, ModPlatform::IndexedVersion&) = 0; + protected: virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; virtual auto getInfoURL(QString const& id) const -> std::optional = 0; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index bdef1a0e5..849d46997 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -6,6 +6,7 @@ #include "Application.h" #include "Json.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" #include "net/ApiDownload.h" #include "net/ApiUpload.h" #include "net/NetJob.h" @@ -162,4 +163,29 @@ QList ModrinthAPI::loadCategories(std::shared_ptr ModrinthAPI::loadModCategories(std::shared_ptr response) { return loadCategories(response, "mod"); -}; +} +Task::Ptr ModrinthAPI::getVersionFromHash(QString hash, ModPlatform::IndexedVersion& output) +{ + auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + auto response = std::make_shared(); + auto ver_task = currentVersion(hash, hash_type, response); + QObject::connect(ver_task.get(), &Task::succeeded, [response, &output] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Modrinth::CurrentVersions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + try { + auto entry = Json::requireObject(doc); + output = Modrinth::loadIndexedPackVersion(entry); + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } + }); + return ver_task; +} diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 2e127dcff..9ccc1856d 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -214,4 +214,6 @@ class ModrinthAPI : public NetworkResourceAPI { .arg(mapMCVersionToModrinth(args.mcVersion)) .arg(getModLoaderStrings(args.loader).join("\",\"")); }; + + virtual Task::Ptr getVersionFromHash(QString hash, ModPlatform::IndexedVersion&) override; };