Merge pull request #576 from Ryex/identify-zip-packs

fix https://github.com/PrismLauncher/PrismLauncher/issues/349
This commit is contained in:
Sefa Eyeoglu
2023-01-07 17:33:34 +01:00
committed by GitHub
48 changed files with 1858 additions and 162 deletions

View File

@ -0,0 +1,108 @@
// 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"
// 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 '" << new_format_id << "' is not a recognized data 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 data 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 data pack, as defined in the pack.mcmeta file.
* See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;
/** The data pack's description, as defined in the pack.mcmeta file.
*/
QString m_description;
};

View File

@ -43,6 +43,7 @@
#include "MetadataHandler.h"
#include "Version.h"
#include "minecraft/mod/ModDetails.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
@ -70,6 +71,10 @@ void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
m_local_details.metadata = metadata;
}
void Mod::setDetails(const ModDetails& details) {
m_local_details = details;
}
std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
{
auto cast_other = dynamic_cast<Mod const*>(&other);
@ -204,3 +209,8 @@ auto Mod::provider() const -> std::optional<QString>
return ProviderCaps.readableName(metadata()->provider);
return {};
}
bool Mod::valid() const
{
return !m_local_details.mod_id.isEmpty();
}

View File

@ -71,6 +71,9 @@ public:
void setStatus(ModStatus status);
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
void setDetails(const ModDetails& details);
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;

View File

@ -81,7 +81,7 @@ struct ModDetails
ModDetails() = default;
/** Metadata should be handled manually to properly set the mod status. */
ModDetails(ModDetails& other)
ModDetails(const ModDetails& other)
: mod_id(other.mod_id)
, name(other.name)
, version(other.version)
@ -92,7 +92,7 @@ struct ModDetails
, status(other.status)
{}
ModDetails& operator=(ModDetails& other)
ModDetails& operator=(const ModDetails& other)
{
this->mod_id = other.mod_id;
this->name = other.name;
@ -106,7 +106,7 @@ struct ModDetails
return *this;
}
ModDetails& operator=(ModDetails&& other)
ModDetails& operator=(const ModDetails&& other)
{
this->mod_id = other.mod_id;
this->name = other.name;

View File

@ -13,11 +13,12 @@
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 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") } },
{ 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") } },
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 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") } },
{ 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("22w42a"), Version("22w44a") } },
{ 12, { Version("1.19.3"), Version("1.19.3") } },
};
void ResourcePack::setPackFormat(int new_format_id)
@ -25,7 +26,7 @@ void ResourcePack::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!";
qWarning() << "Pack format '" << new_format_id << "' is not a recognized resource pack id!";
}
m_pack_format = new_format_id;

View File

@ -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/>.
*/
#include "ShaderPack.h"
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
{
QMutexLocker locker(&m_data_lock);
m_pack_format = new_format;
}
bool ShaderPack::valid() const
{
return m_pack_format != ShaderPackFormat::INVALID;
}

View File

@ -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 "Resource.h"
/* Info:
* Currently For Optifine / Iris shader packs,
* could be expanded to support others should they exist?
*
* This class and enum are mostly here as placeholders for validating
* that a shaderpack exists and is in the right format,
* namely that they contain a folder named 'shaders'.
*
* In the technical sense it would be possible to parse files like `shaders/shaders.properties`
* to get information like the available profiles but this is not all that useful without more knowledge of the
* shader mod used to be able to change settings.
*/
#include <QMutex>
enum class ShaderPackFormat { VALID, INVALID };
class ShaderPack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
[[nodiscard]] ShaderPackFormat packFormat() const { return m_pack_format; }
ShaderPack(QObject* parent = nullptr) : Resource(parent) {}
ShaderPack(QFileInfo file_info) : Resource(file_info) {}
/** Thread-safe. */
void setPackFormat(ShaderPackFormat new_format);
bool valid() const override;
protected:
mutable QMutex m_data_lock;
ShaderPackFormat m_pack_format = ShaderPackFormat::INVALID;
};

View File

@ -0,0 +1,43 @@
// 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 "WorldSave.h"
#include "minecraft/mod/tasks/LocalWorldSaveParseTask.h"
void WorldSave::setSaveFormat(WorldSaveFormat new_save_format)
{
QMutexLocker locker(&m_data_lock);
m_save_format = new_save_format;
}
void WorldSave::setSaveDirName(QString dir_name)
{
QMutexLocker locker(&m_data_lock);
m_save_dir_name = dir_name;
}
bool WorldSave::valid() const
{
return m_save_format != WorldSaveFormat::INVALID;
}

View File

@ -0,0 +1,61 @@
// 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;
enum class WorldSaveFormat { SINGLE, MULTI, INVALID };
class WorldSave : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
WorldSave(QObject* parent = nullptr) : Resource(parent) {}
WorldSave(QFileInfo file_info) : Resource(file_info) {}
/** Gets the format of the save. */
[[nodiscard]] WorldSaveFormat saveFormat() const { return m_save_format; }
/** Gets the name of the save dir (first found in multi mode). */
[[nodiscard]] QString saveDirName() const { return m_save_dir_name; }
/** Thread-safe. */
void setSaveFormat(WorldSaveFormat new_save_format);
/** Thread-safe. */
void setSaveDirName(QString dir_name);
bool valid() const override;
protected:
mutable QMutex m_data_lock;
/** The format in which the save file is in.
* Since saves can be distributed in various slightly different ways, this allows us to treat them separately.
*/
WorldSaveFormat m_save_format = WorldSaveFormat::INVALID;
QString m_save_dir_name;
};

View File

@ -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();
}

View File

@ -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;
};

View File

@ -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())
{}
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 +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();

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;
};

View File

@ -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)

View File

@ -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);

View File

@ -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();
}

View File

@ -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;
};