aboutsummaryrefslogtreecommitdiff
path: root/launcher/modplatform
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/modplatform')
-rw-r--r--launcher/modplatform/ModAPI.h55
-rw-r--r--launcher/modplatform/ModIndex.cpp86
-rw-r--r--launcher/modplatform/ModIndex.h54
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp218
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.h52
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.cpp78
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.h70
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp128
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.h8
-rw-r--r--launcher/modplatform/flame/FlameAPI.h23
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp98
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h2
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp35
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h12
-rw-r--r--launcher/modplatform/flame/PackManifest.cpp35
-rw-r--r--launcher/modplatform/flame/PackManifest.h47
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.cpp25
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.h2
-rw-r--r--launcher/modplatform/legacy_ftb/PackFetchTask.cpp37
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.cpp39
-rw-r--r--launcher/modplatform/legacy_ftb/PrivatePackManager.cpp43
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp48
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h47
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp166
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.cpp49
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.h19
-rw-r--r--launcher/modplatform/packwiz/Packwiz.cpp289
-rw-r--r--launcher/modplatform/packwiz/Packwiz.h93
-rw-r--r--launcher/modplatform/packwiz/Packwiz_test.cpp86
-rw-r--r--launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml13
-rw-r--r--launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml13
-rw-r--r--launcher/modplatform/technic/SolderPackManifest.cpp2
-rw-r--r--launcher/modplatform/technic/TechnicPackProcessor.cpp21
34 files changed, 1704 insertions, 291 deletions
diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h
index 8e6cd45c..cf116353 100644
--- a/launcher/modplatform/ModAPI.h
+++ b/launcher/modplatform/ModAPI.h
@@ -1,12 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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
+ *
+ * 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 <QString>
#include <QList>
+#include <list>
#include "Version.h"
namespace ModPlatform {
class ListModel;
+struct IndexedPack;
}
class ModAPI {
@@ -16,24 +53,32 @@ class ModAPI {
public:
virtual ~ModAPI() = default;
- // https://docs.curseforge.com/?http#tocS_ModLoaderType
- enum ModLoaderType { Unspecified = 0, Forge = 1, Cauldron = 2, LiteLoader = 3, Fabric = 4, Quilt = 5 };
+ enum ModLoaderType {
+ Unspecified = 0,
+ Forge = 1 << 0,
+ Cauldron = 1 << 1,
+ LiteLoader = 1 << 2,
+ Fabric = 1 << 3,
+ Quilt = 1 << 4
+ };
+ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SearchArgs {
int offset;
QString search;
QString sorting;
- ModLoaderType mod_loader;
+ ModLoaderTypes loaders;
std::list<Version> versions;
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
+ virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;
struct VersionSearchArgs {
QString addonId;
std::list<Version> mcVersions;
- ModLoaderType loader;
+ ModLoaderTypes loaders;
};
virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0;
@@ -61,7 +106,7 @@ class ModAPI {
{
QString s;
for(auto& ver : mcVersions){
- s += QString("%1,").arg(ver.toString());
+ s += QString("\"%1\",").arg(ver.toString());
}
s.remove(s.length() - 1, 1); //remove last comma
return s;
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
new file mode 100644
index 00000000..3c4b7887
--- /dev/null
+++ b/launcher/modplatform/ModIndex.cpp
@@ -0,0 +1,86 @@
+// 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/>.
+*/
+
+#include "modplatform/ModIndex.h"
+
+#include <QCryptographicHash>
+
+namespace ModPlatform {
+
+auto ProviderCapabilities::name(Provider p) -> const char*
+{
+ switch (p) {
+ case Provider::MODRINTH:
+ return "modrinth";
+ case Provider::FLAME:
+ return "curseforge";
+ }
+ return {};
+}
+auto ProviderCapabilities::readableName(Provider p) -> QString
+{
+ switch (p) {
+ case Provider::MODRINTH:
+ return "Modrinth";
+ case Provider::FLAME:
+ return "CurseForge";
+ }
+ return {};
+}
+auto ProviderCapabilities::hashType(Provider p) -> QStringList
+{
+ switch (p) {
+ case Provider::MODRINTH:
+ return { "sha512", "sha1" };
+ case Provider::FLAME:
+ // Try newer formats first, fall back to old format
+ return { "sha1", "md5", "murmur2" };
+ }
+ return {};
+}
+auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray
+{
+ switch (p) {
+ case Provider::MODRINTH: {
+ // NOTE: Data is the result of reading the entire JAR file!
+
+ // If 'type' was specified, we use that
+ if (!type.isEmpty() && hashType(p).contains(type)) {
+ if (type == "sha512")
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
+ else if (type == "sha1")
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
+ }
+
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
+ }
+ case Provider::FLAME:
+ // If 'type' was specified, we use that
+ if (!type.isEmpty() && hashType(p).contains(type)) {
+ if(type == "sha1")
+ return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
+ else if (type == "md5")
+ return QCryptographicHash::hash(data, QCryptographicHash::Md5);
+ }
+
+ break;
+ }
+ return {};
+}
+
+} // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 7e1cf254..c27643af 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -1,3 +1,21 @@
+// 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 <QList>
@@ -8,11 +26,30 @@
namespace ModPlatform {
+enum class Provider {
+ MODRINTH,
+ FLAME
+};
+
+class ProviderCapabilities {
+ public:
+ auto name(Provider) -> const char*;
+ auto readableName(Provider) -> QString;
+ auto hashType(Provider) -> QStringList;
+ auto hash(Provider, QByteArray&, QString type = "") -> QByteArray;
+};
+
struct ModpackAuthor {
QString name;
QString url;
};
+struct DonationData {
+ QString id;
+ QString platform;
+ QString url;
+};
+
struct IndexedVersion {
QVariant addonId;
QVariant fileId;
@@ -22,10 +59,22 @@ struct IndexedVersion {
QString date;
QString fileName;
QVector<QString> loaders = {};
+ QString hash_type;
+ QString hash;
+};
+
+struct ExtraPackData {
+ QList<DonationData> donate;
+
+ QString issuesUrl;
+ QString sourceUrl;
+ QString wikiUrl;
+ QString discordUrl;
};
struct IndexedPack {
QVariant addonId;
+ Provider provider;
QString name;
QString description;
QList<ModpackAuthor> authors;
@@ -35,8 +84,13 @@ struct IndexedPack {
bool versionsLoaded = false;
QVector<IndexedVersion> versions;
+
+ // Don't load by default, since some modplatform don't have that info
+ bool extraDataLoaded = true;
+ ExtraPackData extraData;
};
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
+Q_DECLARE_METATYPE(ModPlatform::Provider)
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 9dcb3504..0ed0ad29 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -1,23 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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 "ATLPackInstallTask.h"
-#include <QtConcurrent/QtConcurrent>
+#include <QtConcurrent>
#include <quazip/quazip.h>
@@ -39,10 +58,13 @@
namespace ATLauncher {
-PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
+static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
+
+PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)
{
m_support = support;
- m_pack = pack;
+ m_pack_name = packName;
+ m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version;
}
@@ -60,7 +82,7 @@ void PackInstallTask::executeTask()
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
auto *netJob = new NetJob("ATLauncher::VersionFetch", APPLICATION->network());
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
- .arg(m_pack).arg(m_version_name);
+ .arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
@@ -74,14 +96,13 @@ void PackInstallTask::onDownloadSucceeded()
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
jobPtr.reset();
- QJsonParseError parse_error;
+ QJsonParseError parse_error {};
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
-
auto obj = doc.object();
ATLauncher::PackVersion version;
@@ -96,19 +117,15 @@ void PackInstallTask::onDownloadSucceeded()
}
m_version = version;
- auto vlist = APPLICATION->metadataIndex()->get("net.minecraft");
- if(!vlist)
- {
- emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft"));
- return;
- }
+ // Display install message if one exists
+ if (!m_version.messages.install.isEmpty())
+ m_support->displayMessage(m_version.messages.install);
- auto ver = vlist->getVersion(m_version.minecraft);
+ auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
if (!ver) {
- emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft));
+ emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft", m_version.minecraft));
return;
}
- ver->load(Net::Mode::Online);
minecraftVersion = ver;
if(m_version.noConfigs) {
@@ -303,9 +320,50 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
auto f = std::make_shared<VersionFile>();
- f->name = m_pack + " " + m_version_name + " (libraries)";
+ f->name = m_pack_name + " " + m_version_name + " (libraries)";
+
+ const static QMap<QString, QString> liteLoaderMap = {
+ { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" },
+ { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" },
+ { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" },
+ { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" },
+ { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" },
+ { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" },
+ { "55785ccc82c07ff0ba038fe24be63ea2", "1.7.10_01" },
+ { "63ada46e033d0cb6782bada09ad5ca4e", "1.7.10_04" },
+ { "7983e4b28217c9ae8569074388409c86", "1.7.10_03" },
+ { "c09882458d74fe0697c7681b8993097e", "1.7.10_02" },
+ { "db7235aefd407ac1fde09a7baba50839", "1.7.10_00" },
+ { "6e9028816027f53957bd8fcdfabae064", "1.8" },
+ { "5e732dc446f9fe2abe5f9decaec40cde", "1.10-SNAPSHOT" },
+ { "3a98b5ed95810bf164e71c1a53be568d", "1.11.2-SNAPSHOT" },
+ { "ba8e6285966d7d988a96496f48cbddaa", "1.8.9-SNAPSHOT" },
+ { "8524af3ac3325a82444cc75ae6e9112f", "1.11-SNAPSHOT" },
+ { "53639d52340479ccf206a04f5e16606f", "1.5.2_01" },
+ { "1fcdcf66ce0a0806b7ad8686afdce3f7", "1.6.4_00" },
+ { "531c116f71ae2b11033f9a11a0f8e668", "1.6.4_01" },
+ { "4009eeb99c9068f608d3483a6439af88", "1.7.2_03" },
+ { "66f343354b8417abce1a10d557d2c6e9", "1.7.2_04" },
+ { "ab554c21f28fbc4ae9b098bcb5f4cceb", "1.7.2_05" },
+ { "e1d76a05a3723920e2f80a5e66c45f16", "1.7.2_02" },
+ { "00318cb0c787934d523f63cdfe8ddde4", "1.9-SNAPSHOT" },
+ { "986fd1ee9525cb0dcab7609401cef754", "1.9.4-SNAPSHOT" },
+ { "571ad5e6edd5ff40259570c9be588bb5", "1.9.4" },
+ { "1cdd72f7232e45551f16cc8ffd27ccf3", "1.10.2-SNAPSHOT" },
+ { "8a7c21f32d77ee08b393dd3921ced8eb", "1.10.2" },
+ { "b9bef8abc8dc309069aeba6fbbe58980", "1.12.1-SNAPSHOT" }
+ };
for(const auto & lib : m_version.libraries) {
+ // If the library is LiteLoader, we need to ignore it and handle it separately.
+ if (liteLoaderMap.contains(lib.md5)) {
+ auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5));
+ if (ver) {
+ componentsToInstall.insert("com.mumfrey.liteloader", ver);
+ continue;
+ }
+ }
+
auto libName = detectLibrary(lib);
GradleSpecifier libSpecifier(libName);
@@ -357,7 +415,31 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
{
- if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
+ if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
+ return true;
+ }
+
+ auto mainClass = m_version.mainClass.mainClass;
+ auto extraArguments = m_version.extraArguments.arguments;
+
+ auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty();
+ auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty();
+ if (hasMainClassDepends || hasExtraArgumentsDepends) {
+ QSet<QString> mods;
+ for (const auto& item : m_version.mods) {
+ mods.insert(item.name);
+ }
+
+ if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) {
+ mainClass = "";
+ }
+
+ if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) {
+ extraArguments = "";
+ }
+ }
+
+ if (mainClass.isEmpty() && extraArguments.isEmpty()) {
return true;
}
@@ -384,13 +466,13 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
}
auto f = std::make_shared<VersionFile>();
- f->name = m_pack + " " + m_version_name;
- if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
- f->mainClass = m_version.mainClass;
+ f->name = m_pack_name + " " + m_version_name;
+ if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {
+ f->mainClass = mainClass;
}
// Parse out tweakers
- auto args = m_version.extraArguments.split(" ");
+ auto args = extraArguments.split(" ");
QString previous;
for(auto arg : args) {
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
@@ -426,9 +508,9 @@ void PackInstallTask::installConfigs()
setStatus(tr("Downloading configs..."));
jobPtr = new NetJob(tr("Config download"), APPLICATION->network());
- auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name);
+ auto path = QString("Configs/%1/%2.zip").arg(m_pack_safe_name).arg(m_version_name);
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip")
- .arg(m_pack).arg(m_version_name);
+ .arg(m_pack_safe_name).arg(m_version_name);
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
@@ -475,7 +557,11 @@ void PackInstallTask::extractConfigs()
return;
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/minecraft");
+#else
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
+#endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]()
{
downloadMods();
@@ -502,7 +588,7 @@ void PackInstallTask::downloadMods()
QVector<QString> selectedMods;
if (!optionalMods.isEmpty()) {
setStatus(tr("Selecting optional mods..."));
- selectedMods = m_support->chooseOptionalMods(optionalMods);
+ selectedMods = m_support->chooseOptionalMods(m_version, optionalMods);
}
setStatus(tr("Downloading mods..."));
@@ -574,19 +660,12 @@ void PackInstallTask::downloadMods()
jobPtr->addNetAction(dl);
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
- qDebug() << "Will download" << url << "to" << path;
- modsToCopy[entry->getFullPath()] = path;
if(mod.type == ModType::Forge) {
- auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge");
- if(vlist)
- {
- auto ver = vlist->getVersion(mod.version);
- if(ver) {
- ver->load(Net::Mode::Online);
- componentsToInstall.insert("net.minecraftforge", ver);
- continue;
- }
+ auto ver = getComponentVersion("net.minecraftforge", mod.version);
+ if (ver) {
+ componentsToInstall.insert("net.minecraftforge", ver);
+ continue;
}
qDebug() << "Jarmod: " + path;
@@ -597,6 +676,10 @@ void PackInstallTask::downloadMods()
qDebug() << "Jarmod: " + path;
jarmods.push_back(path);
}
+
+ // Download after Forge handling, to avoid downloading Forge twice.
+ qDebug() << "Will download" << url << "to" << path;
+ modsToCopy[entry->getFullPath()] = path;
}
}
@@ -623,7 +706,11 @@ void PackInstallTask::onModsDownloaded() {
jobPtr.reset();
if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), &PackInstallTask::extractMods, this, modsToExtract, modsToDecomp, modsToCopy);
+#else
m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
+#endif
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
{
@@ -675,7 +762,7 @@ bool PackInstallTask::extractMods(
QString folderToExtract = "";
if(mod.type == ModType::Extract) {
folderToExtract = mod.extractFolder;
- folderToExtract.remove(QRegExp("^/"));
+ folderToExtract.remove(QRegularExpression("^/"));
}
qDebug() << "Extracting " + mod.file + " to " + extractToDir;
@@ -703,6 +790,17 @@ bool PackInstallTask::extractMods(
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
auto &from = iter.key();
auto &to = iter.value();
+
+ // If the file already exists, assume the mod is the correct copy - and remove
+ // the copy from the Configs.zip
+ QFileInfo fileInfo(to);
+ if (fileInfo.exists()) {
+ if (!QFile::remove(to)) {
+ qWarning() << "Failed to delete" << to;
+ return false;
+ }
+ }
+
FS::copy fileCopyOperation(from, to);
if(!fileCopyOperation()) {
qWarning() << "Failed to copy" << from << "to" << to;
@@ -740,14 +838,14 @@ void PackInstallTask::install()
auto version = getVersionForLoader("net.minecraftforge");
if(version == Q_NULLPTR) return;
- components->setComponentVersion("net.minecraftforge", version, true);
+ components->setComponentVersion("net.minecraftforge", version);
}
else if(m_version.loader.type == QString("fabric"))
{
auto version = getVersionForLoader("net.fabricmc.fabric-loader");
if(version == Q_NULLPTR) return;
- components->setComponentVersion("net.fabricmc.fabric-loader", version, true);
+ components->setComponentVersion("net.fabricmc.fabric-loader", version);
}
else if(m_version.loader.type != QString())
{
@@ -773,10 +871,30 @@ void PackInstallTask::install()
instance.setName(m_instName);
instance.setIconKey(m_instIcon);
+ instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name);
instanceSettings->resumeSave();
jarmods.clear();
emitSucceeded();
}
+static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version)
+{
+ auto vlist = APPLICATION->metadataIndex()->get(uid);
+ if (!vlist)
+ return {};
+
+ if (!vlist->isLoaded())
+ vlist->load(Net::Mode::Online);
+
+ auto ver = vlist->getVersion(version);
+ if (!ver)
+ return {};
+
+ if (!ver->isLoaded())
+ ver->load(Net::Mode::Online);
+
+ return ver;
+}
+
}
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
index 783ec19b..f55873e9 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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
@@ -37,7 +56,7 @@ public:
/**
* Requests a user interaction to select which optional mods should be installed.
*/
- virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0;
+ virtual QVector<QString> chooseOptionalMods(PackVersion version, QVector<ATLauncher::VersionMod> mods) = 0;
/**
* Requests a user interaction to select a component version from a given version list
@@ -45,6 +64,10 @@ public:
*/
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
+ /**
+ * Requests a user interaction to display a message.
+ */
+ virtual void displayMessage(QString message) = 0;
};
class PackInstallTask : public InstanceTask
@@ -52,7 +75,7 @@ class PackInstallTask : public InstanceTask
Q_OBJECT
public:
- explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version);
+ explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);
virtual ~PackInstallTask(){}
bool canAbort() const override { return true; }
@@ -94,7 +117,8 @@ private:
NetJob::Ptr jobPtr;
QByteArray response;
- QString m_pack;
+ QString m_pack_name;
+ QString m_pack_safe_name;
QString m_version_name;
PackVersion m_version;
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
index 40be6d53..3af02a09 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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 "ATLPackManifest.h"
@@ -178,6 +197,8 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
p.depends.append(Json::requireString(depends));
}
}
+ p.colour = Json::ensureString(obj, QString("colour"), "");
+ p.warning = Json::ensureString(obj, QString("warning"), "");
p.client = Json::ensureBoolean(obj, QString("client"), false);
@@ -185,6 +206,24 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
p.effectively_hidden = p.hidden || p.library;
}
+static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj)
+{
+ m.install = Json::ensureString(obj, "install", "");
+ m.update = Json::ensureString(obj, "update", "");
+}
+
+static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj)
+{
+ m.mainClass = Json::ensureString(obj, "mainClass", "");
+ m.depends = Json::ensureString(obj, "depends", "");
+}
+
+static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj)
+{
+ a.arguments = Json::ensureString(obj, "arguments", "");
+ a.depends = Json::ensureString(obj, "depends", "");
+}
+
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@@ -193,12 +232,12 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
if(obj.contains("mainClass")) {
auto main = Json::requireObject(obj, "mainClass");
- v.mainClass = Json::ensureString(main, "mainClass", "");
+ loadVersionMainClass(v.mainClass, main);
}
if(obj.contains("extraArguments")) {
auto arguments = Json::requireObject(obj, "extraArguments");
- v.extraArguments = Json::ensureString(arguments, "arguments", "");
+ loadVersionExtraArguments(v.extraArguments, arguments);
}
if(obj.contains("loader")) {
@@ -232,4 +271,17 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
auto configsObj = Json::requireObject(obj, "configs");
loadVersionConfigs(v.configs, configsObj);
}
+
+ auto colourObj = Json::ensureObject(obj, "colours");
+ for (const auto &key : colourObj.keys()) {
+ v.colours[key] = Json::requireString(colourObj.value(key), "colour");
+ }
+
+ auto warningsObj = Json::ensureObject(obj, "warnings");
+ for (const auto &key : warningsObj.keys()) {
+ v.warnings[key] = Json::requireString(warningsObj.value(key), "warning");
+ }
+
+ auto messages = Json::ensureObject(obj, "messages");
+ loadVersionMessages(v.messages, messages);
}
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h
index 673f2f8b..43510c50 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.h
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.h
@@ -1,24 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * 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 2020 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * 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 <QJsonObject>
+#include <QMap>
#include <QString>
#include <QVector>
-#include <QJsonObject>
namespace ATLauncher
{
@@ -109,6 +129,8 @@ struct VersionMod
bool library;
QString group;
QVector<QString> depends;
+ QString colour;
+ QString warning;
bool client;
@@ -122,18 +144,40 @@ struct VersionConfigs
QString sha1;
};
+struct VersionMessages
+{
+ QString install;
+ QString update;
+};
+
+struct PackVersionMainClass
+{
+ QString mainClass;
+ QString depends;
+};
+
+struct PackVersionExtraArguments
+{
+ QString arguments;
+ QString depends;
+};
+
struct PackVersion
{
QString version;
QString minecraft;
bool noConfigs;
- QString mainClass;
- QString extraArguments;
+ PackVersionMainClass mainClass;
+ PackVersionExtraArguments extraArguments;
VersionLoader loader;
QVector<VersionLibrary> libraries;
QVector<VersionMod> mods;
VersionConfigs configs;
+
+ QMap<QString, QString> colours;
+ QMap<QString, QString> warnings;
+ VersionMessages messages;
};
void loadVersion(PackVersion & v, QJsonObject & obj);
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index 95924a68..c1f56658 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -1,49 +1,129 @@
#include "FileResolvingTask.h"
+
#include "Json.h"
+#include "net/Upload.h"
-Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess)
+Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
: m_network(network), m_toProcess(toProcess)
{}
void Flame::FileResolvingTask::executeTask()
{
setStatus(tr("Resolving mod IDs..."));
- setProgress(0, m_toProcess.files.size());
+ setProgress(0, 3);
m_dljob = new NetJob("Mod id resolver", m_network);
- 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("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++;
- }
+ result.reset(new QByteArray());
+ //build json data to send
+ QJsonObject object;
+
+ object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
+ l.push_back(s.fileId);
+ return l;
+ }));
+ QByteArray data = Json::toText(object);
+ auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data);
+ m_dljob->addNetAction(dl);
connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
m_dljob->start();
}
void Flame::FileResolvingTask::netJobFinished()
{
- bool failed = false;
+ setProgress(1, 3);
int index = 0;
- for (auto& bytes : results) {
- auto& out = m_toProcess.files[index];
+ // job to check modrinth for blocked projects
+ auto job = new NetJob("Modrinth check", m_network);
+ blockedProjects = QMap<File *,QByteArray *>();
+ auto doc = Json::requireDocument(*result);
+ auto array = Json::requireArray(doc.object()["data"]);
+ for (QJsonValueRef file : array) {
+ auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
+ auto& out = m_toProcess.files[fileid];
try {
- failed &= (!out.parseFromBytes(bytes));
+ out.parseFromObject(Json::requireObject(file));
} 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;
+ qDebug() << "Blocked mod on curseforge" << out.fileName;
+ auto hash = out.hash;
+ if(!hash.isEmpty()) {
+ auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
+ auto output = new QByteArray();
+ auto dl = Net::Download::makeByteArray(QUrl(url), output);
+ QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
+ out.resolved = true;
+ });
+
+ job->addNetAction(dl);
+ blockedProjects.insert(&out, output);
+ }
}
index++;
}
- if (!failed) {
- emitSucceeded();
+ connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
+
+ job->start();
+}
+
+void Flame::FileResolvingTask::modrinthCheckFinished() {
+ setProgress(2, 3);
+ qDebug() << "Finished with blocked mods : " << blockedProjects.size();
+
+ for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) {
+ auto &out = *it;
+ auto bytes = blockedProjects[out];
+ if (!out->resolved) {
+ delete bytes;
+ continue;
+ }
+ QJsonDocument doc = QJsonDocument::fromJson(*bytes);
+ auto obj = doc.object();
+ auto array = Json::requireArray(obj,"files");
+ for (auto file: array) {
+ auto fileObj = Json::requireObject(file);
+ auto primary = Json::requireBoolean(fileObj,"primary");
+ if (primary) {
+ out->url = Json::requireUrl(fileObj,"url");
+ qDebug() << "Found alternative on modrinth " << out->fileName;
+ break;
+ }
+ }
+ delete bytes;
+ }
+ //copy to an output list and filter out projects found on modrinth
+ auto block = new QList<File *>();
+ auto it = blockedProjects.keys();
+ std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
+ return !f->resolved;
+ });
+ //Display not found mods early
+ if (!block->empty()) {
+ //blocked mods found, we need the slug for displaying.... we need another job :D !
+ auto slugJob = new NetJob("Slug Job", m_network);
+ auto slugs = QVector<QByteArray>(block->size());
+ auto index = 0;
+ for (auto fileInfo: *block) {
+ auto projectId = fileInfo->projectId;
+ slugs[index] = QByteArray();
+ auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
+ auto dl = Net::Download::makeByteArray(url, &slugs[index]);
+ slugJob->addNetAction(dl);
+ index++;
+ }
+ connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() {
+ slugJob->deleteLater();
+ auto index = 0;
+ for (const auto &slugResult: slugs) {
+ auto json = QJsonDocument::fromJson(slugResult);
+ auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
+ "websiteUrl");
+ auto mod = block->at(index);
+ auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
+ mod->websiteUrl = link;
+ index++;
+ }
+ emitSucceeded();
+ });
+ slugJob->start();
} else {
- emitFailed(tr("Some mod ID resolving tasks failed."));
+ emitSucceeded();
}
}
diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h
index 5e5adcd7..87981f0a 100644
--- a/launcher/modplatform/flame/FileResolvingTask.h
+++ b/launcher/modplatform/flame/FileResolvingTask.h
@@ -10,7 +10,7 @@ class FileResolvingTask : public Task
{
Q_OBJECT
public:
- explicit FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest &toProcess);
+ explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
virtual ~FileResolvingTask() {};
const Flame::Manifest &getResults() const
@@ -27,7 +27,11 @@ protected slots:
private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network;
Flame::Manifest m_toProcess;
- QVector<QByteArray> results;
+ std::shared_ptr<QByteArray> result;
NetJob::Ptr m_dljob;
+
+ void modrinthCheckFinished();
+
+ QMap<File *, QByteArray *> blockedProjects;
};
}
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 61628e60..aea76ff1 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -1,5 +1,6 @@
#pragma once
+#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
class FlameAPI : public NetworkModAPI {
@@ -37,14 +38,19 @@ class FlameAPI : public NetworkModAPI {
.arg(args.offset)
.arg(args.search)
.arg(getSortFieldInt(args.sorting))
- .arg(getMappedModLoader(args.mod_loader))
+ .arg(getMappedModLoader(args.loaders))
.arg(gameVersionStr);
};
+ inline auto getModInfoURL(QString& id) const -> QString override
+ {
+ return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
+ };
+
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{
QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : "";
- QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader));
+ QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders));
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3")
.arg(args.addonId)
@@ -53,11 +59,16 @@ class FlameAPI : public NetworkModAPI {
};
public:
- static auto getMappedModLoader(const ModLoaderType type) -> const ModLoaderType
+ static auto getMappedModLoader(const ModLoaderTypes loaders) -> int
{
+ // https://docs.curseforge.com/?http#tocS_ModLoaderType
+ if (loaders & Forge)
+ return 1;
+ if (loaders & Fabric)
+ return 4;
// TODO: remove this once Quilt drops official Fabric support
- if (type == Quilt) // NOTE: Most if not all Fabric mods should work *currently*
- return Fabric;
- return type;
+ if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
+ return 4; // Quilt would probably be 5
+ return 0;
}
};
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index ba0824cf..b99bfb26 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -6,9 +6,12 @@
#include "modplatform/flame/FlameAPI.h"
#include "net/NetJob.h"
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
+ pack.provider = ModPlatform::Provider::FLAME;
pack.name = Json::requireString(obj, "name");
pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
@@ -25,6 +28,38 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
packAuthor.url = Json::requireString(author, "url");
pack.authors.append(packAuthor);
}
+
+ loadExtraPackData(pack, obj);
+}
+
+void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
+{
+ auto links_obj = Json::ensureObject(obj, "links");
+
+ pack.extraData.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
+ if(pack.extraData.issuesUrl.endsWith('/'))
+ pack.extraData.issuesUrl.chop(1);
+
+ pack.extraData.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
+ if(pack.extraData.sourceUrl.endsWith('/'))
+ pack.extraData.sourceUrl.chop(1);
+
+ pack.extraData.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
+ if(pack.extraData.wikiUrl.endsWith('/'))
+ pack.extraData.wikiUrl.chop(1);
+
+ pack.extraDataLoaded = true;
+}
+
+static QString enumToString(int hash_algorithm)
+{
+ switch(hash_algorithm){
+ default:
+ case 1:
+ return "sha1";
+ case 2:
+ return "md5";
+ }
}
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
@@ -38,28 +73,13 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
+
+ auto file = loadIndexedPackVersion(obj);
+ if(!file.addonId.isValid())
+ file.addonId = pack.addonId;
- auto versionArray = Json::requireArray(obj, "gameVersions");
- if (versionArray.isEmpty()) {
- continue;
- }
-
- ModPlatform::IndexedVersion file;
- for (auto mcVer : versionArray) {
- 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");
-
- unsortedVersions.append(file);
+ if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ unsortedVersions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@@ -70,3 +90,39 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
+
+auto FlameMod::loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion
+{
+ auto versionArray = Json::requireArray(obj, "gameVersions");
+ if (versionArray.isEmpty()) {
+ return {};
+ }
+
+ ModPlatform::IndexedVersion file;
+ for (auto mcVer : versionArray) {
+ auto str = mcVer.toString();
+
+ if (str.contains('.'))
+ file.mcVersion.append(str);
+ }
+
+ file.addonId = Json::requireInteger(obj, "modId");
+ 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 hash_list = Json::ensureArray(obj, "hashes");
+ for (auto h : hash_list) {
+ auto hash_entry = Json::ensureObject(h);
+ auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME);
+ auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm"));
+ if (hash_types.contains(hash_algo)) {
+ file.hash = Json::requireString(hash_entry, "value");
+ file.hash_type = hash_algo;
+ break;
+ }
+ }
+ return file;
+}
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index d3171d94..9c6c1c6c 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -12,9 +12,11 @@
namespace FlameMod {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
+void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
+auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
} // namespace FlameMod
diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp
index ac24c647..ad48b7b6 100644
--- a/launcher/modplatform/flame/FlamePackIndex.cpp
+++ b/launcher/modplatform/flame/FlamePackIndex.cpp
@@ -6,7 +6,6 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireInteger(obj, "id");
pack.name = Json::requireString(obj, "name");
- pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", "");
pack.description = Json::ensureString(obj, "summary", "");
auto logo = Json::requireObject(obj, "logo");
@@ -46,6 +45,32 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
if (!found) {
throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name));
}
+
+ loadIndexedInfo(pack, obj);
+}
+
+void Flame::loadIndexedInfo(IndexedPack& pack, QJsonObject& obj)
+{
+ auto links_obj = Json::ensureObject(obj, "links");
+
+ pack.extra.websiteUrl = Json::ensureString(links_obj, "websiteUrl");
+ if(pack.extra.websiteUrl.endsWith('/'))
+ pack.extra.websiteUrl.chop(1);
+
+ pack.extra.issuesUrl = Json::ensureString(links_obj, "issuesUrl");
+ if(pack.extra.issuesUrl.endsWith('/'))
+ pack.extra.issuesUrl.chop(1);
+
+ pack.extra.sourceUrl = Json::ensureString(links_obj, "sourceUrl");
+ if(pack.extra.sourceUrl.endsWith('/'))
+ pack.extra.sourceUrl.chop(1);
+
+ pack.extra.wikiUrl = Json::ensureString(links_obj, "wikiUrl");
+ if(pack.extra.wikiUrl.endsWith('/'))
+ pack.extra.wikiUrl.chop(1);
+
+ pack.extraInfoLoaded = true;
+
}
void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
@@ -65,8 +90,12 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
// 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);
+ file.downloadUrl = Json::ensureString(version, "downloadUrl");
+
+ // only add if we have a download URL (third party distribution is enabled)
+ if (!file.downloadUrl.isEmpty()) {
+ unsortedVersions.append(file);
+ }
}
auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; };
diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h
index 7ffa29c3..1ca0fc0e 100644
--- a/launcher/modplatform/flame/FlamePackIndex.h
+++ b/launcher/modplatform/flame/FlamePackIndex.h
@@ -20,6 +20,13 @@ struct IndexedVersion {
QString downloadUrl;
};
+struct ModpackExtra {
+ QString websiteUrl;
+ QString wikiUrl;
+ QString issuesUrl;
+ QString sourceUrl;
+};
+
struct IndexedPack
{
int addonId;
@@ -28,13 +35,16 @@ struct IndexedPack
QList<ModpackAuthor> authors;
QString logoName;
QString logoUrl;
- QString websiteUrl;
bool versionsLoaded = false;
QVector<IndexedVersion> versions;
+
+ bool extraInfoLoaded = false;
+ ModpackExtra extra;
};
void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
+void loadIndexedInfo(IndexedPack&, QJsonObject&);
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
}
diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp
index e4f90c1a..12a4b990 100644
--- a/launcher/modplatform/flame/PackManifest.cpp
+++ b/launcher/modplatform/flame/PackManifest.cpp
@@ -41,7 +41,7 @@ static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest)
auto obj = Json::requireObject(item);
Flame::File file;
loadFileV1(file, obj);
- m.files.append(file);
+ m.files.insert(file.fileId,file);
}
m.overrides = Json::ensureString(manifest, "overrides", "overrides");
}
@@ -61,21 +61,9 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath)
loadManifestV1(m, obj);
}
-bool Flame::File::parseFromBytes(const QByteArray& bytes)
+bool Flame::File::parseFromObject(const QJsonObject& obj)
{
- 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");
-
fileName = Json::requireString(obj, "fileName");
-
- 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
type = File::Type::SingleFile;
@@ -87,6 +75,25 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
// this is probably a mod, dunno what else could modpacks download
targetFolder = "mods";
}
+ // get the hash
+ hash = QString();
+ auto hashes = Json::ensureArray(obj, "hashes");
+ for(QJsonValueRef item : hashes) {
+ auto hobj = Json::requireObject(item);
+ auto algo = Json::requireInteger(hobj, "algo");
+ auto value = Json::requireString(hobj, "value");
+ if (algo == 1) {
+ hash = value;
+ }
+ }
+
+
+ // may throw, if the project is blocked
+ QString rawUrl = Json::ensureString(obj, "downloadUrl");
+ url = QUrl(rawUrl, QUrl::TolerantMode);
+ if (!url.isValid()) {
+ throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
+ }
resolved = true;
return true;
diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h
index 02f39f0e..677db1c3 100644
--- a/launcher/modplatform/flame/PackManifest.h
+++ b/launcher/modplatform/flame/PackManifest.h
@@ -1,26 +1,66 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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
+ *
+ * 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 <QString>
#include <QVector>
+#include <QMap>
#include <QUrl>
+#include <QJsonObject>
namespace Flame
{
struct File
{
// NOTE: throws JSONValidationError
- bool parseFromBytes(const QByteArray &bytes);
+ bool parseFromObject(const QJsonObject& object);
int projectId = 0;
int fileId = 0;
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
bool required = true;
+ QString hash;
+ // NOTE: only set on blocked files ! Empty otherwise.
+ QString websiteUrl;
// our
bool resolved = false;
QString fileName;
QUrl url;
- QString targetFolder = QLatin1Literal("mods");
+ QString targetFolder = QStringLiteral("mods");
enum class Type
{
Unknown,
@@ -54,7 +94,8 @@ struct Manifest
QString name;
QString version;
QString author;
- QVector<Flame::File> files;
+ //File id -> File
+ QMap<int,Flame::File> files;
QString overrides;
};
diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp
index 6829b837..d7abd10f 100644
--- a/launcher/modplatform/helpers/NetworkModAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkModAPI.cpp
@@ -31,6 +31,31 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
netJob->start();
}
+void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack)
+{
+ auto id_str = pack.addonId.toString();
+ auto netJob = new NetJob(QString("%1::ModInfo").arg(id_str), APPLICATION->network());
+ auto searchUrl = getModInfoURL(id_str);
+
+ auto response = new QByteArray();
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+
+ QObject::connect(netJob, &NetJob::succeeded, [response, &pack, caller] {
+ QJsonParseError parse_error{};
+ auto doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response for " << pack.name << " at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+
+ caller->infoRequestFinished(doc, pack);
+ });
+
+ netJob->start();
+}
+
void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const
{
auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network());
diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h
index 000620b2..87d77ad1 100644
--- a/launcher/modplatform/helpers/NetworkModAPI.h
+++ b/launcher/modplatform/helpers/NetworkModAPI.h
@@ -5,9 +5,11 @@
class NetworkModAPI : public ModAPI {
public:
void searchMods(CallerType* caller, SearchArgs&& args) const override;
+ void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override;
void getVersions(CallerType* caller, VersionSearchArgs&& args) const override;
protected:
virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0;
+ virtual auto getModInfoURL(QString& id) const -> QString = 0;
virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0;
};
diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
index 961fe868..4da6a866 100644
--- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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
+ *
+ * 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 "PackFetchTask.h"
#include "PrivatePackManager.h"
@@ -103,7 +138,7 @@ bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, Modpac
if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol))
{
- auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol);
+ auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:%3!").arg(errorMsg).arg(errorLine).arg(errorCol);
qWarning() << fullErrMsg;
data.clear();
return false;
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index c63a9f1e..83e14969 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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
+ *
+ * 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 "PackInstallTask.h"
#include <QtConcurrent>
@@ -88,7 +123,11 @@ void PackInstallTask::unzip()
return;
}
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip");
+#else
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
+#endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
m_extractFutureWatcher.setFuture(m_extractFuture);
diff --git a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
index 501e6003..1a81f026 100644
--- a/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
+++ b/launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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
+ *
+ * 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 "PrivatePackManager.h"
#include <QDebug>
@@ -10,7 +45,13 @@ void PrivatePackManager::load()
{
try
{
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto foo = QString::fromUtf8(FS::read(m_filename)).split('\n', Qt::SkipEmptyParts);
+ currentPacks = QSet<QString>(foo.begin(), foo.end());
+#else
currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet();
+#endif
+
dirty = false;
}
catch(...)
@@ -28,7 +69,7 @@ void PrivatePackManager::save() const
}
try
{
- QStringList list = currentPacks.toList();
+ QStringList list = currentPacks.values();
FS::write(m_filename, list.join('\n').toUtf8());
dirty = false;
}
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index 33df6fa4..cac432cd 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -1,18 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
- * 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
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * 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.
+ * 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright 2020-2021 Petr Mrazek <peterix@gmail.com>
+ *
+ * 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 "FTBPackInstallTask.h"
@@ -80,7 +99,7 @@ void PackInstallTask::onDownloadSucceeded()
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
@@ -197,10 +216,10 @@ void PackInstallTask::install()
if(target.type != "modloader") continue;
if(target.name == "forge") {
- components->setComponentVersion("net.minecraftforge", target.version, true);
+ components->setComponentVersion("net.minecraftforge", target.version);
}
else if(target.name == "fabric") {
- components->setComponentVersion("net.fabricmc.fabric-loader", target.version, true);
+ components->setComponentVersion("net.fabricmc.fabric-loader", target.version);
}
}
@@ -220,6 +239,7 @@ void PackInstallTask::install()
instance.setName(m_instName);
instance.setIconKey(m_instIcon);
+ instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name);
instanceSettings->resumeSave();
emitSucceeded();
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 6d642b5e..89e52d6c 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -20,6 +20,7 @@
#include "BuildConfig.h"
#include "modplatform/ModAPI.h"
+#include "modplatform/ModIndex.h"
#include "modplatform/helpers/NetworkModAPI.h"
#include <QDebug>
@@ -28,30 +29,25 @@ class ModrinthAPI : public NetworkModAPI {
public:
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
- static auto getModLoaderStrings(ModLoaderType type) -> const QStringList
+ static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
- switch (type)
+ for (auto loader : {Forge, Fabric, Quilt})
{
- case Unspecified:
- for (auto loader : {Forge, Fabric, Quilt})
- {
- l << ModAPI::getModLoaderString(loader);
- }
- break;
-
- case Quilt:
- l << ModAPI::getModLoaderString(Fabric);
- default:
- l << ModAPI::getModLoaderString(type);
+ if ((types & loader) || types == Unspecified)
+ {
+ l << ModAPI::getModLoaderString(loader);
+ }
}
+ if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
+ l << ModAPI::getModLoaderString(Fabric);
return l;
}
- static auto getModLoaderFilters(ModLoaderType type) -> const QString
+ static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
{
QStringList l;
- for (auto loader : getModLoaderStrings(type))
+ for (auto loader : getModLoaderStrings(types))
{
l << QString("\"categories:%1\"").arg(loader);
}
@@ -61,7 +57,7 @@ class ModrinthAPI : public NetworkModAPI {
private:
inline auto getModSearchURL(SearchArgs& args) const -> QString override
{
- if (!validateModLoader(args.mod_loader)) {
+ if (!validateModLoaders(args.loaders)) {
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
return "";
}
@@ -76,19 +72,24 @@ class ModrinthAPI : public NetworkModAPI {
.arg(args.offset)
.arg(args.search)
.arg(args.sorting)
- .arg(getModLoaderFilters(args.mod_loader))
+ .arg(getModLoaderFilters(args.loaders))
.arg(getGameVersionsArray(args.versions));
};
+ inline auto getModInfoURL(QString& id) const -> QString override
+ {
+ return BuildConfig.MODRINTH_PROD_URL + "/project/" + id;
+ };
+
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{
return QString(BuildConfig.MODRINTH_PROD_URL +
"/project/%1/version?"
- "game_versions=[%2]"
+ "game_versions=[%2]&"
"loaders=[\"%3\"]")
- .arg(args.addonId)
- .arg(getGameVersionsString(args.mcVersions))
- .arg(getModLoaderStrings(args.loader).join("\",\""));
+ .arg(args.addonId,
+ getGameVersionsString(args.mcVersions),
+ getModLoaderStrings(args.loaders).join("\",\""));
};
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
@@ -101,9 +102,9 @@ class ModrinthAPI : public NetworkModAPI {
return s.isEmpty() ? QString() : QString("[%1],").arg(s);
}
- inline auto validateModLoader(ModLoaderType modLoader) const -> bool
+ inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
{
- return modLoader == Unspecified || modLoader == Forge || modLoader == Fabric || modLoader == Quilt;
+ return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt));
}
};
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index f7fa9864..b6f5490a 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -1,19 +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/>.
- */
+* 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/>.
+*/
#include "ModrinthPackIndex.h"
#include "ModrinthAPI.h"
@@ -24,10 +25,12 @@
#include "net/NetJob.h"
static ModrinthAPI api;
+static ModPlatform::ProviderCapabilities ProviderCaps;
void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
{
pack.addonId = Json::requireString(obj, "project_id");
+ pack.provider = ModPlatform::Provider::MODRINTH;
pack.name = Json::requireString(obj, "title");
QString slug = Json::ensureString(obj, "slug", "");
@@ -45,6 +48,43 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
modAuthor.name = Json::requireString(obj, "author");
modAuthor.url = api.getAuthorURL(modAuthor.name);
pack.authors.append(modAuthor);
+
+ // Modrinth can have more data than what's provided by the basic search :)
+ pack.extraDataLoaded = false;
+}
+
+void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
+{
+ pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url");
+ if(pack.extraData.issuesUrl.endsWith('/'))
+ pack.extraData.issuesUrl.chop(1);
+
+ pack.extraData.sourceUrl = Json::ensureString(obj, "source_url");
+ if(pack.extraData.sourceUrl.endsWith('/'))
+ pack.extraData.sourceUrl.chop(1);
+
+ pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url");
+ if(pack.extraData.wikiUrl.endsWith('/'))
+ pack.extraData.wikiUrl.chop(1);
+
+ pack.extraData.discordUrl = Json::ensureString(obj, "discord_url");
+ if(pack.extraData.discordUrl.endsWith('/'))
+ pack.extraData.discordUrl.chop(1);
+
+ auto donate_arr = Json::ensureArray(obj, "donation_urls");
+ for(auto d : donate_arr){
+ auto d_obj = Json::requireObject(d);
+
+ ModPlatform::DonationData donate;
+
+ donate.id = Json::ensureString(d_obj, "id");
+ donate.platform = Json::ensureString(d_obj, "platform");
+ donate.url = Json::ensureString(d_obj, "url");
+
+ pack.extraData.donate.append(donate);
+ }
+
+ pack.extraDataLoaded = true;
}
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
@@ -57,46 +97,10 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
- ModPlatform::IndexedVersion file;
- file.addonId = Json::requireString(obj, "project_id");
- file.fileId = Json::requireString(obj, "id");
- file.date = Json::requireString(obj, "date_published");
- auto versionArray = Json::requireArray(obj, "game_versions");
- if (versionArray.empty()) { continue; }
- for (auto mcVer : versionArray) {
- file.mcVersion.append(mcVer.toString());
- }
- auto loaders = Json::requireArray(obj, "loaders");
- for (auto loader : loaders) {
- file.loaders.append(loader.toString());
- }
- file.version = Json::requireString(obj, "name");
-
- auto files = Json::requireArray(obj, "files");
- int i = 0;
-
- // Find correct file (needed in cases where one version may have multiple files)
- // Will default to the last one if there's no primary (though I think Modrinth requires that
- // at least one file is primary, idk)
- // NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed
- while (i < files.count() - 1){
- auto parent = files[i].toObject();
- auto fileName = Json::requireString(parent, "filename");
-
- // Grab the primary file, if available
- if(Json::requireBoolean(parent, "primary"))
- break;
-
- i++;
- }
-
- auto parent = files[i].toObject();
- if (parent.contains("url")) {
- file.downloadUrl = Json::requireString(parent, "url");
- file.fileName = Json::requireString(parent, "filename");
+ auto file = loadIndexedPackVersion(obj);
+ if(file.fileId.isValid()) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
- }
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
// dates are in RFC 3339 format
@@ -106,3 +110,61 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
pack.versions = unsortedVersions;
pack.versionsLoaded = true;
}
+
+auto Modrinth::loadIndexedPackVersion(QJsonObject &obj) -> ModPlatform::IndexedVersion
+{
+ ModPlatform::IndexedVersion file;
+
+ file.addonId = Json::requireString(obj, "project_id");
+ file.fileId = Json::requireString(obj, "id");
+ file.date = Json::requireString(obj, "date_published");
+ auto versionArray = Json::requireArray(obj, "game_versions");
+ if (versionArray.empty()) {
+ return {};
+ }
+ for (auto mcVer : versionArray) {
+ file.mcVersion.append(mcVer.toString());
+ }
+ auto loaders = Json::requireArray(obj, "loaders");
+ for (auto loader : loaders) {
+ file.loaders.append(loader.toString());
+ }
+ file.version = Json::requireString(obj, "name");
+
+ auto files = Json::requireArray(obj, "files");
+ int i = 0;
+
+ // Find correct file (needed in cases where one version may have multiple files)
+ // Will default to the last one if there's no primary (though I think Modrinth requires that
+ // at least one file is primary, idk)
+ // NOTE: files.count() is 1-indexed, so we need to subtract 1 to become 0-indexed
+ while (i < files.count() - 1) {
+ auto parent = files[i].toObject();
+ auto fileName = Json::requireString(parent, "filename");
+
+ // Grab the primary file, if available
+ if (Json::requireBoolean(parent, "primary"))
+ break;
+
+ i++;
+ }
+
+ auto parent = files[i].toObject();
+ if (parent.contains("url")) {
+ file.downloadUrl = Json::requireString(parent, "url");
+ file.fileName = Json::requireString(parent, "filename");
+ auto hash_list = Json::requireObject(parent, "hashes");
+ auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH);
+ for (auto& hash_type : hash_types) {
+ if (hash_list.contains(hash_type)) {
+ file.hash = Json::requireString(hash_list, hash_type);
+ file.hash_type = hash_type;
+ break;
+ }
+ }
+
+ return file;
+ }
+
+ return {};
+}
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index 7f306f25..b7936204 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -25,9 +25,11 @@
namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
+void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
BaseInstance* inst);
+auto loadIndexedPackVersion(QJsonObject& obj) -> ModPlatform::IndexedVersion;
} // namespace Modrinth
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
index f1ad39ce..a4620df9 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
@@ -42,6 +42,8 @@
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
+#include <QSet>
+
static ModrinthAPI api;
namespace Modrinth {
@@ -62,8 +64,35 @@ 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.issuesUrl = Json::ensureString(obj, "issues_url");
+ if(pack.extra.issuesUrl.endsWith('/'))
+ pack.extra.issuesUrl.chop(1);
+
pack.extra.sourceUrl = Json::ensureString(obj, "source_url");
+ if(pack.extra.sourceUrl.endsWith('/'))
+ pack.extra.sourceUrl.chop(1);
+
pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url");
+ if(pack.extra.wikiUrl.endsWith('/'))
+ pack.extra.wikiUrl.chop(1);
+
+ pack.extra.discordUrl = Json::ensureString(obj, "discord_url");
+ if(pack.extra.discordUrl.endsWith('/'))
+ pack.extra.discordUrl.chop(1);
+
+ auto donate_arr = Json::ensureArray(obj, "donation_urls");
+ for(auto d : donate_arr){
+ auto d_obj = Json::requireObject(d);
+
+ DonationData donate;
+
+ donate.id = Json::ensureString(d_obj, "id");
+ donate.platform = Json::ensureString(d_obj, "platform");
+ donate.url = Json::ensureString(d_obj, "url");
+
+ pack.extra.donate.append(donate);
+ }
pack.extraInfoLoaded = true;
}
@@ -93,23 +122,6 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
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;
@@ -139,9 +151,6 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
auto url = Json::requireString(parent, "url");
- if(!validateDownloadUrl(url))
- continue;
-
file.download_url = url;
if(is_primary)
break;
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h
index e5fc9a70..035dc62e 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.h
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h
@@ -40,6 +40,7 @@
#include <QByteArray>
#include <QCryptographicHash>
+#include <QQueue>
#include <QString>
#include <QUrl>
#include <QVector>
@@ -48,22 +49,32 @@ class MinecraftInstance;
namespace Modrinth {
-struct File
-{
+struct File {
QString path;
QCryptographicHash::Algorithm hashAlgorithm;
QByteArray hash;
- // TODO: should this support multiple download URLs, like the JSON does?
- QUrl download;
+ QQueue<QUrl> downloads;
+};
+
+struct DonationData {
+ QString id;
+ QString platform;
+ QString url;
};
struct ModpackExtra {
QString body;
QString projectUrl;
+
+ QString issuesUrl;
QString sourceUrl;
QString wikiUrl;
+ QString discordUrl;
+
+ QList<DonationData> donate;
+
};
struct ModpackVersion {
diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp
new file mode 100644
index 00000000..0782b9f4
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz.cpp
@@ -0,0 +1,289 @@
+// 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/>.
+*/
+
+#include "Packwiz.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QObject>
+
+#include "toml.h"
+#include "FileSystem.h"
+
+#include "minecraft/mod/Mod.h"
+#include "modplatform/ModIndex.h"
+
+namespace Packwiz {
+
+auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
+{
+ QFile index_file(index_dir.absoluteFilePath(normalized_fname));
+
+ QString real_fname = normalized_fname;
+ if (!index_file.exists()) {
+ // Tries to get similar entries
+ for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
+ if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) {
+ real_fname = file_name;
+ break;
+ }
+ }
+
+ if(should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)){
+ qCritical() << "Could not find a match for a valid metadata file!";
+ qCritical() << "File: " << normalized_fname;
+ return {};
+ }
+ }
+
+ return real_fname;
+}
+
+// Helpers
+static inline auto indexFileName(QString const& mod_name) -> QString
+{
+ if(mod_name.endsWith(".pw.toml"))
+ return mod_name;
+ return QString("%1.pw.toml").arg(mod_name);
+}
+
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
+// Helper functions for extracting data from the TOML file
+auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString
+{
+ toml_datum_t var = toml_string_in(parent, entry_name);
+ if (!var.ok) {
+ qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name);
+ return {};
+ }
+
+ QString tmp = var.u.s;
+ free(var.u.s);
+
+ return tmp;
+}
+
+auto intEntry(toml_table_t* parent, const char* entry_name) -> int
+{
+ toml_datum_t var = toml_int_in(parent, entry_name);
+ if (!var.ok) {
+ qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name);
+ return {};
+ }
+
+ return var.u.i;
+}
+
+
+auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod
+{
+ Mod mod;
+
+ mod.name = mod_pack.name;
+ mod.filename = mod_version.fileName;
+
+ if(mod_pack.provider == ModPlatform::Provider::FLAME){
+ mod.mode = "metadata:curseforge";
+ }
+ else {
+ mod.mode = "url";
+ mod.url = mod_version.downloadUrl;
+ }
+
+ mod.hash_format = mod_version.hash_type;
+ mod.hash = mod_version.hash;
+
+ mod.provider = mod_pack.provider;
+ mod.file_id = mod_version.fileId;
+ mod.project_id = mod_pack.addonId;
+
+ return mod;
+}
+
+auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod
+{
+ auto mod_name = internal_mod.name();
+
+ // Try getting metadata if it exists
+ Mod mod { getIndexForMod(index_dir, mod_name) };
+ if(mod.isValid())
+ return mod;
+
+ qWarning() << QString("Tried to create mod metadata with a Mod without metadata!");
+
+ return {};
+}
+
+void V1::updateModIndex(QDir& index_dir, Mod& mod)
+{
+ if(!mod.isValid()){
+ qCritical() << QString("Tried to update metadata of an invalid mod!");
+ return;
+ }
+
+ // Ensure the corresponding mod's info exists, and create it if not
+
+ auto normalized_fname = indexFileName(mod.name);
+ auto real_fname = getRealIndexName(index_dir, normalized_fname);
+
+ QFile index_file(index_dir.absoluteFilePath(real_fname));
+
+ // There's already data on there!
+ // TODO: We should do more stuff here, as the user is likely trying to
+ // override a file. In this case, check versions and ask the user what
+ // they want to do!
+ if (index_file.exists()) { index_file.remove(); }
+
+ if (!index_file.open(QIODevice::ReadWrite)) {
+ qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
+ return;
+ }
+
+ // Put TOML data into the file
+ QTextStream in_stream(&index_file);
+ auto addToStream = [&in_stream](QString&& key, QString value) { in_stream << QString("%1 = \"%2\"\n").arg(key, value); };
+
+ {
+ addToStream("name", mod.name);
+ addToStream("filename", mod.filename);
+ addToStream("side", mod.side);
+
+ in_stream << QString("\n[download]\n");
+ addToStream("mode", mod.mode);
+ addToStream("url", mod.url.toString());
+ addToStream("hash-format", mod.hash_format);
+ addToStream("hash", mod.hash);
+
+ in_stream << QString("\n[update]\n");
+ in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
+ switch(mod.provider){
+ case(ModPlatform::Provider::FLAME):
+ in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
+ in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
+ break;
+ case(ModPlatform::Provider::MODRINTH):
+ addToStream("mod-id", mod.mod_id().toString());
+ addToStream("version", mod.version().toString());
+ break;
+ }
+ }
+
+ index_file.close();
+}
+
+void V1::deleteModIndex(QDir& index_dir, QString& mod_name)
+{
+ auto normalized_fname = indexFileName(mod_name);
+ auto real_fname = getRealIndexName(index_dir, normalized_fname);
+ if (real_fname.isEmpty())
+ return;
+
+ QFile index_file(index_dir.absoluteFilePath(real_fname));
+
+ if(!index_file.exists()){
+ qWarning() << QString("Tried to delete non-existent mod metadata for %1!").arg(mod_name);
+ return;
+ }
+
+ if(!index_file.remove()){
+ qWarning() << QString("Failed to remove metadata for mod %1!").arg(mod_name);
+ }
+}
+
+auto V1::getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod
+{
+ Mod mod;
+
+ auto normalized_fname = indexFileName(index_file_name);
+ auto real_fname = getRealIndexName(index_dir, normalized_fname, true);
+ if (real_fname.isEmpty())
+ return {};
+
+ QFile index_file(index_dir.absoluteFilePath(real_fname));
+
+ if (!index_file.open(QIODevice::ReadOnly)) {
+ qWarning() << QString("Failed to open mod metadata for %1").arg(index_file_name);
+ return {};
+ }
+
+ toml_table_t* table = nullptr;
+
+ // NOLINTNEXTLINE(modernize-avoid-c-arrays)
+ char errbuf[200];
+ auto file_bytearray = index_file.readAll();
+ table = toml_parse(file_bytearray.data(), errbuf, sizeof(errbuf));
+
+ index_file.close();
+
+ if (!table) {
+ qWarning() << QString("Could not open file %1!").arg(indexFileName(index_file_name));
+ qWarning() << "Reason: " << QString(errbuf);
+ return {};
+ }
+
+ { // Basic info
+ mod.name = stringEntry(table, "name");
+ mod.filename = stringEntry(table, "filename");
+ mod.side = stringEntry(table, "side");
+ }
+
+ { // [download] info
+ toml_table_t* download_table = toml_table_in(table, "download");
+ if (!download_table) {
+ qCritical() << QString("No [download] section found on mod metadata!");
+ return {};
+ }
+
+ mod.mode = stringEntry(download_table, "mode");
+ mod.url = stringEntry(download_table, "url");
+ mod.hash_format = stringEntry(download_table, "hash-format");
+ mod.hash = stringEntry(download_table, "hash");
+ }
+
+ { // [update] info
+ using Provider = ModPlatform::Provider;
+
+ toml_table_t* update_table = toml_table_in(table, "update");
+ if (!update_table) {
+ qCritical() << QString("No [update] section found on mod metadata!");
+ return {};
+ }
+
+ toml_table_t* mod_provider_table = nullptr;
+ if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::FLAME)))) {
+ mod.provider = Provider::FLAME;
+ mod.file_id = intEntry(mod_provider_table, "file-id");
+ mod.project_id = intEntry(mod_provider_table, "project-id");
+ } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::MODRINTH)))) {
+ mod.provider = Provider::MODRINTH;
+ mod.mod_id() = stringEntry(mod_provider_table, "mod-id");
+ mod.version() = stringEntry(mod_provider_table, "version");
+ } else {
+ qCritical() << QString("No mod provider on mod metadata!");
+ return {};
+ }
+
+ }
+
+ toml_free(table);
+
+ return mod;
+}
+
+} // namespace Packwiz
diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h
new file mode 100644
index 00000000..3c99769c
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz.h
@@ -0,0 +1,93 @@
+// 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 "modplatform/ModIndex.h"
+
+#include <QString>
+#include <QUrl>
+#include <QVariant>
+
+struct toml_table_t;
+class QDir;
+
+// Mod from launcher/minecraft/mod/Mod.h
+class Mod;
+
+namespace Packwiz {
+
+auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
+
+auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString;
+auto intEntry(toml_table_t* parent, const char* entry_name) -> int;
+
+class V1 {
+ public:
+ struct Mod {
+ QString name {};
+ QString filename {};
+ // FIXME: make side an enum
+ QString side {"both"};
+
+ // [download]
+ QString mode {};
+ QUrl url {};
+ QString hash_format {};
+ QString hash {};
+
+ // [update]
+ ModPlatform::Provider provider {};
+ QVariant file_id {};
+ QVariant project_id {};
+
+ public:
+ // This is a totally heuristic, but should work for now.
+ auto isValid() const -> bool { return !name.isEmpty() && !project_id.isNull(); }
+
+ // Different providers can use different names for the same thing
+ // Modrinth-specific
+ auto mod_id() -> QVariant& { return project_id; }
+ auto version() -> QVariant& { return file_id; }
+ };
+
+ /* Generates the object representing the information in a mod.pw.toml file via
+ * its common representation in the launcher, when downloading mods.
+ * */
+ static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
+ /* Generates the object representing the information in a mod.pw.toml file via
+ * its common representation in the launcher.
+ * */
+ static auto createModFormat(QDir& index_dir, ::Mod& internal_mod) -> Mod;
+
+ /* Updates the mod index for the provided mod.
+ * This creates a new index if one does not exist already
+ * TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one.
+ * */
+ static void updateModIndex(QDir& index_dir, Mod& mod);
+
+ /* Deletes the metadata for the mod with the given name. If the metadata doesn't exist, it does nothing. */
+ static void deleteModIndex(QDir& index_dir, QString& mod_name);
+
+ /* Gets the metadata for a mod with a particular name.
+ * If the mod doesn't have a metadata, it simply returns an empty Mod object.
+ * */
+ static auto getIndexForMod(QDir& index_dir, QString& index_file_name) -> Mod;
+};
+
+} // namespace Packwiz
diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/launcher/modplatform/packwiz/Packwiz_test.cpp
new file mode 100644
index 00000000..d6251148
--- /dev/null
+++ b/launcher/modplatform/packwiz/Packwiz_test.cpp
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * 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 <QTemporaryDir>
+#include <QTest>
+
+#include "Packwiz.h"
+
+class PackwizTest : public QObject {
+ Q_OBJECT
+
+ private slots:
+ // Files taken from https://github.com/packwiz/packwiz-example-pack
+ void loadFromFile_Modrinth()
+ {
+ QString source = QFINDTESTDATA("testdata");
+
+ QDir index_dir(source);
+ QString name_mod("borderless-mining.pw.toml");
+ QVERIFY(index_dir.entryList().contains(name_mod));
+
+ auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);
+
+ QVERIFY(metadata.isValid());
+
+ QCOMPARE(metadata.name, "Borderless Mining");
+ QCOMPARE(metadata.filename, "borderless-mining-1.1.1+1.18.jar");
+ QCOMPARE(metadata.side, "client");
+
+ QCOMPARE(metadata.url, QUrl("https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar"));
+ QCOMPARE(metadata.hash_format, "sha512");
+ QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d");
+
+ QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH);
+ QCOMPARE(metadata.version(), "ug2qKTPR");
+ QCOMPARE(metadata.mod_id(), "kYq5qkSL");
+ }
+
+ void loadFromFile_Curseforge()
+ {
+ QString source = QFINDTESTDATA("testdata");
+
+ QDir index_dir(source);
+ QString name_mod("screenshot-to-clipboard-fabric.pw.toml");
+ QVERIFY(index_dir.entryList().contains(name_mod));
+
+ // Try without the .pw.toml at the end
+ name_mod.chop(8);
+
+ auto metadata = Packwiz::V1::getIndexForMod(index_dir, name_mod);
+
+ QVERIFY(metadata.isValid());
+
+ QCOMPARE(metadata.name, "Screenshot to Clipboard (Fabric)");
+ QCOMPARE(metadata.filename, "screenshot-to-clipboard-1.0.7-fabric.jar");
+ QCOMPARE(metadata.side, "both");
+
+ QCOMPARE(metadata.url, QUrl("https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"));
+ QCOMPARE(metadata.hash_format, "murmur2");
+ QCOMPARE(metadata.hash, "1781245820");
+
+ QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME);
+ QCOMPARE(metadata.file_id, 3509043);
+ QCOMPARE(metadata.project_id, 327154);
+ }
+};
+
+QTEST_GUILESS_MAIN(PackwizTest)
+
+#include "Packwiz_test.moc"
diff --git a/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml b/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml
new file mode 100644
index 00000000..16545fd4
--- /dev/null
+++ b/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml
@@ -0,0 +1,13 @@
+name = "Borderless Mining"
+filename = "borderless-mining-1.1.1+1.18.jar"
+side = "client"
+
+[download]
+url = "https://cdn.modrinth.com/data/kYq5qkSL/versions/1.1.1+1.18/borderless-mining-1.1.1+1.18.jar"
+hash-format = "sha512"
+hash = "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"
+
+[update]
+[update.modrinth]
+mod-id = "kYq5qkSL"
+version = "ug2qKTPR"
diff --git a/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml
new file mode 100644
index 00000000..87d70ada
--- /dev/null
+++ b/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml
@@ -0,0 +1,13 @@
+name = "Screenshot to Clipboard (Fabric)"
+filename = "screenshot-to-clipboard-1.0.7-fabric.jar"
+side = "both"
+
+[download]
+url = "https://edge.forgecdn.net/files/3509/43/screenshot-to-clipboard-1.0.7-fabric.jar"
+hash-format = "murmur2"
+hash = "1781245820"
+
+[update]
+[update.curseforge]
+file-id = 3509043
+project-id = 327154
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");
}
diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp
index 782fb9b2..95feb4b2 100644
--- a/launcher/modplatform/technic/TechnicPackProcessor.cpp
+++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp
@@ -185,13 +185,22 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
}
}
- else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
+ else
{
- components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
- }
- else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
- {
- components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
+ // <Technic library name prefix> -> <our component name>
+ static QMap<QString, QString> loaderMap {
+ {"net.minecraftforge:minecraftforge:", "net.minecraftforge"},
+ {"net.fabricmc:fabric-loader:", "net.fabricmc.fabric-loader"},
+ {"org.quiltmc:quilt-loader:", "org.quiltmc.quilt-loader"}
+ };
+ for (const auto& loader : loaderMap.keys())
+ {
+ if (libraryName.startsWith(loader))
+ {
+ components->setComponentVersion(loaderMap.value(loader), libraryName.section(':', 2));
+ break;
+ }
+ }
}
}
}