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:
Petr Mrázek 2017-04-20 05:22:04 +02:00
parent e9a6199507
commit 6bd2605a79
7 changed files with 298 additions and 11 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View 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."));
}
}

View 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;
};
}

View 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);
}

View 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);
}