diff options
Diffstat (limited to 'launcher/modplatform')
-rw-r--r-- | launcher/modplatform/flame/FileResolvingTask.cpp | 50 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlameAPI.h | 32 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlameModIndex.cpp | 59 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlamePackIndex.cpp | 52 | ||||
-rw-r--r-- | launcher/modplatform/flame/PackManifest.cpp | 96 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthAPI.h | 40 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 17 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthPackIndex.h | 17 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthPackManifest.cpp | 156 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthPackManifest.h | 107 | ||||
-rw-r--r-- | launcher/modplatform/technic/SolderPackManifest.cpp | 2 |
11 files changed, 451 insertions, 177 deletions
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 3889a935..0deb99c4 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,25 @@ 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) - { - + for (auto& bytes : results) { + auto& out = m_toProcess.files[index]; + try { + bool fail = (!out.parseFromBytes(bytes)); + if(fail){ + //failed :( probably disabled mod, try to add to the list + auto doc = Json::requireDocument(bytes); + if (!doc.isObject()) { + throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); + } + auto obj = Json::ensureObject(doc.object(), "data"); + //FIXME : HACK, MAY NOT WORK FOR LONG + out.url = QUrl(QString("https://media.forgecdn.net/files/%1/%2/%3") + .arg(QString::number(QString::number(out.fileId).leftRef(4).toInt()) + ,QString::number(QString::number(out.fileId).rightRef(3).toInt()) + ,QUrl::toPercentEncoding(out.fileName)), QUrl::TolerantMode); + } + failed &= fail; + } catch (const JSONValidationError& e) { qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; qCritical() << e.cause(); qCritical() << "JSON:"; @@ -52,12 +55,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..9846b156 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,49 +34,41 @@ 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; 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.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. + file.downloadUrl = Json::ensureString(obj, "downloadUrl", ""); + if(file.downloadUrl.isEmpty()){ + //FIXME : HACK, MAY NOT WORK FOR LONG + file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3") + .arg(QString::number(QString::number(file.fileId.toInt()).leftRef(4).toInt()) + ,QString::number(QString::number(file.fileId.toInt()).rightRef(3).toInt()) + ,QUrl::toPercentEncoding(file.fileName)); } - 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..ac24c647 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.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)); - } + 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..3217a756 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", QJsonArray()); - for (QJsonValueRef item : arr) - { + for (QJsonValueRef item : arr) { auto obj = Json::requireObject(item); Flame::Modloader loader; loadModloaderV1(loader, obj); @@ -30,16 +29,15 @@ static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) } } -static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) +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"); + m.author = Json::ensureString(manifest, QString("author"), "Anonymous"); auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (QJsonValueRef item : arr) - { + for (QJsonValueRef item : arr) { auto obj = Json::requireObject(item); Flame::File file; loadFileV1(file, obj); @@ -48,18 +46,16 @@ static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } -void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) +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") - { + if (m.manifestType != "minecraftModpack") { throw JSONValidationError("Not a modpack manifest!"); } m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); - if(m.manifestVersion != 1) - { + if (m.manifestVersion != 1) { throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); } loadManifestV1(m, obj); @@ -68,59 +64,35 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) 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; + if (!doc.isObject()) { + throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); + } + auto obj = Json::ensureObject(doc.object(), "data"); + + fileName = Json::requireString(obj, "fileName"); + + // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience + // It is also optional + type = File::Type::SingleFile; + + if (fileName.endsWith(".zip")) { + // this is probably a resource pack + targetFolder = "resourcepacks"; + } else { + // this is probably a mod, dunno what else could modpacks download + targetFolder = "mods"; + } + QString rawUrl = Json::ensureString(obj, "downloadUrl"); + + if(rawUrl.isEmpty()){ + //either there somehow is an emtpy string as a link, or it's null either way it's invalid + //soft failing return false; } - fileName = Json::requireString(obj, "FileNameOnDisk"); - QString rawUrl = Json::requireString(obj, "DownloadURL"); url = QUrl(rawUrl, QUrl::TolerantMode); - if(!url.isValid()) - { + 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/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 86852c94..6d642b5e 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -1,5 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + #pragma once +#include "BuildConfig.h" #include "modplatform/ModAPI.h" #include "modplatform/helpers/NetworkModAPI.h" @@ -47,13 +66,13 @@ class ModrinthAPI : public NetworkModAPI { return ""; } - return QString( - "https://api.modrinth.com/v2/search?" - "offset=%1&" - "limit=25&" - "query=%2&" - "index=%3&" - "facets=[[%4],%5[\"project_type:mod\"]]") + return QString(BuildConfig.MODRINTH_PROD_URL + + "/search?" + "offset=%1&" + "limit=25&" + "query=%2&" + "index=%3&" + "facets=[[%4],%5[\"project_type:mod\"]]") .arg(args.offset) .arg(args.search) .arg(args.sorting) @@ -63,9 +82,10 @@ class ModrinthAPI : public NetworkModAPI { inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { - return QString("https://api.modrinth.com/v2/project/%1/version?" - "game_versions=[%2]" - "loaders=[\"%3\"]") + return QString(BuildConfig.MODRINTH_PROD_URL + + "/project/%1/version?" + "game_versions=[%2]" + "loaders=[\"%3\"]") .arg(args.addonId) .arg(getGameVersionsString(args.mcVersions)) .arg(getModLoaderStrings(args.loader).join("\",\"")); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index a3c2f166..f7fa9864 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + #include "ModrinthPackIndex.h" #include "ModrinthAPI.h" diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index fd17847a..7f306f25 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + #pragma once #include "modplatform/ModIndex.h" diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp new file mode 100644 index 00000000..f1ad39ce --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 kb1000 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthPackManifest.h" +#include "Json.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +static ModrinthAPI api; + +namespace Modrinth { + +void loadIndexedPack(Modpack& pack, QJsonObject& obj) +{ + pack.id = Json::ensureString(obj, "project_id"); + + pack.name = Json::ensureString(obj, "title"); + pack.description = Json::ensureString(obj, "description"); + auto temp_author_name = Json::ensureString(obj, "author"); + pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name)); + pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug")); + pack.iconUrl = Json::ensureString(obj, "icon_url"); +} + +void loadIndexedInfo(Modpack& pack, QJsonObject& obj) +{ + pack.extra.body = Json::ensureString(obj, "body"); + pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug")); + pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); + pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); + + pack.extraInfoLoaded = true; +} + +void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) +{ + QVector<ModpackVersion> unsortedVersions; + + auto arr = Json::requireArray(doc); + + for (auto versionIter : arr) { + auto obj = Json::requireObject(versionIter); + auto file = loadIndexedVersion(obj); + + if(!file.id.isEmpty()) // Heuristic to check if the returned value is valid + unsortedVersions.append(file); + } + auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool { + // dates are in RFC 3339 format + return a.date > b.date; + }; + + std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); + + pack.versions.swap(unsortedVersions); + + pack.versionsLoaded = true; +} + +auto validateDownloadUrl(QUrl url) -> bool +{ + auto domain = url.host(); + if(domain == "cdn.modrinth.com") + return true; + if(domain == "edge.forgecdn.net") + return true; + if(domain == "media.forgecdn.net") + return true; + if(domain == "github.com") + return true; + if(domain == "raw.githubusercontent.com") + return true; + + return false; +} + +auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion +{ + ModpackVersion file; + + file.name = Json::requireString(obj, "name"); + file.version = Json::requireString(obj, "version_number"); + + file.id = Json::requireString(obj, "id"); + file.project_id = Json::requireString(obj, "project_id"); + + file.date = Json::requireString(obj, "date_published"); + + auto files = Json::requireArray(obj, "files"); + + + for (auto file_iter : files) { + File indexed_file; + auto parent = Json::requireObject(file_iter); + auto is_primary = Json::ensureBoolean(parent, "primary", false); + if (!is_primary) { + auto filename = Json::ensureString(parent, "filename"); + // Checking suffix here is fine because it's the response from Modrinth, + // so one would assume it will always be in English. + if(!filename.endsWith("mrpack") && !filename.endsWith("zip")) + continue; + } + + auto url = Json::requireString(parent, "url"); + + if(!validateDownloadUrl(url)) + continue; + + file.download_url = url; + if(is_primary) + break; + } + + if(file.download_url.isEmpty()) + return {}; + + return file; +} + +} // namespace Modrinth diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h new file mode 100644 index 00000000..e5fc9a70 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 kb1000 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QMetaType> + +#include <QByteArray> +#include <QCryptographicHash> +#include <QString> +#include <QUrl> +#include <QVector> + +class MinecraftInstance; + +namespace Modrinth { + +struct File +{ + QString path; + + QCryptographicHash::Algorithm hashAlgorithm; + QByteArray hash; + // TODO: should this support multiple download URLs, like the JSON does? + QUrl download; +}; + +struct ModpackExtra { + QString body; + + QString projectUrl; + QString sourceUrl; + QString wikiUrl; +}; + +struct ModpackVersion { + QString name; + QString version; + + QString id; + QString project_id; + + QString date; + + QString download_url; +}; + +struct Modpack { + QString id; + + QString name; + QString description; + std::tuple<QString, QUrl> author; + QString iconName; + QUrl iconUrl; + + bool versionsLoaded = false; + bool extraInfoLoaded = false; + + ModpackExtra extra; + QVector<ModpackVersion> versions; +}; + +void loadIndexedPack(Modpack&, QJsonObject&); +void loadIndexedInfo(Modpack&, QJsonObject&); +void loadIndexedVersions(Modpack&, QJsonDocument&); +auto loadIndexedVersion(QJsonObject&) -> ModpackVersion; + +auto validateDownloadUrl(QUrl) -> bool; + +} + +Q_DECLARE_METATYPE(Modrinth::Modpack) +Q_DECLARE_METATYPE(Modrinth::ModpackVersion) diff --git a/launcher/modplatform/technic/SolderPackManifest.cpp b/launcher/modplatform/technic/SolderPackManifest.cpp index 16fe0b0e..e52a7ec0 100644 --- a/launcher/modplatform/technic/SolderPackManifest.cpp +++ b/launcher/modplatform/technic/SolderPackManifest.cpp @@ -37,7 +37,7 @@ void loadPack(Pack& v, QJsonObject& obj) static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj) { b.name = Json::requireString(obj, "name"); - b.version = Json::requireString(obj, "version"); + b.version = Json::ensureString(obj, "version", ""); b.md5 = Json::requireString(obj, "md5"); b.url = Json::requireString(obj, "url"); } |