From 07dcefabcbe3436ae6de09bc8c99120ab3f0a745 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 4 Sep 2022 14:45:09 +0200 Subject: [PATCH] feat: add texture pack parsing Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 4 + launcher/minecraft/mod/TexturePack.cpp | 45 +++++ launcher/minecraft/mod/TexturePack.h | 53 ++++++ .../minecraft/mod/TexturePackFolderModel.cpp | 78 +++++---- .../minecraft/mod/TexturePackFolderModel.h | 2 + .../minecraft/mod/TexturePackParse_test.cpp | 70 ++++++++ .../mod/tasks/LocalTexturePackParseTask.cpp | 154 ++++++++++++++++++ .../mod/tasks/LocalTexturePackParseTask.h | 56 +++++++ .../another_test_texturefolder/pack.txt | Bin 0 -> 16 bytes .../mod/testdata/test_texture_pack_idk.zip | Bin 0 -> 184 bytes .../assets/minecraft/textures/blah.txt | Bin 0 -> 2 bytes .../mod/testdata/test_texturefolder/pack.txt | Bin 0 -> 24 bytes launcher/ui/pages/instance/TexturePackPage.h | 12 ++ launcher/ui/widgets/InfoFrame.cpp | 47 +++--- launcher/ui/widgets/InfoFrame.h | 4 + tests/CMakeLists.txt | 3 + 16 files changed, 476 insertions(+), 52 deletions(-) create mode 100644 launcher/minecraft/mod/TexturePack.cpp create mode 100644 launcher/minecraft/mod/TexturePack.h create mode 100644 launcher/minecraft/mod/TexturePackParse_test.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h create mode 100644 launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt create mode 100644 launcher/minecraft/mod/testdata/test_texture_pack_idk.zip create mode 100644 launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt create mode 100644 launcher/minecraft/mod/testdata/test_texturefolder/pack.txt diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e44b98eb..848d2e51 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -320,6 +320,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp + minecraft/mod/TexturePack.h + minecraft/mod/TexturePack.cpp minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/ShaderPackFolderModel.h @@ -332,6 +334,8 @@ set(MINECRAFT_SOURCES minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalResourcePackParseTask.h minecraft/mod/tasks/LocalResourcePackParseTask.cpp + minecraft/mod/tasks/LocalTexturePackParseTask.h + minecraft/mod/tasks/LocalTexturePackParseTask.cpp # Assets minecraft/AssetsUtils.h diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp new file mode 100644 index 00000000..32f69fc7 --- /dev/null +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -0,0 +1,45 @@ +#include "TexturePack.h" + +#include +#include +#include + +#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); +} diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h new file mode 100644 index 00000000..cd5e4b67 --- /dev/null +++ b/launcher/minecraft/mod/TexturePack.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Resource.h" + +#include +#include +#include +#include + +class Version; + +/* TODO: + * + * Store localized descriptions + * */ + +class TexturePack : public Resource { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + 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; +}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 2c7c945b..561f6202 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -1,38 +1,52 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (C) 2022 Sefa Eyeoglu -* -* 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 . -* -* This file incorporates work covered by the following copyright and -* permission notice: -* -* Copyright 2013-2021 MultiMC Contributors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * 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 . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "TexturePackFolderModel.h" +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" + 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(resource)); +} diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 69e98661..f9a95466 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -8,4 +8,6 @@ class TexturePackFolderModel : public ResourceFolderModel public: explicit TexturePackFolderModel(const QString &dir); + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource&) override; }; diff --git a/launcher/minecraft/mod/TexturePackParse_test.cpp b/launcher/minecraft/mod/TexturePackParse_test.cpp new file mode 100644 index 00000000..207c2c87 --- /dev/null +++ b/launcher/minecraft/mod/TexturePackParse_test.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + */ + +#include +#include + +#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" diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp new file mode 100644 index 00000000..2d5a557e --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + */ + +#include "LocalTexturePackParseTask.h" + +#include "FileSystem.h" + +#include +#include + +#include + +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(); +} diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h new file mode 100644 index 00000000..239e1197 --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + */ + +#pragma once + +#include +#include + +#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; +}; diff --git a/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt b/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt new file mode 100644 index 0000000000000000000000000000000000000000..bbc0d271af4cb5e90e82f4ea825a47f02fd27705 GIT binary patch literal 16 XcmXRc%}gyyE#^wgFH$Htraits().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(m_model->at(row)); + ui->frame->updateWithTexturePack(rp); + + return true; + } }; diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 9e0553f8..bb895c9b 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -105,10 +105,7 @@ static const QMap s_value_to_color = { {'f', "#FFFFFF"} }; -void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) -{ - setName(resource_pack.name()); - +QString InfoFrame::renderColorCodes(QString input) { // We have to manually set the colors for use. // // 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: Wrap links inside tags - auto description = resource_pack.description(); - - QString description_parsed(""); + QString html(""); bool in_div = false; - auto desc_it = description.constBegin(); - while (desc_it != description.constEnd()) { - if (*desc_it == u'§') { + auto it = input.constBegin(); + while (it != input.constEnd()) { + if (*it == u'§') { if (in_div) - description_parsed += ""; + html += ""; - auto const& num = *(++desc_it); - description_parsed += QString("").arg(s_value_to_color.constFind(num).value()); + auto const& num = *(++it); + html += QString("").arg(s_value_to_color.constFind(num).value()); in_div = true; - desc_it++; + it++; } - description_parsed += *desc_it; - desc_it++; + html += *it; + it++; } if (in_div) - description_parsed += ""; - description_parsed += ""; + html += ""; + html += ""; - description_parsed.replace("\n", "
"); + html.replace("\n", "
"); + 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})); } +void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) +{ + setName(texture_pack.name()); + setDescription(texture_pack.description()); + setImage(texture_pack.image({64, 64})); +} + void InfoFrame::clear() { setName(); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index 70d15b1e..84523e28 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -19,6 +19,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ResourcePack.h" +#include "minecraft/mod/TexturePack.h" namespace Ui { @@ -41,6 +42,9 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); void updateWithResourcePack(ResourcePack& rp); + void updateWithTexturePack(TexturePack& tp); + + static QString renderColorCodes(QString input); public slots: void descriptionEllipsisHandler(QString link); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1265d7a5..9c86c221 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 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 TEST_NAME ParseUtils)