aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/mod/tasks
diff options
context:
space:
mode:
authorAaron Sonin <10217842+byteduck@users.noreply.github.com>2023-01-12 20:11:46 -0800
committerGitHub <noreply@github.com>2023-01-12 20:11:46 -0800
commitb2f48eaeb8cd6dbdf9b8bbbf1a163339c88173a7 (patch)
tree80773415691e538d437bc03c42f73a8abd01f126 /launcher/minecraft/mod/tasks
parent160dd09fc2788fea17c8e9e332c2877586640971 (diff)
parent5e89ba70797df863c7eeae9b147080e7dbdf6d3b (diff)
downloadPrismLauncher-b2f48eaeb8cd6dbdf9b8bbbf1a163339c88173a7.tar.gz
PrismLauncher-b2f48eaeb8cd6dbdf9b8bbbf1a163339c88173a7.tar.bz2
PrismLauncher-b2f48eaeb8cd6dbdf9b8bbbf1a163339c88173a7.zip
Merge branch 'PrismLauncher:develop' into instance-accounts
Diffstat (limited to 'launcher/minecraft/mod/tasks')
-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.cpp143
-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.cpp60
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourceParse.h31
-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
14 files changed, 1009 insertions, 113 deletions
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
new file mode 100644
index 00000000..3fcb2110
--- /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() << "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.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() << "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 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..8bfe2c84 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -11,9 +11,10 @@
#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
@@ -283,35 +284,46 @@ ModDetails ReadLiteModInfo(QByteArray contents)
return details;
}
-} // namespace
-
-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 process(Mod& mod, ProcessingLevel level)
+{
+ 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;
+ }
+}
-void LocalModParseTask::processAsZip()
+bool processZIP(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("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 +342,131 @@ 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.readAll());
file.close();
zip.close();
- return;
+
+ 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 +475,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..19ddc899
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp
@@ -0,0 +1,60 @@
+// 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 "LocalResourceParse.h"
+
+#include "LocalDataPackParseTask.h"
+#include "LocalModParseTask.h"
+#include "LocalResourcePackParseTask.h"
+#include "LocalShaderPackParseTask.h"
+#include "LocalTexturePackParseTask.h"
+#include "LocalWorldSaveParseTask.h"
+
+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;
+}
+}
diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.h b/launcher/minecraft/mod/tasks/LocalResourceParse.h
new file mode 100644
index 00000000..b07a874c
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/LocalResourceParse.h
@@ -0,0 +1,31 @@
+// 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 <QFileInfo>
+#include <QObject>
+
+enum class PackedResourceType { DataPack, ResourcePack, TexturePack, ShaderPack, WorldSave, Mod, UNKNOWN };
+namespace ResourceUtils {
+PackedResourceType identify(QFileInfo file);
+} // 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;
+};