summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/logic/CMakeLists.txt6
-rw-r--r--api/logic/InstanceImportTask.cpp88
-rw-r--r--api/logic/InstanceImportTask.h11
-rw-r--r--api/logic/minecraft/curse/FileResolvingTask.cpp64
-rw-r--r--api/logic/minecraft/curse/FileResolvingTask.h32
-rw-r--r--api/logic/minecraft/curse/PackManifest.cpp63
-rw-r--r--api/logic/minecraft/curse/PackManifest.h45
7 files changed, 298 insertions, 11 deletions
diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt
index c57d5783..fa1a9f57 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -291,6 +291,12 @@ set(MINECRAFT_SOURCES
minecraft/ftb/FTBPlugin.h
minecraft/ftb/FTBPlugin.cpp
+ # Curse
+ minecraft/curse/PackManifest.h
+ minecraft/curse/PackManifest.cpp
+ minecraft/curse/FileResolvingTask.h
+ minecraft/curse/FileResolvingTask.cpp
+
# Assets
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp
index 23897778..1b44ec7a 100644
--- a/api/logic/InstanceImportTask.cpp
+++ b/api/logic/InstanceImportTask.cpp
@@ -1,3 +1,4 @@
+#include "minecraft/onesix/OneSixInstance.h"
#include "InstanceImportTask.h"
#include "BaseInstance.h"
@@ -9,6 +10,9 @@
#include "settings/INISettingsObject.h"
#include "icons/IIconList.h"
#include <QtConcurrentRun>
+#include "minecraft/curse/FileResolvingTask.h"
+#include "minecraft/curse/PackManifest.h"
+#include "Json.h"
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
const QString &instName, const QString &instIcon, const QString &instGroup)
@@ -107,18 +111,87 @@ void InstanceImportTask::extractFinished()
}
QDir extractDir(m_stagingPath);
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
- if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
+ const QFileInfo curseJson = findRecursive(extractDir.absolutePath(), "manifest.json");
+ if (instanceCfgFile.isFile())
+ {
+ processMultiMC(instanceCfgFile);
+ }
+ else if (curseJson.isFile())
+ {
+ processCurse(curseJson);
+ }
+ else
{
m_target->destroyStagingPath(m_stagingPath);
- emitFailed(tr("Archive does not contain instance.cfg"));
+ emitFailed(tr("Archive does not contain a recognized modpack type."));
+ }
+}
+
+void InstanceImportTask::extractAborted()
+{
+ m_target->destroyStagingPath(m_stagingPath);
+ emitFailed(tr("Instance import has been aborted."));
+ return;
+}
+
+void InstanceImportTask::processCurse(const QFileInfo & manifest)
+{
+ Curse::Manifest pack;
+ try
+ {
+ Curse::loadManifest(pack, manifest.absoluteFilePath());
+ }
+ catch (JSONValidationError & e)
+ {
+ emitFailed(tr("Could not understand curse manifest:\n") + e.cause());
return;
}
+ m_packRoot = manifest.absolutePath();
+ QString configPath = FS::PathCombine(m_packRoot, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+ OneSixInstance instance(m_globalSettings, instanceSettings, m_packRoot);
+ instance.setIntendedVersionId(pack.minecraft.version);
+ instance.setName(m_instName);
+ instance.setIconKey(m_instIcon);
+ m_curseResolver.reset(new Curse::FileResolvingTask(pack.files));
+ connect(m_curseResolver.get(), &Curse::FileResolvingTask::succeeded, this, &InstanceImportTask::curseResolvingSucceeded);
+ connect(m_curseResolver.get(), &Curse::FileResolvingTask::failed, this, &InstanceImportTask::curseResolvingFailed);
+ m_curseResolver->start();
+}
+void InstanceImportTask::curseResolvingFailed(QString reason)
+{
+ m_target->destroyStagingPath(m_stagingPath);
+ m_curseResolver.reset();
+ emitFailed(tr("Unable to resolve Curse mod IDs:\n") + reason);
+}
+
+void InstanceImportTask::curseResolvingSucceeded()
+{
+ auto results = m_curseResolver->getResults();
+ for(auto result: results)
+ {
+ qDebug() << result.fileName << " = " << result.url;
+ }
+ m_curseResolver.reset();
+ if (!m_target->commitStagedInstance(m_stagingPath, m_packRoot, m_instName, m_instGroup))
+ {
+ m_target->destroyStagingPath(m_stagingPath);
+ emitFailed(tr("Unable to commit instance"));
+ return;
+ }
+ emitSucceeded();
+}
+
+void InstanceImportTask::processMultiMC(const QFileInfo & config)
+{
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
- auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
+ auto instanceSettings = std::make_shared<INISettingsObject>(config.absoluteFilePath());
instanceSettings->registerSetting("InstanceType", "Legacy");
- QString actualDir = instanceCfgFile.absolutePath();
+ QString actualDir = config.absolutePath();
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
// reset time played on import... because packs.
@@ -155,10 +228,3 @@ void InstanceImportTask::extractFinished()
}
emitSucceeded();
}
-
-void InstanceImportTask::extractAborted()
-{
- m_target->destroyStagingPath(m_stagingPath);
- emitFailed(tr("Instance import has been aborted."));
- return;
-}
diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h
index fe227e07..0bd7ddf8 100644
--- a/api/logic/InstanceImportTask.h
+++ b/api/logic/InstanceImportTask.h
@@ -7,8 +7,13 @@
#include <QFuture>
#include <QFutureWatcher>
#include "settings/SettingsObject.h"
+#include "QObjectPtr.h"
class BaseInstanceProvider;
+namespace Curse
+{
+ class FileResolvingTask;
+}
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
{
@@ -23,6 +28,8 @@ protected:
private:
void extractAndTweak();
+ void processMultiMC(const QFileInfo &config);
+ void processCurse(const QFileInfo &manifest);
private slots:
void downloadSucceeded();
@@ -30,14 +37,18 @@ private slots:
void downloadProgressChanged(qint64 current, qint64 total);
void extractFinished();
void extractAborted();
+ void curseResolvingSucceeded();
+ void curseResolvingFailed(QString reason);
private: /* data */
SettingsObjectPtr m_globalSettings;
NetJobPtr m_filesNetJob;
+ shared_qobject_ptr<Curse::FileResolvingTask> m_curseResolver;
QUrl m_sourceUrl;
BaseInstanceProvider * m_target;
QString m_archivePath;
bool m_downloadRequired = false;
+ QString m_packRoot;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
diff --git a/api/logic/minecraft/curse/FileResolvingTask.cpp b/api/logic/minecraft/curse/FileResolvingTask.cpp
new file mode 100644
index 00000000..13308202
--- /dev/null
+++ b/api/logic/minecraft/curse/FileResolvingTask.cpp
@@ -0,0 +1,64 @@
+#include "FileResolvingTask.h"
+#include "Json.h"
+
+const char * metabase = "https://cursemeta.dries007.net";
+
+Curse::FileResolvingTask::FileResolvingTask(QVector<Curse::File>& toProcess)
+ : m_toProcess(toProcess)
+{
+}
+
+void Curse::FileResolvingTask::executeTask()
+{
+ m_dljob.reset(new NetJob("Curse file resolver"));
+ results.resize(m_toProcess.size());
+ int index = 0;
+ for(auto & file: m_toProcess)
+ {
+ auto projectIdStr = QString::number(file.projectId);
+ auto fileIdStr = QString::number(file.fileId);
+ QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr);
+ auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]);
+ m_dljob->addNetAction(dl);
+ index ++;
+ }
+ connect(m_dljob.get(), &NetJob::finished, this, &Curse::FileResolvingTask::netJobFinished);
+ m_dljob->start();
+}
+
+void Curse::FileResolvingTask::netJobFinished()
+{
+ bool failed = false;
+ int index = 0;
+ for(auto & bytes: results)
+ {
+ try
+ {
+ auto doc = Json::requireDocument(bytes);
+ auto obj = Json::requireObject(doc);
+ // result code signifies true failure.
+ if(obj.contains("code"))
+ {
+ failed = true;
+ continue;
+ }
+ auto & out = m_toProcess[index];
+ out.fileName = Json::requireString(obj, "FileNameOnDisk");
+ out.url = Json::requireString(obj, "DownloadURL");
+ out.resolved = true;
+ }
+ catch(JSONValidationError & e)
+ {
+ failed = true;
+ }
+ index++;
+ }
+ if(!failed)
+ {
+ emitSucceeded();
+ }
+ else
+ {
+ emitFailed(tr("Some curse ID resolving tasks failed."));
+ }
+}
diff --git a/api/logic/minecraft/curse/FileResolvingTask.h b/api/logic/minecraft/curse/FileResolvingTask.h
new file mode 100644
index 00000000..b7ca85d3
--- /dev/null
+++ b/api/logic/minecraft/curse/FileResolvingTask.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "tasks/Task.h"
+#include "net/NetJob.h"
+#include "PackManifest.h"
+
+#include "multimc_logic_export.h"
+
+namespace Curse
+{
+class MULTIMC_LOGIC_EXPORT FileResolvingTask : public Task
+{
+ Q_OBJECT
+public:
+ explicit FileResolvingTask(QVector<Curse::File> &toProcess);
+ const QVector<Curse::File> &getResults() const
+ {
+ return m_toProcess;
+ }
+
+protected:
+ virtual void executeTask() override;
+
+protected slots:
+ void netJobFinished();
+
+private: /* data */
+ QVector<Curse::File> m_toProcess;
+ QVector<QByteArray> results;
+ NetJobPtr m_dljob;
+};
+}
diff --git a/api/logic/minecraft/curse/PackManifest.cpp b/api/logic/minecraft/curse/PackManifest.cpp
new file mode 100644
index 00000000..a4ea703b
--- /dev/null
+++ b/api/logic/minecraft/curse/PackManifest.cpp
@@ -0,0 +1,63 @@
+#include "PackManifest.h"
+#include "Json.h"
+
+static void loadFileV1(Curse::File & f, QJsonObject & file)
+{
+ f.projectId = Json::requireInteger(file, "projectID");
+ f.fileId = Json::requireInteger(file, "fileID");
+ f.required = Json::requireBoolean(file, "required");
+}
+
+static void loadModloaderV1(Curse::Modloader & m, QJsonObject & modLoader)
+{
+ m.id = Json::requireString(modLoader, "id");
+ m.primary = Json::ensureBoolean(modLoader, "primary", false);
+}
+
+static void loadMinecraftV1(Curse::Minecraft & m, QJsonObject & minecraft)
+{
+ m.version = Json::requireString(minecraft, "version");
+ auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray());
+ for (const auto & item : arr)
+ {
+ auto obj = Json::requireObject(item);
+ Curse::Modloader loader;
+ loadModloaderV1(loader, obj);
+ m.modLoaders.append(loader);
+ }
+}
+
+static void loadManifestV1(Curse::Manifest & m, QJsonObject & manifest)
+{
+ auto mc = Json::requireObject(manifest, "minecraft");
+ loadMinecraftV1(m.minecraft, mc);
+ m.name = Json::requireString(manifest, "name");
+ m.version = Json::requireString(manifest, "version");
+ m.author = Json::requireString(manifest, "author");
+ auto arr = Json::ensureArray(manifest, "files", QJsonArray());
+ for (const auto & item : arr)
+ {
+ auto obj = Json::requireObject(item);
+ Curse::File file;
+ loadFileV1(file, obj);
+ m.files.append(file);
+ }
+ m.overrides = Json::ensureString(manifest, "overrides", "overrides");
+}
+
+void Curse::loadManifest(Curse::Manifest & m, const QString &filepath)
+{
+ auto doc = Json::requireDocument(filepath);
+ auto obj = Json::requireObject(doc);
+ m.manifestType = Json::requireString(obj, "manifestType");
+ if(m.manifestType != "minecraftModpack")
+ {
+ throw JSONValidationError("Not a Curse modpack manifest!");
+ }
+ m.manifestVersion = Json::requireInteger(obj, "manifestVersion");
+ if(m.manifestVersion != 1)
+ {
+ throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion));
+ }
+ loadManifestV1(m, obj);
+}
diff --git a/api/logic/minecraft/curse/PackManifest.h b/api/logic/minecraft/curse/PackManifest.h
new file mode 100644
index 00000000..8b9602a4
--- /dev/null
+++ b/api/logic/minecraft/curse/PackManifest.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <QString>
+#include <QVector>
+
+namespace Curse
+{
+struct File
+{
+ int projectId = 0;
+ int fileId = 0;
+ bool required = true;
+
+ // our
+ bool resolved = false;
+ QString fileName;
+ QString url;
+};
+
+struct Modloader
+{
+ QString id;
+ bool primary = false;
+};
+
+struct Minecraft
+{
+ QString version;
+ QVector<Curse::Modloader> modLoaders;
+};
+
+struct Manifest
+{
+ QString manifestType;
+ int manifestVersion = 0;
+ Curse::Minecraft minecraft;
+ QString name;
+ QString version;
+ QString author;
+ QVector<Curse::File> files;
+ QString overrides;
+};
+
+void loadManifest(Curse::Manifest & m, const QString &filepath);
+}