feat: add texture pack parsing

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
This commit is contained in:
Sefa Eyeoglu 2022-09-04 14:45:09 +02:00
parent 40c68595d7
commit 07dcefabcb
No known key found for this signature in database
GPG Key ID: C10411294912A422
16 changed files with 476 additions and 52 deletions

View File

@ -320,6 +320,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePack.cpp
minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.h
minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/ResourcePackFolderModel.cpp
minecraft/mod/TexturePack.h
minecraft/mod/TexturePack.cpp
minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.h
minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.cpp
minecraft/mod/ShaderPackFolderModel.h minecraft/mod/ShaderPackFolderModel.h
@ -332,6 +334,8 @@ set(MINECRAFT_SOURCES
minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalModUpdateTask.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.cpp
# Assets # Assets
minecraft/AssetsUtils.h minecraft/AssetsUtils.h

View File

@ -0,0 +1,45 @@
#include "TexturePack.h"
#include <QDebug>
#include <QMap>
#include <QRegularExpression>
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
void TexturePack::setDescription(QString new_description)
{
QMutexLocker locker(&m_data_lock);
m_description = new_description;
}
void TexturePack::setImage(QImage new_image)
{
QMutexLocker locker(&m_data_lock);
Q_ASSERT(!new_image.isNull());
if (m_pack_image_cache_key.key.isValid())
QPixmapCache::remove(m_pack_image_cache_key.key);
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
m_pack_image_cache_key.was_ever_used = true;
}
QPixmap TexturePack::image(QSize size)
{
QPixmap cached_image;
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
return cached_image.scaled(size);
}
// No valid image we can get
if (!m_pack_image_cache_key.was_ever_used)
return {};
// Imaged got evicted from the cache. Re-process it and retry.
TexturePackUtils::process(*this);
return image(size);
}

View File

@ -0,0 +1,53 @@
#pragma once
#include "Resource.h"
#include <QImage>
#include <QMutex>
#include <QPixmap>
#include <QPixmapCache>
class Version;
/* TODO:
*
* Store localized descriptions
* */
class TexturePack : public Resource {
Q_OBJECT
public:
using Ptr = shared_qobject_ptr<Resource>;
TexturePack(QObject* parent = nullptr) : Resource(parent) {}
TexturePack(QFileInfo file_info) : Resource(file_info) {}
/** Gets the description of the resource pack. */
[[nodiscard]] QString description() const { return m_description; }
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap image(QSize size);
/** Thread-safe. */
void setDescription(QString new_description);
/** Thread-safe. */
void setImage(QImage new_image);
protected:
mutable QMutex m_data_lock;
/** The texture pack's description, as defined in the pack.txt file.
*/
QString m_description;
/** The texture pack's image file cache key, for access in the QPixmapCache global instance.
*
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
* so as to tell whether a cache entry is inexistent or if it was just evicted from the cache.
*/
struct {
QPixmapCache::Key key;
bool was_ever_used = false;
} m_pack_image_cache_key;
};

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * PolyMC - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -35,4 +36,17 @@
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {}
Task* TexturePackFolderModel::createUpdateTask()
{
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new TexturePack(entry); });
}
Task* TexturePackFolderModel::createParseTask(Resource& resource)
{
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
}

View File

@ -8,4 +8,6 @@ class TexturePackFolderModel : public ResourceFolderModel
public: public:
explicit TexturePackFolderModel(const QString &dir); explicit TexturePackFolderModel(const QString &dir);
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
}; };

View File

@ -0,0 +1,70 @@
// 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 "TexturePack.h"
#include "tasks/LocalTexturePackParseTask.h"
class TexturePackParseTest : public QObject {
Q_OBJECT
private slots:
void test_parseZIP()
{
QString source = QFINDTESTDATA("testdata");
QString zip_rp = FS::PathCombine(source, "test_texture_pack_idk.zip");
TexturePack pack { QFileInfo(zip_rp) };
TexturePackUtils::processZIP(pack);
QVERIFY(pack.description() == "joe biden, wake up");
}
void test_parseFolder()
{
QString source = QFINDTESTDATA("testdata");
QString folder_rp = FS::PathCombine(source, "test_texturefolder");
TexturePack pack { QFileInfo(folder_rp) };
TexturePackUtils::processFolder(pack);
QVERIFY(pack.description() == "Some texture pack surely");
}
void test_parseFolder2()
{
QString source = QFINDTESTDATA("testdata");
QString folder_rp = FS::PathCombine(source, "another_test_texturefolder");
TexturePack pack { QFileInfo(folder_rp) };
TexturePackUtils::process(pack);
QVERIFY(pack.description() == "quieres\nfor real");
}
};
QTEST_GUILESS_MAIN(TexturePackParseTest)
#include "TexturePackParse_test.moc"

View File

@ -0,0 +1,154 @@
// 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 "LocalTexturePackParseTask.h"
#include "FileSystem.h"
#include <quazip/quazip.h>
#include <quazip/quazipfile.h>
#include <QCryptographicHash>
namespace TexturePackUtils {
bool process(TexturePack& pack)
{
switch (pack.type()) {
case ResourceType::FOLDER:
TexturePackUtils::processFolder(pack);
return true;
case ResourceType::ZIPFILE:
TexturePackUtils::processZIP(pack);
return true;
default:
qWarning() << "Invalid type for resource pack parse task!";
return false;
}
}
void processFolder(TexturePack& pack)
{
Q_ASSERT(pack.type() == ResourceType::FOLDER);
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.txt"));
if (mcmeta_file_info.isFile()) {
QFile mcmeta_file(mcmeta_file_info.filePath());
if (!mcmeta_file.open(QIODevice::ReadOnly))
return;
auto data = mcmeta_file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data));
mcmeta_file.close();
}
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;
auto data = mcmeta_file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data));
mcmeta_file.close();
}
}
void processZIP(TexturePack& pack)
{
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
QuaZip zip(pack.fileinfo().filePath());
if (!zip.open(QuaZip::mdUnzip))
return;
QuaZipFile file(&zip);
if (zip.setCurrentFile("pack.txt")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
}
auto data = file.readAll();
TexturePackUtils::processPackTXT(pack, std::move(data));
file.close();
}
if (zip.setCurrentFile("pack.png")) {
if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open file in zip.";
zip.close();
return;
}
auto data = file.readAll();
TexturePackUtils::processPackPNG(pack, std::move(data));
file.close();
}
zip.close();
}
void processPackTXT(TexturePack& pack, QByteArray&& raw_data)
{
pack.setDescription(QString(raw_data));
}
void 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.";
}
}
} // namespace TexturePackUtils
LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp)
: Task(nullptr, false), m_token(token), m_resource_pack(rp)
{}
bool LocalTexturePackParseTask::abort()
{
m_aborted = true;
return true;
}
void LocalTexturePackParseTask::executeTask()
{
Q_ASSERT(m_resource_pack.valid());
if (!TexturePackUtils::process(m_resource_pack))
return;
if (m_aborted)
emitAborted();
else
emitSucceeded();
}

View File

@ -0,0 +1,56 @@
// 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/>.
*/
#pragma once
#include <QDebug>
#include <QObject>
#include "minecraft/mod/TexturePack.h"
#include "tasks/Task.h"
namespace TexturePackUtils {
bool process(TexturePack& pack);
void processZIP(TexturePack& pack);
void processFolder(TexturePack& pack);
void processPackTXT(TexturePack& pack, QByteArray&& raw_data);
void processPackPNG(TexturePack& pack, QByteArray&& raw_data);
} // namespace TexturePackUtils
class LocalTexturePackParseTask : public Task {
Q_OBJECT
public:
LocalTexturePackParseTask(int token, TexturePack& 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;
TexturePack& m_resource_pack;
bool m_aborted = false;
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -39,6 +39,7 @@
#include "ui_ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h"
#include "minecraft/mod/TexturePackFolderModel.h" #include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/TexturePack.h"
class TexturePackPage : public ExternalResourcesPage class TexturePackPage : public ExternalResourcesPage
{ {
@ -60,4 +61,15 @@ public:
{ {
return m_instance->traits().contains("texturepacks"); return m_instance->traits().contains("texturepacks");
} }
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
auto& rp = static_cast<TexturePack&>(m_model->at(row));
ui->frame->updateWithTexturePack(rp);
return true;
}
}; };

View File

@ -105,10 +105,7 @@ static const QMap<QChar, QString> s_value_to_color = {
{'f', "#FFFFFF"} {'f', "#FFFFFF"}
}; };
void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) QString InfoFrame::renderColorCodes(QString input) {
{
setName(resource_pack.name());
// We have to manually set the colors for use. // We have to manually set the colors for use.
// //
// A color is set using §x, with x = a hex number from 0 to f. // A color is set using §x, with x = a hex number from 0 to f.
@ -119,39 +116,49 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
// TODO: Make the same logic for font formatting too. // TODO: Make the same logic for font formatting too.
// TODO: Wrap links inside <a> tags // TODO: Wrap links inside <a> tags
auto description = resource_pack.description(); QString html("<html>");
QString description_parsed("<html>");
bool in_div = false; bool in_div = false;
auto desc_it = description.constBegin(); auto it = input.constBegin();
while (desc_it != description.constEnd()) { while (it != input.constEnd()) {
if (*desc_it == u'§') { if (*it == u'§') {
if (in_div) if (in_div)
description_parsed += "</span>"; html += "</span>";
auto const& num = *(++desc_it); auto const& num = *(++it);
description_parsed += QString("<span style=\"color: %1;\">").arg(s_value_to_color.constFind(num).value()); html += QString("<span style=\"color: %1;\">").arg(s_value_to_color.constFind(num).value());
in_div = true; in_div = true;
desc_it++; it++;
} }
description_parsed += *desc_it; html += *it;
desc_it++; it++;
} }
if (in_div) if (in_div)
description_parsed += "</span>"; html += "</span>";
description_parsed += "</html>"; html += "</html>";
description_parsed.replace("\n", "<br>"); html.replace("\n", "<br>");
return html;
}
setDescription(description_parsed); void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
{
setName(resource_pack.name());
setDescription(renderColorCodes(resource_pack.description()));
setImage(resource_pack.image({64, 64})); setImage(resource_pack.image({64, 64}));
} }
void InfoFrame::updateWithTexturePack(TexturePack& texture_pack)
{
setName(texture_pack.name());
setDescription(texture_pack.description());
setImage(texture_pack.image({64, 64}));
}
void InfoFrame::clear() void InfoFrame::clear()
{ {
setName(); setName();

View File

@ -19,6 +19,7 @@
#include "minecraft/mod/Mod.h" #include "minecraft/mod/Mod.h"
#include "minecraft/mod/ResourcePack.h" #include "minecraft/mod/ResourcePack.h"
#include "minecraft/mod/TexturePack.h"
namespace Ui namespace Ui
{ {
@ -41,6 +42,9 @@ class InfoFrame : public QFrame {
void updateWithMod(Mod const& m); void updateWithMod(Mod const& m);
void updateWithResource(Resource const& resource); void updateWithResource(Resource const& resource);
void updateWithResourcePack(ResourcePack& rp); void updateWithResourcePack(ResourcePack& rp);
void updateWithTexturePack(TexturePack& tp);
static QString renderColorCodes(QString input);
public slots: public slots:
void descriptionEllipsisHandler(QString link); void descriptionEllipsisHandler(QString link);

View File

@ -24,6 +24,9 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V
ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME ResourcePackParse) TEST_NAME ResourcePackParse)
ecm_add_test(minecraft/mod/TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME TexturePackParse)
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)