aboutsummaryrefslogtreecommitdiff
path: root/launcher/modplatform
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/modplatform')
-rw-r--r--launcher/modplatform/EnsureMetadataTask.cpp124
-rw-r--r--launcher/modplatform/EnsureMetadataTask.h21
-rw-r--r--launcher/modplatform/ModAPI.h4
-rw-r--r--launcher/modplatform/ModIndex.cpp36
-rw-r--r--launcher/modplatform/ModIndex.h6
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp144
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.h16
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.cpp64
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.h23
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp7
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.h3
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp37
-rw-r--r--launcher/modplatform/flame/FlameAPI.h1
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp19
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h3
-rw-r--r--launcher/modplatform/helpers/HashUtils.cpp81
-rw-r--r--launcher/modplatform/helpers/HashUtils.h47
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.cpp20
-rw-r--r--launcher/modplatform/helpers/NetworkModAPI.h4
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.h6
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp282
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.h80
-rw-r--r--launcher/modplatform/modpacksch/FTBPackManifest.cpp46
-rw-r--r--launcher/modplatform/modpacksch/FTBPackManifest.h48
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp39
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp2
-rw-r--r--launcher/modplatform/packwiz/Packwiz_test.cpp87
-rw-r--r--launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml13
-rw-r--r--launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml13
-rw-r--r--launcher/modplatform/technic/SingleZipPackInstallTask.h6
30 files changed, 907 insertions, 375 deletions
diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp
index 60c54c4e..234330a7 100644
--- a/launcher/modplatform/EnsureMetadataTask.cpp
+++ b/launcher/modplatform/EnsureMetadataTask.cpp
@@ -3,81 +3,73 @@
#include <MurmurHash2.h>
#include <QDebug>
-#include "FileSystem.h"
#include "Json.h"
+
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
+
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
#include "modplatform/modrinth/ModrinthAPI.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
+
#include "net/NetJob.h"
-#include "tasks/MultipleOptionsTask.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
static ModrinthAPI modrinth_api;
static FlameAPI flame_api;
-EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov)
+EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov)
+ : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr)
{
- auto hash = getHash(mod);
- if (hash.isEmpty())
- emitFail(mod);
- else
- m_mods.insert(hash, mod);
+ auto hash_task = createNewHash(mod);
+ if (!hash_task)
+ return;
+ connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
+ connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
+ hash_task->start();
}
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::Provider prov)
- : Task(nullptr), m_index_dir(dir), m_provider(prov)
+ : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{
+ m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10);
for (auto* mod : mods) {
- if (!mod->valid()) {
- emitFail(mod);
- continue;
- }
-
- auto hash = getHash(mod);
- if (hash.isEmpty()) {
- emitFail(mod);
+ auto hash_task = createNewHash(mod);
+ if (!hash_task)
continue;
- }
-
- m_mods.insert(hash, mod);
+ connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
+ connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
+ m_hashing_task->addTask(hash_task);
}
}
-QString EnsureMetadataTask::getHash(Mod* mod)
+Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod)
{
- /* Here we create a mapping hash -> mod, because we need that relationship to parse the API routes */
- QByteArray jar_data;
- try {
- jar_data = FS::read(mod->fileinfo().absoluteFilePath());
- } catch (FS::FileSystemException& e) {
- qCritical() << QString("Failed to open / read JAR file of %1").arg(mod->name());
- qCritical() << QString("Reason: ") << e.cause();
+ if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER)
+ return nullptr;
- return {};
- }
-
- switch (m_provider) {
- case ModPlatform::Provider::MODRINTH: {
- auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first();
+ return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider);
+}
- return QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, hash_type).toHex());
- }
- case ModPlatform::Provider::FLAME: {
- QByteArray jar_data_treated;
- for (char c : jar_data) {
- // CF-specific
- if (!(c == 9 || c == 10 || c == 13 || c == 32))
- jar_data_treated.push_back(c);
- }
+QString EnsureMetadataTask::getExistingHash(Mod* mod)
+{
+ // Check for already computed hashes
+ // (linear on the number of mods vs. linear on the size of the mod's JAR)
+ auto it = m_mods.keyValueBegin();
+ while (it != m_mods.keyValueEnd()) {
+ if ((*it).second == mod)
+ break;
+ it++;
+ }
- return QString::number(MurmurHash2(jar_data_treated, jar_data_treated.length()));
- }
+ // We already have the hash computed
+ if (it != m_mods.keyValueEnd()) {
+ return (*it).first;
}
+ // No existing hash
return {};
}
@@ -110,7 +102,7 @@ void EnsureMetadataTask::executeTask()
}
// Folders don't have metadata
- if (mod->type() == Mod::MOD_FOLDER) {
+ if (mod->type() == ResourceType::FOLDER) {
emitReady(mod);
}
}
@@ -127,11 +119,9 @@ void EnsureMetadataTask::executeTask()
}
auto invalidade_leftover = [this] {
- QMutableHashIterator<QString, Mod*> mods_iter(m_mods);
- while (mods_iter.hasNext()) {
- auto mod = mods_iter.next();
- emitFail(mod.value());
- }
+ for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
+ emitFail(mod.value(), mod.key(), RemoveFromList::No);
+ m_mods.clear();
emitSucceeded();
};
@@ -178,20 +168,44 @@ void EnsureMetadataTask::executeTask()
version_task->start();
}
-void EnsureMetadataTask::emitReady(Mod* m)
+void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove)
{
+ if (!m) {
+ qCritical() << "Tried to mark a null mod as ready.";
+ if (!key.isEmpty())
+ m_mods.remove(key);
+
+ return;
+ }
+
qDebug() << QString("Generated metadata for %1").arg(m->name());
emit metadataReady(m);
- m_mods.remove(getHash(m));
+ if (remove == RemoveFromList::Yes) {
+ if (key.isEmpty())
+ key = getExistingHash(m);
+ m_mods.remove(key);
+ }
}
-void EnsureMetadataTask::emitFail(Mod* m)
+void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
{
+ if (!m) {
+ qCritical() << "Tried to mark a null mod as failed.";
+ if (!key.isEmpty())
+ m_mods.remove(key);
+
+ return;
+ }
+
qDebug() << QString("Failed to generate metadata for %1").arg(m->name());
emit metadataFailed(m);
- m_mods.remove(getHash(m));
+ if (remove == RemoveFromList::Yes) {
+ if (key.isEmpty())
+ key = getExistingHash(m);
+ m_mods.remove(key);
+ }
}
// Modrinth
diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h
index 79db6976..a8b0851e 100644
--- a/launcher/modplatform/EnsureMetadataTask.h
+++ b/launcher/modplatform/EnsureMetadataTask.h
@@ -1,12 +1,14 @@
#pragma once
#include "ModIndex.h"
-#include "tasks/SequentialTask.h"
#include "net/NetJob.h"
+#include "modplatform/helpers/HashUtils.h"
+
+#include "tasks/ConcurrentTask.h"
+
class Mod;
class QDir;
-class MultipleOptionsTask;
class EnsureMetadataTask : public Task {
Q_OBJECT
@@ -17,6 +19,8 @@ class EnsureMetadataTask : public Task {
~EnsureMetadataTask() = default;
+ Task::Ptr getHashingTask() { return m_hashing_task; }
+
public slots:
bool abort() override;
protected slots:
@@ -31,10 +35,16 @@ class EnsureMetadataTask : public Task {
auto flameProjectsTask() -> NetJob::Ptr;
// Helpers
- void emitReady(Mod*);
- void emitFail(Mod*);
+ enum class RemoveFromList {
+ Yes,
+ No
+ };
+ void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
+ void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
- auto getHash(Mod*) -> QString;
+ // Hashes and stuff
+ auto createNewHash(Mod*) -> Hashing::Hasher::Ptr;
+ auto getExistingHash(Mod*) -> QString;
private slots:
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
@@ -50,5 +60,6 @@ class EnsureMetadataTask : public Task {
ModPlatform::Provider m_provider;
QHash<QString, ModPlatform::IndexedVersion> m_temp_versions;
+ ConcurrentTask* m_hashing_task;
NetJob* m_current_task;
};
diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h
index 4114d83c..c7408835 100644
--- a/launcher/modplatform/ModAPI.h
+++ b/launcher/modplatform/ModAPI.h
@@ -73,7 +73,7 @@ class ModAPI {
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
- virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;
+ virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) = 0;
virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0;
virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0;
@@ -85,7 +85,7 @@ class ModAPI {
ModLoaderTypes loaders;
};
- virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0;
+ virtual void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const = 0;
static auto getModLoaderString(ModLoaderType type) -> const QString {
switch (type) {
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
index 3c4b7887..34fd9f30 100644
--- a/launcher/modplatform/ModIndex.cpp
+++ b/launcher/modplatform/ModIndex.cpp
@@ -19,6 +19,8 @@
#include "modplatform/ModIndex.h"
#include <QCryptographicHash>
+#include <QDebug>
+#include <QIODevice>
namespace ModPlatform {
@@ -53,34 +55,26 @@ auto ProviderCapabilities::hashType(Provider p) -> QStringList
}
return {};
}
-auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray
+
+auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString
{
+ QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1;
switch (p) {
case Provider::MODRINTH: {
- // NOTE: Data is the result of reading the entire JAR file!
-
- // If 'type' was specified, we use that
- if (!type.isEmpty() && hashType(p).contains(type)) {
- if (type == "sha512")
- return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
- else if (type == "sha1")
- return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
- }
-
- return QCryptographicHash::hash(data, QCryptographicHash::Sha512);
+ algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512;
+ break;
}
case Provider::FLAME:
- // If 'type' was specified, we use that
- if (!type.isEmpty() && hashType(p).contains(type)) {
- if(type == "sha1")
- return QCryptographicHash::hash(data, QCryptographicHash::Sha1);
- else if (type == "md5")
- return QCryptographicHash::hash(data, QCryptographicHash::Md5);
- }
-
+ algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5;
break;
}
- return {};
+
+ QCryptographicHash hash(algo);
+ if(!hash.addData(device))
+ qCritical() << "Failed to read JAR to create hash!";
+
+ Q_ASSERT(hash.result().length() == hash.hashLength(algo));
+ return { hash.result().toHex() };
}
} // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index dc297d03..518fed7c 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -24,6 +24,8 @@
#include <QVariant>
#include <QVector>
+class QIODevice;
+
namespace ModPlatform {
enum class Provider {
@@ -36,7 +38,7 @@ class ProviderCapabilities {
auto name(Provider) -> const char*;
auto readableName(Provider) -> QString;
auto hashType(Provider) -> QStringList;
- auto hash(Provider, QByteArray&, QString type = "") -> QByteArray;
+ auto hash(Provider, QIODevice*, QString type = "") -> QString;
};
struct ModpackAuthor {
@@ -73,6 +75,8 @@ struct ExtraPackData {
QString sourceUrl;
QString wikiUrl;
QString discordUrl;
+
+ QString body;
};
struct IndexedPack {
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 0ed0ad29..70a35395 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -60,12 +60,13 @@ namespace ATLauncher {
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
-PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)
+PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode)
{
m_support = support;
m_pack_name = packName;
m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version;
+ m_install_mode = installMode;
}
bool PackInstallTask::abort()
@@ -117,9 +118,30 @@ void PackInstallTask::onDownloadSucceeded()
}
m_version = version;
- // Display install message if one exists
- if (!m_version.messages.install.isEmpty())
- m_support->displayMessage(m_version.messages.install);
+ // Derived from the installation mode
+ QString message;
+ bool resetDirectory;
+
+ switch (m_install_mode) {
+ case InstallMode::Reinstall:
+ case InstallMode::Update:
+ message = m_version.messages.update;
+ resetDirectory = true;
+ break;
+
+ case InstallMode::Install:
+ message = m_version.messages.install;
+ resetDirectory = false;
+ break;
+
+ default:
+ emitFailed(tr("Unsupported installation mode"));
+ return;
+ }
+
+ // Display message if one exists
+ if (!message.isEmpty())
+ m_support->displayMessage(message);
auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
if (!ver) {
@@ -128,6 +150,10 @@ void PackInstallTask::onDownloadSucceeded()
}
minecraftVersion = ver;
+ if (resetDirectory) {
+ deleteExistingFiles();
+ }
+
if(m_version.noConfigs) {
downloadMods();
}
@@ -143,6 +169,116 @@ void PackInstallTask::onDownloadFailed(QString reason)
emitFailed(reason);
}
+void PackInstallTask::deleteExistingFiles()
+{
+ setStatus(tr("Deleting existing files..."));
+
+ // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/delete
+ VersionDeletes deletes;
+ deletes.folders.append(VersionDelete{ "root", "mods%s%" });
+ deletes.folders.append(VersionDelete{ "root", "configs%s%" });
+ deletes.folders.append(VersionDelete{ "root", "bin%s%" });
+
+ // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/keep
+ VersionKeeps keeps;
+ keeps.files.append(VersionKeep{ "root", "mods%s%PortalGunSounds.pak" });
+ keeps.folders.append(VersionKeep{ "root", "mods%s%rei_minimap%s%" });
+ keeps.folders.append(VersionKeep{ "root", "mods%s%VoxelMods%s%" });
+ keeps.files.append(VersionKeep{ "root", "config%s%NEI.cfg" });
+ keeps.files.append(VersionKeep{ "root", "options.txt" });
+ keeps.files.append(VersionKeep{ "root", "servers.dat" });
+
+ // Merge with version deletes and keeps
+ for (const auto& item : m_version.deletes.files)
+ deletes.files.append(item);
+ for (const auto& item : m_version.deletes.folders)
+ deletes.folders.append(item);
+ for (const auto& item : m_version.keeps.files)
+ keeps.files.append(item);
+ for (const auto& item : m_version.keeps.folders)
+ keeps.folders.append(item);
+
+ auto getPathForBase = [this](const QString& base) {
+ auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft");
+
+ if (base == "root") {
+ return minecraftPath;
+ }
+ else if (base == "config") {
+ return FS::PathCombine(minecraftPath, "config");
+ }
+ else {
+ qWarning() << "Unrecognised base path" << base;
+ return minecraftPath;
+ }
+ };
+
+ auto convertToSystemPath = [](const QString& path) {
+ auto t = path;
+ t.replace("%s%", QDir::separator());
+ return t;
+ };
+
+ auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) {
+ for (const auto& item : keeps.files) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto path = FS::PathCombine(basePath, targetPath);
+
+ if (fullPath == path) {
+ return true;
+ }
+ }
+
+ for (const auto& item : keeps.folders) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto path = FS::PathCombine(basePath, targetPath);
+
+ if (fullPath.startsWith(path)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ // Keep track of files to delete
+ QSet<QString> filesToDelete;
+
+ for (const auto& item : deletes.files) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto fullPath = FS::PathCombine(basePath, targetPath);
+
+ if (shouldKeep(fullPath))
+ continue;
+
+ filesToDelete.insert(fullPath);
+ }
+
+ for (const auto& item : deletes.folders) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto fullPath = FS::PathCombine(basePath, targetPath);
+
+ QDirIterator it(fullPath, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ auto path = it.next();
+
+ if (shouldKeep(path))
+ continue;
+
+ filesToDelete.insert(path);
+ }
+ }
+
+ // Delete the files
+ for (const auto& item : filesToDelete) {
+ QFile::remove(item);
+ }
+}
+
QString PackInstallTask::getDirForModType(ModType type, QString raw)
{
switch (type) {
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
index f55873e9..a7124d59 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
@@ -46,10 +46,16 @@
#include "minecraft/PackProfile.h"
#include "meta/Version.h"
-#include <nonstd/optional>
+#include <optional>
namespace ATLauncher {
+enum class InstallMode {
+ Install,
+ Reinstall,
+ Update,
+};
+
class UserInteractionSupport {
public:
@@ -75,7 +81,7 @@ class PackInstallTask : public InstanceTask
Q_OBJECT
public:
- explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);
+ explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install);
virtual ~PackInstallTask(){}
bool canAbort() const override { return true; }
@@ -99,6 +105,7 @@ private:
bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
+ void deleteExistingFiles();
void installConfigs();
void extractConfigs();
void downloadMods();
@@ -117,6 +124,7 @@ private:
NetJob::Ptr jobPtr;
QByteArray response;
+ InstallMode m_install_mode;
QString m_pack_name;
QString m_pack_safe_name;
QString m_version_name;
@@ -131,8 +139,8 @@ private:
Meta::VersionPtr minecraftVersion;
QMap<QString, Meta::VersionPtr> componentsToInstall;
- QFuture<nonstd::optional<QStringList>> m_extractFuture;
- QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+ QFuture<std::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<std::optional<QStringList>> m_extractFutureWatcher;
QFuture<bool> m_modExtractFuture;
QFutureWatcher<bool> m_modExtractFutureWatcher;
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
index 3af02a09..5a458f4e 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
@@ -224,6 +224,64 @@ static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a,
a.depends = Json::ensureString(obj, "depends", "");
}
+static void loadVersionKeep(ATLauncher::VersionKeep& k, QJsonObject& obj)
+{
+ k.base = Json::requireString(obj, "base");
+ k.target = Json::requireString(obj, "target");
+}
+
+static void loadVersionKeeps(ATLauncher::VersionKeeps& k, QJsonObject& obj)
+{
+ if (obj.contains("files")) {
+ auto files = Json::requireArray(obj, "files");
+ for (const auto keepRaw : files) {
+ auto keepObj = Json::requireObject(keepRaw);
+ ATLauncher::VersionKeep keep;
+ loadVersionKeep(keep, keepObj);
+ k.files.append(keep);
+ }
+ }
+
+ if (obj.contains("folders")) {
+ auto folders = Json::requireArray(obj, "folders");
+ for (const auto keepRaw : folders) {
+ auto keepObj = Json::requireObject(keepRaw);
+ ATLauncher::VersionKeep keep;
+ loadVersionKeep(keep, keepObj);
+ k.folders.append(keep);
+ }
+ }
+}
+
+static void loadVersionDelete(ATLauncher::VersionDelete& d, QJsonObject& obj)
+{
+ d.base = Json::requireString(obj, "base");
+ d.target = Json::requireString(obj, "target");
+}
+
+static void loadVersionDeletes(ATLauncher::VersionDeletes& d, QJsonObject& obj)
+{
+ if (obj.contains("files")) {
+ auto files = Json::requireArray(obj, "files");
+ for (const auto deleteRaw : files) {
+ auto deleteObj = Json::requireObject(deleteRaw);
+ ATLauncher::VersionDelete versionDelete;
+ loadVersionDelete(versionDelete, deleteObj);
+ d.files.append(versionDelete);
+ }
+ }
+
+ if (obj.contains("folders")) {
+ auto folders = Json::requireArray(obj, "folders");
+ for (const auto deleteRaw : folders) {
+ auto deleteObj = Json::requireObject(deleteRaw);
+ ATLauncher::VersionDelete versionDelete;
+ loadVersionDelete(versionDelete, deleteObj);
+ d.folders.append(versionDelete);
+ }
+ }
+}
+
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@@ -284,4 +342,10 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
auto messages = Json::ensureObject(obj, "messages");
loadVersionMessages(v.messages, messages);
+
+ auto keeps = Json::ensureObject(obj, "keeps");
+ loadVersionKeeps(v.keeps, keeps);
+
+ auto deletes = Json::ensureObject(obj, "deletes");
+ loadVersionDeletes(v.deletes, deletes);
}
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h
index 43510c50..571c976d 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.h
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.h
@@ -150,6 +150,26 @@ struct VersionMessages
QString update;
};
+struct VersionKeep {
+ QString base;
+ QString target;
+};
+
+struct VersionKeeps {
+ QVector<VersionKeep> files;
+ QVector<VersionKeep> folders;
+};
+
+struct VersionDelete {
+ QString base;
+ QString target;
+};
+
+struct VersionDeletes {
+ QVector<VersionDelete> files;
+ QVector<VersionDelete> folders;
+};
+
struct PackVersionMainClass
{
QString mainClass;
@@ -178,6 +198,9 @@ struct PackVersion
QMap<QString, QString> colours;
QMap<QString, QString> warnings;
VersionMessages messages;
+
+ VersionKeeps keeps;
+ VersionDeletes deletes;
};
void loadVersion(PackVersion & v, QJsonObject & obj);
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index c1f56658..058d2471 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -7,6 +7,13 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAcc
: m_network(network), m_toProcess(toProcess)
{}
+bool Flame::FileResolvingTask::abort()
+{
+ if (m_dljob)
+ return m_dljob->abort();
+ return true;
+}
+
void Flame::FileResolvingTask::executeTask()
{
setStatus(tr("Resolving mod IDs..."));
diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h
index 87981f0a..f71b87ce 100644
--- a/launcher/modplatform/flame/FileResolvingTask.h
+++ b/launc