diff options
Diffstat (limited to 'launcher/modplatform/flame')
-rw-r--r-- | launcher/modplatform/flame/FileResolvingTask.cpp | 63 | ||||
-rw-r--r-- | launcher/modplatform/flame/FileResolvingTask.h | 32 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlamePackIndex.cpp | 92 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlamePackIndex.h | 41 | ||||
-rw-r--r-- | launcher/modplatform/flame/PackManifest.cpp | 126 | ||||
-rw-r--r-- | launcher/modplatform/flame/PackManifest.h | 62 |
6 files changed, 416 insertions, 0 deletions
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp new file mode 100644 index 00000000..295574f0 --- /dev/null +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -0,0 +1,63 @@ +#include "FileResolvingTask.h" +#include "Json.h" + +namespace { + const char * metabase = "https://cursemeta.dries007.net"; +} + +Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& 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(); +} + +void Flame::FileResolvingTask::netJobFinished() +{ + 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/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h new file mode 100644 index 00000000..78a38fcb --- /dev/null +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -0,0 +1,32 @@ +#pragma once + +#include "tasks/Task.h" +#include "net/NetJob.h" +#include "PackManifest.h" + +namespace Flame +{ +class FileResolvingTask : public Task +{ + Q_OBJECT +public: + explicit FileResolvingTask(Flame::Manifest &toProcess); + virtual ~FileResolvingTask() {}; + + const Flame::Manifest &getResults() const + { + return m_toProcess; + } + +protected: + virtual void executeTask() override; + +protected slots: + void netJobFinished(); + +private: /* data */ + Flame::Manifest m_toProcess; + QVector<QByteArray> results; + NetJobPtr m_dljob; +}; +} diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp new file mode 100644 index 00000000..3d8ea22a --- /dev/null +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -0,0 +1,92 @@ +#include "FlamePackIndex.h" + +#include "Json.h" + +void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj) +{ + pack.addonId = Json::requireInteger(obj, "id"); + pack.name = Json::requireString(obj, "name"); + pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); + pack.description = Json::ensureString(obj, "summary", ""); + + bool thumbnailFound = false; + auto attachments = Json::requireArray(obj, "attachments"); + for(auto attachmentRaw: attachments) { + auto attachmentObj = Json::requireObject(attachmentRaw); + bool isDefault = attachmentObj.value("isDefault").toBool(false); + if(isDefault) { + thumbnailFound = true; + pack.logoName = Json::requireString(attachmentObj, "title"); + pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); + break; + } + } + + if(!thumbnailFound) { + throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); + } + + auto authors = Json::requireArray(obj, "authors"); + for(auto authorIter: authors) { + auto author = Json::requireObject(authorIter); + Flame::ModpackAuthor packAuthor; + packAuthor.name = Json::requireString(author, "name"); + packAuthor.url = Json::requireString(author, "url"); + pack.authors.append(packAuthor); + } + int defaultFileId = Json::requireInteger(obj, "defaultFileId"); + + bool found = false; + // check if there are some files before adding the pack + auto files = Json::requireArray(obj, "latestFiles"); + for(auto fileIter: files) { + auto file = Json::requireObject(fileIter); + int id = Json::requireInteger(file, "id"); + + // NOTE: for now, ignore everything that's not the default... + if(id != defaultFileId) { + continue; + } + + auto versionArray = Json::requireArray(file, "gameVersion"); + if(versionArray.size() < 1) { + continue; + } + + found = true; + break; + } + if(!found) { + throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); + } +} + +void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) +{ + QVector<Flame::IndexedVersion> unsortedVersions; + for(auto versionIter: arr) { + auto version = Json::requireObject(versionIter); + Flame::IndexedVersion file; + + file.addonId = pack.addonId; + file.fileId = Json::requireInteger(version, "id"); + auto versionArray = Json::requireArray(version, "gameVersion"); + if(versionArray.size() < 1) { + continue; + } + + // pick the latest version supported + file.mcVersion = versionArray[0].toString(); + file.version = Json::requireString(version, "displayName"); + file.downloadUrl = Json::requireString(version, "downloadUrl"); + unsortedVersions.append(file); + } + + auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool + { + return a.fileId > b.fileId; + }; + std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); + pack.versions = unsortedVersions; + pack.versionsLoaded = true; +} diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h new file mode 100644 index 00000000..7ffa29c3 --- /dev/null +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -0,0 +1,41 @@ +#pragma once + +#include <QList> +#include <QMetaType> +#include <QString> +#include <QVector> + +namespace Flame { + +struct ModpackAuthor { + QString name; + QString url; +}; + +struct IndexedVersion { + int addonId; + int fileId; + QString version; + QString mcVersion; + QString downloadUrl; +}; + +struct IndexedPack +{ + int addonId; + QString name; + QString description; + QList<ModpackAuthor> authors; + QString logoName; + QString logoUrl; + QString websiteUrl; + + bool versionsLoaded = false; + QVector<IndexedVersion> versions; +}; + +void loadIndexedPack(IndexedPack & m, QJsonObject & obj); +void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr); +} + +Q_DECLARE_METATYPE(Flame::IndexedPack) diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp new file mode 100644 index 00000000..b928fd16 --- /dev/null +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -0,0 +1,126 @@ +#include "PackManifest.h" +#include "Json.h" + +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); +} + +static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader) +{ + 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 (QJsonValueRef 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 (QJsonValueRef 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); +} + +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/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h new file mode 100644 index 00000000..02f39f0e --- /dev/null +++ b/launcher/modplatform/flame/PackManifest.h @@ -0,0 +1,62 @@ +#pragma once + +#include <QString> +#include <QVector> +#include <QUrl> + +namespace Flame +{ +struct File +{ + // 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; +}; + +struct Minecraft +{ + 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; +}; + +void loadManifest(Flame::Manifest & m, const QString &filepath); +} |