aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/CMakeLists.txt2
-rw-r--r--launcher/minecraft/auth/Parsers.cpp175
-rw-r--r--launcher/minecraft/auth/Parsers.h1
-rw-r--r--launcher/minecraft/auth/Yggdrasil.cpp22
-rw-r--r--launcher/minecraft/auth/flows/Mojang.cpp6
-rw-r--r--launcher/minecraft/auth/steps/LauncherLoginStep.cpp2
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp94
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h22
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp34
-rw-r--r--launcher/modplatform/flame/FlameAPI.h32
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp52
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp50
-rw-r--r--launcher/modplatform/flame/PackManifest.cpp87
-rw-r--r--launcher/net/Download.cpp112
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp49
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp129
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp71
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