This commit is contained in:
Alexandru Ionut Tripon 2026-04-22 20:00:59 -06:00 committed by GitHub
commit 18fb799934
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 414 additions and 2 deletions

View file

@ -510,6 +510,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

View file

@ -46,7 +46,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"
@ -58,6 +60,7 @@
#include <QFileInfo>
#include <QtConcurrentRun>
#include <memory>
#include <utility>
InstanceImportTask::InstanceImportTask(const QUrl& sourceUrl, QWidget* parent, QMap<QString, QString>&& extra_info)
: m_sourceUrl(sourceUrl), m_extra_info(extra_info), m_parent(parent)
@ -80,7 +83,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()));
@ -430,3 +433,39 @@ 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().fileId.toString());
m_original_version = task->getVersion().version;
setIcon(task->getLogoName());
};
auto modrinthTask = makeShared<GetModPackExtraInfoTask>(m_archivePath, ModPlatform::ResourceProvider::MODRINTH);
connect(modrinthTask.get(), &Task::succeeded, [populateExtraInfo, modrinthTask] { populateExtraInfo(modrinthTask.get()); });
auto progressStep = std::make_shared<TaskStepProgress>();
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::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 = std::move(status);
stepProgress(*progressStep);
});
m_task.reset(modrinthTask);
modrinthTask->start();
}

View file

@ -59,6 +59,7 @@ class InstanceImportTask : public InstanceTask {
private slots:
void processZipPack();
void processExtraInfoPack();
void extractFinished();
private: /* data */

View file

@ -156,4 +156,6 @@ class ResourceAPI {
*/
virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) const = 0;
virtual Task::Ptr getVersionFromHash(QString hash, ModPlatform::IndexedVersion&) = 0;
};

View file

@ -273,3 +273,49 @@ std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(QList<ModP
}
return {};
}
Task::Ptr FlameAPI::getVersionFromHash(QString hash, ModPlatform::IndexedVersion& output)
{
auto [ver_task, response] = matchFingerprints({ hash.toUInt() });
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 = match.toObject();
auto file_obj = match_obj["file"].toObject();
if (match_obj.isEmpty() || file_obj.isEmpty()) {
qWarning() << "Fingerprint match is empty!";
continue;
}
auto fingerprint = QString::number(file_obj["fileFingerprint"].toInt());
if (fingerprint != hash)
continue;
output = FlameMod::loadIndexedPackVersion(file_obj);
}
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << doc;
}
});
return ver_task;
}

View file

@ -172,4 +172,6 @@ class FlameAPI : public ResourceAPI {
}
return url;
}
virtual Task::Ptr getVersionFromHash(QString hash, ModPlatform::IndexedVersion&) override;
};

View file

@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
*/
#include "modplatform/helpers/GetModPackExtraInfoTask.h"
#include <memory>
#include <utility>
#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(std::move(path)), m_provider(provider)
{
switch (m_provider) {
case ModPlatform::ResourceProvider::MODRINTH:
m_api = std::make_unique<ModrinthAPI>();
break;
case ModPlatform::ResourceProvider::FLAME:
m_api = std::make_unique<FlameAPI>();
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<TaskStepProgress>();
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(std::move(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 = std::move(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(std::move(result), m_version);
(dynamic_cast<NetJob*>(verTask.get()))->setAskRetry(false);
auto progressStep = std::make_shared<TaskStepProgress>();
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(std::move(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 = std::move(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 [projectTask, responseInfo] = m_api->getProject(m_version.addonId.toString());
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<TaskStepProgress>();
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(std::move(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 = std::move(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<NetJob>(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<TaskStepProgress>();
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(std::move(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 = std::move(status);
stepProgress(*progressStep);
});
m_current_task.reset(logoTask);
logoTask->start();
}

View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <memory>
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/Task.h"
class GetModPackExtraInfoTask : public Task {
Q_OBJECT
public:
GetModPackExtraInfoTask(QString path, ModPlatform::ResourceProvider provider);
~GetModPackExtraInfoTask() override = 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<ResourceAPI> m_api;
ModPlatform::IndexedVersion m_version;
ModPlatform::IndexedPack m_pack;
QString m_logo_name;
QString m_logo_full_path;
};

View file

@ -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"
@ -167,4 +168,29 @@ QList<ModPlatform::Category> ModrinthAPI::loadCategories(const QByteArray& respo
QList<ModPlatform::Category> ModrinthAPI::loadModCategories(const QByteArray& 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 [ver_task, response] = currentVersion(hash, hash_type);
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;
}

View file

@ -236,4 +236,5 @@ class ModrinthAPI : public ResourceAPI {
return Modrinth::loadIndexedPackVersion(obj);
};
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) const override { Modrinth::loadExtraPackData(m, obj); }
virtual Task::Ptr getVersionFromHash(QString hash, ModPlatform::IndexedVersion&) override;
};