diff options
Diffstat (limited to 'launcher')
18 files changed, 569 insertions, 375 deletions
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 6ed86726..075c183a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -235,6 +235,8 @@ set(MINECRAFT_SOURCES minecraft/auth/steps/MigrationEligibilityStep.h minecraft/auth/steps/MinecraftProfileStep.cpp minecraft/auth/steps/MinecraftProfileStep.h + minecraft/auth/steps/MinecraftProfileStepMojang.cpp + minecraft/auth/steps/MinecraftProfileStepMojang.h minecraft/auth/steps/MSAStep.cpp minecraft/auth/steps/MSAStep.h minecraft/auth/steps/XboxAuthorizationStep.cpp diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index 2dd36562..47473899 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -1,4 +1,5 @@ #include "Parsers.h" +#include "Json.h" #include <QJsonDocument> #include <QJsonArray> @@ -212,6 +213,180 @@ bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) { return true; } +namespace { + // these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee) + // they are needed because the session server doesn't return skin urls for default skins + static const QString SKIN_URL_STEVE = "http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b"; + static const QString SKIN_URL_ALEX = "http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032"; + + bool isDefaultModelSteve(QString uuid) { + // need to calculate *Java* hashCode of UUID + // if number is even, skin/model is steve, otherwise it is alex + + // just in case dashes are in the id + uuid.remove('-'); + + if (uuid.size() != 32) { + return true; + } + + // qulonglong is guaranteed to be 64 bits + // we need to use unsigned numbers to guarantee truncation below + qulonglong most = uuid.left(16).toULongLong(nullptr, 16); + qulonglong least = uuid.right(16).toULongLong(nullptr, 16); + qulonglong xored = most ^ least; + return ((static_cast<quint32>(xored >> 32)) ^ static_cast<quint32>(xored)) % 2 == 0; + } +} + +/** +Uses session server for skin/cape lookup instead of profile, +because locked Mojang accounts cannot access profile endpoint +(https://api.minecraftservices.com/minecraft/profile/) + +ref: https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape + +{ + "id": "<profile identifier>", + "name": "<player name>", + "properties": [ + { + "name": "textures", + "value": "<base64 string>" + } + ] +} + +decoded base64 "value": +{ + "timestamp": <java time in ms>, + "profileId": "<profile uuid>", + "profileName": "<player name>", + "textures": { + "SKIN": { + "url": "<player skin URL>" + }, + "CAPE": { + "url": "<player cape URL>" + } + } +} +*/ + +bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) { + qDebug() << "Parsing Minecraft profile..."; +#ifndef NDEBUG + qDebug() << data; +#endif + + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if(jsonError.error) { + qWarning() << "Failed to parse response as JSON: " << jsonError.errorString(); + return false; + } + + auto obj = Json::requireObject(doc, "mojang minecraft profile"); + if(!getString(obj.value("id"), output.id)) { + qWarning() << "Minecraft profile id is not a string"; + return false; + } + + if(!getString(obj.value("name"), output.name)) { + qWarning() << "Minecraft profile name is not a string"; + return false; + } + + auto propsArray = obj.value("properties").toArray(); + QByteArray texturePayload; + for( auto p : propsArray) { + auto pObj = p.toObject(); + auto name = pObj.value("name"); + if (!name.isString() || name.toString() != "textures") { + continue; + } + + auto value = pObj.value("value"); + if (value.isString()) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + texturePayload = QByteArray::fromBase64(value.toString().toUtf8(), QByteArray::AbortOnBase64DecodingErrors); +#else + texturePayload = QByteArray::fromBase64(value.toString().toUtf8()); +#endif + } + + if (!texturePayload.isEmpty()) { + break; + } + } + + if (texturePayload.isNull()) { + qWarning() << "No texture payload data"; + return false; + } + + doc = QJsonDocument::fromJson(texturePayload, &jsonError); + if(jsonError.error) { + qWarning() << "Failed to parse response as JSON: " << jsonError.errorString(); + return false; + } + + obj = Json::requireObject(doc, "session texture payload"); + auto textures = obj.value("textures"); + if (!textures.isObject()) { + qWarning() << "No textures array in response"; + return false; + } + + Skin skinOut; + // fill in default skin info ourselves, as this endpoint doesn't provide it + bool steve = isDefaultModelSteve(output.id); + skinOut.variant = steve ? "classic" : "slim"; + skinOut.url = steve ? SKIN_URL_STEVE : SKIN_URL_ALEX; + // sadly we can't figure this out, but I don't think it really matters... + skinOut.id = "00000000-0000-0000-0000-000000000000"; + Cape capeOut; + auto tObj = textures.toObject(); + for (auto idx = tObj.constBegin(); idx != tObj.constEnd(); ++idx) { + if (idx->isObject()) { + if (idx.key() == "SKIN") { + auto skin = idx->toObject(); + if (!getString(skin.value("url"), skinOut.url)) { + qWarning() << "Skin url is not a string"; + return false; + } + + auto maybeMeta = skin.find("metadata"); + if (maybeMeta != skin.end() && maybeMeta->isObject()) { + auto meta = maybeMeta->toObject(); + // might not be present + getString(meta.value("model"), skinOut.variant); + } + } + else if (idx.key() == "CAPE") { + auto cape = idx->toObject(); + if (!getString(cape.value("url"), capeOut.url)) { + qWarning() << "Cape url is not a string"; + return false; + } + + // we don't know the cape ID as it is not returned from the session server + // so just fake it - changing capes is probably locked anyway :( + capeOut.alias = "cape"; + } + } + } + + output.skin = skinOut; + if (capeOut.alias == "cape") { + output.capes = QMap<QString, Cape>({{capeOut.alias, capeOut}}); + output.currentCape = capeOut.alias; + } + + output.validity = Katabasis::Validity::Certain; + return true; +} + bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) { qDebug() << "Parsing Minecraft entitlements..."; #ifndef NDEBUG diff --git a/launcher/minecraft/auth/Parsers.h b/launcher/minecraft/auth/Parsers.h index dac7f69b..2666d890 100644 --- a/launcher/minecraft/auth/Parsers.h +++ b/launcher/minecraft/auth/Parsers.h @@ -14,6 +14,7 @@ namespace Parsers bool parseMojangResponse(QByteArray &data, Katabasis::Token &output); bool parseMinecraftProfile(QByteArray &data, MinecraftProfile &output); + bool parseMinecraftProfileMojang(QByteArray &data, MinecraftProfile &output); bool parseMinecraftEntitlements(QByteArray &data, MinecraftEntitlement &output); bool parseRolloutResponse(QByteArray &data, bool& result); } diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp index 7ac842a6..29978411 100644 --- a/launcher/minecraft/auth/Yggdrasil.cpp +++ b/launcher/minecraft/auth/Yggdrasil.cpp @@ -209,6 +209,28 @@ void Yggdrasil::processResponse(QJsonObject responseData) { m_data->yggdrasilToken.validity = Katabasis::Validity::Certain; m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc(); + // Get UUID here since we need it for later + auto profile = responseData.value("selectedProfile"); + if (!profile.isObject()) { + changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a selected profile.")); + return; + } + + auto profileObj = profile.toObject(); + for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) { + if (i.key() == "name" && i.value().isString()) { + m_data->minecraftProfile.name = i->toString(); + } + else if (i.key() == "id" && i.value().isString()) { + m_data->minecraftProfile.id = i->toString(); + } + } + + if (m_data->minecraftProfile.id.isEmpty()) { + changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile.")); + return; + } + // We've made it through the minefield of possible errors. Return true to indicate that // we've succeeded. qDebug() << "Finished reading authentication response."; diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp index 4661dbe2..b86b0936 100644 --- a/launcher/minecraft/auth/flows/Mojang.cpp +++ b/launcher/minecraft/auth/flows/Mojang.cpp @@ -1,7 +1,7 @@ #include "Mojang.h" #include "minecraft/auth/steps/YggdrasilStep.h" -#include "minecraft/auth/steps/MinecraftProfileStep.h" +#include "minecraft/auth/steps/MinecraftProfileStepMojang.h" #include "minecraft/auth/steps/MigrationEligibilityStep.h" #include "minecraft/auth/steps/GetSkinStep.h" @@ -10,7 +10,7 @@ MojangRefresh::MojangRefresh( QObject *parent ) : AuthFlow(data, parent) { m_steps.append(new YggdrasilStep(m_data, QString())); - m_steps.append(new MinecraftProfileStep(m_data)); + m_steps.append(new MinecraftProfileStepMojang(m_data)); m_steps.append(new MigrationEligibilityStep(m_data)); m_steps.append(new GetSkinStep(m_data)); } @@ -21,7 +21,7 @@ MojangLogin::MojangLogin( QObject *parent ): AuthFlow(data, parent), m_password(password) { m_steps.append(new YggdrasilStep(m_data, m_password)); - m_steps.append(new MinecraftProfileStep(m_data)); + m_steps.append(new MinecraftProfileStepMojang(m_data)); m_steps.append(new MigrationEligibilityStep(m_data)); m_steps.append(new GetSkinStep(m_data)); } diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index c978bd07..f5697223 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -50,7 +50,9 @@ void LauncherLoginStep::onRequestDone( auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); requestor->deleteLater(); +#ifndef NDEBUG qDebug() << data; +#endif if (error != QNetworkReply::NoError) { qWarning() << "Reply error:" << error; #ifndef NDEBUG diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp new file mode 100644 index 00000000..d3035272 --- /dev/null +++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp @@ -0,0 +1,94 @@ +#include "MinecraftProfileStepMojang.h" + +#include <QNetworkRequest> + +#include "minecraft/auth/AuthRequest.h" +#include "minecraft/auth/Parsers.h" + +MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) { + +} + +MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default; + +QString MinecraftProfileStepMojang::describe() { + return tr("Fetching the Minecraft profile."); +} + + +void MinecraftProfileStepMojang::perform() { + if (m_data->minecraftProfile.id.isEmpty()) { + emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile.")); + return; + } + + // use session server instead of profile due to profile endpoint being locked for locked Mojang accounts + QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id); + QNetworkRequest req = QNetworkRequest(url); + AuthRequest *request = new AuthRequest(this); + connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone); + request->get(req); +} + +void MinecraftProfileStepMojang::rehydrate() { + // NOOP, for now. We only save bools and there's nothing to check. +} + +void MinecraftProfileStepMojang::onRequestDone( + QNetworkReply::NetworkError error, + QByteArray data, + QList<QNetworkReply::RawHeaderPair> headers +) { + auto requestor = qobject_cast<AuthRequest *>(QObject::sender()); + requestor->deleteLater(); + +#ifndef NDEBUG + qDebug() << data; +#endif + if (error == QNetworkReply::ContentNotFoundError) { + // NOTE: Succeed even if we do not have a profile. This is a valid account state. + if(m_data->type == AccountType::Mojang) { + m_data->minecraftEntitlement.canPlayMinecraft = false; + m_data->minecraftEntitlement.ownsMinecraft = false; + } + m_data->minecraftProfile = MinecraftProfile(); + emit finished( + AccountTaskState::STATE_SUCCEEDED, + tr("Account has no Minecraft profile.") + ); + return; + } + if (error != QNetworkReply::NoError) { + qWarning() << "Error getting profile:"; + qWarning() << " HTTP Status: " << requestor->httpStatus_; + qWarning() << " Internal error no.: " << error; + qWarning() << " Error string: " << requestor->errorString_; + + qWarning() << " Response:"; + qWarning() << QString::fromUtf8(data); + + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile acquisition failed.") + ); + return; + } + if(!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) { + m_data->minecraftProfile = MinecraftProfile(); + emit finished( + AccountTaskState::STATE_FAILED_SOFT, + tr("Minecraft Java profile response could not be parsed") + ); + return; + } + + if(m_data->type == AccountType::Mojang) { + auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain; + m_data->minecraftEntitlement.canPlayMinecraft = validProfile; + m_data->minecraftEntitlement.ownsMinecraft = validProfile; + } + emit finished( + AccountTaskState::STATE_WORKING, + tr("Minecraft Java profile acquisition succeeded.") + ); +} diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h new file mode 100644 index 00000000..e06b30ab --- /dev/null +++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h @@ -0,0 +1,22 @@ +#pragma once +#include <QObject> + +#include "QObjectPtr.h" +#include "minecraft/auth/AuthStep.h" + + +class MinecraftProfileStepMojang : public AuthStep { + Q_OBJECT + +public: + explicit MinecraftProfileStepMojang(AccountData *data); + virtual ~MinecraftProfileStepMojang() noexcept; + + void perform() override; + void rehydrate() override; + + QString describe() override; + +private slots: + void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>); +}; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 3889a935..95924a68 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -1,14 +1,9 @@ #include "FileResolvingTask.h" #include "Json.h" -namespace { - const char * metabase = "https://cursemeta.dries007.net"; -} - Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) -{ -} +{} void Flame::FileResolvingTask::executeTask() { @@ -17,14 +12,13 @@ void Flame::FileResolvingTask::executeTask() m_dljob = new NetJob("Mod id resolver", m_network); results.resize(m_toProcess.files.size()); int index = 0; - for(auto & file: m_toProcess.files) - { + 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); + QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr); auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); m_dljob->addNetAction(dl); - index ++; + index++; } connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); m_dljob->start(); @@ -34,16 +28,11 @@ void Flame::FileResolvingTask::netJobFinished() { bool failed = false; int index = 0; - for(auto & bytes: results) - { - auto & out = m_toProcess.files[index]; - try - { + for (auto& bytes : results) { + auto& out = m_toProcess.files[index]; + try { failed &= (!out.parseFromBytes(bytes)); - } - catch (const JSONValidationError &e) - { - + } catch (const JSONValidationError& e) { qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; qCritical() << e.cause(); qCritical() << "JSON:"; @@ -52,12 +41,9 @@ void Flame::FileResolvingTask::netJobFinished() } index++; } - if(!failed) - { + if (!failed) { emitSucceeded(); - } - else - { + } else { emitFailed(tr("Some mod ID resolving tasks failed.")); } } diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index ce02df65..61628e60 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -4,32 +4,52 @@ class FlameAPI : public NetworkModAPI { private: + inline auto getSortFieldInt(QString sortString) const -> int + { + return sortString == "Featured" ? 1 + : sortString == "Popularity" ? 2 + : sortString == "LastUpdated" ? 3 + : sortString == "Name" ? 4 + : sortString == "Author" ? 5 + : sortString == "TotalDownloads" ? 6 + : sortString == "Category" ? 7 + : sortString == "GameVersion" ? 8 + : 1; + } + + private: inline auto getModSearchURL(SearchArgs& args) const -> QString override { auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); return QString( - "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "https://api.curseforge.com/v1/mods/search?" "gameId=432&" - "categoryId=0&" - "sectionId=6&" + "classId=6&" "index=%1&" "pageSize=25&" "searchFilter=%2&" - "sort=%3&" + "sortField=%3&" + "sortOrder=desc&" "modLoaderType=%4&" "%5") .arg(args.offset) .arg(args.search) - .arg(args.sorting) + .arg(getSortFieldInt(args.sorting)) .arg(getMappedModLoader(args.mod_loader)) .arg(gameVersionStr); }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { - return QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(args.addonId); + QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; + QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader)); + + return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") + .arg(args.addonId) + .arg(gameVersionQuery) + .arg(modLoaderQuery); }; public: diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index c7b86b5c..ba0824cf 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -10,23 +10,12 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); - pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); + pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "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)); } + QJsonObject logo = Json::requireObject(obj, "logo"); + pack.logoName = Json::requireString(logo, "title"); + pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); auto authors = Json::requireArray(obj, "authors"); for (auto authorIter : authors) { @@ -45,18 +34,22 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, { QVector<ModPlatform::IndexedVersion> unsortedVersions; auto profile = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile(); - bool hasFabric = FlameAPI::getMappedModLoader(profile->getModLoader()) == ModAPI::Fabric; QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); - auto versionArray = Json::requireArray(obj, "gameVersion"); - if (versionArray.isEmpty()) { continue; } + auto versionArray = Json::requireArray(obj, "gameVersions"); + if (versionArray.isEmpty()) { + continue; + } ModPlatform::IndexedVersion file; for (auto mcVer : versionArray) { - file.mcVersion.append(mcVer.toString()); + auto str = mcVer.toString(); + + if (str.contains('.')) + file.mcVersion.append(str); } file.addonId = pack.addonId; @@ -66,28 +59,9 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.downloadUrl = Json::requireString(obj, "downloadUrl"); file.fileName = Json::requireString(obj, "fileName"); - auto modules = Json::requireArray(obj, "modules"); - bool is_valid_fabric_version = false; - for (auto m : modules) { - auto fname = Json::requireString(m.toObject(), "foldername"); - // FIXME: This does not work properly when a mod supports more than one mod loader, since - // FIXME: This also doesn't deal with Quilt mods at the moment - // they bundle the meta files for all of them in the same arquive, even when that version - // doesn't support the given mod loader. - if (hasFabric) { - if (fname == "fabric.mod.json") { - is_valid_fabric_version = true; - break; - } - } else - break; - // NOTE: Since we're not validating forge versions, we can just skip this loop. - } - - if (hasFabric && !is_valid_fabric_version) continue; - unsortedVersions.append(file); } + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { // dates are in RFC 3339 format return a.date > b.date; diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 3d8ea22a..549cace6 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -2,76 +2,63 @@ #include "Json.h" -void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj) +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 logo = Json::requireObject(obj, "logo"); + pack.logoName = Json::requireString(logo, "title"); + pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); auto authors = Json::requireArray(obj, "authors"); - for(auto authorIter: 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"); + int defaultFileId = Json::requireInteger(obj, "mainFileId"); bool found = false; // check if there are some files before adding the pack auto files = Json::requireArray(obj, "latestFiles"); - for(auto fileIter: files) { + 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) { + if (id != defaultFileId) { continue; } - auto versionArray = Json::requireArray(file, "gameVersion"); - if(versionArray.size() < 1) { + auto versionArray = Json::requireArray(file, "gameVersions"); + if (versionArray.size() < 1) { continue; } found = true; break; } - if(!found) { + if (!found) { throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); } } -void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) +void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) { QVector<Flame::IndexedVersion> unsortedVersions; - for(auto versionIter: arr) { + for (auto versionIter : arr) { auto version = Json::requireObject(versionIter); - Flame::IndexedVersion file; + Flame::IndexedVersion file; file.addonId = pack.addonId; file.fileId = Json::requireInteger(version, "id"); - auto versionArray = Json::requireArray(version, "gameVersion"); - if(versionArray.size() < 1) { + auto versionArray = Json::requireArray(version, "gameVersions"); + if (versionArray.size() < 1) { continue; } @@ -82,10 +69,7 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) unsortedVersions.append(file); } - auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool - { - return a.fileId > b.fileId; - }; + 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/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index b928fd16..e4f90c1a 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -1,28 +1,27 @@ #include "PackManifest.h" #include "Json.h" -static void loadFileV1(Flame::File & f, QJsonObject & file) +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) +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) +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 |
