aboutsummaryrefslogtreecommitdiff
path: root/launcher/modplatform
diff options
context:
space:
mode:
authorflow <flowlnlnln@gmail.com>2022-07-07 21:10:41 -0300
committerflow <flowlnlnln@gmail.com>2022-09-20 18:32:36 -0300
commit4441b373385f9b7f77deed2a27751337951f38f6 (patch)
treec134c3ce56312b3fdda9c37ee5f5eb3db460e1d5 /launcher/modplatform
parent941d75824af8d8e3deb1dfc0597c9493911f0cf0 (diff)
downloadPrismLauncher-4441b373385f9b7f77deed2a27751337951f38f6.tar.gz
PrismLauncher-4441b373385f9b7f77deed2a27751337951f38f6.tar.bz2
PrismLauncher-4441b373385f9b7f77deed2a27751337951f38f6.zip
refactor: move modrinth modpack import to separate file
Signed-off-by: flow <flowlnlnln@gmail.com>
Diffstat (limited to 'launcher/modplatform')
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp225
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h39
2 files changed, 264 insertions, 0 deletions
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
new file mode 100644
index 00000000..7eb6cc8f
--- /dev/null
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
@@ -0,0 +1,225 @@
+#include "ModrinthInstanceCreationTask.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "Json.h"
+
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+
+#include "net/NetJob.h"
+#include "net/ChecksumValidator.h"
+
+#include "settings/INISettingsObject.h"
+
+#include "ui/dialogs/CustomMessageBox.h"
+
+bool ModrinthCreationTask::createInstance()
+{
+ QEventLoop loop;
+
+ if (m_files.empty() && !parseManifest())
+ return false;
+
+ auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
+
+ auto override_path = FS::PathCombine(m_stagingPath, "overrides");
+ if (QFile::exists(override_path)) {
+ if (!QFile::rename(override_path, mcPath)) {
+ setError(tr("Could not rename the overrides folder:\n") + "overrides");
+ return false;
+ }
+ }
+
+ // Do client overrides
+ auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
+ if (QFile::exists(client_override_path)) {
+ if (!FS::overrideFolder(mcPath, client_override_path)) {
+ setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
+ return false;
+ }
+ }
+
+ QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
+ MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
+ auto components = instance.getPackProfile();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", minecraftVersion, true);
+
+ if (!fabricVersion.isEmpty())
+ components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
+ if (!quiltVersion.isEmpty())
+ components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion);
+ if (!forgeVersion.isEmpty())
+ components->setComponentVersion("net.minecraftforge", forgeVersion);
+ if (m_instIcon != "default") {
+ instance.setIconKey(m_instIcon);
+ } else {
+ instance.setIconKey("modrinth");
+ }
+ instance.setName(m_instName);
+ instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {});
+ instance.saveNow();
+
+ m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
+
+ for (auto file : m_files) {
+ auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
+ qDebug() << "Will try to download" << file.downloads.front() << "to" << path;
+ auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
+ dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
+ m_files_job->addNetAction(dl);
+
+ if (file.downloads.size() > 0) {
+ // FIXME: This really needs to be put into a ConcurrentTask of
+ // MultipleOptionsTask's , once those exist :)
+ connect(dl.get(), &NetAction::failed, [this, &file, path, dl] {
+ auto dl = Net::Download::makeFile(file.downloads.dequeue(), path);
+ dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
+ m_files_job->addNetAction(dl);
+ dl->succeeded();
+ });
+ }
+ }
+
+ bool ended_well = false;
+
+ connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { ended_well = true; });
+ connect(m_files_job.get(), &NetJob::failed, [&](const QString& reason) {
+ ended_well = false;
+ setError(reason);
+ });
+ connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
+ connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); });
+
+ setStatus(tr("Downloading mods..."));
+ m_files_job->start();
+
+ loop.exec();
+
+ return ended_well;
+}
+
+bool ModrinthCreationTask::parseManifest()
+{
+ try {
+ QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
+ auto doc = Json::requireDocument(indexPath);
+ auto obj = Json::requireObject(doc, "modrinth.index.json");
+ int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
+ if (formatVersion == 1) {
+ auto game = Json::requireString(obj, "game", "modrinth.index.json");
+ if (game != "minecraft") {
+ throw JSONValidationError("Unknown game: " + game);
+ }
+
+ m_managed_version_id = Json::ensureString(obj, "versionId", "Managed ID");
+ m_managed_name = Json::ensureString(obj, "name", "Managed Name");
+
+ auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
+ bool had_optional = false;
+ for (auto modInfo : jsonFiles) {
+ Modrinth::File file;
+ file.path = Json::requireString(modInfo, "path");
+
+ auto env = Json::ensureObject(modInfo, "env");
+ // 'env' field is optional
+ if (!env.isEmpty()) {
+ QString support = Json::ensureString(env, "client", "unsupported");
+ if (support == "unsupported") {
+ continue;
+ } else if (support == "optional") {
+ // TODO: Make a review dialog for choosing which ones the user wants!
+ if (!had_optional) {
+ had_optional = true;
+ auto info = CustomMessageBox::selectable(
+ m_parent, tr("Optional mod detected!"),
+ tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
+ QMessageBox::Information);
+ info->exec();
+ }
+
+ if (file.path.endsWith(".jar"))
+ file.path += ".disabled";
+ }
+ }
+
+ QJsonObject hashes = Json::requireObject(modInfo, "hashes");
+ QString hash;
+ QCryptographicHash::Algorithm hashAlgorithm;
+ hash = Json::ensureString(hashes, "sha1");
+ hashAlgorithm = QCryptographicHash::Sha1;
+ if (hash.isEmpty()) {
+ hash = Json::ensureString(hashes, "sha512");
+ hashAlgorithm = QCryptographicHash::Sha512;
+ if (hash.isEmpty()) {
+ hash = Json::ensureString(hashes, "sha256");
+ hashAlgorithm = QCryptographicHash::Sha256;
+ if (hash.isEmpty()) {
+ throw JSONValidationError("No hash found for: " + file.path);
+ }
+ }
+ }
+ file.hash = QByteArray::fromHex(hash.toLatin1());
+ file.hashAlgorithm = hashAlgorithm;
+
+ // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
+ // (as Modrinth seems to incorrectly handle spaces)
+
+ auto download_arr = Json::ensureArray(modInfo, "downloads");
+ for (auto download : download_arr) {
+ qWarning() << download.toString();
+ bool is_last = download.toString() == download_arr.last().toString();
+
+ auto download_url = QUrl(download.toString());
+
+ if (!download_url.isValid()) {
+ qDebug()
+ << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path);
+ if (is_last && file.downloads.isEmpty())
+ throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
+ } else {
+ file.downloads.push_back(download_url);
+ }
+ }
+
+ m_files.push_back(file);
+ }
+
+ auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
+ for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
+ QString name = it.key();
+ if (name == "minecraft") {
+ minecraftVersion = Json::requireString(*it, "Minecraft version");
+ } else if (name == "fabric-loader") {
+ fabricVersion = Json::requireString(*it, "Fabric Loader version");
+ } else if (name == "quilt-loader") {
+ quiltVersion = Json::requireString(*it, "Quilt Loader version");
+ } else if (name == "forge") {
+ forgeVersion = Json::requireString(*it, "Forge version");
+ } else {
+ throw JSONValidationError("Unknown dependency type: " + name);
+ }
+ }
+ } else {
+ throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
+ }
+ QFile::remove(indexPath);
+ } catch (const JSONValidationError& e) {
+ setError(tr("Could not understand pack index:\n") + e.cause());
+ return false;
+ }
+
+ return true;
+}
+
+QString ModrinthCreationTask::getManagedPackID() const
+{
+ if (!m_source_url.isEmpty()) {
+ QRegularExpression regex(R"(data\/(.*)\/versions)");
+ return regex.match(m_source_url).captured(0);
+ }
+
+ return {};
+}
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
new file mode 100644
index 00000000..61f7dd5c
--- /dev/null
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "InstanceCreationTask.h"
+
+#include "modplatform/modrinth/ModrinthPackManifest.h"
+
+#include "net/NetJob.h"
+
+class ModrinthCreationTask final : public InstanceCreationTask {
+ Q_OBJECT
+
+ public:
+ ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {})
+ : InstanceCreationTask(), m_parent(parent)
+ {
+ setStagingPath(staging_path);
+ setParentSettings(global_settings);
+ }
+
+ bool abort() override;
+ bool canAbort() const override { return true; }
+
+ bool updateInstance() override;
+ bool createInstance() override;
+
+ private:
+ bool parseManifest();
+ QString getManagedPackID() const;
+
+ private:
+ QWidget* m_parent = nullptr;
+
+ QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
+ QString m_managed_id, m_managed_version_id, m_managed_name;
+ QString m_source_url;
+
+ std::vector<Modrinth::File> m_files;
+ NetJob::Ptr m_files_job;
+};