aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/mod/tasks
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/mod/tasks')
-rw-r--r--launcher/minecraft/mod/tasks/BasicFolderLoadTask.h8
-rw-r--r--launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp177
-rw-r--r--launcher/minecraft/mod/tasks/LocalDataPackParseTask.h65
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp207
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.h34
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp110
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h8
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourceParse.cpp78
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourceParse.h37
-rw-r--r--launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp113
-rw-r--r--launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h62
-rw-r--r--launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp59
-rw-r--r--launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h8
-rw-r--r--launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp190
-rw-r--r--launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h62
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp8
16 files changed, 1100 insertions, 126 deletions
diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
index 2fce2942..3ee7e2e0 100644
--- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
+++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
@@ -26,11 +26,11 @@ class BasicFolderLoadTask : public Task {
public:
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
{
- m_create_func = [](QFileInfo const& entry) -> Resource* {
- return new Resource(entry);
+ m_create_func = [](QFileInfo const& entry) -> Resource::Ptr {
+ return makeShared<Resource>(entry);
};
}
- BasicFolderLoadTask(QDir dir, std::function<Resource*(QFileInfo const&)> create_function)
+ BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
: Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread())
{}
@@ -65,7 +65,7 @@ private:
std::atomic<bool> m_aborted = false;
- std::function<Resource*(QFileInfo const&)> m_create_func;
+ std::function<Resource::Ptr(QFileInfo const&)> m_create_func;
/** This is the thread in which we should put new mod objects */
QThread* m_thread_to_spawn_into;
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
new file mode 100644
index 00000000..5bb44877
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
@@ -0,0 +1,177 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "LocalDataPackParseTask.h"
+
+#include "FileSystem.h"
+#include "Json.h"
+
+#include <quazip/quazip.h>
+#include <quazip/quazipdir.h>
+#include <quazip/quazipfile.h>
+
+#include <QCryptographicHash>
+
+namespace DataPackUtils {
+
+bool process(DataPack& pack, ProcessingLevel level)
+{
+ switch (pack.type()) {
+ case ResourceType::FOLDER:
+ return DataPackUtils::processFolder(pack, level);
+ case ResourceType::ZIPFILE:
+ return DataPackUtils::processZIP(pack, level);
+ default:
+ qWarning() << "Invalid type for data pack parse task!";
+ return false;
+ }
+}
+
+bool processFolder(DataPack& pack, ProcessingLevel level)
+{
+ Q_ASSERT(pack.type() == ResourceType::FOLDER);
+
+ auto mcmeta_invalid = [&pack]() {
+ qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
+ return false; // the mcmeta is not optional
+ };
+
+ QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
+ if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
+ QFile mcmeta_file(mcmeta_file_info.filePath());
+ if (!mcmeta_file.open(QIODevice::ReadOnly))
+ return mcmeta_invalid(); // can't open mcmeta file
+
+ auto data = mcmeta_file.readAll();
+
+ bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
+
+ mcmeta_file.close();
+ if (!mcmeta_result) {
+ return mcmeta_invalid(); // mcmeta invalid
+ }
+ } else {
+ return mcmeta_invalid(); // mcmeta file isn't a valid file
+ }
+
+ QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data"));
+ if (!data_dir_info.exists() || !data_dir_info.isDir()) {
+ return false; // data dir does not exists or isn't valid
+ }
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ return true; // only need basic info already checked
+ }
+
+ return true; // all tests passed
+}
+
+bool processZIP(DataPack& pack, ProcessingLevel level)
+{
+ Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
+
+ QuaZip zip(pack.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return false; // can't open zip file
+
+ QuaZipFile file(&zip);
+
+ auto mcmeta_invalid = [&pack]() {
+ qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
+ return false; // the mcmeta is not optional
+ };
+
+ if (zip.setCurrentFile("pack.mcmeta")) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return mcmeta_invalid();
+ }
+
+ auto data = file.readAll();
+
+ bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
+
+ file.close();
+ if (!mcmeta_result) {
+ return mcmeta_invalid(); // mcmeta invalid
+ }
+ } else {
+ return mcmeta_invalid(); // could not set pack.mcmeta as current file.
+ }
+
+ QuaZipDir zipDir(&zip);
+ if (!zipDir.exists("/data")) {
+ return false; // data dir does not exists at zip root
+ }
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ zip.close();
+ return true; // only need basic info already checked
+ }
+
+ zip.close();
+
+ return true;
+}
+
+// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
+{
+ try {
+ auto json_doc = QJsonDocument::fromJson(raw_data);
+ auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
+
+ pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
+ pack.setDescription(Json::ensureString(pack_obj, "description", ""));
+ } catch (Json::JsonException& e) {
+ qWarning() << "JsonException: " << e.what() << e.cause();
+ return false;
+ }
+ return true;
+}
+
+bool validate(QFileInfo file)
+{
+ DataPack dp{ file };
+ return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
+}
+
+} // namespace DataPackUtils
+
+LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(nullptr, false), m_token(token), m_data_pack(dp) {}
+
+bool LocalDataPackParseTask::abort()
+{
+ m_aborted = true;
+ return true;
+}
+
+void LocalDataPackParseTask::executeTask()
+{
+ if (!DataPackUtils::process(m_data_pack))
+ return;
+
+ if (m_aborted)
+ emitAborted();
+ else
+ emitSucceeded();
+}
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h
new file mode 100644
index 00000000..12fd8c82
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h
@@ -0,0 +1,65 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDebug>
+#include <QObject>
+
+#include "minecraft/mod/DataPack.h"
+
+#include "tasks/Task.h"
+
+namespace DataPackUtils {
+
+enum class ProcessingLevel { Full, BasicInfoOnly };
+
+bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+
+bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+
+bool processMCMeta(DataPack& pack, QByteArray&& raw_data);
+
+/** Checks whether a file is valid as a data pack or not. */
+bool validate(QFileInfo file);
+
+} // namespace DataPackUtils
+
+class LocalDataPackParseTask : public Task {
+ Q_OBJECT
+ public:
+ LocalDataPackParseTask(int token, DataPack& dp);
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override;
+
+ void executeTask() override;
+
+ [[nodiscard]] int token() const { return m_token; }
+
+ private:
+ int m_token;
+
+ DataPack& m_data_pack;
+
+ bool m_aborted = false;
+};
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 774f6114..5342d693 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -3,6 +3,7 @@
#include <quazip/quazip.h>
#include <quazip/quazipfile.h>
#include <toml++/toml.h>
+#include <qdcss.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@@ -11,12 +12,13 @@
#include "FileSystem.h"
#include "Json.h"
+#include "minecraft/mod/ModDetails.h"
#include "settings/INIFile.h"
-namespace {
+namespace ModUtils {
// NEW format
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/c8d8f1929aff9979e322af79a59ce81f3e02db6a
// OLD format:
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
@@ -73,10 +75,11 @@ ModDetails ReadMCModInfo(QByteArray contents)
version = Json::ensureString(val, "").toInt();
if (version != 2) {
- qCritical() << "BAD stuff happened to mod json:";
- qCritical() << contents;
- return {};
+ qWarning() << QString(R"(The value of 'modListVersion' is "%1" (expected "2")! The file may be corrupted.)").arg(version);
+ qWarning() << "The contents of 'mcmod.info' are as follows:";
+ qWarning() << contents;
}
+
auto arrVal = jsonDoc.object().value("modlist");
if (arrVal.isUndefined()) {
arrVal = jsonDoc.object().value("modList");
@@ -239,7 +242,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
return details;
}
-ModDetails ReadForgeInfo(QByteArray contents)
+ModDetails ReadForgeInfo(QString fileName)
{
ModDetails details;
// Read the data
@@ -247,7 +250,7 @@ ModDetails ReadForgeInfo(QByteArray contents)
details.mod_id = "Forge";
details.homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
- if (!ini.loadFile(contents))
+ if (!ini.loadFile(fileName))
return details;
QString major = ini.get("forge.major.number", "0").toString();
@@ -283,35 +286,72 @@ ModDetails ReadLiteModInfo(QByteArray contents)
return details;
}
-} // namespace
+// https://git.sleeping.town/unascribed/NilLoader/src/commit/d7fc87b255fc31019ff90f80d45894927fac6efc/src/main/java/nilloader/api/NilMetadata.java#L64
+ModDetails ReadNilModInfo(QByteArray contents, QString fname)
+{
+ ModDetails details;
+
+ QDCSS cssData = QDCSS(contents);
+ auto name = cssData.get("@nilmod.name");
+ auto desc = cssData.get("@nilmod.description");
+ auto authors = cssData.get("@nilmod.authors");
-LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
- : Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
-{}
+ if (name->has_value()) {
+ details.name = name->value();
+ }
+ if (desc->has_value()) {
+ details.description = desc->value();
+ }
+ if (authors->has_value()) {
+ details.authors.append(authors->value());
+ }
+ details.version = cssData.get("@nilmod.version")->value_or("?");
+
+ details.mod_id = fname.remove(".nilmod.css");
+
+ return details;
+}
-void LocalModParseTask::processAsZip()
+bool process(Mod& mod, ProcessingLevel level)
{
- QuaZip zip(m_modFile.filePath());
+ switch (mod.type()) {
+ case ResourceType::FOLDER:
+ return processFolder(mod, level);
+ case ResourceType::ZIPFILE:
+ return processZIP(mod, level);
+ case ResourceType::LITEMOD:
+ return processLitemod(mod);
+ default:
+ qWarning() << "Invalid type for mod parse task!";
+ return false;
+ }
+}
+
+bool processZIP(Mod& mod, ProcessingLevel level)
+{
+ ModDetails details;
+
+ QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
- return;
+ return false;
QuaZipFile file(&zip);
if (zip.setCurrentFile("META-INF/mods.toml")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
- m_result->details = ReadMCModTOML(file.readAll());
+ details = ReadMCModTOML(file.readAll());
file.close();
// to replace ${file.jarVersion} with the actual version, as needed
- if (m_result->details.version == "${file.jarVersion}") {
+ if (details.version == "${file.jarVersion}") {
if (zip.setCurrentFile("META-INF/MANIFEST.MF")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
// quick and dirty line-by-line parser
@@ -330,93 +370,157 @@ void LocalModParseTask::processAsZip()
manifestVersion = "NONE";
}
- m_result->details.version = manifestVersion;
+ details.version = manifestVersion;
file.close();
}
}
zip.close();
- return;
+ mod.setDetails(details);
+
+ return true;
} else if (zip.setCurrentFile("mcmod.info")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
- m_result->details = ReadMCModInfo(file.readAll());
+ details = ReadMCModInfo(file.readAll());
file.close();
zip.close();
- return;
+
+ mod.setDetails(details);
+ return true;
} else if (zip.setCurrentFile("quilt.mod.json")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
- m_result->details = ReadQuiltModInfo(file.readAll());
+ details = ReadQuiltModInfo(file.readAll());
file.close();
zip.close();
- return;
+
+ mod.setDetails(details);
+ return true;
} else if (zip.setCurrentFile("fabric.mod.json")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
- m_result->details = ReadFabricModInfo(file.readAll());
+ details = ReadFabricModInfo(file.readAll());
file.close();
zip.close();
- return;
+
+ mod.setDetails(details);
+ return true;
} else if (zip.setCurrentFile("forgeversion.properties")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
- m_result->details = ReadForgeInfo(file.readAll());
+ details = ReadForgeInfo(file.getFileName());
file.close();
zip.close();
- return;
+
+ mod.setDetails(details);
+ return true;
+ } else if (zip.setCurrentFile("META-INF/nil/mappings.json")) {
+ // nilloader uses the filename of the metadata file for the modid, so we can't know the exact filename
+ // thankfully, there is a good file to use as a canary so we don't look for nil meta all the time
+
+ QString foundNilMeta;
+ for (auto& fname : zip.getFileNameList()) {
+ // nilmods can shade nilloader to be able to run as a standalone agent - which includes nilloader's own meta file
+ if (fname.endsWith(".nilmod.css") && fname != "nilloader.nilmod.css") {
+ foundNilMeta = fname;
+ break;
+ }
+ }
+
+ if (zip.setCurrentFile(foundNilMeta)) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ zip.close();
+ return false;
+ }
+
+ details = ReadNilModInfo(file.readAll(), foundNilMeta);
+ file.close();
+ zip.close();
+
+ mod.setDetails(details);
+ return true;
+ }
}
zip.close();
+ return false; // no valid mod found in archive
}
-void LocalModParseTask::processAsFolder()
+bool processFolder(Mod& mod, ProcessingLevel level)
{
- QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
- if (mcmod_info.isFile()) {
+ ModDetails details;
+
+ QFileInfo mcmod_info(FS::PathCombine(mod.fileinfo().filePath(), "mcmod.info"));
+ if (mcmod_info.exists() && mcmod_info.isFile()) {
QFile mcmod(mcmod_info.filePath());
if (!mcmod.open(QIODevice::ReadOnly))
- return;
+ return false;
auto data = mcmod.readAll();
if (data.isEmpty() || data.isNull())
- return;
- m_result->details = ReadMCModInfo(data);
+ return false;
+ details = ReadMCModInfo(data);
+
+ mod.setDetails(details);
+ return true;
}
+
+ return false; // no valid mcmod.info file found
}
-void LocalModParseTask::processAsLitemod()
+bool processLitemod(Mod& mod, ProcessingLevel level)
{
- QuaZip zip(m_modFile.filePath());
+ ModDetails details;
+
+ QuaZip zip(mod.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
- return;
+ return false;
QuaZipFile file(&zip);
if (zip.setCurrentFile("litemod.json")) {
if (!file.open(QIODevice::ReadOnly)) {
zip.close();
- return;
+ return false;
}
- m_result->details = ReadLiteModInfo(file.readAll());
+ details = ReadLiteModInfo(file.readAll());
file.close();
+
+ mod.setDetails(details);
+ return true;
}
zip.close();
+
+ return false; // no valid litemod.json found in archive
+}
+
+/** Checks whether a file is valid as a mod or not. */
+bool validate(QFileInfo file)
+{
+ Mod mod{ file };
+ return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
}
+} // namespace ModUtils
+
+LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
+ : Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result())
+{}
+
bool LocalModParseTask::abort()
{
m_aborted.store(true);
@@ -425,19 +529,10 @@ bool LocalModParseTask::abort()
void LocalModParseTask::executeTask()
{
- switch (m_type) {
- case ResourceType::ZIPFILE:
- processAsZip();
- break;
- case ResourceType::FOLDER:
- processAsFolder();
- break;
- case ResourceType::LITEMOD:
- processAsLitemod();
- break;
- default:
- break;
- }
+ Mod mod{ m_modFile };
+ ModUtils::process(mod, ModUtils::ProcessingLevel::Full);
+
+ m_result->details = mod.details();
if (m_aborted)
emit finished();
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h
index 413eb2d1..38dae135 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h
@@ -8,32 +8,48 @@
#include "tasks/Task.h"
-class LocalModParseTask : public Task
-{
+namespace ModUtils {
+
+ModDetails ReadFabricModInfo(QByteArray contents);
+ModDetails ReadQuiltModInfo(QByteArray contents);
+ModDetails ReadForgeInfo(QByteArray contents);
+ModDetails ReadLiteModInfo(QByteArray contents);
+
+enum class ProcessingLevel { Full, BasicInfoOnly };
+
+bool process(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
+
+bool processZIP(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
+bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
+
+/** Checks whether a file is valid as a mod or not. */
+bool validate(QFileInfo file);
+} // namespace ModUtils
+
+class LocalModParseTask : public Task {
Q_OBJECT
-public:
+ public:
struct Result {
ModDetails details;
};
using ResultPtr = std::shared_ptr<Result>;
- ResultPtr result() const {
- return m_result;
- }
+ ResultPtr result() const { return m_result; }
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
- LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile);
+ LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile);
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
-private:
+ private:
void processAsZip();
void processAsFolder();
void processAsLitemod();
-private:
+ private:
int m_token;
ResourceType m_type;
QFileInfo m_modFile;
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 6fd4b024..4bf0b80d 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -22,6 +22,7 @@
#include "Json.h"
#include <quazip/quazip.h>
+#include <quazip/quazipdir.h>
#include <quazip/quazipfile.h>
#include <QCryptographicHash>
@@ -32,99 +33,152 @@ bool process(ResourcePack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
- ResourcePackUtils::processFolder(pack, level);
- return true;
+ return ResourcePackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
- ResourcePackUtils::processZIP(pack, level);
- return true;
+ return ResourcePackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
-void processFolder(ResourcePack& pack, ProcessingLevel level)
+bool processFolder(ResourcePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
+ auto mcmeta_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
+ return false; // the mcmeta is not optional
+ };
+
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
- if (mcmeta_file_info.isFile()) {
+ if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
- return;
+ return mcmeta_invalid(); // can't open mcmeta file
auto data = mcmeta_file.readAll();
- ResourcePackUtils::processMCMeta(pack, std::move(data));
+ bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
mcmeta_file.close();
+ if (!mcmeta_result) {
+ return mcmeta_invalid(); // mcmeta invalid
+ }
+ } else {
+ return mcmeta_invalid(); // mcmeta file isn't a valid file
}
- if (level == ProcessingLevel::BasicInfoOnly)
- return;
+ QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
+ if (!assets_dir_info.exists() || !assets_dir_info.isDir()) {
+ return false; // assets dir does not exists or isn't valid
+ }
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ return true; // only need basic info already checked
+ }
+
+ auto png_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
+ return true; // the png is optional
+ };
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
- if (image_file_info.isFile()) {
- QFile mcmeta_file(image_file_info.filePath());
- if (!mcmeta_file.open(QIODevice::ReadOnly))
- return;
+ if (image_file_info.exists() && image_file_info.isFile()) {
+ QFile pack_png_file(image_file_info.filePath());
+ if (!pack_png_file.open(QIODevice::ReadOnly))
+ return png_invalid(); // can't open pack.png file
- auto data = mcmeta_file.readAll();
+ auto data = pack_png_file.readAll();
- ResourcePackUtils::processPackPNG(pack, std::move(data));
+ bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
- mcmeta_file.close();
+ pack_png_file.close();
+ if (!pack_png_result) {
+ return png_invalid(); // pack.png invalid
+ }
+ } else {
+ return png_invalid(); // pack.png does not exists or is not a valid file.
}
+
+ return true; // all tests passed
}
-void processZIP(ResourcePack& pack, ProcessingLevel level)
+bool processZIP(ResourcePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
- return;
+ return false; // can't open zip file
QuaZipFile file(&zip);
+ auto mcmeta_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
+ return false; // the mcmeta is not optional
+ };
+
if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
- return;
+ return mcmeta_invalid();
}
auto data = file.readAll();
- ResourcePackUtils::processMCMeta(pack, std::move(data));
+ bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
file.close();
+ if (!mcmeta_result) {
+ return mcmeta_invalid(); // mcmeta invalid
+ }
+ } else {
+ return mcmeta_invalid(); // could not set pack.mcmeta as current file.
+ }
+
+ QuaZipDir zipDir(&zip);
+ if (!zipDir.exists("/assets")) {
+ return false; // assets dir does not exists at zip root
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
- return;
+ return true; // only need basic info already checked
}
+ auto png_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
+ return true; // the png is optional
+ };
+
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
- return;
+ return png_invalid();
}
auto data = file.readAll();
- ResourcePackUtils::processPackPNG(pack, std::move(data));
+ bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close();
+ if (!pack_png_result) {
+ return png_invalid(); // pack.png invalid
+ }
+ } else {
+ return png_invalid(); // could not set pack.mcmeta as current file.
}
zip.close();
+
+ return true;
}
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
-void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
+bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
try {
auto json_doc = QJsonDocument::fromJson(raw_data);
@@ -134,17 +188,21 @@ void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
+ return false;
}
+ return true;
}
-void processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
+bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
pack.setImage(img);
} else {
qWarning() << "Failed to parse pack.png.";
+ return false;
}
+ return true;
}
bool validate(QFileInfo file)
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
index 69dbd6ad..d0c24c2b 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
@@ -31,11 +31,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-void processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
-void processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
+bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
+bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file);
diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp
new file mode 100644
index 00000000..4d760df2
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <QObject>
+
+#include "LocalResourceParse.h"
+
+#include "LocalDataPackParseTask.h"
+#include "LocalModParseTask.h"
+#include "LocalResourcePackParseTask.h"
+#include "LocalShaderPackParseTask.h"
+#include "LocalTexturePackParseTask.h"
+#include "LocalWorldSaveParseTask.h"
+
+
+static const QMap<PackedResourceType, QString> s_packed_type_names = {
+ {PackedResourceType::ResourcePack, QObject::tr("resource pack")},
+ {PackedResourceType::TexturePack, QObject::tr("texture pack")},
+ {PackedResourceType::DataPack, QObject::tr("data pack")},
+ {PackedResourceType::ShaderPack, QObject::tr("shader pack")},
+ {PackedResourceType::WorldSave, QObject::tr("world save")},
+ {PackedResourceType::Mod , QObject::tr("mod")},
+ {PackedResourceType::UNKNOWN, QObject::tr("unknown")}
+};
+
+namespace ResourceUtils {
+PackedResourceType identify(QFileInfo file){
+ if (file.exists() && file.isFile()) {
+ if (ResourcePackUtils::validate(file)) {
+ qDebug() << file.fileName() << "is a resource pack";
+ return PackedResourceType::ResourcePack;
+ } else if (TexturePackUtils::validate(file)) {
+ qDebug() << file.fileName() << "is a pre 1.6 texture pack";
+ return PackedResourceType::TexturePack;
+ } else if (DataPackUtils::validate(file)) {
+ qDebug() << file.fileName() << "is a data pack";
+ return PackedResourceType::DataPack;
+ } else if (ModUtils::validate(file)) {
+ qDebug() << file.fileName() << "is a mod";
+ return PackedResourceType::Mod;
+ } else if (WorldSaveUtils::validate(file)) {
+ qDebug() << file.fileName() << "is a world save";
+ return PackedResourceType::WorldSave;
+ } else if (ShaderPackUtils::validate(file)) {
+ qDebug() << file.fileName() << "is a shader pack";
+ return PackedResourceType::ShaderPack;
+ } else {
+ qDebug() << "Can't Identify" << file.fileName() ;
+ }
+ } else {
+ qDebug() << "Can't find" << file.absolutePath();
+ }
+ return PackedResourceType::UNKNOWN;
+}
+
+QString getPackedTypeName(PackedResourceType type) {
+ return s_packed_type_names.constFind(type).value();
+}
+
+}
diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.h b/launcher/minecraft/mod/tasks/LocalResourceParse.h
new file mode 100644
index 00000000..7385d24b
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalResourceParse.h
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <set>
+
+#include <QDebug>
+#include <QFileInfo>
+#include <QObject>
+
+enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN };
+namespace ResourceUtils {
+static const std::set<PackedResourceType> ValidResourceTypes = { PackedResourceType::DataPack, PackedResourceType::ResourcePack,
+ PackedResourceType::TexturePack, PackedResourceType::ShaderPack,
+ PackedResourceType::WorldSave, PackedResourceType::Mod };
+PackedResourceType identify(QFileInfo file);
+QString getPackedTypeName(PackedResourceType type);
+} // namespace ResourceUtils
diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp
new file mode 100644
index 00000000..a9949735
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.cpp
@@ -0,0 +1,113 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "LocalShaderPackParseTask.h"
+
+#include "FileSystem.h"
+
+#include <quazip/quazip.h>
+#include <quazip/quazipdir.h>
+#include <quazip/quazipfile.h>
+
+namespace ShaderPackUtils {
+
+bool process(ShaderPack& pack, ProcessingLevel level)
+{
+ switch (pack.type()) {
+ case ResourceType::FOLDER:
+ return ShaderPackUtils::processFolder(pack, level);
+ case ResourceType::ZIPFILE:
+ return ShaderPackUtils::processZIP(pack, level);
+ default:
+ qWarning() << "Invalid type for shader pack parse task!";
+ return false;
+ }
+}
+
+bool processFolder(ShaderPack& pack, ProcessingLevel level)
+{
+ Q_ASSERT(pack.type() == ResourceType::FOLDER);
+
+ QFileInfo shaders_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "shaders"));
+ if (!shaders_dir_info.exists() || !shaders_dir_info.isDir()) {
+ return false; // assets dir does not exists or isn't valid
+ }
+ pack.setPackFormat(ShaderPackFormat::VALID);
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ return true; // only need basic info already checked
+ }
+
+ return true; // all tests passed
+}
+
+bool processZIP(ShaderPack& pack, ProcessingLevel level)
+{
+ Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
+
+ QuaZip zip(pack.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return false; // can't open zip file
+
+ QuaZipFile file(&zip);
+
+ QuaZipDir zipDir(&zip);
+ if (!zipDir.exists("/shaders")) {
+ return false; // assets dir does not exists at zip root
+ }
+ pack.setPackFormat(ShaderPackFormat::VALID);
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ zip.close();
+ return true; // only need basic info already checked
+ }
+
+ zip.close();
+
+ return true;
+}
+
+bool validate(QFileInfo file)
+{
+ ShaderPack sp{ file };
+ return ShaderPackUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
+}
+
+} // namespace ShaderPackUtils
+
+LocalShaderPackParseTask::LocalShaderPackParseTask(int token, ShaderPack& sp) : Task(nullptr, false), m_token(token), m_shader_pack(sp) {}
+
+bool LocalShaderPackParseTask::abort()
+{
+ m_aborted = true;
+ return true;
+}
+
+void LocalShaderPackParseTask::executeTask()
+{
+ if (!ShaderPackUtils::process(m_shader_pack))
+ return;
+
+ if (m_aborted)
+ emitAborted();
+ else
+ emitSucceeded();
+}
diff --git a/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h
new file mode 100644
index 00000000..6be2183c
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalShaderPackParseTask.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDebug>
+#include <QObject>
+
+#include "minecraft/mod/ShaderPack.h"
+
+#include "tasks/Task.h"
+
+namespace ShaderPackUtils {
+
+enum class ProcessingLevel { Full, BasicInfoOnly };
+
+bool process(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+
+bool processZIP(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(ShaderPack& pack, ProcessingLevel level = ProcessingLevel::Full);
+
+/** Checks whether a file is valid as a shader pack or not. */
+bool validate(QFileInfo file);
+} // namespace ShaderPackUtils
+
+class LocalShaderPackParseTask : public Task {
+ Q_OBJECT
+ public:
+ LocalShaderPackParseTask(int token, ShaderPack& sp);
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override;
+
+ void executeTask() override;
+
+ [[nodiscard]] int token() const { return m_token; }
+
+ private:
+ int m_token;
+
+ ShaderPack& m_shader_pack;
+
+ bool m_aborted = false;
+};
diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
index adb19aca..38f1d7c1 100644
--- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
@@ -32,18 +32,16 @@ bool process(TexturePack& pack, ProcessingLevel level)
{
switch (pack.type()) {
case ResourceType::FOLDER:
- TexturePackUtils::processFolder(pack, level);
- return true;
+ return TexturePackUtils::processFolder(pack, level);
case ResourceType::ZIPFILE:
- TexturePackUtils::processZIP(pack, level);
- return true;
+ return TexturePackUtils::processZIP(pack, level);
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
-void processFolder(TexturePack& pack, ProcessingLevel level)
+bool processFolder(TexturePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
@@ -51,39 +49,51 @@ void processFolder(TexturePack& pack, ProcessingLevel level)
if (mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
- return;
+ return false;
auto data = mcmeta_file.readAll();
- TexturePackUtils::processPackTXT(pack, std::move(data));
+ bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
mcmeta_file.close();
+ if (!packTXT_result) {
+ return false;
+ }
+ } else {
+ return false;
}
if (level == ProcessingLevel::BasicInfoOnly)
- return;
+ return true;
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
- return;
+ return false;
auto data = mcmeta_file.readAll();
- TexturePackUtils::processPackPNG(pack, std::move(data));
+ bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close();
+ if (!packPNG_result) {
+ return false;
+ }
+ } else {
+ return false;
}
+
+ return true;
}
-void processZIP(TexturePack& pack, ProcessingLevel level)
+bool processZIP(TexturePack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
- return;
+ return false;
QuaZipFile file(&zip);
@@ -91,51 +101,62 @@ void processZIP(TexturePack& pack, ProcessingLevel level)
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
- return;
+ return false;
}
auto data = file.readAll();
- TexturePackUtils::processPackTXT(pack, std::move(data));
+ bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
file.close();
+ if (!packTXT_result) {
+ return false;
+ }
}
if (level == ProcessingLevel::BasicInfoOnly) {
zip.close();
- return;
+ return true;
}
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
- return;
+ return false;
}
auto data = file.readAll();
- TexturePackUtils::processPackPNG(pack, std::move(data));
+ bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close();
+ if (!packPNG_result) {
+ return false;
+ }
}
zip.close();
+
+ return true;
}
-void processPackTXT(TexturePack& pack, QByteArray&& raw_data)
+bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
{
pack.setDescription(QString(raw_data));
+ return true;
}
-void processPackPNG(TexturePack& pack, QByteArray&& raw_data)
+bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
pack.setImage(img);
} else {
qWarning() << "Failed to parse pack.png.";
+ return false;
}
+ return true;
}
bool validate(QFileInfo file)
diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h
index 9f7aab75..1589f8cb 100644
--- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h
@@ -32,11 +32,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
-void processPackTXT(TexturePack& pack, QByteArray&& raw_data);
-void processPackPNG(TexturePack& pack, QByteArray&& raw_data);
+bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
+bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file);
diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
new file mode 100644
index 00000000..cbc8f8ce
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
@@ -0,0 +1,190 @@
+
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "LocalWorldSaveParseTask.h"
+
+#include "FileSystem.h"
+
+#include <quazip/quazip.h>
+#include <quazip/quazipdir.h>
+#include <quazip/quazipfile.h>
+
+#include <QDir>
+#include <QFileInfo>
+
+namespace WorldSaveUtils {
+
+bool process(WorldSave& pack, ProcessingLevel level)
+{
+ switch (pack.type()) {
+ case ResourceType::FOLDER:
+ return WorldSaveUtils::processFolder(pack, level);
+ case ResourceType::ZIPFILE:
+ return WorldSaveUtils::processZIP(pack, level);
+ default:
+ qWarning() << "Invalid type for world save parse task!";
+ return false;
+ }
+}
+
+/// @brief checks a folder structure to see if it contains a level.dat
+/// @param dir the path to check
+/// @param saves used in recursive call if a "saves" dir was found
+/// @return std::tuple of (
+/// bool <found level.dat>,
+/// QString <name of folder containing level.dat>,
+/// bool <saves folder found>
+/// )
+static std::tuple<bool, QString, bool> contains_level_dat(QDir dir, bool saves = false)
+{
+ for (auto const& entry : dir.entryInfoList()) {
+ if (!entry.isDir()) {
+ continue;
+ }
+ if (!saves && entry.fileName() == "saves") {
+ return contains_level_dat(QDir(entry.filePath()), true);
+ }
+ QFileInfo level_dat(FS::PathCombine(entry.filePath(), "level.dat"));
+ if (level_dat.exists() && level_dat.isFile()) {
+ return std::make_tuple(true, entry.fileName(), saves);
+ }
+ }
+ return std::make_tuple(false, "", saves);
+}
+
+bool processFolder(WorldSave& save, ProcessingLevel level)
+{
+ Q_ASSERT(save.type() == ResourceType::FOLDER);
+
+ auto [found, save_dir_name, found_saves_dir] = contains_level_dat(QDir(save.fileinfo().filePath()));
+
+ if (!found) {
+ return false;
+ }
+
+ save.setSaveDirName(save_dir_name);
+
+ if (found_saves_dir) {
+ save.setSaveFormat(WorldSaveFormat::MULTI);
+ } else {
+ save.setSaveFormat(WorldSaveFormat::SINGLE);
+ }
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ return true; // only need basic info already checked
+ }
+
+ // reserved for more intensive processing
+
+ return true; // all tests passed
+}
+
+/// @brief checks a folder structure to see if it contains a level.dat
+/// @param zip the zip file to check
+/// @return std::tuple of (
+/// bool <found level.dat>,
+/// QString <name of folder containing level.dat>,
+/// bool <saves folder found>
+/// )
+static std::tuple<bool, QString, bool> contains_level_dat(QuaZip& zip)
+{
+ bool saves = false;
+ QuaZipDir zipDir(&zip);
+ if (zipDir.exists("/saves")) {
+ saves = true;
+ zipDir.cd("/saves");
+ }
+
+ for (auto const& entry : zipDir.entryList()) {
+ zipDir.cd(entry);
+ if (zipDir.exists("level.dat")) {
+ return std::make_tuple(true, entry, saves);
+ }
+ zipDir.cd("..");
+ }
+ return std::make_tuple(false, "", saves);
+}
+
+bool processZIP(WorldSave& save, ProcessingLevel level)
+{
+ Q_ASSERT(save.type() == ResourceType::ZIPFILE);
+
+ QuaZip zip(save.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return false; // can't open zip file
+
+ auto [found, save_dir_name, found_saves_dir] = contains_level_dat(zip);
+
+ if (save_dir_name.endsWith("/")) {
+ save_dir_name.chop(1);
+ }
+
+ if (!found) {
+ return false;
+ }
+
+ save.setSaveDirName(save_dir_name);
+
+ if (found_saves_dir) {
+ save.setSaveFormat(WorldSaveFormat::MULTI);
+ } else {
+ save.setSaveFormat(WorldSaveFormat::SINGLE);
+ }
+
+ if (level == ProcessingLevel::BasicInfoOnly) {
+ zip.close();
+ return true; // only need basic info already checked
+ }
+
+ // reserved for more intensive processing
+
+ zip.close();
+
+ return true;
+}
+
+bool validate(QFileInfo file)
+{
+ WorldSave sp{ file };
+ return WorldSaveUtils::process(sp, ProcessingLevel::BasicInfoOnly) && sp.valid();
+}
+
+} // namespace WorldSaveUtils
+
+LocalWorldSaveParseTask::LocalWorldSaveParseTask(int token, WorldSave& save) : Task(nullptr, false), m_token(token), m_save(save) {}
+
+bool LocalWorldSaveParseTask::abort()
+{
+ m_aborted = true;
+ return true;
+}
+
+void LocalWorldSaveParseTask::executeTask()
+{
+ if (!WorldSaveUtils::process(m_save))
+ return;
+
+ if (m_aborted)
+ emitAborted();
+ else
+ emitSucceeded();
+}
diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h
new file mode 100644
index 00000000..9dcdca2b
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.h
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDebug>
+#include <QObject>
+
+#include "minecraft/mod/WorldSave.h"
+
+#include "tasks/Task.h"
+
+namespace WorldSaveUtils {
+
+enum class ProcessingLevel { Full, BasicInfoOnly };
+
+bool process(WorldSave& save, ProcessingLevel level = ProcessingLevel::Full);
+
+bool processZIP(WorldSave& pack, ProcessingLevel level = ProcessingLevel::Full);
+bool processFolder(WorldSave& pack, ProcessingLevel level = ProcessingLevel::Full);
+
+bool validate(QFileInfo file);
+
+} // namespace WorldSaveUtils
+
+class LocalWorldSaveParseTask : public Task {
+ Q_OBJECT
+ public:
+ LocalWorldSaveParseTask(int token, WorldSave& save);
+
+ [[nodiscard]] bool canAbort() const override { return true; }
+ bool abort() override;
+
+ void executeTask() override;
+
+ [[nodiscard]] int token() const { return m_token; }
+
+ private:
+ int m_token;
+
+ WorldSave& m_save;
+
+ bool m_aborted = false;
+};
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index 78ef4386..3677a1dc 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -72,14 +72,14 @@ void ModFolderLoadTask::executeTask()
delete mod;
}
else {
- m_result->mods[mod->internal_id()] = mod;
+ m_result->mods[mod->internal_id()].reset(std::move(mod));
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
}
}
else {
QString chopped_id = mod->internal_id().chopped(9);
if (m_result->mods.contains(chopped_id)) {
- m_result->mods[mod->internal_id()] = mod;
+ m_result->mods[mod->internal_id()].reset(std::move(mod));
auto metadata = m_result->mods[chopped_id]->metadata();
if (metadata) {
@@ -90,7 +90,7 @@ void ModFolderLoadTask::executeTask()
}
}
else {
- m_result->mods[mod->internal_id()] = mod;
+ m_result->mods[mod->internal_id()].reset(std::move(mod));
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
}
}
@@ -130,6 +130,6 @@ void ModFolderLoadTask::getFromMetadata()
auto* mod = new Mod(m_mods_dir, metadata);
mod->setStatus(ModStatus::NotInstalled);
- m_result->mods[mod->internal_id()] = mod;
+ m_result->mods[mod->internal_id()].reset(std::move(mod));
}
}