aboutsummaryrefslogtreecommitdiff
path: root/launcher/modplatform/flame
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/modplatform/flame')
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp148
-rw-r--r--launcher/modplatform/flame/FlameAPI.h8
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.cpp179
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.h25
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp18
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h2
6 files changed, 373 insertions, 7 deletions
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
new file mode 100644
index 00000000..0ff04f72
--- /dev/null
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -0,0 +1,148 @@
+#include "FlameAPI.h"
+#include "FlameModIndex.h"
+
+#include "Application.h"
+#include "BuildConfig.h"
+#include "Json.h"
+
+#include "net/Upload.h"
+
+auto FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr
+{
+ auto* netJob = new NetJob(QString("Flame::MatchFingerprints"), APPLICATION->network());
+
+ QJsonObject body_obj;
+ QJsonArray fingerprints_arr;
+ for (auto& fp : fingerprints) {
+ fingerprints_arr.append(QString("%1").arg(fp));
+ }
+
+ body_obj["fingerprints"] = fingerprints_arr;
+
+ QJsonDocument body(body_obj);
+ auto body_raw = body.toJson();
+
+ netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
+
+ QObject::connect(netJob, &NetJob::finished, [response] { delete response; });
+
+ return netJob;
+}
+
+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();
+ 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));
+
+ QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &changelog] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Flame::FileChangelog at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+
+ netJob->failed(parse_error.errorString());
+ return;
+ }
+
+ changelog = Json::ensureString(doc.object(), "data");
+ });
+
+ QObject::connect(netJob, &NetJob::finished, [response, &lock] {
+ delete response;
+ lock.quit();
+ });
+
+ netJob->start();
+ lock.exec();
+
+ return changelog;
+}
+
+auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion
+{
+ QEventLoop loop;
+
+ auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network());
+ auto response = new QByteArray();
+ ModPlatform::IndexedVersion ver;
+
+ netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response));
+
+ QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+
+ try {
+ auto obj = Json::requireObject(doc);
+ auto arr = Json::requireArray(obj, "data");
+
+ QJsonObject latest_file_obj;
+ ModPlatform::IndexedVersion ver_tmp;
+
+ for (auto file : arr) {
+ auto file_obj = Json::requireObject(file);
+ auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
+ if(file_tmp.date > ver_tmp.date) {
+ ver_tmp = file_tmp;
+ latest_file_obj = file_obj;
+ }
+ }
+
+ ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
+ } catch (Json::JsonException& e) {
+ qCritical() << "Failed to parse response from a version request.";
+ qCritical() << e.what();
+ qDebug() << doc;
+ }
+ });
+
+ QObject::connect(netJob, &NetJob::finished, [response, netJob, &loop] {
+ netJob->deleteLater();
+ delete response;
+ loop.quit();
+ });
+
+ netJob->start();
+
+ loop.exec();
+
+ return ver;
+}
+
+auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob*
+{
+ auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network());
+
+ QJsonObject body_obj;
+ QJsonArray addons_arr;
+ for (auto& addonId : addonIds) {
+ addons_arr.append(addonId);
+ }
+
+ body_obj["modIds"] = addons_arr;
+
+ QJsonDocument body(body_obj);
+ auto body_raw = body.toJson();
+
+ 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; });
+
+ return netJob;
+}
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index aea76ff1..336df387 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -4,6 +4,14 @@
#include "modplatform/helpers/NetworkModAPI.h"
class FlameAPI : public NetworkModAPI {
+ public:
+ auto matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr;
+ auto getModFileChangelog(int modId, int fileId) -> QString;
+
+ auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion;
+
+ auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override;
+
private:
inline auto getSortFieldInt(QString sortString) const -> int
{
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp
new file mode 100644
index 00000000..68a4589b
--- /dev/null
+++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp
@@ -0,0 +1,179 @@
+#include "FlameCheckUpdate.h"
+#include "FlameAPI.h"
+#include "FlameModIndex.h"
+
+#include <MurmurHash2.h>
+
+#include "FileSystem.h"
+#include "Json.h"
+
+#include "ModDownloadTask.h"
+
+static FlameAPI api;
+
+bool FlameCheckUpdate::abort()
+{
+ m_was_aborted = true;
+ if (m_net_job)
+ return m_net_job->abort();
+ return true;
+}
+
+ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
+{
+ ModPlatform::IndexedPack pack;
+
+ QEventLoop loop;
+
+ auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network());
+
+ auto response = new QByteArray();
+ auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
+ auto dl = Net::Download::makeByteArray(url, response);
+ get_project_job->addNetAction(dl);
+
+ QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from FlameCheckUpdate 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");
+ FlameMod::loadIndexedPack(pack, data_obj);
+ } catch (Json::JsonException& e) {
+ qWarning() << e.cause();
+ qDebug() << doc;
+ }
+ });
+
+ QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
+ get_project_job->deleteLater();
+ loop.quit();
+ });
+
+ get_project_job->start();
+ loop.exec();
+
+ return pack;
+}
+
+ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
+{
+ ModPlatform::IndexedVersion ver;
+
+ QEventLoop loop;
+
+ auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network());
+
+ auto response = new QByteArray();
+ auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
+ auto dl = Net::Download::makeByteArray(url, response);
+ get_file_info_job->addNetAction(dl);
+
+ QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from FlameCheckUpdate 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");
+ ver = FlameMod::loadIndexedPackVersion(data_obj);
+ } catch (Json::JsonException& e) {
+ qWarning() << e.cause();
+ qDebug() << doc;
+ }
+ });
+
+ QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
+ get_file_info_job->deleteLater();
+ loop.quit();
+ });
+
+ get_file_info_job->start();
+ loop.exec();
+
+ return ver;
+}
+
+/* Check for update:
+ * - Get latest version available
+ * - Compare hash of the latest version with the current hash
+ * - If equal, no updates, else, there's updates, so add to the list
+ * */
+void FlameCheckUpdate::executeTask()
+{
+ setStatus(tr("Preparing mods for CurseForge..."));
+
+ int i = 0;
+ for (auto* mod : m_mods) {
+ if (!mod->enabled()) {
+ emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
+ continue;
+ }
+
+ 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 });
+
+ // Check if we were aborted while getting the latest version
+ if (m_was_aborted) {
+ aborted();
+ return;
+ }
+
+ setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name()));
+
+ if (!latest_ver.addonId.isValid()) {
+ emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game "
+ "version / mod loader."));
+ continue;
+ }
+
+ if (latest_ver.downloadUrl.isEmpty() && latest_ver.fileId != mod->metadata()->file_id) {
+ auto pack = getProjectInfo(latest_ver);
+ auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver.fileId.toString());
+ emit checkFailed(mod, tr("Mod has a new update available, but is opted-out on CurseForge"), recover_url);
+
+ continue;
+ }
+
+ if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) {
+ // Fake pack with the necessary info to pass to the download task :)
+ ModPlatform::IndexedPack pack;
+ pack.name = mod->name();
+ pack.slug = mod->metadata()->slug;
+ pack.addonId = mod->metadata()->project_id;
+ pack.websiteUrl = mod->homeurl();
+ for (auto& author : mod->authors())
+ pack.authors.append({ author });
+ pack.description = mod->description();
+ pack.provider = ModPlatform::Provider::FLAME;
+
+ auto old_version = mod->version();
+ if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
+ auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt());
+ old_version = current_ver.version;
+ }
+
+ auto download_task = new ModDownloadTask(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);
+ }
+ }
+
+ emitSucceeded();
+}
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h
new file mode 100644
index 00000000..163c706c
--- /dev/null
+++ b/launcher/modplatform/flame/FlameCheckUpdate.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "Application.h"
+#include "modplatform/CheckUpdateTask.h"
+#include "net/NetJob.h"
+
+class FlameCheckUpdate : public CheckUpdateTask {
+ Q_OBJECT
+
+ public:
+ FlameCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr<ModFolderModel> mods_folder)
+ : CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
+ {}
+
+ public slots:
+ bool abort() override;
+
+ protected slots:
+ void executeTask() override;
+
+ private:
+ NetJob* m_net_job = nullptr;
+
+ bool m_was_aborted = false;
+};
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index b99bfb26..746018e2 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -7,20 +7,22 @@
#include "net/NetJob.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
+static FlameAPI api;
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
pack.provider = ModPlatform::Provider::FLAME;
pack.name = Json::requireString(obj, "name");
+ pack.slug = Json::requireString(obj, "slug");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
- QJsonObject logo = Json::requireObject(obj, "logo");
- pack.logoName = Json::requireString(logo, "title");
- pack.logoUrl = Json::requireString(logo, "thumbnailUrl");
+ QJsonObject logo = Json::ensureObject(obj, "logo");
+ pack.logoName = Json::ensureString(logo, "title");
+ pack.logoUrl = Json::ensureString(logo, "thumbnailUrl");
- auto authors = Json::requireArray(obj, "authors");
+ auto authors = Json::ensureArray(obj, "authors");
for (auto authorIter : authors) {
auto author = Json::requireObject(authorIter);
ModPlatform::ModpackAuthor packAuthor;
@@ -91,7 +93,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versionsLoaded = true;
}
-auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion
+auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> ModPlatform::IndexedVersion
{
auto versionArray = Json::requireArray(obj, "gameVersions");
if (versionArray.isEmpty()) {
@@ -110,7 +112,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedV
file.fileId = Json::requireInteger(obj, "id");
file.date = Json::requireString(obj, "fileDate");
file.version = Json::requireString(obj, "displayName");
- file.downloadUrl = Json::requireString(obj, "downloadUrl");
+ file.downloadUrl = Json::ensureString(obj, "downloadUrl");
file.fileName = Json::requireString(obj, "fileName");
auto hash_list = Json::ensureArray(obj, "hashes");
@@ -124,5 +126,9 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedV
break;
}
}
+
+ if(load_changelog)
+ file.changelog = api.getModFileChangelog(file.addonId.toInt(), file.fileId.toInt());
+
return file;
}
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index 9c6c1c6c..a839dd83 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -17,6 +17,6 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
-auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
+auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
} // namespace FlameMod