NOISSUE Flatten gui and logic libraries into MultiMC
This commit is contained in:
33
launcher/modplatform/atlauncher/ATLPackIndex.cpp
Normal file
33
launcher/modplatform/atlauncher/ATLPackIndex.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "ATLPackIndex.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
static void loadIndexedVersion(ATLauncher::IndexedVersion & v, QJsonObject & obj)
|
||||
{
|
||||
v.version = Json::requireString(obj, "version");
|
||||
v.minecraft = Json::requireString(obj, "minecraft");
|
||||
}
|
||||
|
||||
void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack & m, QJsonObject & obj)
|
||||
{
|
||||
m.id = Json::requireInteger(obj, "id");
|
||||
m.position = Json::requireInteger(obj, "position");
|
||||
m.name = Json::requireString(obj, "name");
|
||||
m.type = Json::requireString(obj, "type") == "private" ?
|
||||
ATLauncher::PackType::Private :
|
||||
ATLauncher::PackType::Public;
|
||||
auto versionsArr = Json::requireArray(obj, "versions");
|
||||
for (const auto versionRaw : versionsArr)
|
||||
{
|
||||
auto versionObj = Json::requireObject(versionRaw);
|
||||
ATLauncher::IndexedVersion version;
|
||||
loadIndexedVersion(version, versionObj);
|
||||
m.versions.append(version);
|
||||
}
|
||||
m.system = Json::ensureBoolean(obj, QString("system"), false);
|
||||
m.description = Json::ensureString(obj, "description", "");
|
||||
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "");
|
||||
}
|
34
launcher/modplatform/atlauncher/ATLPackIndex.h
Normal file
34
launcher/modplatform/atlauncher/ATLPackIndex.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace ATLauncher
|
||||
{
|
||||
|
||||
struct IndexedVersion
|
||||
{
|
||||
QString version;
|
||||
QString minecraft;
|
||||
};
|
||||
|
||||
struct IndexedPack
|
||||
{
|
||||
int id;
|
||||
int position;
|
||||
QString name;
|
||||
PackType type;
|
||||
QVector<IndexedVersion> versions;
|
||||
bool system;
|
||||
QString description;
|
||||
|
||||
QString safeName;
|
||||
};
|
||||
|
||||
void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(ATLauncher::IndexedPack)
|
764
launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
Normal file
764
launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
Normal file
@ -0,0 +1,764 @@
|
||||
#include <Env.h>
|
||||
#include <quazip.h>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <MMCZip.h>
|
||||
#include <minecraft/OneSixVersionFormat.h>
|
||||
#include <Version.h>
|
||||
#include <net/ChecksumValidator.h>
|
||||
#include "ATLPackInstallTask.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
|
||||
{
|
||||
m_support = support;
|
||||
m_pack = pack;
|
||||
m_version_name = version;
|
||||
}
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
if(abortable)
|
||||
{
|
||||
return jobPtr->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
{
|
||||
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
|
||||
auto *netJob = new NetJob("ATLauncher::VersionFetch");
|
||||
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
|
||||
.arg(m_pack).arg(m_version_name);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadSucceeded()
|
||||
{
|
||||
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
|
||||
jobPtr.reset();
|
||||
|
||||
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() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
|
||||
ATLauncher::PackVersion version;
|
||||
try
|
||||
{
|
||||
ATLauncher::loadVersion(version, obj);
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||
return;
|
||||
}
|
||||
m_version = version;
|
||||
|
||||
auto vlist = ENV.metadataIndex()->get("net.minecraft");
|
||||
if(!vlist)
|
||||
{
|
||||
emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto ver = vlist->getVersion(m_version.minecraft);
|
||||
if (!ver) {
|
||||
emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft));
|
||||
return;
|
||||
}
|
||||
ver->load(Net::Mode::Online);
|
||||
minecraftVersion = ver;
|
||||
|
||||
if(m_version.noConfigs) {
|
||||
downloadMods();
|
||||
}
|
||||
else {
|
||||
installConfigs();
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadFailed(QString reason)
|
||||
{
|
||||
qDebug() << "PackInstallTask::onDownloadFailed: " << QThread::currentThreadId();
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
QString PackInstallTask::getDirForModType(ModType type, QString raw)
|
||||
{
|
||||
switch (type) {
|
||||
// Mod types that can either be ignored at this stage, or ignored
|
||||
// completely.
|
||||
case ModType::Root:
|
||||
case ModType::Extract:
|
||||
case ModType::Decomp:
|
||||
case ModType::TexturePackExtract:
|
||||
case ModType::ResourcePackExtract:
|
||||
case ModType::MCPC:
|
||||
return Q_NULLPTR;
|
||||
case ModType::Forge:
|
||||
// Forge detection happens later on, if it cannot be detected it will
|
||||
// install a jarmod component.
|
||||
case ModType::Jar:
|
||||
return "jarmods";
|
||||
case ModType::Mods:
|
||||
return "mods";
|
||||
case ModType::Flan:
|
||||
return "Flan";
|
||||
case ModType::Dependency:
|
||||
return FS::PathCombine("mods", m_version.minecraft);
|
||||
case ModType::Ic2Lib:
|
||||
return FS::PathCombine("mods", "ic2");
|
||||
case ModType::DenLib:
|
||||
return FS::PathCombine("mods", "denlib");
|
||||
case ModType::Coremods:
|
||||
return "coremods";
|
||||
case ModType::Plugins:
|
||||
return "plugins";
|
||||
case ModType::TexturePack:
|
||||
return "texturepacks";
|
||||
case ModType::ResourcePack:
|
||||
return "resourcepacks";
|
||||
case ModType::ShaderPack:
|
||||
return "shaderpacks";
|
||||
case ModType::Millenaire:
|
||||
qWarning() << "Unsupported mod type: " + raw;
|
||||
return Q_NULLPTR;
|
||||
case ModType::Unknown:
|
||||
emitFailed(tr("Unknown mod type: %1").arg(raw));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QString PackInstallTask::getVersionForLoader(QString uid)
|
||||
{
|
||||
if(m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) {
|
||||
auto vlist = ENV.metadataIndex()->get(uid);
|
||||
if(!vlist)
|
||||
{
|
||||
emitFailed(tr("Failed to get local metadata index for %1").arg(uid));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
if(!vlist->isLoaded()) {
|
||||
vlist->load(Net::Mode::Online);
|
||||
}
|
||||
|
||||
if(m_version.loader.recommended || m_version.loader.latest) {
|
||||
for (int i = 0; i < vlist->versions().size(); i++) {
|
||||
auto version = vlist->versions().at(i);
|
||||
auto reqs = version->requires();
|
||||
|
||||
// filter by minecraft version, if the loader depends on a certain version.
|
||||
// not all mod loaders depend on a given Minecraft version, so we won't do this
|
||||
// filtering for those loaders.
|
||||
if (m_version.loader.type != "fabric") {
|
||||
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
|
||||
return req.uid == "net.minecraft";
|
||||
});
|
||||
if (iter == reqs.end()) continue;
|
||||
if (iter->equalsVersion != m_version.minecraft) continue;
|
||||
}
|
||||
|
||||
if (m_version.loader.recommended) {
|
||||
// first recommended build we find, we use.
|
||||
if (!version->isRecommended()) continue;
|
||||
}
|
||||
|
||||
return version->descriptor();
|
||||
}
|
||||
|
||||
emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
else if(m_version.loader.choose) {
|
||||
// Fabric Loader doesn't depend on a given Minecraft version.
|
||||
if (m_version.loader.type == "fabric") {
|
||||
return m_support->chooseVersion(vlist, Q_NULLPTR);
|
||||
}
|
||||
|
||||
return m_support->chooseVersion(vlist, m_version.minecraft);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_version.loader.version == Q_NULLPTR || m_version.loader.version.isEmpty()) {
|
||||
emitFailed(tr("No loader version set for modpack!"));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
return m_version.loader.version;
|
||||
}
|
||||
|
||||
QString PackInstallTask::detectLibrary(VersionLibrary library)
|
||||
{
|
||||
// Try to detect what the library is
|
||||
if (!library.server.isEmpty() && library.server.split("/").length() >= 3) {
|
||||
auto lastSlash = library.server.lastIndexOf("/");
|
||||
auto locationAndVersion = library.server.mid(0, lastSlash);
|
||||
auto fileName = library.server.mid(lastSlash + 1);
|
||||
|
||||
lastSlash = locationAndVersion.lastIndexOf("/");
|
||||
auto location = locationAndVersion.mid(0, lastSlash);
|
||||
auto version = locationAndVersion.mid(lastSlash + 1);
|
||||
|
||||
lastSlash = location.lastIndexOf("/");
|
||||
auto group = location.mid(0, lastSlash).replace("/", ".");
|
||||
auto artefact = location.mid(lastSlash + 1);
|
||||
|
||||
return group + ":" + artefact + ":" + version;
|
||||
}
|
||||
|
||||
if(library.file.contains("-")) {
|
||||
auto lastSlash = library.file.lastIndexOf("-");
|
||||
auto name = library.file.mid(0, lastSlash);
|
||||
auto version = library.file.mid(lastSlash + 1).remove(".jar");
|
||||
|
||||
if(name == QString("guava")) {
|
||||
return "com.google.guava:guava:" + version;
|
||||
}
|
||||
else if(name == QString("commons-lang3")) {
|
||||
return "org.apache.commons:commons-lang3:" + version;
|
||||
}
|
||||
}
|
||||
|
||||
return "org.multimc.atlauncher:" + library.md5 + ":1";
|
||||
}
|
||||
|
||||
bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
|
||||
{
|
||||
if(m_version.libraries.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QList<GradleSpecifier> exempt;
|
||||
for(const auto & componentUid : componentsToInstall.keys()) {
|
||||
auto componentVersion = componentsToInstall.value(componentUid);
|
||||
|
||||
for(const auto & library : componentVersion->data()->libraries) {
|
||||
GradleSpecifier lib(library->rawName());
|
||||
exempt.append(lib);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for(const auto & library : minecraftVersion->data()->libraries) {
|
||||
GradleSpecifier lib(library->rawName());
|
||||
exempt.append(lib);
|
||||
}
|
||||
}
|
||||
|
||||
auto uuid = QUuid::createUuid();
|
||||
auto id = uuid.toString().remove('{').remove('}');
|
||||
auto target_id = "org.multimc.atlauncher." + id;
|
||||
|
||||
auto patchDir = FS::PathCombine(instanceRoot, "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
|
||||
|
||||
auto f = std::make_shared<VersionFile>();
|
||||
f->name = m_pack + " " + m_version_name + " (libraries)";
|
||||
|
||||
for(const auto & lib : m_version.libraries) {
|
||||
auto libName = detectLibrary(lib);
|
||||
GradleSpecifier libSpecifier(libName);
|
||||
|
||||
bool libExempt = false;
|
||||
for(const auto & existingLib : exempt) {
|
||||
if(libSpecifier.matchName(existingLib)) {
|
||||
// If the pack specifies a newer version of the lib, use that!
|
||||
libExempt = Version(libSpecifier.version()) >= Version(existingLib.version());
|
||||
}
|
||||
}
|
||||
if(libExempt) continue;
|
||||
|
||||
auto library = std::make_shared<Library>();
|
||||
library->setRawName(libName);
|
||||
|
||||
switch(lib.download) {
|
||||
case DownloadType::Server:
|
||||
library->setAbsoluteUrl(BuildConfig.ATL_DOWNLOAD_SERVER_URL + lib.url);
|
||||
break;
|
||||
case DownloadType::Direct:
|
||||
library->setAbsoluteUrl(lib.url);
|
||||
break;
|
||||
case DownloadType::Browser:
|
||||
case DownloadType::Unknown:
|
||||
emitFailed(tr("Unknown or unsupported download type: %1").arg(lib.download_raw));
|
||||
return false;
|
||||
}
|
||||
|
||||
f->libraries.append(library);
|
||||
}
|
||||
|
||||
if(f->libraries.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
file.close();
|
||||
|
||||
profile->appendComponent(new Component(profile.get(), target_id, f));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
|
||||
{
|
||||
if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto uuid = QUuid::createUuid();
|
||||
auto id = uuid.toString().remove('{').remove('}');
|
||||
auto target_id = "org.multimc.atlauncher." + id;
|
||||
|
||||
auto patchDir = FS::PathCombine(instanceRoot, "patches");
|
||||
if(!FS::ensureFolderPathExists(patchDir))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
|
||||
|
||||
QStringList mainClasses;
|
||||
QStringList tweakers;
|
||||
for(const auto & componentUid : componentsToInstall.keys()) {
|
||||
auto componentVersion = componentsToInstall.value(componentUid);
|
||||
|
||||
if(componentVersion->data()->mainClass != QString("")) {
|
||||
mainClasses.append(componentVersion->data()->mainClass);
|
||||
}
|
||||
tweakers.append(componentVersion->data()->addTweakers);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Parse out tweakers
|
||||
auto args = m_version.extraArguments.split(" ");
|
||||
QString previous;
|
||||
for(auto arg : args) {
|
||||
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
|
||||
auto tweakClass = arg.remove("--tweakClass=");
|
||||
if(tweakers.contains(tweakClass)) continue;
|
||||
|
||||
f->addTweakers.append(tweakClass);
|
||||
}
|
||||
previous = arg;
|
||||
}
|
||||
|
||||
if(f->mainClass == QString() && f->addTweakers.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile file(patchFileName);
|
||||
if (!file.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Error opening" << file.fileName()
|
||||
<< "for reading:" << file.errorString();
|
||||
return false;
|
||||
}
|
||||
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
|
||||
file.close();
|
||||
|
||||
profile->appendComponent(new Component(profile.get(), target_id, f));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackInstallTask::installConfigs()
|
||||
{
|
||||
qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId();
|
||||
setStatus(tr("Downloading configs..."));
|
||||
jobPtr.reset(new NetJob(tr("Config download")));
|
||||
|
||||
auto path = QString("Configs/%1/%2.zip").arg(m_pack).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);
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", path);
|
||||
entry->setStale(true);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!m_version.configs.sha1.isEmpty()) {
|
||||
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
archivePath = entry->getFullPath();
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
extractConfigs();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
|
||||
void PackInstallTask::extractConfigs()
|
||||
{
|
||||
qDebug() << "PackInstallTask::extractConfigs: " << QThread::currentThreadId();
|
||||
setStatus(tr("Extracting configs..."));
|
||||
|
||||
QDir extractDir(m_stagingPath);
|
||||
|
||||
QuaZip packZip(archivePath);
|
||||
if(!packZip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed(tr("Failed to open pack configs %1!").arg(archivePath));
|
||||
return;
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]()
|
||||
{
|
||||
downloadMods();
|
||||
});
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
|
||||
{
|
||||
emitAborted();
|
||||
});
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void PackInstallTask::downloadMods()
|
||||
{
|
||||
qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId();
|
||||
|
||||
QVector<ATLauncher::VersionMod> optionalMods;
|
||||
for (const auto& mod : m_version.mods) {
|
||||
if (mod.optional) {
|
||||
optionalMods.push_back(mod);
|
||||
}
|
||||
}
|
||||
|
||||
// Select optional mods, if pack contains any
|
||||
QVector<QString> selectedMods;
|
||||
if (!optionalMods.isEmpty()) {
|
||||
setStatus(tr("Selecting optional mods..."));
|
||||
selectedMods = m_support->chooseOptionalMods(optionalMods);
|
||||
}
|
||||
|
||||
setStatus(tr("Downloading mods..."));
|
||||
|
||||
jarmods.clear();
|
||||
jobPtr.reset(new NetJob(tr("Mod download")));
|
||||
for(const auto& mod : m_version.mods) {
|
||||
// skip non-client mods
|
||||
if(!mod.client) continue;
|
||||
|
||||
// skip optional mods that were not selected
|
||||
if(mod.optional && !selectedMods.contains(mod.name)) continue;
|
||||
|
||||
QString url;
|
||||
switch(mod.download) {
|
||||
case DownloadType::Server:
|
||||
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
|
||||
break;
|
||||
case DownloadType::Browser:
|
||||
emitFailed(tr("Unsupported download type: %1").arg(mod.download_raw));
|
||||
return;
|
||||
case DownloadType::Direct:
|
||||
url = mod.url;
|
||||
break;
|
||||
case DownloadType::Unknown:
|
||||
emitFailed(tr("Unknown download type: %1").arg(mod.download_raw));
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo fileName(mod.file);
|
||||
auto cacheName = fileName.completeBaseName() + "-" + mod.md5 + "." + fileName.suffix();
|
||||
|
||||
if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
modsToExtract.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
else if(mod.type == ModType::Decomp) {
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
modsToDecomp.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
else {
|
||||
auto relpath = getDirForModType(mod.type, mod.type_raw);
|
||||
if(relpath == Q_NULLPTR) continue;
|
||||
|
||||
auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
}
|
||||
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 = ENV.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;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
|
||||
if(mod.type == ModType::Jar) {
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModsDownloaded);
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
|
||||
void PackInstallTask::onModsDownloaded() {
|
||||
abortable = false;
|
||||
|
||||
qDebug() << "PackInstallTask::onModsDownloaded: " << QThread::currentThreadId();
|
||||
jobPtr.reset();
|
||||
|
||||
if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) {
|
||||
m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
|
||||
{
|
||||
emitAborted();
|
||||
});
|
||||
m_modExtractFutureWatcher.setFuture(m_modExtractFuture);
|
||||
}
|
||||
else {
|
||||
install();
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstallTask::onModsExtracted() {
|
||||
qDebug() << "PackInstallTask::onModsExtracted: " << QThread::currentThreadId();
|
||||
if(m_modExtractFuture.result()) {
|
||||
install();
|
||||
}
|
||||
else {
|
||||
emitFailed(tr("Failed to extract mods..."));
|
||||
}
|
||||
}
|
||||
|
||||
bool PackInstallTask::extractMods(
|
||||
const QMap<QString, VersionMod> &toExtract,
|
||||
const QMap<QString, VersionMod> &toDecomp,
|
||||
const QMap<QString, QString> &toCopy
|
||||
) {
|
||||
qDebug() << "PackInstallTask::extractMods: " << QThread::currentThreadId();
|
||||
|
||||
setStatus(tr("Extracting mods..."));
|
||||
for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) {
|
||||
auto &modPath = iter.key();
|
||||
auto &mod = iter.value();
|
||||
|
||||
QString extractToDir;
|
||||
if(mod.type == ModType::Extract) {
|
||||
extractToDir = getDirForModType(mod.extractTo, mod.extractTo_raw);
|
||||
}
|
||||
else if(mod.type == ModType::TexturePackExtract) {
|
||||
extractToDir = FS::PathCombine("texturepacks", "extracted");
|
||||
}
|
||||
else if(mod.type == ModType::ResourcePackExtract) {
|
||||
extractToDir = FS::PathCombine("resourcepacks", "extracted");
|
||||
}
|
||||
|
||||
QDir extractDir(m_stagingPath);
|
||||
auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir);
|
||||
|
||||
QString folderToExtract = "";
|
||||
if(mod.type == ModType::Extract) {
|
||||
folderToExtract = mod.extractFolder;
|
||||
folderToExtract.remove(QRegExp("^/"));
|
||||
}
|
||||
|
||||
qDebug() << "Extracting " + mod.file + " to " + extractToDir;
|
||||
if(!MMCZip::extractDir(modPath, folderToExtract, extractToPath)) {
|
||||
// assume error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) {
|
||||
auto &modPath = iter.key();
|
||||
auto &mod = iter.value();
|
||||
auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw);
|
||||
|
||||
QDir extractDir(m_stagingPath);
|
||||
auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile);
|
||||
|
||||
qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir;
|
||||
if(!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) {
|
||||
qWarning() << "Failed to extract" << mod.decompFile;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
|
||||
auto &from = iter.key();
|
||||
auto &to = iter.value();
|
||||
FS::copy fileCopyOperation(from, to);
|
||||
if(!fileCopyOperation()) {
|
||||
qWarning() << "Failed to copy" << from << "to" << to;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackInstallTask::install()
|
||||
{
|
||||
qDebug() << "PackInstallTask::install: " << QThread::currentThreadId();
|
||||
setStatus(tr("Installing modpack"));
|
||||
|
||||
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
||||
instanceSettings->suspendSave();
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
|
||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
|
||||
// Use a component to add libraries BEFORE Minecraft
|
||||
if(!createLibrariesComponent(instance.instanceRoot(), components)) {
|
||||
emitFailed(tr("Failed to create libraries component"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
components->setComponentVersion("net.minecraft", m_version.minecraft, true);
|
||||
|
||||
// Loader
|
||||
if(m_version.loader.type == QString("forge"))
|
||||
{
|
||||
auto version = getVersionForLoader("net.minecraftforge");
|
||||
if(version == Q_NULLPTR) return;
|
||||
|
||||
components->setComponentVersion("net.minecraftforge", version, true);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else if(m_version.loader.type != QString())
|
||||
{
|
||||
emitFailed(tr("Unknown loader type: ") + m_version.loader.type);
|
||||
return;
|
||||
}
|
||||
|
||||
for(const auto & componentUid : componentsToInstall.keys()) {
|
||||
auto version = componentsToInstall.value(componentUid);
|
||||
components->setComponentVersion(componentUid, version->version());
|
||||
}
|
||||
|
||||
components->installJarMods(jarmods);
|
||||
|
||||
// Use a component to fill in the rest of the data
|
||||
// todo: use more detection
|
||||
if(!createPackComponent(instance.instanceRoot(), components)) {
|
||||
emitFailed(tr("Failed to create pack component"));
|
||||
return;
|
||||
}
|
||||
|
||||
components->saveNow();
|
||||
|
||||
instance.setName(m_instName);
|
||||
instance.setIconKey(m_instIcon);
|
||||
instanceSettings->resumeSave();
|
||||
|
||||
jarmods.clear();
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
}
|
101
launcher/modplatform/atlauncher/ATLPackInstallTask.h
Normal file
101
launcher/modplatform/atlauncher/ATLPackInstallTask.h
Normal file
@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <meta/VersionList.h>
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include "InstanceTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "meta/Version.h"
|
||||
|
||||
#include <nonstd/optional>
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
class UserInteractionSupport {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Requests a user interaction to select which optional mods should be installed.
|
||||
*/
|
||||
virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0;
|
||||
|
||||
/**
|
||||
* Requests a user interaction to select a component version from a given version list
|
||||
* and constrained to a given Minecraft version.
|
||||
*/
|
||||
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
|
||||
|
||||
};
|
||||
|
||||
class PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void onDownloadSucceeded();
|
||||
void onDownloadFailed(QString reason);
|
||||
|
||||
void onModsDownloaded();
|
||||
void onModsExtracted();
|
||||
|
||||
private:
|
||||
QString getDirForModType(ModType type, QString raw);
|
||||
QString getVersionForLoader(QString uid);
|
||||
QString detectLibrary(VersionLibrary library);
|
||||
|
||||
bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
|
||||
bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
|
||||
|
||||
void installConfigs();
|
||||
void extractConfigs();
|
||||
void downloadMods();
|
||||
bool extractMods(
|
||||
const QMap<QString, VersionMod> &toExtract,
|
||||
const QMap<QString, VersionMod> &toDecomp,
|
||||
const QMap<QString, QString> &toCopy
|
||||
);
|
||||
void install();
|
||||
|
||||
private:
|
||||
UserInteractionSupport *m_support;
|
||||
|
||||
bool abortable = false;
|
||||
|
||||
NetJobPtr jobPtr;
|
||||
QByteArray response;
|
||||
|
||||
QString m_pack;
|
||||
QString m_version_name;
|
||||
PackVersion m_version;
|
||||
|
||||
QMap<QString, VersionMod> modsToExtract;
|
||||
QMap<QString, VersionMod> modsToDecomp;
|
||||
QMap<QString, QString> modsToCopy;
|
||||
|
||||
QString archivePath;
|
||||
QStringList jarmods;
|
||||
Meta::VersionPtr minecraftVersion;
|
||||
QMap<QString, Meta::VersionPtr> componentsToInstall;
|
||||
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
|
||||
QFuture<bool> m_modExtractFuture;
|
||||
QFutureWatcher<bool> m_modExtractFutureWatcher;
|
||||
|
||||
};
|
||||
|
||||
}
|
218
launcher/modplatform/atlauncher/ATLPackManifest.cpp
Normal file
218
launcher/modplatform/atlauncher/ATLPackManifest.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
static ATLauncher::DownloadType parseDownloadType(QString rawType) {
|
||||
if(rawType == QString("server")) {
|
||||
return ATLauncher::DownloadType::Server;
|
||||
}
|
||||
else if(rawType == QString("browser")) {
|
||||
return ATLauncher::DownloadType::Browser;
|
||||
}
|
||||
else if(rawType == QString("direct")) {
|
||||
return ATLauncher::DownloadType::Direct;
|
||||
}
|
||||
|
||||
return ATLauncher::DownloadType::Unknown;
|
||||
}
|
||||
|
||||
static ATLauncher::ModType parseModType(QString rawType) {
|
||||
// See https://wiki.atlauncher.com/mod_types
|
||||
if(rawType == QString("root")) {
|
||||
return ATLauncher::ModType::Root;
|
||||
}
|
||||
else if(rawType == QString("forge")) {
|
||||
return ATLauncher::ModType::Forge;
|
||||
}
|
||||
else if(rawType == QString("jar")) {
|
||||
return ATLauncher::ModType::Jar;
|
||||
}
|
||||
else if(rawType == QString("mods")) {
|
||||
return ATLauncher::ModType::Mods;
|
||||
}
|
||||
else if(rawType == QString("flan")) {
|
||||
return ATLauncher::ModType::Flan;
|
||||
}
|
||||
else if(rawType == QString("dependency") || rawType == QString("depandency")) {
|
||||
return ATLauncher::ModType::Dependency;
|
||||
}
|
||||
else if(rawType == QString("ic2lib")) {
|
||||
return ATLauncher::ModType::Ic2Lib;
|
||||
}
|
||||
else if(rawType == QString("denlib")) {
|
||||
return ATLauncher::ModType::DenLib;
|
||||
}
|
||||
else if(rawType == QString("coremods")) {
|
||||
return ATLauncher::ModType::Coremods;
|
||||
}
|
||||
else if(rawType == QString("mcpc")) {
|
||||
return ATLauncher::ModType::MCPC;
|
||||
}
|
||||
else if(rawType == QString("plugins")) {
|
||||
return ATLauncher::ModType::Plugins;
|
||||
}
|
||||
else if(rawType == QString("extract")) {
|
||||
return ATLauncher::ModType::Extract;
|
||||
}
|
||||
else if(rawType == QString("decomp")) {
|
||||
return ATLauncher::ModType::Decomp;
|
||||
}
|
||||
else if(rawType == QString("texturepack")) {
|
||||
return ATLauncher::ModType::TexturePack;
|
||||
}
|
||||
else if(rawType == QString("resourcepack")) {
|
||||
return ATLauncher::ModType::ResourcePack;
|
||||
}
|
||||
else if(rawType == QString("shaderpack")) {
|
||||
return ATLauncher::ModType::ShaderPack;
|
||||
}
|
||||
else if(rawType == QString("texturepackextract")) {
|
||||
return ATLauncher::ModType::TexturePackExtract;
|
||||
}
|
||||
else if(rawType == QString("resourcepackextract")) {
|
||||
return ATLauncher::ModType::ResourcePackExtract;
|
||||
}
|
||||
else if(rawType == QString("millenaire")) {
|
||||
return ATLauncher::ModType::Millenaire;
|
||||
}
|
||||
|
||||
return ATLauncher::ModType::Unknown;
|
||||
}
|
||||
|
||||
static void loadVersionLoader(ATLauncher::VersionLoader & p, QJsonObject & obj) {
|
||||
p.type = Json::requireString(obj, "type");
|
||||
p.choose = Json::ensureBoolean(obj, QString("choose"), false);
|
||||
|
||||
auto metadata = Json::requireObject(obj, "metadata");
|
||||
p.latest = Json::ensureBoolean(metadata, QString("latest"), false);
|
||||
p.recommended = Json::ensureBoolean(metadata, QString("recommended"), false);
|
||||
|
||||
// Minecraft Forge
|
||||
if (p.type == "forge") {
|
||||
p.version = Json::ensureString(metadata, "version", "");
|
||||
}
|
||||
|
||||
// Fabric Loader
|
||||
if (p.type == "fabric") {
|
||||
p.version = Json::ensureString(metadata, "loader", "");
|
||||
}
|
||||
}
|
||||
|
||||
static void loadVersionLibrary(ATLauncher::VersionLibrary & p, QJsonObject & obj) {
|
||||
p.url = Json::requireString(obj, "url");
|
||||
p.file = Json::requireString(obj, "file");
|
||||
p.md5 = Json::requireString(obj, "md5");
|
||||
|
||||
p.download_raw = Json::requireString(obj, "download");
|
||||
p.download = parseDownloadType(p.download_raw);
|
||||
|
||||
p.server = Json::ensureString(obj, "server", "");
|
||||
}
|
||||
|
||||
static void loadVersionConfigs(ATLauncher::VersionConfigs & p, QJsonObject & obj) {
|
||||
p.filesize = Json::requireInteger(obj, "filesize");
|
||||
p.sha1 = Json::requireString(obj, "sha1");
|
||||
}
|
||||
|
||||
static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
|
||||
p.name = Json::requireString(obj, "name");
|
||||
p.version = Json::requireString(obj, "version");
|
||||
p.url = Json::requireString(obj, "url");
|
||||
p.file = Json::requireString(obj, "file");
|
||||
p.md5 = Json::ensureString(obj, "md5", "");
|
||||
|
||||
p.download_raw = Json::requireString(obj, "download");
|
||||
p.download = parseDownloadType(p.download_raw);
|
||||
|
||||
p.type_raw = Json::requireString(obj, "type");
|
||||
p.type = parseModType(p.type_raw);
|
||||
|
||||
// This contributes to the Minecraft Forge detection, where we rely on mod.type being "Forge"
|
||||
// when the mod represents Forge. As there is little difference between "Jar" and "Forge, some
|
||||
// packs regretfully use "Jar". This will correct the type to "Forge" in these cases (as best
|
||||
// it can).
|
||||
if(p.name == QString("Minecraft Forge") && p.type == ATLauncher::ModType::Jar) {
|
||||
p.type_raw = "forge";
|
||||
p.type = ATLauncher::ModType::Forge;
|
||||
}
|
||||
|
||||
if(obj.contains("extractTo")) {
|
||||
p.extractTo_raw = Json::requireString(obj, "extractTo");
|
||||
p.extractTo = parseModType(p.extractTo_raw);
|
||||
p.extractFolder = Json::ensureString(obj, "extractFolder", "").replace("%s%", "/");
|
||||
}
|
||||
|
||||
if(obj.contains("decompType")) {
|
||||
p.decompType_raw = Json::requireString(obj, "decompType");
|
||||
p.decompType = parseModType(p.decompType_raw);
|
||||
p.decompFile = Json::requireString(obj, "decompFile");
|
||||
}
|
||||
|
||||
p.description = Json::ensureString(obj, QString("description"), "");
|
||||
p.optional = Json::ensureBoolean(obj, QString("optional"), false);
|
||||
p.recommended = Json::ensureBoolean(obj, QString("recommended"), false);
|
||||
p.selected = Json::ensureBoolean(obj, QString("selected"), false);
|
||||
p.hidden = Json::ensureBoolean(obj, QString("hidden"), false);
|
||||
p.library = Json::ensureBoolean(obj, QString("library"), false);
|
||||
p.group = Json::ensureString(obj, QString("group"), "");
|
||||
if(obj.contains("depends")) {
|
||||
auto dependsArr = Json::requireArray(obj, "depends");
|
||||
for (const auto depends : dependsArr) {
|
||||
p.depends.append(Json::requireString(depends));
|
||||
}
|
||||
}
|
||||
|
||||
p.client = Json::ensureBoolean(obj, QString("client"), false);
|
||||
|
||||
// computed
|
||||
p.effectively_hidden = p.hidden || p.library;
|
||||
}
|
||||
|
||||
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
||||
{
|
||||
v.version = Json::requireString(obj, "version");
|
||||
v.minecraft = Json::requireString(obj, "minecraft");
|
||||
v.noConfigs = Json::ensureBoolean(obj, QString("noConfigs"), false);
|
||||
|
||||
if(obj.contains("mainClass")) {
|
||||
auto main = Json::requireObject(obj, "mainClass");
|
||||
v.mainClass = Json::ensureString(main, "mainClass", "");
|
||||
}
|
||||
|
||||
if(obj.contains("extraArguments")) {
|
||||
auto arguments = Json::requireObject(obj, "extraArguments");
|
||||
v.extraArguments = Json::ensureString(arguments, "arguments", "");
|
||||
}
|
||||
|
||||
if(obj.contains("loader")) {
|
||||
auto loader = Json::requireObject(obj, "loader");
|
||||
loadVersionLoader(v.loader, loader);
|
||||
}
|
||||
|
||||
if(obj.contains("libraries")) {
|
||||
auto libraries = Json::requireArray(obj, "libraries");
|
||||
for (const auto libraryRaw : libraries)
|
||||
{
|
||||
auto libraryObj = Json::requireObject(libraryRaw);
|
||||
ATLauncher::VersionLibrary target;
|
||||
loadVersionLibrary(target, libraryObj);
|
||||
v.libraries.append(target);
|
||||
}
|
||||
}
|
||||
|
||||
if(obj.contains("mods")) {
|
||||
auto mods = Json::requireArray(obj, "mods");
|
||||
for (const auto modRaw : mods)
|
||||
{
|
||||
auto modObj = Json::requireObject(modRaw);
|
||||
ATLauncher::VersionMod mod;
|
||||
loadVersionMod(mod, modObj);
|
||||
v.mods.append(mod);
|
||||
}
|
||||
}
|
||||
|
||||
if(obj.contains("configs")) {
|
||||
auto configsObj = Json::requireObject(obj, "configs");
|
||||
loadVersionConfigs(v.configs, configsObj);
|
||||
}
|
||||
}
|
125
launcher/modplatform/atlauncher/ATLPackManifest.h
Normal file
125
launcher/modplatform/atlauncher/ATLPackManifest.h
Normal file
@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace ATLauncher
|
||||
{
|
||||
|
||||
enum class PackType
|
||||
{
|
||||
Public,
|
||||
Private
|
||||
};
|
||||
|
||||
enum class ModType
|
||||
{
|
||||
Root,
|
||||
Forge,
|
||||
Jar,
|
||||
Mods,
|
||||
Flan,
|
||||
Dependency,
|
||||
Ic2Lib,
|
||||
DenLib,
|
||||
Coremods,
|
||||
MCPC,
|
||||
Plugins,
|
||||
Extract,
|
||||
Decomp,
|
||||
TexturePack,
|
||||
ResourcePack,
|
||||
ShaderPack,
|
||||
TexturePackExtract,
|
||||
ResourcePackExtract,
|
||||
Millenaire,
|
||||
Unknown
|
||||
};
|
||||
|
||||
enum class DownloadType
|
||||
{
|
||||
Server,
|
||||
Browser,
|
||||
Direct,
|
||||
Unknown
|
||||
};
|
||||
|
||||
struct VersionLoader
|
||||
{
|
||||
QString type;
|
||||
bool latest;
|
||||
bool recommended;
|
||||
bool choose;
|
||||
|
||||
QString version;
|
||||
};
|
||||
|
||||
struct VersionLibrary
|
||||
{
|
||||
QString url;
|
||||
QString file;
|
||||
QString server;
|
||||
QString md5;
|
||||
DownloadType download;
|
||||
QString download_raw;
|
||||
};
|
||||
|
||||
struct VersionMod
|
||||
{
|
||||
QString name;
|
||||
QString version;
|
||||
QString url;
|
||||
QString file;
|
||||
QString md5;
|
||||
DownloadType download;
|
||||
QString download_raw;
|
||||
ModType type;
|
||||
QString type_raw;
|
||||
|
||||
ModType extractTo;
|
||||
QString extractTo_raw;
|
||||
QString extractFolder;
|
||||
|
||||
ModType decompType;
|
||||
QString decompType_raw;
|
||||
QString decompFile;
|
||||
|
||||
QString description;
|
||||
bool optional;
|
||||
bool recommended;
|
||||
bool selected;
|
||||
bool hidden;
|
||||
bool library;
|
||||
QString group;
|
||||
QVector<QString> depends;
|
||||
|
||||
bool client;
|
||||
|
||||
// computed
|
||||
bool effectively_hidden;
|
||||
};
|
||||
|
||||
struct VersionConfigs
|
||||
{
|
||||
int filesize;
|
||||
QString sha1;
|
||||
};
|
||||
|
||||
struct PackVersion
|
||||
{
|
||||
QString version;
|
||||
QString minecraft;
|
||||
bool noConfigs;
|
||||
QString mainClass;
|
||||
QString extraArguments;
|
||||
|
||||
VersionLoader loader;
|
||||
QVector<VersionLibrary> libraries;
|
||||
QVector<VersionMod> mods;
|
||||
VersionConfigs configs;
|
||||
};
|
||||
|
||||
void loadVersion(PackVersion & v, QJsonObject & obj);
|
||||
|
||||
}
|
63
launcher/modplatform/flame/FileResolvingTask.cpp
Normal file
63
launcher/modplatform/flame/FileResolvingTask.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "FileResolvingTask.h"
|
||||
#include "Json.h"
|
||||
|
||||
namespace {
|
||||
const char * metabase = "https://cursemeta.dries007.net";
|
||||
}
|
||||
|
||||
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
|
||||
: m_toProcess(toProcess)
|
||||
{
|
||||
}
|
||||
|
||||
void Flame::FileResolvingTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Resolving mod IDs..."));
|
||||
setProgress(0, m_toProcess.files.size());
|
||||
m_dljob.reset(new NetJob("Mod id resolver"));
|
||||
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("%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, &Flame::FileResolvingTask::netJobFinished);
|
||||
m_dljob->start();
|
||||
}
|
||||
|
||||
void Flame::FileResolvingTask::netJobFinished()
|
||||
{
|
||||
bool failed = false;
|
||||
int index = 0;
|
||||
for(auto & bytes: results)
|
||||
{
|
||||
auto & out = m_toProcess.files[index];
|
||||
try
|
||||
{
|
||||
failed &= (!out.parseFromBytes(bytes));
|
||||
}
|
||||
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;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if(!failed)
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
emitFailed(tr("Some mod ID resolving tasks failed."));
|
||||
}
|
||||
}
|
32
launcher/modplatform/flame/FileResolvingTask.h
Normal file
32
launcher/modplatform/flame/FileResolvingTask.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "PackManifest.h"
|
||||
|
||||
namespace Flame
|
||||
{
|
||||
class FileResolvingTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileResolvingTask(Flame::Manifest &toProcess);
|
||||
virtual ~FileResolvingTask() {};
|
||||
|
||||
const Flame::Manifest &getResults() const
|
||||
{
|
||||
return m_toProcess;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
protected slots:
|
||||
void netJobFinished();
|
||||
|
||||
private: /* data */
|
||||
Flame::Manifest m_toProcess;
|
||||
QVector<QByteArray> results;
|
||||
NetJobPtr m_dljob;
|
||||
};
|
||||
}
|
92
launcher/modplatform/flame/FlamePackIndex.cpp
Normal file
92
launcher/modplatform/flame/FlamePackIndex.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
#include "FlamePackIndex.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj)
|
||||
{
|
||||
pack.addonId = Json::requireInteger(obj, "id");
|
||||
pack.name = Json::requireString(obj, "name");
|
||||
pack.websiteUrl = Json::ensureString(obj, "websiteUrl", "");
|
||||
pack.description = Json::ensureString(obj, "summary", "");
|
||||
|
||||
bool thumbnailFound = false;
|
||||
auto attachments = Json::requireArray(obj, "attachments");
|
||||
for(auto attachmentRaw: attachments) {
|
||||
auto attachmentObj = Json::requireObject(attachmentRaw);
|
||||
bool isDefault = attachmentObj.value("isDefault").toBool(false);
|
||||
if(isDefault) {
|
||||
thumbnailFound = true;
|
||||
pack.logoName = Json::requireString(attachmentObj, "title");
|
||||
pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!thumbnailFound) {
|
||||
throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name));
|
||||
}
|
||||
|
||||
auto authors = Json::requireArray(obj, "authors");
|
||||
for(auto authorIter: authors) {
|
||||
auto author = Json::requireObject(authorIter);
|
||||
Flame::ModpackAuthor packAuthor;
|
||||
packAuthor.name = Json::requireString(author, "name");
|
||||
packAuthor.url = Json::requireString(author, "url");
|
||||
pack.authors.append(packAuthor);
|
||||
}
|
||||
int defaultFileId = Json::requireInteger(obj, "defaultFileId");
|
||||
|
||||
bool found = false;
|
||||
// check if there are some files before adding the pack
|
||||
auto files = Json::requireArray(obj, "latestFiles");
|
||||
for(auto fileIter: files) {
|
||||
auto file = Json::requireObject(fileIter);
|
||||
int id = Json::requireInteger(file, "id");
|
||||
|
||||
// NOTE: for now, ignore everything that's not the default...
|
||||
if(id != defaultFileId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto versionArray = Json::requireArray(file, "gameVersion");
|
||||
if(versionArray.size() < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if(!found) {
|
||||
throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name));
|
||||
}
|
||||
}
|
||||
|
||||
void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr)
|
||||
{
|
||||
QVector<Flame::IndexedVersion> unsortedVersions;
|
||||
for(auto versionIter: arr) {
|
||||
auto version = Json::requireObject(versionIter);
|
||||
Flame::IndexedVersion file;
|
||||
|
||||
file.addonId = pack.addonId;
|
||||
file.fileId = Json::requireInteger(version, "id");
|
||||
auto versionArray = Json::requireArray(version, "gameVersion");
|
||||
if(versionArray.size() < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool
|
||||
{
|
||||
return a.fileId > b.fileId;
|
||||
};
|
||||
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
|
||||
pack.versions = unsortedVersions;
|
||||
pack.versionsLoaded = true;
|
||||
}
|
41
launcher/modplatform/flame/FlamePackIndex.h
Normal file
41
launcher/modplatform/flame/FlamePackIndex.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace Flame {
|
||||
|
||||
struct ModpackAuthor {
|
||||
QString name;
|
||||
QString url;
|
||||
};
|
||||
|
||||
struct IndexedVersion {
|
||||
int addonId;
|
||||
int fileId;
|
||||
QString version;
|
||||
QString mcVersion;
|
||||
QString downloadUrl;
|
||||
};
|
||||
|
||||
struct IndexedPack
|
||||
{
|
||||
int addonId;
|
||||
QString name;
|
||||
QString description;
|
||||
QList<ModpackAuthor> authors;
|
||||
QString logoName;
|
||||
QString logoUrl;
|
||||
QString websiteUrl;
|
||||
|
||||
bool versionsLoaded = false;
|
||||
QVector<IndexedVersion> versions;
|
||||
};
|
||||
|
||||
void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
|
||||
void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(Flame::IndexedPack)
|
126
launcher/modplatform/flame/PackManifest.cpp
Normal file
126
launcher/modplatform/flame/PackManifest.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#include "PackManifest.h"
|
||||
#include "Json.h"
|
||||
|
||||
static void loadFileV1(Flame::File & f, QJsonObject & file)
|
||||
{
|
||||
f.projectId = Json::requireInteger(file, "projectID");
|
||||
f.fileId = Json::requireInteger(file, "fileID");
|
||||
f.required = Json::ensureBoolean(file, QString("required"), true);
|
||||
}
|
||||
|
||||
static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader)
|
||||
{
|
||||
m.id = Json::requireString(modLoader, "id");
|
||||
m.primary = Json::ensureBoolean(modLoader, QString("primary"), false);
|
||||
}
|
||||
|
||||
static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft)
|
||||
{
|
||||
m.version = Json::requireString(minecraft, "version");
|
||||
// extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack
|
||||
// intended use is likely hardcoded in the 'Flame' client, the manifest says nothing
|
||||
m.libraries = Json::ensureString(minecraft, QString("libraries"), QString());
|
||||
auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray());
|
||||
for (QJsonValueRef item : arr)
|
||||
{
|
||||
auto obj = Json::requireObject(item);
|
||||
Flame::Modloader loader;
|
||||
loadModloaderV1(loader, obj);
|
||||
m.modLoaders.append(loader);
|
||||
}
|
||||
}
|
||||
|
||||
static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest)
|
||||
{
|
||||
auto mc = Json::requireObject(manifest, "minecraft");
|
||||
loadMinecraftV1(m.minecraft, mc);
|
||||
m.name = Json::ensureString(manifest, QString("name"), "Unnamed");
|
||||
m.version = Json::ensureString(manifest, QString("version"), QString());
|
||||
m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward");
|
||||
auto arr = Json::ensureArray(manifest, "files", QJsonArray());
|
||||
for (QJsonValueRef item : arr)
|
||||
{
|
||||
auto obj = Json::requireObject(item);
|
||||
Flame::File file;
|
||||
loadFileV1(file, obj);
|
||||
m.files.append(file);
|
||||
}
|
||||
m.overrides = Json::ensureString(manifest, "overrides", "overrides");
|
||||
}
|
||||
|
||||
void Flame::loadManifest(Flame::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 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);
|
||||
}
|
||||
|
||||
bool Flame::File::parseFromBytes(const QByteArray& bytes)
|
||||
{
|
||||
auto doc = Json::requireDocument(bytes);
|
||||
auto obj = Json::requireObject(doc);
|
||||
// result code signifies true failure.
|
||||
if(obj.contains("code"))
|
||||
{
|
||||
qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:";
|
||||
qCritical() << bytes;
|
||||
return false;
|
||||
}
|
||||
fileName = Json::requireString(obj, "FileNameOnDisk");
|
||||
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
|
||||
QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
|
||||
if(!projObj.isEmpty())
|
||||
{
|
||||
QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
|
||||
if(strType == "singlefile")
|
||||
{
|
||||
type = File::Type::SingleFile;
|
||||
}
|
||||
else if(strType == "ctoc")
|
||||
{
|
||||
type = File::Type::Ctoc;
|
||||
}
|
||||
else if(strType == "cmod2")
|
||||
{
|
||||
type = File::Type::Cmod2;
|
||||
}
|
||||
else if(strType == "mod")
|
||||
{
|
||||
type = File::Type::Mod;
|
||||
}
|
||||
else if(strType == "folder")
|
||||
{
|
||||
type = File::Type::Folder;
|
||||
}
|
||||
else if(strType == "modpack")
|
||||
{
|
||||
type = File::Type::Modpack;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType;
|
||||
type = File::Type::Unknown;
|
||||
return false;
|
||||
}
|
||||
targetFolder = Json::ensureString(projObj, "Path", "mods");
|
||||
}
|
||||
resolved = true;
|
||||
return true;
|
||||
}
|
62
launcher/modplatform/flame/PackManifest.h
Normal file
62
launcher/modplatform/flame/PackManifest.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QUrl>
|
||||
|
||||
namespace Flame
|
||||
{
|
||||
struct File
|
||||
{
|
||||
// NOTE: throws JSONValidationError
|
||||
bool parseFromBytes(const QByteArray &bytes);
|
||||
|
||||
int projectId = 0;
|
||||
int fileId = 0;
|
||||
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
|
||||
bool required = true;
|
||||
|
||||
// our
|
||||
bool resolved = false;
|
||||
QString fileName;
|
||||
QUrl url;
|
||||
QString targetFolder = QLatin1Literal("mods");
|
||||
enum class Type
|
||||
{
|
||||
Unknown,
|
||||
Folder,
|
||||
Ctoc,
|
||||
SingleFile,
|
||||
Cmod2,
|
||||
Modpack,
|
||||
Mod
|
||||
} type = Type::Mod;
|
||||
};
|
||||
|
||||
struct Modloader
|
||||
{
|
||||
QString id;
|
||||
bool primary = false;
|
||||
};
|
||||
|
||||
struct Minecraft
|
||||
{
|
||||
QString version;
|
||||
QString libraries;
|
||||
QVector<Flame::Modloader> modLoaders;
|
||||
};
|
||||
|
||||
struct Manifest
|
||||
{
|
||||
QString manifestType;
|
||||
int manifestVersion = 0;
|
||||
Flame::Minecraft minecraft;
|
||||
QString name;
|
||||
QString version;
|
||||
QString author;
|
||||
QVector<Flame::File> files;
|
||||
QString overrides;
|
||||
};
|
||||
|
||||
void loadManifest(Flame::Manifest & m, const QString &filepath);
|
||||
}
|
172
launcher/modplatform/legacy_ftb/PackFetchTask.cpp
Normal file
172
launcher/modplatform/legacy_ftb/PackFetchTask.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
#include "PackFetchTask.h"
|
||||
#include "PrivatePackManager.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <BuildConfig.h>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PackFetchTask::fetch()
|
||||
{
|
||||
publicPacks.clear();
|
||||
thirdPartyPacks.clear();
|
||||
|
||||
NetJob *netJob = new NetJob("LegacyFTB::ModpackFetch");
|
||||
|
||||
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
|
||||
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
|
||||
netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData));
|
||||
|
||||
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
||||
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
|
||||
netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &PackFetchTask::fileDownloadFailed);
|
||||
|
||||
jobPtr.reset(netJob);
|
||||
netJob->start();
|
||||
}
|
||||
|
||||
void PackFetchTask::fetchPrivate(const QStringList & toFetch)
|
||||
{
|
||||
QString privatePackBaseUrl = BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1.xml";
|
||||
|
||||
for (auto &packCode: toFetch)
|
||||
{
|
||||
QByteArray *data = new QByteArray();
|
||||
NetJob *job = new NetJob("Fetching private pack");
|
||||
job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data));
|
||||
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode]
|
||||
{
|
||||
ModpackList packs;
|
||||
parseAndAddPacks(*data, PackType::Private, packs);
|
||||
foreach(Modpack currentPack, packs)
|
||||
{
|
||||
currentPack.packCode = packCode;
|
||||
emit privateFileDownloadFinished(currentPack);
|
||||
}
|
||||
|
||||
job->deleteLater();
|
||||
|
||||
data->clear();
|
||||
delete data;
|
||||
});
|
||||
|
||||
QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason)
|
||||
{
|
||||
emit privateFileDownloadFailed(reason, packCode);
|
||||
job->deleteLater();
|
||||
|
||||
data->clear();
|
||||
delete data;
|
||||
});
|
||||
|
||||
job->start();
|
||||
}
|
||||
}
|
||||
|
||||
void PackFetchTask::fileDownloadFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
QStringList failedLists;
|
||||
|
||||
if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks))
|
||||
{
|
||||
failedLists.append(tr("Public Packs"));
|
||||
}
|
||||
|
||||
if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks))
|
||||
{
|
||||
failedLists.append(tr("Third Party Packs"));
|
||||
}
|
||||
|
||||
if(failedLists.size() > 0)
|
||||
{
|
||||
emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- ")));
|
||||
}
|
||||
else
|
||||
{
|
||||
emit finished(publicPacks, thirdPartyPacks);
|
||||
}
|
||||
}
|
||||
|
||||
bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list)
|
||||
{
|
||||
QDomDocument doc;
|
||||
|
||||
QString errorMsg = "Unknown error.";
|
||||
int errorLine = -1;
|
||||
int errorCol = -1;
|
||||
|
||||
if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol))
|
||||
{
|
||||
auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol);
|
||||
qWarning() << fullErrMsg;
|
||||
data.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
QDomNodeList nodes = doc.elementsByTagName("modpack");
|
||||
for(int i = 0; i < nodes.length(); i++)
|
||||
{
|
||||
QDomElement element = nodes.at(i).toElement();
|
||||
|
||||
Modpack modpack;
|
||||
modpack.name = element.attribute("name");
|
||||
modpack.currentVersion = element.attribute("version");
|
||||
modpack.mcVersion = element.attribute("mcVersion");
|
||||
modpack.description = element.attribute("description");
|
||||
modpack.mods = element.attribute("mods");
|
||||
modpack.logo = element.attribute("logo");
|
||||
modpack.oldVersions = element.attribute("oldVersions").split(";");
|
||||
modpack.broken = false;
|
||||
modpack.bugged = false;
|
||||
|
||||
//remove empty if the xml is bugged
|
||||
for(QString curr : modpack.oldVersions)
|
||||
{
|
||||
if(curr.isNull() || curr.isEmpty())
|
||||
{
|
||||
modpack.oldVersions.removeAll(curr);
|
||||
modpack.bugged = true;
|
||||
qWarning() << "Removed some empty versions from" << modpack.name;
|
||||
}
|
||||
}
|
||||
|
||||
if(modpack.oldVersions.size() < 1)
|
||||
{
|
||||
if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty())
|
||||
{
|
||||
modpack.oldVersions.append(modpack.currentVersion);
|
||||
qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
modpack.broken = true;
|
||||
qWarning() << "Broken pack:" << modpack.name << " => No valid version!";
|
||||
}
|
||||
}
|
||||
|
||||
modpack.author = element.attribute("author");
|
||||
|
||||
modpack.dir = element.attribute("dir");
|
||||
modpack.file = element.attribute("url");
|
||||
|
||||
modpack.type = packType;
|
||||
|
||||
list.append(modpack);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PackFetchTask::fileDownloadFailed(QString reason)
|
||||
{
|
||||
qWarning() << "Fetching FTBPacks failed:" << reason;
|
||||
emit failed(reason);
|
||||
}
|
||||
|
||||
}
|
44
launcher/modplatform/legacy_ftb/PackFetchTask.h
Normal file
44
launcher/modplatform/legacy_ftb/PackFetchTask.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include <QTemporaryDir>
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include "PackHelpers.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
class PackFetchTask : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PackFetchTask() = default;
|
||||
virtual ~PackFetchTask() = default;
|
||||
|
||||
void fetch();
|
||||
void fetchPrivate(const QStringList &toFetch);
|
||||
|
||||
private:
|
||||
NetJobPtr jobPtr;
|
||||
|
||||
QByteArray publicModpacksXmlFileData;
|
||||
QByteArray thirdPartyModpacksXmlFileData;
|
||||
|
||||
bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list);
|
||||
ModpackList publicPacks;
|
||||
ModpackList thirdPartyPacks;
|
||||
|
||||
protected slots:
|
||||
void fileDownloadFinished();
|
||||
void fileDownloadFailed(QString reason);
|
||||
|
||||
signals:
|
||||
void finished(ModpackList publicPacks, ModpackList thirdPartyPacks);
|
||||
void failed(QString reason);
|
||||
|
||||
void privateFileDownloadFinished(Modpack modpack);
|
||||
void privateFileDownloadFailed(QString reason, QString packCode);
|
||||
};
|
||||
|
||||
}
|
45
launcher/modplatform/legacy_ftb/PackHelpers.h
Normal file
45
launcher/modplatform/legacy_ftb/PackHelpers.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
//Header for structs etc...
|
||||
enum class PackType
|
||||
{
|
||||
Public,
|
||||
ThirdParty,
|
||||
Private
|
||||
};
|
||||
|
||||
struct Modpack
|
||||
{
|
||||
QString name;
|
||||
QString description;
|
||||
QString author;
|
||||
QStringList oldVersions;
|
||||
QString currentVersion;
|
||||
QString mcVersion;
|
||||
QString mods;
|
||||
QString logo;
|
||||
|
||||
//Technical data
|
||||
QString dir;
|
||||
QString file; //<- Url in the xml, but doesn't make much sense
|
||||
|
||||
bool bugged = false;
|
||||
bool broken = false;
|
||||
|
||||
PackType type;
|
||||
QString packCode;
|
||||
};
|
||||
|
||||
typedef QList<Modpack> ModpackList;
|
||||
|
||||
}
|
||||
|
||||
//We need it for the proxy model
|
||||
Q_DECLARE_METATYPE(LegacyFTB::Modpack)
|
214
launcher/modplatform/legacy_ftb/PackInstallTask.cpp
Normal file
214
launcher/modplatform/legacy_ftb/PackInstallTask.cpp
Normal file
@ -0,0 +1,214 @@
|
||||
#include "PackInstallTask.h"
|
||||
|
||||
#include "Env.h"
|
||||
#include "MMCZip.h"
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "FileSystem.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/GradleSpecifier.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
PackInstallTask::PackInstallTask(Modpack pack, QString version)
|
||||
{
|
||||
m_pack = pack;
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
{
|
||||
downloadPack();
|
||||
}
|
||||
|
||||
void PackInstallTask::downloadPack()
|
||||
{
|
||||
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
|
||||
|
||||
auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
|
||||
auto entry = ENV.metacache()->resolveEntry("FTBPacks", packoffset);
|
||||
NetJob *job = new NetJob("Download FTB Pack");
|
||||
|
||||
entry->setStale(true);
|
||||
QString url;
|
||||
if(m_pack.type == PackType::Private)
|
||||
{
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset);
|
||||
}
|
||||
job->addNetAction(Net::Download::makeCached(url, entry));
|
||||
archivePath = entry->getFullPath();
|
||||
|
||||
netJobContainer.reset(job);
|
||||
connect(job, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
connect(job, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
connect(job, &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
|
||||
job->start();
|
||||
|
||||
progress(1, 4);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadSucceeded()
|
||||
{
|
||||
abortable = false;
|
||||
unzip();
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadFailed(QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
progress(current, total * 4);
|
||||
setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
|
||||
}
|
||||
|
||||
void PackInstallTask::unzip()
|
||||
{
|
||||
progress(2, 4);
|
||||
setStatus(tr("Extracting modpack"));
|
||||
QDir extractDir(m_stagingPath);
|
||||
|
||||
m_packZip.reset(new QuaZip(archivePath));
|
||||
if(!m_packZip->open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));
|
||||
return;
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void PackInstallTask::onUnzipFinished()
|
||||
{
|
||||
install();
|
||||
}
|
||||
|
||||
void PackInstallTask::onUnzipCanceled()
|
||||
{
|
||||
emitAborted();
|
||||
}
|
||||
|
||||
void PackInstallTask::install()
|
||||
{
|
||||
progress(3, 4);
|
||||
setStatus(tr("Installing modpack"));
|
||||
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
|
||||
if(unzipMcDir.exists())
|
||||
{
|
||||
//ok, found minecraft dir, move contents to instance dir
|
||||
if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft"))
|
||||
{
|
||||
emitFailed(tr("Failed to move unzipped minecraft!"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
||||
instanceSettings->suspendSave();
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
|
||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
|
||||
|
||||
bool fallback = true;
|
||||
|
||||
//handle different versions
|
||||
QFile packJson(m_stagingPath + "/.minecraft/pack.json");
|
||||
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
|
||||
if(packJson.exists())
|
||||
{
|
||||
packJson.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());
|
||||
packJson.close();
|
||||
|
||||
//we only care about the libs
|
||||
QJsonArray libs = doc.object().value("libraries").toArray();
|
||||
|
||||
foreach (const QJsonValue &value, libs)
|
||||
{
|
||||
QString nameValue = value.toObject().value("name").toString();
|
||||
if(!nameValue.startsWith("net.minecraftforge"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GradleSpecifier forgeVersion(nameValue);
|
||||
|
||||
components->setComponentVersion("net.minecraftforge", forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", ""));
|
||||
packJson.remove();
|
||||
fallback = false;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(jarmodDir.exists())
|
||||
{
|
||||
qDebug() << "Found jarmods, installing...";
|
||||
|
||||
QStringList jarmods;
|
||||
for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
|
||||
{
|
||||
qDebug() << "Jarmod:" << info.fileName();
|
||||
jarmods.push_back(info.absoluteFilePath());
|
||||
}
|
||||
|
||||
components->installJarMods(jarmods);
|
||||
fallback = false;
|
||||
}
|
||||
|
||||
//just nuke unzip directory, it s not needed anymore
|
||||
FS::deletePath(m_stagingPath + "/unzip");
|
||||
|
||||
if(fallback)
|
||||
{
|
||||
//TODO: Some fallback mechanism... or just keep failing!
|
||||
emitFailed(tr("No installation method found!"));
|
||||
return;
|
||||
}
|
||||
|
||||
components->saveNow();
|
||||
|
||||
progress(4, 4);
|
||||
|
||||
instance.setName(m_instName);
|
||||
if(m_instIcon == "default")
|
||||
{
|
||||
m_instIcon = "ftb_logo";
|
||||
}
|
||||
instance.setIconKey(m_instIcon);
|
||||
instanceSettings->resumeSave();
|
||||
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
if(abortable)
|
||||
{
|
||||
return netJobContainer->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
55
launcher/modplatform/legacy_ftb/PackInstallTask.h
Normal file
55
launcher/modplatform/legacy_ftb/PackInstallTask.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include "InstanceTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "quazip.h"
|
||||
#include "quazipdir.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "PackHelpers.h"
|
||||
|
||||
#include <nonstd/optional>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
class PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(Modpack pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private:
|
||||
void downloadPack();
|
||||
void unzip();
|
||||
void install();
|
||||
|
||||
private slots:
|
||||
void onDownloadSucceeded();
|
||||
void onDownloadFailed(QString reason);
|
||||
void onDownloadProgress(qint64 current, qint64 total);
|
||||
|
||||
void onUnzipFinished();
|
||||
void onUnzipCanceled();
|
||||
|
||||
private: /* data */
|
||||
bool abortable = false;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
NetJobPtr netJobContainer;
|
||||
QString archivePath;
|
||||
|
||||
Modpack m_pack;
|
||||
QString m_version;
|
||||
};
|
||||
|
||||
}
|
41
launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
Normal file
41
launcher/modplatform/legacy_ftb/PrivatePackManager.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "PrivatePackManager.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PrivatePackManager::load()
|
||||
{
|
||||
try
|
||||
{
|
||||
currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet();
|
||||
dirty = false;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
currentPacks = {};
|
||||
qWarning() << "Failed to read third party FTB pack codes from" << m_filename;
|
||||
}
|
||||
}
|
||||
|
||||
void PrivatePackManager::save() const
|
||||
{
|
||||
if(!dirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
QStringList list = currentPacks.toList();
|
||||
FS::write(m_filename, list.join('\n').toUtf8());
|
||||
dirty = false;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
qWarning() << "Failed to write third party FTB pack codes to" << m_filename;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
launcher/modplatform/legacy_ftb/PrivatePackManager.h
Normal file
43
launcher/modplatform/legacy_ftb/PrivatePackManager.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
class PrivatePackManager
|
||||
{
|
||||
public:
|
||||
~PrivatePackManager()
|
||||
{
|
||||
save();
|
||||
}
|
||||
void load();
|
||||
void save() const;
|
||||
bool empty() const
|
||||
{
|
||||
return currentPacks.empty();
|
||||
}
|
||||
const QSet<QString> &getCurrentPackCodes() const
|
||||
{
|
||||
return currentPacks;
|
||||
}
|
||||
void add(const QString &code)
|
||||
{
|
||||
currentPacks.insert(code);
|
||||
dirty = true;
|
||||
}
|
||||
void remove(const QString &code)
|
||||
{
|
||||
currentPacks.remove(code);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
private:
|
||||
QSet<QString> currentPacks;
|
||||
QString m_filename = "private_packs.txt";
|
||||
mutable bool dirty = false;
|
||||
};
|
||||
|
||||
}
|
209
launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
Normal file
209
launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#include "FTBPackInstallTask.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "Env.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
namespace ModpacksCH {
|
||||
|
||||
PackInstallTask::PackInstallTask(Modpack pack, QString version)
|
||||
{
|
||||
m_pack = pack;
|
||||
m_version_name = version;
|
||||
}
|
||||
|
||||
bool PackInstallTask::abort()
|
||||
{
|
||||
if(abortable)
|
||||
{
|
||||
return jobPtr->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PackInstallTask::executeTask()
|
||||
{
|
||||
// Find pack version
|
||||
bool found = false;
|
||||
VersionInfo version;
|
||||
|
||||
for(auto vInfo : m_pack.versions) {
|
||||
if (vInfo.name == m_version_name) {
|
||||
found = true;
|
||||
version = vInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
|
||||
return;
|
||||
}
|
||||
|
||||
auto *netJob = new NetJob("ModpacksCH::VersionFetch");
|
||||
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2")
|
||||
.arg(m_pack.id).arg(version.id);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadSucceeded()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
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() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
|
||||
ModpacksCH::Version version;
|
||||
try
|
||||
{
|
||||
ModpacksCH::loadVersion(version, obj);
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||
return;
|
||||
}
|
||||
m_version = version;
|
||||
|
||||
downloadPack();
|
||||
}
|
||||
|
||||
void PackInstallTask::onDownloadFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void PackInstallTask::downloadPack()
|
||||
{
|
||||
setStatus(tr("Downloading mods..."));
|
||||
|
||||
jobPtr.reset(new NetJob(tr("Mod download")));
|
||||
for(auto file : m_version.files) {
|
||||
if(file.serverOnly) continue;
|
||||
|
||||
QFileInfo fileName(file.name);
|
||||
auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix();
|
||||
|
||||
auto entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", file.path, file.name);
|
||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||
|
||||
qDebug() << "Will download" << file.url << "to" << path;
|
||||
filesToCopy[entry->getFullPath()] = path;
|
||||
|
||||
auto dl = Net::Download::makeCached(file.url, entry);
|
||||
if (!file.sha1.isEmpty()) {
|
||||
auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
}
|
||||
jobPtr->addNetAction(dl);
|
||||
}
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
install();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
|
||||
void PackInstallTask::install()
|
||||
{
|
||||
setStatus(tr("Copying modpack files"));
|
||||
|
||||
for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) {
|
||||
auto &from = iter.key();
|
||||
auto &to = iter.value();
|
||||
FS::copy fileCopyOperation(from, to);
|
||||
if(!fileCopyOperation()) {
|
||||
qWarning() << "Failed to copy" << from << "to" << to;
|
||||
emitFailed(tr("Failed to copy files"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(tr("Installing modpack"));
|
||||
|
||||
auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
|
||||
instanceSettings->suspendSave();
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
|
||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
|
||||
for(auto target : m_version.targets) {
|
||||
if(target.type == "game" && target.name == "minecraft") {
|
||||
components->setComponentVersion("net.minecraft", target.version, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto target : m_version.targets) {
|
||||
if(target.type != "modloader") continue;
|
||||
|
||||
if(target.name == "forge") {
|
||||
components->setComponentVersion("net.minecraftforge", target.version, true);
|
||||
}
|
||||
else if(target.name == "fabric") {
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", target.version, true);
|
||||
}
|
||||
}
|
||||
|
||||
// install any jar mods
|
||||
QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods"));
|
||||
if (jarModsDir.exists()) {
|
||||
QStringList jarMods;
|
||||
|
||||
for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
|
||||
jarMods.push_back(info.absoluteFilePath());
|
||||
}
|
||||
|
||||
components->installJarMods(jarMods);
|
||||
}
|
||||
|
||||
components->saveNow();
|
||||
|
||||
instance.setName(m_instName);
|
||||
instance.setIconKey(m_instIcon);
|
||||
instanceSettings->resumeSave();
|
||||
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
}
|
46
launcher/modplatform/modpacksch/FTBPackInstallTask.h
Normal file
46
launcher/modplatform/modpacksch/FTBPackInstallTask.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "FTBPackManifest.h"
|
||||
|
||||
#include "InstanceTask.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
namespace ModpacksCH {
|
||||
|
||||
class PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(Modpack pack, QString version);
|
||||
virtual ~PackInstallTask(){}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void onDownloadSucceeded();
|
||||
void onDownloadFailed(QString reason);
|
||||
|
||||
private:
|
||||
void downloadPack();
|
||||
void install();
|
||||
|
||||
private:
|
||||
bool abortable = false;
|
||||
|
||||
NetJobPtr jobPtr;
|
||||
QByteArray response;
|
||||
|
||||
Modpack m_pack;
|
||||
QString m_version_name;
|
||||
Version m_version;
|
||||
|
||||
QMap<QString, QString> filesToCopy;
|
||||
|
||||
};
|
||||
|
||||
}
|
156
launcher/modplatform/modpacksch/FTBPackManifest.cpp
Normal file
156
launcher/modplatform/modpacksch/FTBPackManifest.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include "FTBPackManifest.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj)
|
||||
{
|
||||
s.id = Json::requireInteger(obj, "id");
|
||||
s.minimum = Json::requireInteger(obj, "minimum");
|
||||
s.recommended = Json::requireInteger(obj, "recommended");
|
||||
}
|
||||
|
||||
static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj)
|
||||
{
|
||||
t.id = Json::requireInteger(obj, "id");
|
||||
t.name = Json::requireString(obj, "name");
|
||||
}
|
||||
|
||||
static void loadArt(ModpacksCH::Art & a, QJsonObject & obj)
|
||||
{
|
||||
a.id = Json::requireInteger(obj, "id");
|
||||
a.url = Json::requireString(obj, "url");
|
||||
a.type = Json::requireString(obj, "type");
|
||||
a.width = Json::requireInteger(obj, "width");
|
||||
a.height = Json::requireInteger(obj, "height");
|
||||
a.compressed = Json::requireBoolean(obj, "compressed");
|
||||
a.sha1 = Json::requireString(obj, "sha1");
|
||||
a.size = Json::requireInteger(obj, "size");
|
||||
a.updated = Json::requireInteger(obj, "updated");
|
||||
}
|
||||
|
||||
static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj)
|
||||
{
|
||||
a.id = Json::requireInteger(obj, "id");
|
||||
a.name = Json::requireString(obj, "name");
|
||||
a.type = Json::requireString(obj, "type");
|
||||
a.website = Json::requireString(obj, "website");
|
||||
a.updated = Json::requireInteger(obj, "updated");
|
||||
}
|
||||
|
||||
static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj)
|
||||
{
|
||||
v.id = Json::requireInteger(obj, "id");
|
||||
v.name = Json::requireString(obj, "name");
|
||||
v.type = Json::requireString(obj, "type");
|
||||
v.updated = Json::requireInteger(obj, "updated");
|
||||
auto specs = Json::requireObject(obj, "specs");
|
||||
loadSpecs(v.specs, specs);
|
||||
}
|
||||
|
||||
void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj)
|
||||
{
|
||||
m.id = Json::requireInteger(obj, "id");
|
||||
m.name = Json::requireString(obj, "name");
|
||||
m.synopsis = Json::requireString(obj, "synopsis");
|
||||
m.description = Json::requireString(obj, "description");
|
||||
m.type = Json::requireString(obj, "type");
|
||||
m.featured = Json::requireBoolean(obj, "featured");
|
||||
m.installs = Json::requireInteger(obj, "installs");
|
||||
m.plays = Json::requireInteger(obj, "plays");
|
||||
m.updated = Json::requireInteger(obj, "updated");
|
||||
m.refreshed = Json::requireInteger(obj, "refreshed");
|
||||
auto artArr = Json::requireArray(obj, "art");
|
||||
for (QJsonValueRef artRaw : artArr)
|
||||
{
|
||||
auto artObj = Json::requireObject(artRaw);
|
||||
ModpacksCH::Art art;
|
||||
loadArt(art, artObj);
|
||||
m.art.append(art);
|
||||
}
|
||||
auto authorArr = Json::requireArray(obj, "authors");
|
||||
for (QJsonValueRef authorRaw : authorArr)
|
||||
{
|
||||
auto authorObj = Json::requireObject(authorRaw);
|
||||
ModpacksCH::Author author;
|
||||
loadAuthor(author, authorObj);
|
||||
m.authors.append(author);
|
||||
}
|
||||
auto versionArr = Json::requireArray(obj, "versions");
|
||||
for (QJsonValueRef versionRaw : versionArr)
|
||||
{
|
||||
auto versionObj = Json::requireObject(versionRaw);
|
||||
ModpacksCH::VersionInfo version;
|
||||
loadVersionInfo(version, versionObj);
|
||||
m.versions.append(version);
|
||||
}
|
||||
auto tagArr = Json::requireArray(obj, "tags");
|
||||
for (QJsonValueRef tagRaw : tagArr)
|
||||
{
|
||||
auto tagObj = Json::requireObject(tagRaw);
|
||||
ModpacksCH::Tag tag;
|
||||
loadTag(tag, tagObj);
|
||||
m.tags.append(tag);
|
||||
}
|
||||
m.updated = Json::requireInteger(obj, "updated");
|
||||
}
|
||||
|
||||
static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj)
|
||||
{
|
||||
a.id = Json::requireInteger(obj, "id");
|
||||
a.name = Json::requireString(obj, "name");
|
||||
a.type = Json::requireString(obj, "type");
|
||||
a.version = Json::requireString(obj, "version");
|
||||
a.updated = Json::requireInteger(obj, "updated");
|
||||
}
|
||||
|
||||
static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
|
||||
{
|
||||
a.id = Json::requireInteger(obj, "id");
|
||||
a.type = Json::requireString(obj, "type");
|
||||
a.path = Json::requireString(obj, "path");
|
||||
a.name = Json::requireString(obj, "name");
|
||||
a.version = Json::requireString(obj, "version");
|
||||
a.url = Json::requireString(obj, "url");
|
||||
a.sha1 = Json::requireString(obj, "sha1");
|
||||
a.size = Json::requireInteger(obj, "size");
|
||||
a.clientOnly = Json::requireBoolean(obj, "clientonly");
|
||||
a.serverOnly = Json::requireBoolean(obj, "serveronly");
|
||||
a.optional = Json::requireBoolean(obj, "optional");
|
||||
a.updated = Json::requireInteger(obj, "updated");
|
||||
}
|
||||
|
||||
void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
|
||||
{
|
||||
m.id = Json::requireInteger(obj, "id");
|
||||
m.parent = Json::requireInteger(obj, "parent");
|
||||
m.name = Json::requireString(obj, "name");
|
||||
m.type = Json::requireString(obj, "type");
|
||||
m.installs = Json::requireInteger(obj, "installs");
|
||||
m.plays = Json::requireInteger(obj, "plays");
|
||||
m.updated = Json::requireInteger(obj, "updated");
|
||||
m.refreshed = Json::requireInteger(obj, "refreshed");
|
||||
auto specs = Json::requireObject(obj, "specs");
|
||||
loadSpecs(m.specs, specs);
|
||||
auto targetArr = Json::requireArray(obj, "targets");
|
||||
for (QJsonValueRef targetRaw : targetArr)
|
||||
{
|
||||
auto versionObj = Json::requireObject(targetRaw);
|
||||
ModpacksCH::VersionTarget target;
|
||||
loadVersionTarget(target, versionObj);
|
||||
m.targets.append(target);
|
||||
}
|
||||
auto fileArr = Json::requireArray(obj, "files");
|
||||
for (QJsonValueRef fileRaw : fileArr)
|
||||
{
|
||||
auto fileObj = Json::requireObject(fileRaw);
|
||||
ModpacksCH::VersionFile file;
|
||||
loadVersionFile(file, fileObj);
|
||||
m.files.append(file);
|
||||
}
|
||||
}
|
||||
|
||||
//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj)
|
||||
//{
|
||||
// m.content = Json::requireString(obj, "content");
|
||||
// m.updated = Json::requireInteger(obj, "updated");
|
||||
//}
|
125
launcher/modplatform/modpacksch/FTBPackManifest.h
Normal file
125
launcher/modplatform/modpacksch/FTBPackManifest.h
Normal file
@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QUrl>
|
||||
#include <QJsonObject>
|
||||
#include <QMetaType>
|
||||
|
||||
namespace ModpacksCH
|
||||
{
|
||||
|
||||
struct Specs
|
||||
{
|
||||
int id;
|
||||
int minimum;
|
||||
int recommended;
|
||||
};
|
||||
|
||||
struct Tag
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
};
|
||||
|
||||
struct Art
|
||||
{
|
||||
int id;
|
||||
QString url;
|
||||
QString type;
|
||||
int width;
|
||||
int height;
|
||||
bool compressed;
|
||||
QString sha1;
|
||||
int size;
|
||||
int64_t updated;
|
||||
};
|
||||
|
||||
struct Author
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
QString type;
|
||||
QString website;
|
||||
int64_t updated;
|
||||
};
|
||||
|
||||
struct VersionInfo
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
QString type;
|
||||
int64_t updated;
|
||||
Specs specs;
|
||||
};
|
||||
|
||||
struct Modpack
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
QString synopsis;
|
||||
QString description;
|
||||
QString type;
|
||||
bool featured;
|
||||
int installs;
|
||||
int plays;
|
||||
int64_t updated;
|
||||
int64_t refreshed;
|
||||
QVector<Art> art;
|
||||
QVector<Author> authors;
|
||||
QVector<VersionInfo> versions;
|
||||
QVector<Tag> tags;
|
||||
};
|
||||
|
||||
struct VersionTarget
|
||||
{
|
||||
int id;
|
||||
QString type;
|
||||
QString name;
|
||||
QString version;
|
||||
int64_t updated;
|
||||
};
|
||||
|
||||
struct VersionFile
|
||||
{
|
||||
int id;
|
||||
QString type;
|
||||
QString path;
|
||||
QString name;
|
||||
QString version;
|
||||
QString url;
|
||||
QString sha1;
|
||||
int size;
|
||||
bool clientOnly;
|
||||
bool serverOnly;
|
||||
bool optional;
|
||||
int64_t updated;
|
||||
};
|
||||
|
||||
struct Version
|
||||
{
|
||||
int id;
|
||||
int parent;
|
||||
QString name;
|
||||
QString type;
|
||||
int installs;
|
||||
int plays;
|
||||
int64_t updated;
|
||||
int64_t refreshed;
|
||||
Specs specs;
|
||||
QVector<VersionTarget> targets;
|
||||
QVector<VersionFile> files;
|
||||
};
|
||||
|
||||
struct VersionChangelog
|
||||
{
|
||||
QString content;
|
||||
int64_t updated;
|
||||
};
|
||||
|
||||
void loadModpack(Modpack & m, QJsonObject & obj);
|
||||
|
||||
void loadVersion(Version & m, QJsonObject & obj);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(ModpacksCH::Modpack)
|
141
launcher/modplatform/technic/SingleZipPackInstallTask.cpp
Normal file
141
launcher/modplatform/technic/SingleZipPackInstallTask.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/* 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 "SingleZipPackInstallTask.h"
|
||||
|
||||
#include "Env.h"
|
||||
#include "MMCZip.h"
|
||||
#include "TechnicPackProcessor.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <FileSystem.h>
|
||||
|
||||
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
||||
{
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_minecraftVersion = minecraftVersion;
|
||||
}
|
||||
|
||||
bool Technic::SingleZipPackInstallTask::abort() {
|
||||
if(m_abortable)
|
||||
{
|
||||
return m_filesNetJob->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||
|
||||
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
|
||||
auto entry = ENV.metacache()->resolveEntry("general", path);
|
||||
entry->setStale(true);
|
||||
m_filesNetJob.reset(new NetJob(tr("Modpack download")));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||
m_archivePath = entry->getFullPath();
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
||||
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
|
||||
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::downloadSucceeded()
|
||||
{
|
||||
m_abortable = false;
|
||||
|
||||
setStatus(tr("Extracting modpack"));
|
||||
QDir extractDir(FS::PathCombine(m_stagingPath, ".minecraft"));
|
||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||
|
||||
// open the zip and find relevant files in it
|
||||
m_packZip.reset(new QuaZip(m_archivePath));
|
||||
if (!m_packZip->open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed(tr("Unable to open supplied modpack zip file."));
|
||||
return;
|
||||
}
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath());
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SingleZipPackInstallTask::extractAborted);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::downloadFailed(QString reason)
|
||||
{
|
||||
m_abortable = false;
|
||||
emitFailed(reason);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
{
|
||||
m_abortable = true;
|
||||
setProgress(current / 2, total);
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::extractFinished()
|
||||
{
|
||||
m_packZip.reset();
|
||||
if (!m_extractFuture.result())
|
||||
{
|
||||
emitFailed(tr("Failed to extract modpack"));
|
||||
return;
|
||||
}
|
||||
QDir extractDir(m_stagingPath);
|
||||
|
||||
qDebug() << "Fixing permissions for extracted pack files...";
|
||||
QDirIterator it(extractDir, QDirIterator::Subdirectories);
|
||||
while (it.hasNext())
|
||||
{
|
||||
auto filepath = it.next();
|
||||
QFileInfo file(filepath);
|
||||
auto permissions = QFile::permissions(filepath);
|
||||
auto origPermissions = permissions;
|
||||
if (file.isDir())
|
||||
{
|
||||
// Folder +rwx for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
|
||||
}
|
||||
else
|
||||
{
|
||||
// File +rw for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||
}
|
||||
if (origPermissions != permissions)
|
||||
{
|
||||
if (!QFile::setPermissions(filepath, permissions))
|
||||
{
|
||||
logWarning(tr("Could not fix permissions for %1").arg(filepath));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Fixed" << filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
||||
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded);
|
||||
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed);
|
||||
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion);
|
||||
}
|
||||
|
||||
void Technic::SingleZipPackInstallTask::extractAborted()
|
||||
{
|
||||
emitFailed(tr("Instance import has been aborted."));
|
||||
}
|
64
launcher/modplatform/technic/SingleZipPackInstallTask.h
Normal file
64
launcher/modplatform/technic/SingleZipPackInstallTask.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* 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 "InstanceTask.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "quazip.h"
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include <nonstd/optional>
|
||||
|
||||
namespace Technic {
|
||||
|
||||
class SingleZipPackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
void executeTask() override;
|
||||
|
||||
|
||||
private slots:
|
||||
void downloadSucceeded();
|
||||
void downloadFailed(QString reason);
|
||||
void downloadProgressChanged(qint64 current, qint64 total);
|
||||
void extractFinished();
|
||||
void extractAborted();
|
||||
|
||||
private:
|
||||
bool m_abortable = false;
|
||||
|
||||
QUrl m_sourceUrl;
|
||||
QString m_minecraftVersion;
|
||||
QString m_archivePath;
|
||||
NetJobPtr m_filesNetJob;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<nonstd::optional<QStringList>> m_extractFuture;
|
||||
QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
|
||||
};
|
||||
|
||||
} // namespace Technic
|
207
launcher/modplatform/technic/SolderPackInstallTask.cpp
Normal file
207
launcher/modplatform/technic/SolderPackInstallTask.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
/* 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 "SolderPackInstallTask.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <Json.h>
|
||||
#include <QtConcurrentRun>
|
||||
#include <MMCZip.h>
|
||||
#include "TechnicPackProcessor.h"
|
||||
|
||||
Technic::SolderPackInstallTask::SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
||||
{
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_minecraftVersion = minecraftVersion;
|
||||
}
|
||||
|
||||
bool Technic::SolderPackInstallTask::abort() {
|
||||
if(m_abortable)
|
||||
{
|
||||
return m_filesNetJob->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString()));
|
||||
m_filesNetJob.reset(new NetJob(tr("Finding recommended version")));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded);
|
||||
connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::versionSucceeded()
|
||||
{
|
||||
try
|
||||
{
|
||||
QJsonDocument doc = Json::requireDocument(m_response);
|
||||
QJsonObject obj = Json::requireObject(doc);
|
||||
QString version = Json::requireString(obj, "recommended", "__placeholder__");
|
||||
m_sourceUrl = m_sourceUrl.toString() + '/' + version;
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(e.cause());
|
||||
m_filesNetJob.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString()));
|
||||
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files")));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
|
||||
connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::fileListSucceeded()
|
||||
{
|
||||
setStatus(tr("Downloading modpack:"));
|
||||
QStringList modUrls;
|
||||
try
|
||||
{
|
||||
QJsonDocument doc = Json::requireDocument(m_response);
|
||||
QJsonObject obj = Json::requireObject(doc);
|
||||
QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__");
|
||||
if (!minecraftVersion.isEmpty())
|
||||
m_minecraftVersion = minecraftVersion;
|
||||
QJsonArray mods = Json::requireArray(obj, "mods", "'mods'");
|
||||
for (auto mod: mods)
|
||||
{
|
||||
QJsonObject modObject = Json::requireObject(mod);
|
||||
modUrls.append(Json::requireString(modObject, "url", "'url'"));
|
||||
}
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(e.cause());
|
||||
m_filesNetJob.reset();
|
||||
return;
|
||||
}
|
||||
m_filesNetJob.reset(new NetJob(tr("Downloading modpack")));
|
||||
int i = 0;
|
||||
for (auto &modUrl: modUrls)
|
||||
{
|
||||
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, path));
|
||||
i++;
|
||||
}
|
||||
|
||||
m_modCount = modUrls.size();
|
||||
|
||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
|
||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
|
||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::downloadSucceeded()
|
||||
{
|
||||
m_abortable = false;
|
||||
|
||||
setStatus(tr("Extracting modpack"));
|
||||
m_filesNetJob.reset();
|
||||
m_extractFuture = QtConcurrent::run([this]()
|
||||
{
|
||||
int i = 0;
|
||||
QString extractDir = FS::PathCombine(m_stagingPath, ".minecraft");
|
||||
FS::ensureFolderPathExists(extractDir);
|
||||
|
||||
while (m_modCount > i)
|
||||
{
|
||||
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
|
||||
if (!MMCZip::extractDir(path, extractDir))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SolderPackInstallTask::extractFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SolderPackInstallTask::extractAborted);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::downloadFailed(QString reason)
|
||||
{
|
||||
m_abortable = false;
|
||||
emitFailed(reason);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
{
|
||||
m_abortable = true;
|
||||
setProgress(current / 2, total);
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::extractFinished()
|
||||
{
|
||||
if (!m_extractFuture.result())
|
||||
{
|
||||
emitFailed(tr("Failed to extract modpack"));
|
||||
return;
|
||||
}
|
||||
QDir extractDir(m_stagingPath);
|
||||
|
||||
qDebug() << "Fixing permissions for extracted pack files...";
|
||||
QDirIterator it(extractDir, QDirIterator::Subdirectories);
|
||||
while (it.hasNext())
|
||||
{
|
||||
auto filepath = it.next();
|
||||
QFileInfo file(filepath);
|
||||
auto permissions = QFile::permissions(filepath);
|
||||
auto origPermissions = permissions;
|
||||
if(file.isDir())
|
||||
{
|
||||
// Folder +rwx for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
|
||||
}
|
||||
else
|
||||
{
|
||||
// File +rw for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||
}
|
||||
if(origPermissions != permissions)
|
||||
{
|
||||
if(!QFile::setPermissions(filepath, permissions))
|
||||
{
|
||||
logWarning(tr("Could not fix permissions for %1").arg(filepath));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Fixed" << filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
|
||||
connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded);
|
||||
connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed);
|
||||
packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion, true);
|
||||
}
|
||||
|
||||
void Technic::SolderPackInstallTask::extractAborted()
|
||||
{
|
||||
emitFailed(tr("Instance import has been aborted."));
|
||||
return;
|
||||
}
|
||||
|
60
launcher/modplatform/technic/SolderPackInstallTask.h
Normal file
60
launcher/modplatform/technic/SolderPackInstallTask.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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 <InstanceTask.h>
|
||||
#include <net/NetJob.h>
|
||||
#include <tasks/Task.h>
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
namespace Technic
|
||||
{
|
||||
class SolderPackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void versionSucceeded();
|
||||
void fileListSucceeded();
|
||||
void downloadSucceeded();
|
||||
void downloadFailed(QString reason);
|
||||
void downloadProgressChanged(qint64 current, qint64 total);
|
||||
void extractFinished();
|
||||
void extractAborted();
|
||||
|
||||
private:
|
||||
bool m_abortable = false;
|
||||
|
||||
NetJobPtr m_filesNetJob;
|
||||
QUrl m_sourceUrl;
|
||||
QString m_minecraftVersion;
|
||||
QByteArray m_response;
|
||||
QTemporaryDir m_outputDir;
|
||||
int m_modCount;
|
||||
QFuture<bool> m_extractFuture;
|
||||
QFutureWatcher<bool> m_extractFutureWatcher;
|
||||
};
|
||||
}
|
208
launcher/modplatform/technic/TechnicPackProcessor.cpp
Normal file
208
launcher/modplatform/technic/TechnicPackProcessor.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
/* Copyright 2020-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 "TechnicPackProcessor.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <Json.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
#include <minecraft/PackProfile.h>
|
||||
#include <quazip.h>
|
||||
#include <quazipdir.h>
|
||||
#include <quazipfile.h>
|
||||
#include <settings/INISettingsObject.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion, const bool isSolder)
|
||||
{
|
||||
QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft");
|
||||
QString configPath = FS::PathCombine(stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
MinecraftInstance instance(globalSettings, instanceSettings, stagingPath);
|
||||
|
||||
instance.setName(instName);
|
||||
|
||||
if (instIcon != "default")
|
||||
{
|
||||
instance.setIconKey(instIcon);
|
||||
}
|
||||
|
||||
auto components = instance.getPackProfile();
|
||||
components->buildingFromScratch();
|
||||
|
||||
QByteArray data;
|
||||
|
||||
QString modpackJar = FS::PathCombine(minecraftPath, "bin", "modpack.jar");
|
||||
QString versionJson = FS::PathCombine(minecraftPath, "bin", "version.json");
|
||||
QString fmlMinecraftVersion;
|
||||
if (QFile::exists(modpackJar))
|
||||
{
|
||||
QuaZip zipFile(modpackJar);
|
||||
if (!zipFile.open(QuaZip::mdUnzip))
|
||||
{
|
||||
emit failed(tr("Unable to open \"bin/modpack.jar\" file!"));
|
||||
return;
|
||||
}
|
||||
QuaZipDir zipFileRoot(&zipFile, "/");
|
||||
if (zipFileRoot.exists("/version.json"))
|
||||
{
|
||||
if (zipFileRoot.exists("/fmlversion.properties"))
|
||||
{
|
||||
zipFile.setCurrentFile("fmlversion.properties");
|
||||
QuaZipFile file(&zipFile);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
emit failed(tr("Unable to open \"fmlversion.properties\"!"));
|
||||
return;
|
||||
}
|
||||
QByteArray fmlVersionData = file.readAll();
|
||||
file.close();
|
||||
INIFile iniFile;
|
||||
iniFile.loadFile(fmlVersionData);
|
||||
// If not present, this evaluates to a null string
|
||||
fmlMinecraftVersion = iniFile["fmlbuild.mcversion"].toString();
|
||||
}
|
||||
zipFile.setCurrentFile("version.json", QuaZip::csSensitive);
|
||||
QuaZipFile file(&zipFile);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
emit failed(tr("Unable to open \"version.json\"!"));
|
||||
return;
|
||||
}
|
||||
data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (minecraftVersion.isEmpty())
|
||||
emit failed(tr("Could not find \"version.json\" inside \"bin/modpack.jar\", but minecraft version is unknown"));
|
||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||
components->installJarMods({modpackJar});
|
||||
|
||||
// Forge for 1.4.7 and for 1.5.2 require extra libraries.
|
||||
// Figure out the forge version and add it as a component
|
||||
// (the code still comes from the jar mod installed above)
|
||||
if (zipFileRoot.exists("/forgeversion.properties"))
|
||||
{
|
||||
zipFile.setCurrentFile("forgeversion.properties", QuaZip::csSensitive);
|
||||
QuaZipFile file(&zipFile);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
// Really shouldn't happen, but error handling shall not be forgotten
|
||||
emit failed(tr("Unable to open \"forgeversion.properties\""));
|
||||
return;
|
||||
}
|
||||
QByteArray forgeVersionData = file.readAll();
|
||||
file.close();
|
||||
INIFile iniFile;
|
||||
iniFile.loadFile(forgeVersionData);
|
||||
QString major, minor, revision, build;
|
||||
major = iniFile["forge.major.number"].toString();
|
||||
minor = iniFile["forge.minor.number"].toString();
|
||||
revision = iniFile["forge.revision.number"].toString();
|
||||
build = iniFile["forge.build.number"].toString();
|
||||
|
||||
if (major.isEmpty() || minor.isEmpty() || revision.isEmpty() || build.isEmpty())
|
||||
{
|
||||
emit failed(tr("Invalid \"forgeversion.properties\"!"));
|
||||
return;
|
||||
}
|
||||
|
||||
components->setComponentVersion("net.minecraftforge", major + '.' + minor + '.' + revision + '.' + build);
|
||||
}
|
||||
|
||||
components->saveNow();
|
||||
emit succeeded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (QFile::exists(versionJson))
|
||||
{
|
||||
QFile file(versionJson);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
{
|
||||
emit failed(tr("Unable to open \"version.json\"!"));
|
||||
return;
|
||||
}
|
||||
data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the "Vanilla" modpack, excluded by the search code
|
||||
emit failed(tr("Unable to find a \"version.json\"!"));
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
QJsonDocument doc = Json::requireDocument(data);
|
||||
QJsonObject root = Json::requireObject(doc, "version.json");
|
||||
QString minecraftVersion = Json::ensureString(root, "inheritsFrom", QString(), "");
|
||||
if (minecraftVersion.isEmpty())
|
||||
{
|
||||
if (fmlMinecraftVersion.isEmpty())
|
||||
{
|
||||
emit failed(tr("Could not understand \"version.json\":\ninheritsFrom is missing"));
|
||||
return;
|
||||
}
|
||||
minecraftVersion = fmlMinecraftVersion;
|
||||
}
|
||||
components->setComponentVersion("net.minecraft", minecraftVersion, true);
|
||||
for (auto library: Json::ensureArray(root, "libraries", {}))
|
||||
{
|
||||
if (!library.isObject())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto libraryObject = Json::ensureObject(library, {}, "");
|
||||
auto libraryName = Json::ensureString(libraryObject, "name", "", "");
|
||||
|
||||
if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-'))
|
||||
{
|
||||
QString libraryVersion = libraryName.section(':', 2);
|
||||
if (!libraryVersion.startsWith("1.7.10-"))
|
||||
{
|
||||
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part
|
||||
components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
|
||||
}
|
||||
}
|
||||
else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
|
||||
{
|
||||
components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
|
||||
}
|
||||
else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
|
||||
{
|
||||
components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emit failed(tr("Could not understand \"version.json\":\n") + e.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
components->saveNow();
|
||||
emit succeeded();
|
||||
}
|
35
launcher/modplatform/technic/TechnicPackProcessor.h
Normal file
35
launcher/modplatform/technic/TechnicPackProcessor.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* Copyright 2020-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 "settings/SettingsObject.h"
|
||||
|
||||
namespace Technic
|
||||
{
|
||||
// not exporting it, only used in SingleZipPackInstallTask, InstanceImportTask and SolderPackInstallTask
|
||||
class TechnicPackProcessor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void succeeded();
|
||||
void failed(QString reason);
|
||||
|
||||
public:
|
||||
void run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion=QString(), const bool isSolder = false);
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user