aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/mod
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/mod')
-rw-r--r--launcher/minecraft/mod/Mod.cpp57
-rw-r--r--launcher/minecraft/mod/Mod.h22
-rw-r--r--launcher/minecraft/mod/ModDetails.h97
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp18
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h3
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp76
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h15
-rw-r--r--launcher/minecraft/mod/ResourcePack.cpp19
-rw-r--r--launcher/minecraft/mod/ResourcePack.h6
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp27
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.h3
-rw-r--r--launcher/minecraft/mod/ShaderPackFolderModel.h2
-rw-r--r--launcher/minecraft/mod/TexturePack.cpp25
-rw-r--r--launcher/minecraft/mod/TexturePack.h6
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.cpp104
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.h23
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp211
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.h3
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp67
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h5
-rw-r--r--launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp67
-rw-r--r--launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h5
22 files changed, 816 insertions, 45 deletions
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index c495cd47..e613ddeb 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -41,9 +41,11 @@
#include <QString>
#include <QRegularExpression>
+#include "MTPixmapCache.h"
#include "MetadataHandler.h"
#include "Version.h"
#include "minecraft/mod/ModDetails.h"
+#include "minecraft/mod/tasks/LocalModParseTask.h"
static ModPlatform::ProviderCapabilities ProviderCaps;
@@ -201,7 +203,10 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
m_local_details = std::move(details);
if (metadata)
setMetadata(std::move(metadata));
-};
+ if (!iconPath().isEmpty()) {
+ m_pack_image_cache_key.was_read_attempt = false;
+ }
+}
auto Mod::provider() const -> std::optional<QString>
{
@@ -210,6 +215,56 @@ auto Mod::provider() const -> std::optional<QString>
return {};
}
+auto Mod::licenses() const -> const QList<ModLicense>&
+{
+ return details().licenses;
+}
+
+ auto Mod::issueTracker() const -> QString
+{
+ return details().issue_tracker;
+}
+
+void Mod::setIcon(QImage new_image) const
+{
+ QMutexLocker locker(&m_data_lock);
+
+ Q_ASSERT(!new_image.isNull());
+
+ if (m_pack_image_cache_key.key.isValid())
+ PixmapCache::remove(m_pack_image_cache_key.key);
+
+ // scale the image to avoid flooding the pixmapcache
+ auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+
+ m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
+ m_pack_image_cache_key.was_ever_used = true;
+ m_pack_image_cache_key.was_read_attempt = true;
+}
+
+QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
+{
+ QPixmap cached_image;
+ if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
+ if (size.isNull())
+ return cached_image;
+ return cached_image.scaled(size, mode);
+ }
+
+ // No valid image we can get
+ if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
+ return {};
+
+ if (m_pack_image_cache_key.was_ever_used) {
+ qDebug() << "Mod" << name() << "Had it's icon evicted form the cache. reloading...";
+ PixmapCache::markCacheMissByEviciton();
+ }
+ // Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
+ m_pack_image_cache_key.was_read_attempt = true;
+ ModUtils::loadIconFile(*this);
+ return icon(size);
+}
+
bool Mod::valid() const
{
return !m_local_details.mod_id.isEmpty();
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index c4032538..d4e419f4 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -38,6 +38,10 @@
#include <QDateTime>
#include <QFileInfo>
#include <QList>
+#include <QImage>
+#include <QMutex>
+#include <QPixmap>
+#include <QPixmapCache>
#include <optional>
@@ -64,6 +68,15 @@ public:
auto authors() const -> QStringList;
auto status() const -> ModStatus;
auto provider() const -> std::optional<QString>;
+ auto licenses() const -> const QList<ModLicense>&;
+ auto issueTracker() const -> QString;
+
+ /** Get the intneral path to the mod's icon file*/
+ QString iconPath() const { return m_local_details.icon_file; };
+ /** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
+ [[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
+ /** Thread-safe. */
+ void setIcon(QImage new_image) const;
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
@@ -85,4 +98,13 @@ public:
protected:
ModDetails m_local_details;
+
+ mutable QMutex m_data_lock;
+
+ struct {
+ QPixmapCache::Key key;
+ bool was_ever_used = false;
+ bool was_read_attempt = false;
+ } mutable m_pack_image_cache_key;
+
};
diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h
index 176e4fc1..b4e59d52 100644
--- a/launcher/minecraft/mod/ModDetails.h
+++ b/launcher/minecraft/mod/ModDetails.h
@@ -39,6 +39,7 @@
#include <QString>
#include <QStringList>
+#include <QUrl>
#include "minecraft/mod/MetadataHandler.h"
@@ -49,6 +50,84 @@ enum class ModStatus {
Unknown, // Default status
};
+struct ModLicense {
+ QString name = {};
+ QString id = {};
+ QString url = {};
+ QString description = {};
+
+ ModLicense() {}
+
+ ModLicense(const QString license) {
+ // FIXME: come up with a better license parseing.
+ // handle SPDX identifiers? https://spdx.org/licenses/
+ auto parts = license.split(' ');
+ QStringList notNameParts = {};
+ for (auto part : parts) {
+ auto url = QUrl(part);
+ if (part.startsWith("(") && part.endsWith(")"))
+ url = QUrl(part.mid(1, part.size() - 2));
+
+ if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
+ this->url = url.toString();
+ notNameParts.append(part);
+ continue;
+ }
+ }
+
+ for (auto part : notNameParts) {
+ parts.removeOne(part);
+ }
+
+ auto licensePart = parts.join(' ');
+ this->name = licensePart;
+ this->description = licensePart;
+
+ if (parts.size() == 1) {
+ this->id = parts.first();
+ }
+
+ }
+
+ ModLicense(const QString name, const QString id, const QString url, const QString description) {
+ this->name = name;
+ this->id = id;
+ this->url = url;
+ this->description = description;
+ }
+
+ ModLicense(const ModLicense& other)
+ : name(other.name)
+ , id(other.id)
+ , url(other.url)
+ , description(other.description)
+ {}
+
+ ModLicense& operator=(const ModLicense& other)
+ {
+ this->name = other.name;
+ this->id = other.id;
+ this->url = other.url;
+ this->description = other.description;
+
+ return *this;
+ }
+
+ ModLicense& operator=(const ModLicense&& other)
+ {
+ this->name = other.name;
+ this->id = other.id;
+ this->url = other.url;
+ this->description = other.description;
+
+ return *this;
+ }
+
+ bool isEmpty() {
+ return this->name.isEmpty() && this->id.isEmpty() && this->url.isEmpty() && this->description.isEmpty();
+ }
+};
+
struct ModDetails
{
/* Mod ID as defined in the ModLoader-specific metadata */
@@ -72,6 +151,15 @@ struct ModDetails
/* List of the author's names */
QStringList authors = {};
+ /* Issue Tracker URL */
+ QString issue_tracker = {};
+
+ /* License */
+ QList<ModLicense> licenses = {};
+
+ /* Path of mod logo */
+ QString icon_file = {};
+
/* Installation status of the mod */
ModStatus status = ModStatus::Unknown;
@@ -89,6 +177,9 @@ struct ModDetails
, homeurl(other.homeurl)
, description(other.description)
, authors(other.authors)
+ , issue_tracker(other.issue_tracker)
+ , licenses(other.licenses)
+ , icon_file(other.icon_file)
, status(other.status)
{}
@@ -101,6 +192,9 @@ struct ModDetails
this->homeurl = other.homeurl;
this->description = other.description;
this->authors = other.authors;
+ this->issue_tracker = other.issue_tracker;
+ this->licenses = other.licenses;
+ this->icon_file = other.icon_file;
this->status = other.status;
return *this;
@@ -115,6 +209,9 @@ struct ModDetails
this->homeurl = other.homeurl;
this->description = other.description;
this->authors = other.authors;
+ this->issue_tracker = other.issue_tracker;
+ this->licenses = other.licenses;
+ this->icon_file = other.icon_file;
this->status = other.status;
return *this;
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 5e3b31e0..af98d834 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -37,6 +37,7 @@
#include "ModFolderModel.h"
#include <FileSystem.h>
+#include <qheaderview.h>
#include <QDebug>
#include <QFileSystemWatcher>
#include <QIcon>
@@ -52,12 +53,14 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
-#include "modplatform/ModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
{
- m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
+ m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
+ m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
+ m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
+ m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
}
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
@@ -118,7 +121,9 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
case Qt::DecorationRole: {
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
-
+ if (column == ImageColumn) {
+ return at(row)->icon({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
+ }
return {};
}
case Qt::CheckStateRole:
@@ -142,15 +147,12 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
switch (section)
{
case ActiveColumn:
- return QString();
case NameColumn:
- return tr("Name");
case VersionColumn:
- return tr("Version");
case DateColumn:
- return tr("Last changed");
case ProviderColumn:
- return tr("Provider");
+ case ImageColumn:
+ return columnNames().at(section);
default:
return QVariant();
}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index d337fe29..6ccaba23 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -64,6 +64,7 @@ public:
enum Columns
{
ActiveColumn = 0,
+ ImageColumn,
NameColumn,
VersionColumn,
DateColumn,
@@ -77,6 +78,8 @@ public:
};
ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
+ virtual QString id() const override { return "mods"; }
+
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index d2d875e4..7700fd36 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -8,12 +8,15 @@
#include <QStyle>
#include <QThreadPool>
#include <QUrl>
+#include <QMenu>
#include "Application.h"
#include "FileSystem.h"
+#include "QVariantUtils.h"
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+#include "settings/Setting.h"
#include "tasks/Task.h"
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
@@ -471,10 +474,10 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
switch (role) {
case Qt::DisplayRole:
switch (section) {
+ case ACTIVE_COLUMN:
case NAME_COLUMN:
- return tr("Name");
case DATE_COLUMN:
- return tr("Last modified");
+ return columnNames().at(section);
default:
return {};
}
@@ -500,6 +503,75 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
return {};
}
+void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
+{
+ Q_ASSERT(act);
+
+ act->setText(columnNames().at(column));
+}
+
+void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
+{
+ auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
+ auto setting = (m_instance->settings()->contains(setting_name)) ?
+ m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
+
+ auto hiddenColumns = setting->get().toStringList();
+ auto name = columnNames(false).at(column);
+ auto index = hiddenColumns.indexOf(name);
+ if (index >= 0 && !hidden) {
+ hiddenColumns.removeAt(index);
+ } else if ( index < 0 && hidden) {
+ hiddenColumns.append(name);
+ }
+ setting->set(hiddenColumns);
+}
+
+void ResourceFolderModel::loadHiddenColumns(QTreeView *tree)
+{
+ auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
+ auto setting = (m_instance->settings()->contains(setting_name)) ?
+ m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
+
+ auto hiddenColumns = setting->get().toStringList();
+ auto col_names = columnNames(false);
+ for (auto col_name : hiddenColumns) {
+ auto index = col_names.indexOf(col_name);
+ if (index >= 0)
+ tree->setColumnHidden(index, true);
+ }
+
+}
+
+QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
+{
+ auto menu = new QMenu(tree);
+
+ menu->addSeparator()->setText(tr("Show / Hide Columns"));
+
+ for (int col = 0; col < columnCount(); ++col) {
+ auto act = new QAction(menu);
+ setupHeaderAction(act, col);
+
+ act->setCheckable(true);
+ act->setChecked(!tree->isColumnHidden(col));
+
+ connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled){
+ tree->setColumnHidden(col, !toggled);
+ for(int c = 0; c < columnCount(); ++c) {
+ if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
+ tree->resizeColumnToContents(c);
+ }
+ saveHiddenColumn(col, !toggled);
+ });
+
+ menu->addAction(act);
+
+ }
+
+ return menu;
+}
+
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
{
return new ProxyModel(parent);
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 0a35e1bc..eb1d7c4f 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -1,5 +1,8 @@
#pragma once
+#include <QHeaderView>
+#include <QAction>
+#include <QTreeView>
#include <QAbstractListModel>
#include <QDir>
#include <QFileSystemWatcher>
@@ -29,6 +32,8 @@ class ResourceFolderModel : public QAbstractListModel {
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
~ResourceFolderModel() override;
+ virtual QString id() const { return "resource"; }
+
/** Starts watching the paths for changes.
*
* Returns whether starting to watch all the paths was successful.
@@ -92,6 +97,7 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
+ QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; };
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
@@ -110,6 +116,11 @@ class ResourceFolderModel : public QAbstractListModel {
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ void setupHeaderAction(QAction* act, int column);
+ void saveHiddenColumn(int column, bool hidden);
+ void loadHiddenColumns(QTreeView* tree);
+ QMenu* createHeaderContextMenu(QTreeView* tree);
+
/** This creates a proxy model to filter / sort the model for a UI.
*
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
@@ -117,6 +128,7 @@ class ResourceFolderModel : public QAbstractListModel {
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
[[nodiscard]] SortType columnToSortKey(size_t column) const;
+ [[nodiscard]] QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_column_resize_modes; }
class ProxyModel : public QSortFilterProxyModel {
public:
@@ -187,6 +199,9 @@ class ResourceFolderModel : public QAbstractListModel {
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important!
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
+ QStringList m_column_names = {"Enable", "Name", "Last Modified"};
+ QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
+ QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
bool m_can_interact = true;
diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp
index 759d2b56..e06c1ac1 100644
--- a/launcher/minecraft/mod/ResourcePack.cpp
+++ b/launcher/minecraft/mod/ResourcePack.cpp
@@ -40,7 +40,7 @@ void ResourcePack::setDescription(QString new_description)
m_description = new_description;
}
-void ResourcePack::setImage(QImage new_image)
+void ResourcePack::setImage(QImage new_image) const
{
QMutexLocker locker(&m_data_lock);
@@ -49,7 +49,10 @@ void ResourcePack::setImage(QImage new_image)
if (m_pack_image_cache_key.key.isValid())
PixmapCache::instance().remove(m_pack_image_cache_key.key);
- m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image));
+ // scale the image to avoid flooding the pixmapcache
+ auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+
+ m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
// This can happen if the pixmap is too big to fit in the cache :c
@@ -59,21 +62,25 @@ void ResourcePack::setImage(QImage new_image)
}
}
-QPixmap ResourcePack::image(QSize size)
+QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
{
QPixmap cached_image;
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size);
+ return cached_image.scaled(size, mode);
}
// No valid image we can get
- if (!m_pack_image_cache_key.was_ever_used)
+ if (!m_pack_image_cache_key.was_ever_used) {
return {};
+ } else {
+ qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading...";
+ PixmapCache::markCacheMissByEviciton();
+ }
// Imaged got evicted from the cache. Re-process it and retry.
- ResourcePackUtils::process(*this);
+ ResourcePackUtils::processPackPNG(*this);
return image(size);
}
diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h
index 7cb414d8..da354bc1 100644
--- a/launcher/minecraft/mod/ResourcePack.h
+++ b/launcher/minecraft/mod/ResourcePack.h
@@ -31,7 +31,7 @@ class ResourcePack : public Resource {
[[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);
+ [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */
void setPackFormat(int new_format_id);
@@ -40,7 +40,7 @@ class ResourcePack : public Resource {
void setDescription(QString new_description);
/** Thread-safe. */
- void setImage(QImage new_image);
+ void setImage(QImage new_image) const;
bool valid() const override;
@@ -67,5 +67,5 @@ class ResourcePack : public Resource {
struct {
QPixmapCache::Key key;
bool was_ever_used = false;
- } m_pack_image_cache_key;
+ } mutable m_pack_image_cache_key;
};
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index c12d1f23..14a28b47 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -35,6 +35,8 @@
*/
#include "ResourcePackFolderModel.h"
+#include <qnamespace.h>
+#include <qsize.h>
#include <QIcon>
#include <QStyle>
@@ -48,7 +50,11 @@
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance)
{
- m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
+ m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
+ m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
+ m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE};
+ m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
+
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@@ -84,9 +90,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return {};
}
case Qt::DecorationRole: {
- if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
return APPLICATION->getThemedIcon("status-yellow");
-
+ if (column == ImageColumn) {
+ return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
+ }
return {};
}
case Qt::ToolTipRole: {
@@ -94,7 +102,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
//: 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.");
}
- if (column == NAME_COLUMN) {
+ if (column == NameColumn) {
if (at(row)->isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
@@ -126,13 +134,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
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");
+ case ImageColumn:
+ return columnNames().at(section);
default:
return {};
}
@@ -151,6 +157,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
default:
return {};
}
+ case Qt::SizeHintRole:
+ if (section == ImageColumn) {
+ return QSize(64,0);
+ }
+ return {};
default:
return {};
}
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h
index db4b14fb..531d8192 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.h
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.h
@@ -11,6 +11,7 @@ public:
enum Columns
{
ActiveColumn = 0,
+ ImageColumn,
NameColumn,
PackFormatColumn,
DateColumn,
@@ -19,6 +20,8 @@ public:
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
+ virtual QString id() const override { return "resourcepacks"; }
+
[[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;
diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h
index dc5acf80..f8249962 100644
--- a/launcher/minecraft/mod/ShaderPackFolderModel.h
+++ b/launcher/minecraft/mod/ShaderPackFolderModel.h
@@ -9,4 +9,6 @@ class ShaderPackFolderModel : public ResourceFolderModel {
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance)
{}
+
+ virtual QString id() const override { return "shaderpacks"; }
};
diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp
index 99d55584..c7a50a97 100644
--- a/launcher/minecraft/mod/TexturePack.cpp
+++ b/launcher/minecraft/mod/TexturePack.cpp
@@ -23,6 +23,8 @@
#include <QMap>
#include <QRegularExpression>
+#include "MTPixmapCache.h"
+
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
void TexturePack::setDescription(QString new_description)
@@ -32,34 +34,41 @@ void TexturePack::setDescription(QString new_description)
m_description = new_description;
}
-void TexturePack::setImage(QImage new_image)
+void TexturePack::setImage(QImage new_image) const
{
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);
+ PixmapCache::remove(m_pack_image_cache_key.key);
+
+ // scale the image to avoid flooding the pixmapcache
+ auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
- m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
+ m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
}
-QPixmap TexturePack::image(QSize size)
+QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
{
QPixmap cached_image;
- if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
+ if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size);
+ return cached_image.scaled(size, mode);
}
// No valid image we can get
- if (!m_pack_image_cache_key.was_ever_used)
+ if (!m_pack_image_cache_key.was_ever_used) {
return {};
+ } else {
+ qDebug() << "Texture Pack" << name() << "Had it's image evicted from the cache. reloading...";
+ PixmapCache::markCacheMissByEviciton();
+ }
// Imaged got evicted from the cache. Re-process it and retry.
- TexturePackUtils::process(*this);
+ TexturePackUtils::processPackPNG(*this);
return image(size);
}
diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h
index 81bd5c69..57700565 100644
--- a/launcher/minecraft/mod/TexturePack.h
+++ b/launcher/minecraft/mod/TexturePack.h
@@ -40,13 +40,13 @@ class TexturePack : public Resource {
[[nodiscard]] QString description() const { return m_description; }
/** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */
- [[nodiscard]] QPixmap image(QSize size);
+ [[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */
void setDescription(QString new_description);
/** Thread-safe. */
- void setImage(QImage new_image);
+ void setImage(QImage new_image) const;
bool valid() const override;
@@ -65,5 +65,5 @@ class TexturePack : public Resource {
struct {
QPixmapCache::Key key;
bool was_ever_used = false;
- } m_pack_image_cache_key;
+ } mutable m_pack_image_cache_key;
};
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index c6609ed1..531a7023 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -33,6 +33,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <QCoreApplication>
+
+#include "Application.h"
#include "TexturePackFolderModel.h"
@@ -41,7 +44,13 @@
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance)
: ResourceFolderModel(QDir(dir), instance)
-{}
+{
+ m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
+ m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
+ m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
+ m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
+
+}
Task* TexturePackFolderModel::createUpdateTask()
{
@@ -52,3 +61,96 @@ Task* TexturePackFolderModel::createParseTask(Resource& resource)
{
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
}
+
+
+QVariant TexturePackFolderModel::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 DateColumn:
+ return m_resources[row]->dateTimeChanged();
+ default:
+ return {};
+ }
+ case Qt::ToolTipRole:
+ if (column == NameColumn) {
+ if (at(row)->isSymLinkUnder(instDirPath())) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
+ "\nCanonical Path: %1")
+ .arg(at(row)->fileinfo().canonicalFilePath());;
+ }
+ if (at(row)->isMoreThanOneHardLink()) {
+ return m_resources[row]->internal_id() +
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
+ }
+ }
+
+ return m_resources[row]->internal_id();
+ case Qt::DecorationRole: {
+ if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
+ return APPLICATION->getThemedIcon("status-yellow");
+ if (column == ImageColumn) {
+ return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
+ }
+ return {};
+ }
+ case Qt::CheckStateRole:
+ if (column == ActiveColumn) {
+ return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
+ }
+ return {};
+ default:
+ return {};
+ }
+}
+
+QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (section) {
+ case ActiveColumn:
+ case NameColumn:
+ case DateColumn:
+ case ImageColumn:
+ return columnNames().at(section);
+ default:
+ return {};
+ }
+ case Qt::ToolTipRole: {
+ switch (section) {
+ case ActiveColumn:
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
+ return tr("Is the resource enabled?");
+ case NameColumn:
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
+ return tr("The name of the resource.");
+ case DateColumn:
+ //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
+ return tr("The date and time this resource was last changed (or added).");
+ default:
+ return {};
+ }
+ }
+ default:
+ break;
+ }
+
+ return {};
+}
+
+int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
+{
+ return parent.isValid() ? 0 : NUM_COLUMNS;
+}
+
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h
index 425a71e4..71a8bdd1 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.h
+++ b/launcher/minecraft/mod/TexturePackFolderModel.h
@@ -38,12 +38,35 @@
#include "ResourceFolderModel.h"
+#include "TexturePack.h"
+
class TexturePackFolderModel : public ResourceFolderModel
{
Q_OBJECT
public:
+
+ enum Columns
+ {
+ ActiveColumn = 0,
+ ImageColumn,
+ NameColumn,
+ DateColumn,
+ NUM_COLUMNS
+ };
+
+ explicit TexturePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
+
+ virtual QString id() const override { return "texturepacks"; }
+
+ [[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;
+
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
[[nodiscard]] Task* createUpdateTask() override;
[[nodiscard]] Task* createParseTask(Resource&) override;
+
+ RESOURCE_HELPERS(TexturePack)
};
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 5342d693..264019f8 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -52,6 +52,10 @@ ModDetails ReadMCModInfo(QByteArray contents)
authors = firstObj.value("authors").toArray();
}
+ if (firstObj.contains("logoFile")) {
+ details.icon_file = firstObj.value("logoFile").toString();
+ }
+
for (auto author : authors) {
details.authors.append(author.toString());
}
@@ -166,6 +170,31 @@ ModDetails ReadMCModTOML(QByteArray contents)
}
details.homeurl = homeurl;
+ QString issueTrackerURL = "";
+ if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
+ issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
+ } else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
+ issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
+ }
+ details.issue_tracker = issueTrackerURL;
+
+ QString license = "";
+ if (auto licenseDatum = tomlData["license"].as_string()) {
+ license = QString::fromStdString(licenseDatum->get());
+ } else if (auto licenseDatum =(*modsTable)["license"].as_string()) {
+ license = QString::fromStdString(licenseDatum->get());
+ }
+ if (!license.isEmpty())
+ details.licenses.append(ModLicense(license));
+
+ QString logoFile = "";
+ if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
+ logoFile = QString::fromStdString(logoFileDatum->get());
+ } else if (auto logoFileDatum =(*modsTable)["logoFile"].as_string()) {
+ logoFile = QString::fromStdString(logoFileDatum->get());
+ }
+ details.icon_file = logoFile;
+
return details;
}
@@ -201,6 +230,57 @@ ModDetails ReadFabricModInfo(QByteArray contents)
if (contact.contains("homepage")) {
details.homeurl = contact.value("homepage").toString();
}
+ if (contact.contains("issues")) {
+ details.issue_tracker = contact.value("issues").toString();
+ }
+ }
+
+ if (object.contains("license")) {
+ auto license = object.value("license");
+ if (license.isArray()) {
+ for (auto l : license.toArray()) {
+ if (l.isString()) {
+ details.licenses.append(ModLicense(l.toString()));
+ } else if (l.isObject()) {
+ auto obj = l.toObject();
+ details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
+ obj.value("url").toString(), obj.value("description").toString()));
+ }
+ }
+ } else if (license.isString()) {
+ details.licenses.append(ModLicense(license.toString()));
+ } else if (license.isObject()) {
+ auto obj = license.toObject();
+ details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
+ obj.value("description").toString()));
+ }
+ }
+
+ if (object.contains("icon")) {
+ auto icon = object.value("icon");
+ if (icon.isObject()) {
+ auto obj = icon.toObject();
+ // take the largest icon
+ int largest = 0;
+ for (auto key : obj.keys()) {
+ auto size = key.split('x').first().toInt();
+ if (size > largest) {
+ largest = size;
+ }
+ }
+ if (largest > 0) {
+ auto key = QString::number(largest) + "x" + QString::number(largest);
+ details.icon_file = obj.value(key).toString();
+ } else { // parsing the sizes failed
+ // take the first
+ for (auto i : obj) {
+ details.icon_file = i.toString();
+ break;
+ }
+ }
+ } else if (icon.isString()) {
+ details.icon_file = icon.toString();
+ }
}
}
return details;
@@ -238,6 +318,58 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
if (modContact.contains("homepage")) {
details.homeurl = Json::requireString(modContact.value("homepage"));
}
+ if (modContact.contains("issues")) {
+ details.issue_tracker = Json::requireString(modContact.value("issues"));
+ }
+
+ if (modMetadata.contains("license")) {
+ auto license = modMetadata.value("license");
+ if (license.isArray()) {
+ for (auto l : license.toArray()) {
+ if (l.isString()) {
+ details.licenses.append(ModLicense(l.toString()));
+ } else if (l.isObject()) {
+ auto obj = l.toObject();
+ details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
+ obj.value("url").toString(), obj.value("description").toString()));
+ }
+ }
+ } else if (license.isString()) {
+ details.licenses.append(ModLicense(license.toString()));
+ } else if (license.isObject()) {
+ auto obj = license.toObject();
+ details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
+ obj.value("description").toString()));
+ }
+ }
+
+ if (modMetadata.contains("icon")) {
+ auto icon = modMetadata.value("icon");
+ if (icon.isObject()) {
+ auto obj = icon.toObject();
+ // take the largest icon
+ int largest = 0;
+ for (auto key : obj.keys()) {
+ auto size = key.split('x').first().toInt();
+ if (size > largest) {
+ largest = size;
+ }
+ }
+ if (largest > 0) {
+ auto key = QString::number(largest) + "x" + QString::number(largest);
+ details.icon_file = obj.value(key).toString();
+ } else { // parsing the sizes failed
+ // take the first
+ for (auto i : obj) {
+ details.icon_file = i.toString();
+ break;
+ }
+ }
+ } else if (icon.isString()) {
+ details.icon_file = icon.toString();
+ }
+ }
+
}
return details;
}
@@ -515,6 +647,85 @@ bool validate(QFileInfo file)
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
}
+bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
+{
+ auto img = QImage::fromData(raw_data);
+ if (!img.isNull()) {
+ mod.setIcon(img);
+ } else {
+ qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
+ return false;
+ }
+ return true;
+}
+
+bool loadIconFile(const Mod& mod) {
+ if (mod.iconPath().isEmpty()) {
+ qWarning() << "No Iconfile set, be sure to parse the mod first";
+ return false;
+ }
+
+ auto png_invalid = [&mod]() {
+ qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon";
+ return false;
+ };
+
+ switch (mod.type()) {
+ case ResourceType::FOLDER:
+ {
+ QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
+ if (icon_info.exists() && icon_info.isFile()) {
+ QFile icon(icon_info.filePath());
+ if (!icon.open(QIODevice::ReadOnly))
+ return false;
+ auto data = icon.readAll();
+
+ bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
+
+ icon.close();
+
+ if (!icon_result) {
+ return png_invalid(); // icon invalid
+ }
+ }
+ }
+ case ResourceType::ZIPFILE:
+ {
+ QuaZip zip(mod.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return false;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile(mod.iconPath())) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return png_invalid();
+ }
+
+ auto data = file.readAll();
+
+ bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
+
+ file.close();
+ if (!icon_result) {
+ return png_invalid(); // icon png invalid
+ }
+ } else {
+ return png_invalid(); // could not set icon as current file.
+ }
+ }
+ case ResourceType::LITEMOD:
+ {
+ return false; // can lightmods even have icons?
+ }
+ default:
+ qWarning() << "Invalid type for mod, can not load icon.";
+ return false;
+ }
+}
+
} // namespace ModUtils
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h
index 38dae135..a0321709 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h
@@ -25,6 +25,9 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
/** Checks whether a file is valid as a mod or not. */
bool validate(QFileInfo file);
+
+bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
+bool loadIconFile(const Mod& mod);
} // namespace ModUtils
class LocalModParseTask : public Task {
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 4bf0b80d..a67c56a8 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -165,15 +165,16 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
file.close();
+ zip.close();
if (!pack_png_result) {
return png_invalid(); // pack.png invalid
}
} else {
+ zip.close();
return png_invalid(); // could not set pack.mcmeta as current file.
}
zip.close();
-
return true;
}
@@ -193,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
return true;
}
-bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
+bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
@@ -205,6 +206,68 @@ bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
return true;
}
+bool processPackPNG(const ResourcePack& pack)
+{
+ auto png_invalid = [&pack]() {
+ qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
+ return false;
+ };
+
+ switch (pack.type()) {
+ case ResourceType::FOLDER:
+ {
+ QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
+ 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 = pack_png_file.readAll();
+
+ bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
+
+ 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.
+ }
+ }
+ case ResourceType::ZIPFILE:
+ {
+ Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
+
+ QuaZip zip(pack.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return false; // can't open zip file
+
+ QuaZipFile file(&zip);
+ if (zip.setCurrentFile("pack.png")) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return png_invalid();
+ }
+
+ auto data = file.readAll();
+
+ 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.
+ }
+ }
+ default:
+ qWarning() << "Invalid type for resource pack parse task!";
+ return false;
+ }
+}
+
bool validate(QFileInfo file)
{
ResourcePack rp{ file };
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
index d0c24c2b..58d90b3b 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h
@@ -35,7 +35,10 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
-bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
+bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
+
+/// processes ONLY the pack.png (rest of the pack may be invalid)
+bool processPackPNG(const ResourcePack& pack);
/** Checks whether a file is valid as a resource pack or not. */
bool validate(QFileInfo file);
diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
index 38f1d7c1..a72e8115 100644
--- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp
@@ -131,6 +131,7 @@ bool processZIP(TexturePack& pack, ProcessingLevel level)
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
file.close();
+ zip.close();
if (!packPNG_result) {
return false;
}
@@ -147,7 +148,7 @@ bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
return true;
}
-bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
+bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data)
{
auto img = QImage::fromData(raw_data);
if (!img.isNull()) {
@@ -159,6 +160,70 @@ bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
return true;
}
+bool processPackPNG(const TexturePack& pack)
+{
+ auto png_invalid = [&pack]() {
+ qWarning() << "Texture pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
+ return false;
+ };
+
+ switch (pack.type()) {
+ case ResourceType::FOLDER:
+ {
+ QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
+ 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 = pack_png_file.readAll();
+
+ bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
+
+ 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.
+ }
+ }
+ case ResourceType::ZIPFILE:
+ {
+ Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
+
+ QuaZip zip(pack.fileinfo().filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return false; // can't open zip file
+
+ QuaZipFile file(&zip);
+ if (zip.setCurrentFile("pack.png")) {
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCritical() << "Failed to open file in zip.";
+ zip.close();
+ return png_invalid();
+ }
+
+ auto data = file.readAll();
+
+ bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
+
+ file.close();
+ if (!pack_png_result) {
+ zip.close();
+ return png_invalid(); // pack.png invalid
+ }
+ } else {
+ zip.close();
+ return png_invalid(); // could not set pack.mcmeta as current file.
+ }
+ }
+ default:
+ qWarning() << "Invalid type for resource pack parse task!";
+ return false;
+ }
+}
+
bool validate(QFileInfo file)
{
TexturePack rp{ file };
diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h
index 1589f8cb..6b91565a 100644
--- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h
@@ -36,7 +36,10 @@ bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
-bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
+bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data);
+
+/// processes ONLY the pack.png (rest of the pack may be invalid)
+bool processPackPNG(const TexturePack& pack);
/** Checks whether a file is valid as a texture pack or not. */
bool validate(QFileInfo file);