NOISSUE add import from curse zip packs
Does not actually grab mods, but resolves them and prints the results in logs.
This commit is contained in:
parent
e9a6199507
commit
6bd2605a79
@ -291,6 +291,12 @@ set(MINECRAFT_SOURCES
|
|||||||
minecraft/ftb/FTBPlugin.h
|
minecraft/ftb/FTBPlugin.h
|
||||||
minecraft/ftb/FTBPlugin.cpp
|
minecraft/ftb/FTBPlugin.cpp
|
||||||
|
|
||||||
|
# Curse
|
||||||
|
minecraft/curse/PackManifest.h
|
||||||
|
minecraft/curse/PackManifest.cpp
|
||||||
|
minecraft/curse/FileResolvingTask.h
|
||||||
|
minecraft/curse/FileResolvingTask.cpp
|
||||||
|
|
||||||
# Assets
|
# Assets
|
||||||
minecraft/AssetsUtils.h
|
minecraft/AssetsUtils.h
|
||||||
minecraft/AssetsUtils.cpp
|
minecraft/AssetsUtils.cpp
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#include "minecraft/onesix/OneSixInstance.h"
|
||||||
|
|
||||||
#include "InstanceImportTask.h"
|
#include "InstanceImportTask.h"
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
@ -9,6 +10,9 @@
|
|||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
#include "icons/IIconList.h"
|
#include "icons/IIconList.h"
|
||||||
#include <QtConcurrentRun>
|
#include <QtConcurrentRun>
|
||||||
|
#include "minecraft/curse/FileResolvingTask.h"
|
||||||
|
#include "minecraft/curse/PackManifest.h"
|
||||||
|
#include "Json.h"
|
||||||
|
|
||||||
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
|
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
|
||||||
const QString &instName, const QString &instIcon, const QString &instGroup)
|
const QString &instName, const QString &instIcon, const QString &instGroup)
|
||||||
@ -107,18 +111,87 @@ void InstanceImportTask::extractFinished()
|
|||||||
}
|
}
|
||||||
QDir extractDir(m_stagingPath);
|
QDir extractDir(m_stagingPath);
|
||||||
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
|
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);
|
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;
|
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!!!
|
// 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");
|
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||||
|
|
||||||
QString actualDir = instanceCfgFile.absolutePath();
|
QString actualDir = config.absolutePath();
|
||||||
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
|
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
|
||||||
|
|
||||||
// reset time played on import... because packs.
|
// reset time played on import... because packs.
|
||||||
@ -155,10 +228,3 @@ void InstanceImportTask::extractFinished()
|
|||||||
}
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceImportTask::extractAborted()
|
|
||||||
{
|
|
||||||
m_target->destroyStagingPath(m_stagingPath);
|
|
||||||
emitFailed(tr("Instance import has been aborted."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
@ -7,8 +7,13 @@
|
|||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include "settings/SettingsObject.h"
|
#include "settings/SettingsObject.h"
|
||||||
|
#include "QObjectPtr.h"
|
||||||
|
|
||||||
class BaseInstanceProvider;
|
class BaseInstanceProvider;
|
||||||
|
namespace Curse
|
||||||
|
{
|
||||||
|
class FileResolvingTask;
|
||||||
|
}
|
||||||
|
|
||||||
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
|
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
|
||||||
{
|
{
|
||||||
@ -23,6 +28,8 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void extractAndTweak();
|
void extractAndTweak();
|
||||||
|
void processMultiMC(const QFileInfo &config);
|
||||||
|
void processCurse(const QFileInfo &manifest);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void downloadSucceeded();
|
void downloadSucceeded();
|
||||||
@ -30,14 +37,18 @@ private slots:
|
|||||||
void downloadProgressChanged(qint64 current, qint64 total);
|
void downloadProgressChanged(qint64 current, qint64 total);
|
||||||
void extractFinished();
|
void extractFinished();
|
||||||
void extractAborted();
|
void extractAborted();
|
||||||
|
void curseResolvingSucceeded();
|
||||||
|
void curseResolvingFailed(QString reason);
|
||||||
|
|
||||||
private: /* data */
|
private: /* data */
|
||||||
SettingsObjectPtr m_globalSettings;
|
SettingsObjectPtr m_globalSettings;
|
||||||
NetJobPtr m_filesNetJob;
|
NetJobPtr m_filesNetJob;
|
||||||
|
shared_qobject_ptr<Curse::FileResolvingTask> m_curseResolver;
|
||||||
QUrl m_sourceUrl;
|
QUrl m_sourceUrl;
|
||||||
BaseInstanceProvider * m_target;
|
BaseInstanceProvider * m_target;
|
||||||
QString m_archivePath;
|
QString m_archivePath;
|
||||||
bool m_downloadRequired = false;
|
bool m_downloadRequired = false;
|
||||||
|
QString m_packRoot;
|
||||||
QString m_instName;
|
QString m_instName;
|
||||||
QString m_instIcon;
|
QString m_instIcon;
|
||||||
QString m_instGroup;
|
QString m_instGroup;
|
||||||
|
64
api/logic/minecraft/curse/FileResolvingTask.cpp
Normal file
64
api/logic/minecraft/curse/FileResolvingTask.cpp
Normal file
@ -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."));
|
||||||
|
}
|
||||||
|
}
|
32
api/logic/minecraft/curse/FileResolvingTask.h
Normal file
32
api/logic/minecraft/curse/FileResolvingTask.h
Normal file
@ -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;
|
||||||
|
};
|
||||||
|
}
|
63
api/logic/minecraft/curse/PackManifest.cpp
Normal file
63
api/logic/minecraft/curse/PackManifest.cpp
Normal file
@ -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);
|
||||||
|
}
|
45
api/logic/minecraft/curse/PackManifest.h
Normal file
45
api/logic/minecraft/curse/PackManifest.h
Normal file
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user