feat: add basic resource pack parsing of pack.mcmeta

This parses the pack format ID and the description from the local file,
from both a ZIP and a folder, and hooks it into the model.

Signed-off-by: flow <flowlnlnln@gmail.com>
This commit is contained in:
flow 2022-08-28 22:33:44 -03:00
parent afa1a5e932
commit f21ae66265
No known key found for this signature in database
GPG Key ID: 8D0F221F0A59F469
8 changed files with 292 additions and 36 deletions

View File

@ -336,6 +336,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalModParseTask.cpp
minecraft/mod/tasks/LocalModUpdateTask.h
minecraft/mod/tasks/LocalModUpdateTask.cpp
minecraft/mod/tasks/LocalResourcePackParseTask.h
minecraft/mod/tasks/LocalResourcePackParseTask.cpp
# Assets
minecraft/AssetsUtils.h

View File

@ -156,7 +156,7 @@ Task* ModFolderModel::createUpdateTask()
return task;
}
Task* ModFolderModel::createParseTask(Resource const& resource)
Task* ModFolderModel::createParseTask(Resource& resource)
{
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
}

View File

@ -82,7 +82,7 @@ public:
int columnCount(const QModelIndex &parent) const override;
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource const&) override;
[[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
bool uninstallMod(const QString& filename, bool preserve_metadata = false);

View File

@ -145,7 +145,7 @@ class ResourceFolderModel : public QAbstractListModel {
* This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
* in the background, so it slowly updates the UI as tasks get done.
*/
[[nodiscard]] virtual Task* createParseTask(Resource const&) { return nullptr; };
[[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; };
/** Standard implementation of the model update logic.
*

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
@ -35,4 +36,111 @@
#include "ResourcePackFolderModel.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {}
#include "Version.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir))
{
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
{
if (!validateIndex(index))
return {};
int row = index.row();
int column = index.column();
switch (role) {
case Qt::DisplayRole:
switch (column) {
case NameColumn:
return m_resources[row]->name();
case PackFormatColumn: {
auto version_bounds = at(row)->compatibleVersions();
if (version_bounds.first.toString().isEmpty())
return QString::number(at(row)->packFormat());
return QString("%1 (%2 - %3)")
.arg(QString::number(at(row)->packFormat()), version_bounds.first.toString(), version_bounds.second.toString());
}
case DateColumn:
return m_resources[row]->dateTimeChanged();
default:
return {};
}
case Qt::ToolTipRole: {
if (column == PackFormatColumn) {
//: The string being explained by this is in the format: ID (Lower version - Upper version)
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
}
return m_resources[row]->internal_id();
}
case Qt::CheckStateRole:
switch (column) {
case ActiveColumn:
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return {};
}
default:
return {};
}
}
QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
switch (section) {
case ActiveColumn:
return QString();
case NameColumn:
return tr("Name");
case PackFormatColumn:
return tr("Pack Format");
case DateColumn:
return tr("Last changed");
default:
return {};
}
case Qt::ToolTipRole:
switch (section) {
case ActiveColumn:
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
case NameColumn:
return tr("The name of the resource pack.");
case PackFormatColumn:
//: The string being explained by this is in the format: ID (Lower version - Upper version)
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
case DateColumn:
return tr("The date and time this resource pack was last changed (or added).");
default:
return {};
}
default:
return {};
}
return {};
}
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
{
return NUM_COLUMNS;
}
Task* ResourcePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new ResourcePack(entry); });
}
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
{
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
}

View File

@ -8,7 +8,24 @@ class ResourcePackFolderModel : public ResourceFolderModel
{
Q_OBJECT
public:
enum Columns
{
ActiveColumn = 0,
NameColumn,
PackFormatColumn,
DateColumn,
NUM_COLUMNS
};
explicit ResourcePackFolderModel(const QString &dir);
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
[[nodiscard]] int columnCount(const QModelIndex &parent) const override;
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(ResourcePack)
};

View File

@ -0,0 +1,95 @@
#include "LocalResourcePackParseTask.h"
#include "FileSystem.h"
#include "Json.h"
#include <quazip/quazip.h>
#include <quazip/quazipfile.h>
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp)
: Task(nullptr, false), m_token(token), m_resource_pack(rp)
{}
bool LocalResourcePackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalResourcePackParseTask::executeTask()
{
switch (m_resource_pack.type()) {
case ResourceType::FOLDER:
processAsFolder();
break;
case ResourceType::ZIPFILE:
processAsZip();
break;
default:
qWarning() << "Invalid type for resource pack parse task!";
emitFailed(tr("Invalid type."));
}
if (isFinished())
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
void LocalResourcePackParseTask::processMCMeta(QByteArray&& raw_data)
{
try {
auto json_doc = QJsonDocument::fromJson(raw_data);
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
m_resource_pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
m_resource_pack.setDescription(Json::ensureString(pack_obj, "description", ""));
} catch (Json::JsonException& e) {
qWarning() << "JsonException: " << e.what() << e.cause();
emitFailed(tr("Failed to process .mcmeta file."));
}
}
void LocalResourcePackParseTask::processAsFolder()
{
QFileInfo mcmeta_file_info(FS::PathCombine(m_resource_pack.fileinfo().filePath(), "pack.mcmeta"));
if (mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return;
auto data = mcmeta_file.readAll();
if (data.isEmpty() || data.isNull())
return;
processMCMeta(std::move(data));
}
}
void LocalResourcePackParseTask::processAsZip()
{
QuaZip zip(m_resource_pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
if (zip.setCurrentFile("pack.mcmeta")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
}
auto data = file.readAll();
processMCMeta(std::move(data));
file.close();
zip.close();
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <QDebug>
#include <QObject>
#include "minecraft/mod/ResourcePack.h"
#include "tasks/Task.h"
class LocalResourcePackParseTask : public Task {
Q_OBJECT
public:
LocalResourcePackParseTask(int token, ResourcePack& rp);
[[nodiscard]] bool canAbort() const override { return true; }
bool abort() override;
void executeTask() override;
[[nodiscard]] int token() const { return m_token; }
private:
void processMCMeta(QByteArray&& raw_data);
void processAsFolder();
void processAsZip();
private:
int m_token;
ResourcePack& m_resource_pack;
bool m_aborted = false;
};