summaryrefslogtreecommitdiffstats
path: root/api/logic/modplatform/flame
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/modplatform/flame')
-rw-r--r--api/logic/modplatform/flame/FileResolvingTask.cpp150
-rw-r--r--api/logic/modplatform/flame/FileResolvingTask.h24
-rw-r--r--api/logic/modplatform/flame/PackManifest.cpp148
-rw-r--r--api/logic/modplatform/flame/PackManifest.h69
-rw-r--r--api/logic/modplatform/flame/UrlResolvingTask.cpp175
-rw-r--r--api/logic/modplatform/flame/UrlResolvingTask.h43
6 files changed, 419 insertions, 190 deletions
diff --git a/api/logic/modplatform/flame/FileResolvingTask.cpp b/api/logic/modplatform/flame/FileResolvingTask.cpp
index efc73621..295574f0 100644
--- a/api/logic/modplatform/flame/FileResolvingTask.cpp
+++ b/api/logic/modplatform/flame/FileResolvingTask.cpp
@@ -1,117 +1,63 @@
#include "FileResolvingTask.h"
#include "Json.h"
-const char * metabase = "https://cursemeta.dries007.net";
+namespace {
+ const char * metabase = "https://cursemeta.dries007.net";
+}
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
- : m_toProcess(toProcess)
+ : m_toProcess(toProcess)
{
}
void Flame::FileResolvingTask::executeTask()
{
- setStatus(tr("Resolving mod IDs..."));
- setProgress(0, m_toProcess.files.size());
- m_dljob.reset(new NetJob("Mod id resolver"));
- results.resize(m_toProcess.files.size());
- int index = 0;
- for(auto & file: m_toProcess.files)
- {
- auto projectIdStr = QString::number(file.projectId);
- auto fileIdStr = QString::number(file.fileId);
- QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
- auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]);
- m_dljob->addNetAction(dl);
- index ++;
- }
- connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
- m_dljob->start();
+ setStatus(tr("Resolving mod IDs..."));
+ setProgress(0, m_toProcess.files.size());
+ m_dljob.reset(new NetJob("Mod id resolver"));
+ results.resize(m_toProcess.files.size());
+ int index = 0;
+ for(auto & file: m_toProcess.files)
+ {
+ auto projectIdStr = QString::number(file.projectId);
+ auto fileIdStr = QString::number(file.fileId);
+ QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
+ auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]);
+ m_dljob->addNetAction(dl);
+ index ++;
+ }
+ connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
+ m_dljob->start();
}
void Flame::FileResolvingTask::netJobFinished()
{
- bool failed = false;
- int index = 0;
- for(auto & bytes: results)
- {
- try
- {
- auto doc = Json::requireDocument(bytes);
- auto obj = Json::requireObject(doc);
- auto & out = m_toProcess.files[index];
- // result code signifies true failure.
- if(obj.contains("code"))
- {
- qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:";
- qCritical() << bytes;
- failed = true;
- continue;
- }
- out.fileName = Json::requireString(obj, "FileNameOnDisk");
- QString rawUrl = Json::requireString(obj, "DownloadURL");
- out.url = QUrl(rawUrl, QUrl::TolerantMode);
- if(!out.url.isValid())
- {
- throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
- }
- // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
- // It is also optional
- QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
- if(!projObj.isEmpty())
- {
- QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
- if(strType == "singlefile")
- {
- out.type = File::Type::SingleFile;
- }
- else if(strType == "ctoc")
- {
- out.type = File::Type::Ctoc;
- }
- else if(strType == "cmod2")
- {
- out.type = File::Type::Cmod2;
- }
- else if(strType == "mod")
- {
- out.type = File::Type::Mod;
- }
- else if(strType == "folder")
- {
- out.type = File::Type::Folder;
- }
- else if(strType == "modpack")
- {
- out.type = File::Type::Modpack;
- }
- else
- {
- qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType;
- out.type = File::Type::Unknown;
- failed = true;
- continue;
- }
- out.targetFolder = Json::ensureString(projObj, "Path", "mods");
- }
- out.resolved = true;
- }
- catch(JSONValidationError & e)
- {
- auto & out = m_toProcess.files[index];
- qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
- qCritical() << e.cause();
- qCritical() << "JSON:";
- qCritical() << bytes;
- failed = true;
- }
- index++;
- }
- if(!failed)
- {
- emitSucceeded();
- }
- else
- {
- emitFailed(tr("Some mod ID resolving tasks failed."));
- }
+ bool failed = false;
+ int index = 0;
+ for(auto & bytes: results)
+ {
+ auto & out = m_toProcess.files[index];
+ try
+ {
+ failed &= (!out.parseFromBytes(bytes));
+ }
+ catch (const JSONValidationError &e)
+ {
+
+ qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
+ qCritical() << e.cause();
+ qCritical() << "JSON:";
+ qCritical() << bytes;
+ failed = true;
+ }
+ index++;
+ }
+ if(!failed)
+ {
+ emitSucceeded();
+ }
+ else
+ {
+ emitFailed(tr("Some mod ID resolving tasks failed."));
+ }
}
diff --git a/api/logic/modplatform/flame/FileResolvingTask.h b/api/logic/modplatform/flame/FileResolvingTask.h
index 00658452..5679b907 100644
--- a/api/logic/modplatform/flame/FileResolvingTask.h
+++ b/api/logic/modplatform/flame/FileResolvingTask.h
@@ -10,23 +10,25 @@ namespace Flame
{
class MULTIMC_LOGIC_EXPORT FileResolvingTask : public Task
{
- Q_OBJECT
+ Q_OBJECT
public:
- explicit FileResolvingTask(Flame::Manifest &toProcess);
- const Flame::Manifest &getResults() const
- {
- return m_toProcess;
- }
+ explicit FileResolvingTask(Flame::Manifest &toProcess);
+ virtual ~FileResolvingTask() {};
+
+ const Flame::Manifest &getResults() const
+ {
+ return m_toProcess;
+ }
protected:
- virtual void executeTask() override;
+ virtual void executeTask() override;
protected slots:
- void netJobFinished();
+ void netJobFinished();
private: /* data */
- Flame::Manifest m_toProcess;
- QVector<QByteArray> results;
- NetJobPtr m_dljob;
+ Flame::Manifest m_toProcess;
+ QVector<QByteArray> results;
+ NetJobPtr m_dljob;
};
}
diff --git a/api/logic/modplatform/flame/PackManifest.cpp b/api/logic/modplatform/flame/PackManifest.cpp
index 6a9324fe..1db0a161 100644
--- a/api/logic/modplatform/flame/PackManifest.cpp
+++ b/api/logic/modplatform/flame/PackManifest.cpp
@@ -3,64 +3,124 @@
static void loadFileV1(Flame::File & f, QJsonObject & file)
{
- f.projectId = Json::requireInteger(file, "projectID");
- f.fileId = Json::requireInteger(file, "fileID");
- f.required = Json::ensureBoolean(file, QString("required"), true);
+ f.projectId = Json::requireInteger(file, "projectID");
+ f.fileId = Json::requireInteger(file, "fileID");
+ f.required = Json::ensureBoolean(file, QString("required"), true);
}
static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader)
{
- m.id = Json::requireString(modLoader, "id");
- m.primary = Json::ensureBoolean(modLoader, QString("primary"), false);
+ m.id = Json::requireString(modLoader, "id");
+ m.primary = Json::ensureBoolean(modLoader, QString("primary"), false);
}
static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft)
{
- m.version = Json::requireString(minecraft, "version");
- // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack
- // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing
- m.libraries = Json::ensureString(minecraft, QString("libraries"), QString());
- auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray());
- for (const auto & item : arr)
- {
- auto obj = Json::requireObject(item);
- Flame::Modloader loader;
- loadModloaderV1(loader, obj);
- m.modLoaders.append(loader);
- }
+ m.version = Json::requireString(minecraft, "version");
+ // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack
+ // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing
+ m.libraries = Json::ensureString(minecraft, QString("libraries"), QString());
+ auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray());
+ for (const auto & item : arr)
+ {
+ auto obj = Json::requireObject(item);
+ Flame::Modloader loader;
+ loadModloaderV1(loader, obj);
+ m.modLoaders.append(loader);
+ }
}
static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest)
{
- auto mc = Json::requireObject(manifest, "minecraft");
- loadMinecraftV1(m.minecraft, mc);
- m.name = Json::ensureString(manifest, QString("name"), "Unnamed");
- m.version = Json::ensureString(manifest, QString("version"), QString());
- m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward");
- auto arr = Json::ensureArray(manifest, "files", QJsonArray());
- for (const auto & item : arr)
- {
- auto obj = Json::requireObject(item);
- Flame::File file;
- loadFileV1(file, obj);
- m.files.append(file);
- }
- m.overrides = Json::ensureString(manifest, "overrides", "overrides");
+ auto mc = Json::requireObject(manifest, "minecraft");
+ loadMinecraftV1(m.minecraft, mc);
+ m.name = Json::ensureString(manifest, QString("name"), "Unnamed");
+ m.version = Json::ensureString(manifest, QString("version"), QString());
+ m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward");
+ auto arr = Json::ensureArray(manifest, "files", QJsonArray());
+ for (const auto & item : arr)
+ {
+ auto obj = Json::requireObject(item);
+ Flame::File file;
+ loadFileV1(file, obj);
+ m.files.append(file);
+ }
+ m.overrides = Json::ensureString(manifest, "overrides", "overrides");
}
void Flame::loadManifest(Flame::Manifest & m, const QString &filepath)
{
- auto doc = Json::requireDocument(filepath);
- auto obj = Json::requireObject(doc);
- m.manifestType = Json::requireString(obj, "manifestType");
- if(m.manifestType != "minecraftModpack")
- {
- throw JSONValidationError("Not a modpack manifest!");
- }
- m.manifestVersion = Json::requireInteger(obj, "manifestVersion");
- if(m.manifestVersion != 1)
- {
- throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion));
- }
- loadManifestV1(m, obj);
+ auto doc = Json::requireDocument(filepath);
+ auto obj = Json::requireObject(doc);
+ m.manifestType = Json::requireString(obj, "manifestType");
+ if(m.manifestType != "minecraftModpack")
+ {
+ throw JSONValidationError("Not a modpack manifest!");
+ }
+ m.manifestVersion = Json::requireInteger(obj, "manifestVersion");
+ if(m.manifestVersion != 1)
+ {
+ throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion));
+ }
+ loadManifestV1(m, obj);
+}
+
+bool Flame::File::parseFromBytes(const QByteArray& bytes)
+{
+ auto doc = Json::requireDocument(bytes);
+ auto obj = Json::requireObject(doc);
+ // result code signifies true failure.
+ if(obj.contains("code"))
+ {
+ qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:";
+ qCritical() << bytes;
+ return false;
+ }
+ fileName = Json::requireString(obj, "FileNameOnDisk");
+ QString rawUrl = Json::requireString(obj, "DownloadURL");
+ url = QUrl(rawUrl, QUrl::TolerantMode);
+ if(!url.isValid())
+ {
+ throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
+ }
+ // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
+ // It is also optional
+ QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
+ if(!projObj.isEmpty())
+ {
+ QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
+ if(strType == "singlefile")
+ {
+ type = File::Type::SingleFile;
+ }
+ else if(strType == "ctoc")
+ {
+ type = File::Type::Ctoc;
+ }
+ else if(strType == "cmod2")
+ {
+ type = File::Type::Cmod2;
+ }
+ else if(strType == "mod")
+ {
+ type = File::Type::Mod;
+ }
+ else if(strType == "folder")
+ {
+ type = File::Type::Folder;
+ }
+ else if(strType == "modpack")
+ {
+ type = File::Type::Modpack;
+ }
+ else
+ {
+ qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType;
+ type = File::Type::Unknown;
+ return false;
+ }
+ targetFolder = Json::ensureString(projObj, "Path", "mods");
+ }
+ resolved = true;
+ return true;
}
diff --git a/api/logic/modplatform/flame/PackManifest.h b/api/logic/modplatform/flame/PackManifest.h
index 1a5254a8..02f39f0e 100644
--- a/api/logic/modplatform/flame/PackManifest.h
+++ b/api/logic/modplatform/flame/PackManifest.h
@@ -8,51 +8,54 @@ namespace Flame
{
struct File
{
- int projectId = 0;
- int fileId = 0;
- // NOTE: the opposite to 'optional'. This is at the time of writing unused.
- bool required = true;
-
- // our
- bool resolved = false;
- QString fileName;
- QUrl url;
- QString targetFolder = QLatin1Literal("mods");
- enum class Type
- {
- Unknown,
- Folder,
- Ctoc,
- SingleFile,
- Cmod2,
- Modpack,
- Mod
- } type = Type::Mod;
+ // NOTE: throws JSONValidationError
+ bool parseFromBytes(const QByteArray &bytes);
+
+ int projectId = 0;
+ int fileId = 0;
+ // NOTE: the opposite to 'optional'. This is at the time of writing unused.
+ bool required = true;
+
+ // our
+ bool resolved = false;
+ QString fileName;
+ QUrl url;
+ QString targetFolder = QLatin1Literal("mods");
+ enum class Type
+ {
+ Unknown,
+ Folder,
+ Ctoc,
+ SingleFile,
+ Cmod2,
+ Modpack,
+ Mod
+ } type = Type::Mod;
};
struct Modloader
{
- QString id;
- bool primary = false;
+ QString id;
+ bool primary = false;
};
struct Minecraft
{
- QString version;
- QString libraries;
- QVector<Flame::Modloader> modLoaders;
+ QString version;
+ QString libraries;
+ QVector<Flame::Modloader> modLoaders;
};
struct Manifest
{
- QString manifestType;
- int manifestVersion = 0;
- Flame::Minecraft minecraft;
- QString name;
- QString version;
- QString author;
- QVector<Flame::File> files;
- QString overrides;
+ QString manifestType;
+ int manifestVersion = 0;
+ Flame::Minecraft minecraft;
+ QString name;
+ QString version;
+ QString author;
+ QVector<Flame::File> files;
+ QString overrides;
};
void loadManifest(Flame::Manifest & m, const QString &filepath);
diff --git a/api/logic/modplatform/flame/UrlResolvingTask.cpp b/api/logic/modplatform/flame/UrlResolvingTask.cpp
new file mode 100644
index 00000000..efea350c
--- /dev/null
+++ b/api/logic/modplatform/flame/UrlResolvingTask.cpp
@@ -0,0 +1,175 @@
+#include "UrlResolvingTask.h"
+#include <QtXml>
+#include <Json.h>
+
+
+namespace {
+ const char * metabase = "https://cursemeta.dries007.net";
+}
+
+Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess)
+ : m_url(toProcess)
+{
+}
+
+void Flame::UrlResolvingTask::executeTask()
+{
+ resolveUrl();
+}
+
+void Flame::UrlResolvingTask::resolveUrl()
+{
+ setStatus(tr("Resolving URL..."));
+ setProgress(0, 1);
+ QUrl actualUrl(m_url);
+ if(actualUrl.host() != "www.curseforge.com") {
+ emitFailed(tr("Not a Twitch URL."));
+ return;
+ }
+ m_dljob.reset(new NetJob("URL resolver"));
+
+ bool weAreDigging = false;
+ needle = QString();
+
+ if(m_url.startsWith("https://")) {
+ if(m_url.endsWith("?client=y")) {
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y
+ m_url.chop(9);
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
+ }
+ if(m_url.endsWith("/download")) {
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html...
+ weAreDigging = true;
+ needle = m_url;
+ needle.replace("https://", "twitch://");
+ needle.replace("/download", "/download-client/");
+ m_url.append("?client=y");
+ } else if (m_url.contains("/download/")) {
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088
+ m_url.replace("/download/", "/download-client/");
+ }
+ }
+ else if(m_url.startsWith("twitch://")) {
+ // twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
+ m_url.replace(0, 9, "https://");
+ // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
+ }
+ auto dl = Net::Download::makeByteArray(QUrl(m_url), &results);
+ m_dljob->addNetAction(dl);
+ if(weAreDigging) {
+ connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML);
+ } else {
+ connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP);
+ }
+ m_dljob->start();
+}
+
+void Flame::UrlResolvingTask::processHTML()
+{
+ QString htmlDoc = QString::fromUtf8(results);
+ auto index = htmlDoc.indexOf(needle);
+ if(index < 0) {
+ emitFailed(tr("Couldn't find the needle in the haystack..."));
+ return;
+ }
+ auto indexStart = index;
+ int indexEnd = -1;
+ while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') {
+ index ++;
+ if(htmlDoc[index] == '"') {
+ indexEnd = index;
+ break;
+ }
+ }
+ if(indexEnd > 0) {
+ QString found = htmlDoc.mid(indexStart, indexEnd - indexStart);
+ qDebug() << "Found needle: " << found;
+ // twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088
+ m_url = found;
+ resolveUrl();
+ return;
+ }
+ emitFailed(tr("Couldn't find the end of the needle in the haystack..."));
+ return;
+}
+
+void Flame::UrlResolvingTask::processCCIP()
+{
+ QDomDocument doc;
+ if (!doc.setContent(results)) {
+ qDebug() << results;
+ emitFailed(tr("Resolving failed."));
+ return;
+ }
+ auto packageNode = doc.namedItem("package");
+ if(!packageNode.isElement()) {
+ emitFailed(tr("Resolving failed: missing package root element."));
+ return;
+ }
+ auto projectNode = packageNode.namedItem("project");
+ if(!projectNode.isElement()) {
+ emitFailed(tr("Resolving failed: missing project element."));
+ return;
+ }
+ auto attribs = projectNode.attributes();
+
+ auto projectIdNode = attribs.namedItem("id");
+ if(!projectIdNode.isAttr()) {
+ emitFailed(tr("Resolving failed: missing id attribute."));
+ return;
+ }
+ auto fileIdNode = attribs.namedItem("file");
+ if(!fileIdNode.isAttr()) {
+ emitFailed(tr("Resolving failed: missing file attribute."));
+ return;
+ }
+
+ auto projectId = projectIdNode.nodeValue();
+ auto fileId = fileIdNode.nodeValue();
+ bool success = true;
+ m_result.projectId = projectId.toInt(&success);
+ if(!success) {
+ emitFailed(tr("Failed to resove projectId as a number."));
+ return;
+ }
+ m_result.fileId = fileId.toInt(&success);
+ if(!success) {
+ emitFailed(tr("Failed to resove fileId as a number."));
+ return;
+ }
+ qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId;
+ resolveIDs();
+}
+
+void Flame::UrlResolvingTask::resolveIDs()
+{
+ setStatus(tr("Resolving mod IDs..."));
+ m_dljob.reset(new NetJob("Mod id resolver"));
+ auto projectIdStr = QString::number(m_result.projectId);
+ auto fileIdStr = QString::number(m_result.fileId);
+ QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
+ auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results);
+ m_dljob->addNetAction(dl);
+ connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta);
+ m_dljob->start();
+}
+
+void Flame::UrlResolvingTask::processCursemeta()
+{
+ try {
+ if(m_result.parseFromBytes(results)) {
+ emitSucceeded();
+ qDebug() << results;
+ return;
+ }
+ } catch (const JSONValidationError &e) {
+
+ qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:";
+ qCritical() << e.cause();
+ qCritical() << "JSON:";
+ qCritical() << results;
+ }
+ emitFailed(tr("Failed to resolve the modpack file."));
+}
diff --git a/api/logic/modplatform/flame/UrlResolvingTask.h b/api/logic/modplatform/flame/UrlResolvingTask.h
new file mode 100644
index 00000000..98b78f67
--- /dev/null
+++ b/api/logic/modplatform/flame/UrlResolvingTask.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "tasks/Task.h"
+#include "net/NetJob.h"
+#include "PackManifest.h"
+
+#include "multimc_logic_export.h"
+
+namespace Flame
+{
+class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task
+{
+ Q_OBJECT
+public:
+ explicit UrlResolvingTask(const QString &toProcess);
+ virtual ~UrlResolvingTask() {};
+
+ const Flame::File &getResults() const
+ {
+ return m_result;
+ }
+
+protected:
+ virtual void executeTask() override;
+
+protected slots:
+ void processCCIP();
+ void processHTML();
+ void processCursemeta();
+
+private:
+ void resolveUrl();
+ void resolveIDs();
+
+private: /* data */
+ QString m_url;
+ QString needle;
+ Flame::File m_result;
+ QByteArray results;
+ NetJobPtr m_dljob;
+};
+}
+