feat: add initial support for parseing datapacks

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2022-12-09 20:26:05 -07:00
parent dd3848d7b1
commit 64c51a70a3
15 changed files with 622 additions and 50 deletions

View File

@ -331,6 +331,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/Resource.cpp minecraft/mod/Resource.cpp
minecraft/mod/ResourceFolderModel.h minecraft/mod/ResourceFolderModel.h
minecraft/mod/ResourceFolderModel.cpp minecraft/mod/ResourceFolderModel.cpp
minecraft/mod/DataPack.h
minecraft/mod/DataPack.cpp
minecraft/mod/ResourcePack.h minecraft/mod/ResourcePack.h
minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePack.cpp
minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.h
@ -347,6 +349,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalModParseTask.cpp minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalDataPackParseTask.h
minecraft/mod/tasks/LocalDataPackParseTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h minecraft/mod/tasks/LocalResourcePackParseTask.h
minecraft/mod/tasks/LocalResourcePackParseTask.cpp minecraft/mod/tasks/LocalResourcePackParseTask.cpp
minecraft/mod/tasks/LocalTexturePackParseTask.h minecraft/mod/tasks/LocalTexturePackParseTask.h

View File

@ -0,0 +1,110 @@
// 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 "DataPack.h"
#include <QDebug>
#include <QMap>
#include <QRegularExpression>
#include "Version.h"
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
{ 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } },
{ 10, { Version("1.19"), Version("1.19.3") } },
};
void DataPack::setPackFormat(int new_format_id)
{
QMutexLocker locker(&m_data_lock);
if (!s_pack_format_versions.contains(new_format_id)) {
qWarning() << "Pack format '%1' is not a recognized resource pack id!";
}
m_pack_format = new_format_id;
}
void DataPack::setDescription(QString new_description)
{
QMutexLocker locker(&m_data_lock);
m_description = new_description;
}
std::pair<Version, Version> DataPack::compatibleVersions() const
{
if (!s_pack_format_versions.contains(m_pack_format)) {
return { {}, {} };
}
return s_pack_format_versions.constFind(m_pack_format).value();
}
std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) const
{
auto const& cast_other = static_cast<DataPack const&>(other);
switch (type) {
default: {
auto res = Resource::compare(other, type);
if (res.first != 0)
return res;
}
case SortType::PACK_FORMAT: {
auto this_ver = packFormat();
auto other_ver = cast_other.packFormat();
if (this_ver > other_ver)
return { 1, type == SortType::PACK_FORMAT };
if (this_ver < other_ver)
return { -1, type == SortType::PACK_FORMAT };
}
}
return { 0, false };
}
bool DataPack::applyFilter(QRegularExpression filter) const
{
if (filter.match(description()).hasMatch())
return true;
if (filter.match(QString::number(packFormat())).hasMatch())
return true;
if (filter.match(compatibleVersions().first.toString()).hasMatch())
return true;
if (filter.match(compatibleVersions().second.toString()).hasMatch())
return true;
return Resource::applyFilter(filter);
}
bool DataPack::valid() const
{
return m_pack_format != 0;
}

View File

@ -0,0 +1,73 @@
// 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 "Resource.h"
#include <QMutex>
class Version;
/* TODO:
*
* Store localized descriptions
* */
class DataPack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
DataPack(QObject* parent = nullptr) : Resource(parent) {}
DataPack(QFileInfo file_info) : Resource(file_info) {}
/** Gets the numerical ID of the pack format. */
[[nodiscard]] int packFormat() const { return m_pack_format; }
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
/** Gets the description of the resource pack. */
[[nodiscard]] QString description() const { return m_description; }
/** Thread-safe. */
void setPackFormat(int new_format_id);
/** Thread-safe. */
void setDescription(QString new_description);
bool valid() const override;
[[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair<int, bool> override;
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
protected:
mutable QMutex m_data_lock;
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
* See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
*/
int m_pack_format = 0;
/** The resource pack's description, as defined in the pack.mcmeta file.
*/
QString m_description;
};

View File

@ -17,7 +17,9 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("1.19.3"), Version("1.19.3") } }, { 9, { Version("1.19"), Version("1.19.2") } },
// { 11, { Version("22w42a"), Version("22w44a") } }
{ 12, { Version("1.19.3"), Version("1.19.3") } },
}; };
void ResourcePack::setPackFormat(int new_format_id) void ResourcePack::setPackFormat(int new_format_id)

View File

@ -0,0 +1,169 @@
// 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/quazipfile.h>
#include <quazip/quazipdir.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 resource pack parse task!";
return false;
}
}
bool processFolder(DataPack& pack, ProcessingLevel level)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
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 false; // 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 false; // mcmeta invalid
}
} else {
return false; // 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);
if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return false;
}
auto data = file.readAll();
bool mcmeta_result = DataPackUtils::processMCMeta(pack, std::move(data));
file.close();
if (!mcmeta_result) {
return false; // mcmeta invalid
}
} else {
return false; // 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/Tutorials/Creating_a_resource_pack#Formatting_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_resource_pack(dp)
{}
bool LocalDataPackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalDataPackParseTask::executeTask()
{
if (!DataPackUtils::process(m_resource_pack))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,64 @@
// 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 resource pack or not. */
bool validate(QFileInfo file);
} // namespace ResourcePackUtils
class LocalDataPackParseTask : public Task {
Q_OBJECT
public:
LocalDataPackParseTask(int token, DataPack& rp);
[[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_resource_pack;
bool m_aborted = false;
};

View File

@ -23,6 +23,7 @@
#include <quazip/quazip.h> #include <quazip/quazip.h>
#include <quazip/quazipfile.h> #include <quazip/quazipfile.h>
#include <quazip/quazipdir.h>
#include <QCryptographicHash> #include <QCryptographicHash>
@ -32,58 +33,74 @@ bool process(ResourcePack& pack, ProcessingLevel level)
{ {
switch (pack.type()) { switch (pack.type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
ResourcePackUtils::processFolder(pack, level); return ResourcePackUtils::processFolder(pack, level);
return true;
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
ResourcePackUtils::processZIP(pack, level); return ResourcePackUtils::processZIP(pack, level);
return true;
default: default:
qWarning() << "Invalid type for resource pack parse task!"; qWarning() << "Invalid type for resource pack parse task!";
return false; return false;
} }
} }
void processFolder(ResourcePack& pack, ProcessingLevel level) bool processFolder(ResourcePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::FOLDER); Q_ASSERT(pack.type() == ResourceType::FOLDER);
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); 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()); QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return false; // can't open mcmeta file
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
ResourcePackUtils::processMCMeta(pack, std::move(data)); bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!mcmeta_result) {
return false; // mcmeta invalid
}
} else {
return false; // mcmeta file isn't a valid file
} }
if (level == ProcessingLevel::BasicInfoOnly) QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
return; 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
}
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) { if (image_file_info.exists() && image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath()); QFile mcmeta_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return false; // can't open pack.png file
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
ResourcePackUtils::processPackPNG(pack, std::move(data)); bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!pack_png_result) {
return false; // pack.png invalid
} }
} else {
return false; // pack.png does not exists or is not a valid file.
} }
void processZIP(ResourcePack& pack, ProcessingLevel level) return true; // all tests passed
}
bool processZIP(ResourcePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::ZIPFILE); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath()); QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return; return false; // can't open zip file
QuaZipFile file(&zip); QuaZipFile file(&zip);
@ -91,40 +108,57 @@ void processZIP(ResourcePack& pack, ProcessingLevel level)
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return false;
} }
auto data = file.readAll(); auto data = file.readAll();
ResourcePackUtils::processMCMeta(pack, std::move(data)); bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
file.close(); file.close();
if (!mcmeta_result) {
return false; // mcmeta invalid
}
} else {
return false; // 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) { if (level == ProcessingLevel::BasicInfoOnly) {
zip.close(); zip.close();
return; return true; // only need basic info already checked
} }
if (zip.setCurrentFile("pack.png")) { if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return false;
} }
auto data = file.readAll(); auto data = file.readAll();
ResourcePackUtils::processPackPNG(pack, std::move(data)); bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
if (!pack_png_result) {
return false; // pack.png invalid
}
} else {
return false; // could not set pack.mcmeta as current file.
} }
zip.close(); zip.close();
return true;
} }
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta // 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 { try {
auto json_doc = QJsonDocument::fromJson(raw_data); auto json_doc = QJsonDocument::fromJson(raw_data);
@ -134,17 +168,21 @@ void processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
pack.setDescription(Json::ensureString(pack_obj, "description", "")); pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause(); 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); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
pack.setImage(img); pack.setImage(img);
} else { } else {
qWarning() << "Failed to parse pack.png."; qWarning() << "Failed to parse pack.png.";
return false;
} }
return true;
} }
bool validate(QFileInfo file) bool validate(QFileInfo file)

View File

@ -31,11 +31,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processMCMeta(ResourcePack& pack, QByteArray&& raw_data); bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
void processPackPNG(ResourcePack& pack, QByteArray&& raw_data); bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a resource pack or not. */ /** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -32,18 +32,16 @@ bool process(TexturePack& pack, ProcessingLevel level)
{ {
switch (pack.type()) { switch (pack.type()) {
case ResourceType::FOLDER: case ResourceType::FOLDER:
TexturePackUtils::processFolder(pack, level); return TexturePackUtils::processFolder(pack, level);
return true;
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
TexturePackUtils::processZIP(pack, level); return TexturePackUtils::processZIP(pack, level);
return true;
default: default:
qWarning() << "Invalid type for resource pack parse task!"; qWarning() << "Invalid type for resource pack parse task!";
return false; return false;
} }
} }
void processFolder(TexturePack& pack, ProcessingLevel level) bool processFolder(TexturePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::FOLDER); Q_ASSERT(pack.type() == ResourceType::FOLDER);
@ -51,39 +49,51 @@ void processFolder(TexturePack& pack, ProcessingLevel level)
if (mcmeta_file_info.isFile()) { if (mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath()); QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return false;
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data)); bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!packTXT_result) {
return false;
}
} else {
return false;
} }
if (level == ProcessingLevel::BasicInfoOnly) if (level == ProcessingLevel::BasicInfoOnly)
return; return true;
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
if (image_file_info.isFile()) { if (image_file_info.isFile()) {
QFile mcmeta_file(image_file_info.filePath()); QFile mcmeta_file(image_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly)) if (!mcmeta_file.open(QIODevice::ReadOnly))
return; return false;
auto data = mcmeta_file.readAll(); auto data = mcmeta_file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data)); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close(); mcmeta_file.close();
if (!packPNG_result) {
return false;
} }
} else {
return false;
} }
void processZIP(TexturePack& pack, ProcessingLevel level) return true;
}
bool processZIP(TexturePack& pack, ProcessingLevel level)
{ {
Q_ASSERT(pack.type() == ResourceType::ZIPFILE); Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath()); QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip)) if (!zip.open(QuaZip::mdUnzip))
return; return false;
QuaZipFile file(&zip); QuaZipFile file(&zip);
@ -91,51 +101,62 @@ void processZIP(TexturePack& pack, ProcessingLevel level)
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return false;
} }
auto data = file.readAll(); auto data = file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data)); bool packTXT_result = TexturePackUtils::processPackTXT(pack, std::move(data));
file.close(); file.close();
if (!packTXT_result) {
return false;
}
} }
if (level == ProcessingLevel::BasicInfoOnly) { if (level == ProcessingLevel::BasicInfoOnly) {
zip.close(); zip.close();
return; return false;
} }
if (zip.setCurrentFile("pack.png")) { if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip."; qCritical() << "Failed to open file in zip.";
zip.close(); zip.close();
return; return false;
} }
auto data = file.readAll(); auto data = file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data)); bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close(); file.close();
if (!packPNG_result) {
return false;
}
} }
zip.close(); zip.close();
return true;
} }
void processPackTXT(TexturePack& pack, QByteArray&& raw_data) bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
{ {
pack.setDescription(QString(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); auto img = QImage::fromData(raw_data);
if (!img.isNull()) { if (!img.isNull()) {
pack.setImage(img); pack.setImage(img);
} else { } else {
qWarning() << "Failed to parse pack.png."; qWarning() << "Failed to parse pack.png.";
return false;
} }
return true;
} }
bool validate(QFileInfo file) bool validate(QFileInfo file)

View File

@ -32,11 +32,11 @@ enum class ProcessingLevel { Full, BasicInfoOnly };
bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool process(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full); bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
void processPackTXT(TexturePack& pack, QByteArray&& raw_data); bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
void processPackPNG(TexturePack& pack, QByteArray&& raw_data); bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
/** Checks whether a file is valid as a texture pack or not. */ /** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file); bool validate(QFileInfo file);

View File

@ -27,6 +27,9 @@ ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VER
ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME TexturePackParse) TEST_NAME TexturePackParse)
ecm_add_test(DataPackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME DataPackParse)
ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME ParseUtils) TEST_NAME ParseUtils)

View File

@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.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 <QTest>
#include <QTimer>
#include <FileSystem.h>
#include <minecraft/mod/DataPack.h>
#include <minecraft/mod/tasks/LocalDataPackParseTask.h>
class DataPackParseTest : public QObject {
Q_OBJECT
private slots:
void test_parseZIP()
{
QString source = QFINDTESTDATA("testdata/DataPackParse");
QString zip_dp = FS::PathCombine(source, "test_data_pack_boogaloo.zip");
DataPack pack { QFileInfo(zip_dp) };
bool valid = DataPackUtils::processZIP(pack);
QVERIFY(pack.packFormat() == 4);
QVERIFY(pack.description() == "Some data pack 2 boobgaloo");
QVERIFY(valid == true);
}
void test_parseFolder()
{
QString source = QFINDTESTDATA("testdata/DataPackParse");
QString folder_dp = FS::PathCombine(source, "test_folder");
DataPack pack { QFileInfo(folder_dp) };
bool valid = DataPackUtils::processFolder(pack);
QVERIFY(pack.packFormat() == 10);
QVERIFY(pack.description() == "Some data pack, maybe");
QVERIFY(valid == true);
}
void test_parseFolder2()
{
QString source = QFINDTESTDATA("testdata/DataPackParse");
QString folder_dp = FS::PathCombine(source, "another_test_folder");
DataPack pack { QFileInfo(folder_dp) };
bool valid = DataPackUtils::process(pack);
QVERIFY(pack.packFormat() == 6);
QVERIFY(pack.description() == "Some data pack three, leaves on the tree");
QVERIFY(valid == true);
}
};
QTEST_GUILESS_MAIN(DataPackParseTest)
#include "DataPackParse_test.moc"

View File

@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 6,
"description": "Some data pack three, leaves on the tree"
}
}

Binary file not shown.

View File

@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 10,
"description": "Some data pack, maybe"
}
}