aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft')
-rw-r--r--launcher/minecraft/mod/Mod.cpp46
-rw-r--r--launcher/minecraft/mod/Mod.h20
-rw-r--r--launcher/minecraft/mod/ModDetails.h90
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp210
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.h3
5 files changed, 369 insertions, 0 deletions
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index c495cd47..392f7f2e 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,6 +203,9 @@ 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,47 @@ auto Mod::provider() const -> std::optional<QString>
return {};
}
+
+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({128, 128}, 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();
+ }
+ // Imaged got evicted from the cache or an attmept 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..4be0842f 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>
@@ -65,6 +69,13 @@ public:
auto status() const -> ModStatus;
auto provider() const -> std::optional<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 +96,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..eb3770d6 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,77 @@ 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::fromUserInput(part);
+ if (url.isValid()) {
+ 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;
+ }
+};
+
struct ModDetails
{
/* Mod ID as defined in the ModLoader-specific metadata */
@@ -72,6 +144,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 +170,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 +185,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 +202,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/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 5342d693..084b0afb 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,30 @@ 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());
+ }
+ details.licenses.push_back(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 +229,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" + largest;
+ details.icon_file = obj.value(key).toString();
+ } else { // parsing the sizes failed
+ // take the first
+ for (auto icon : obj) {
+ details.icon_file = icon.toString();
+ break;
+ }
+ }
+ } else if (icon.isString()) {
+ details.icon_file = icon.toString();
+ }
}
}
return details;
@@ -238,6 +317,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" + largest;
+ details.icon_file = obj.value(key).toString();
+ } else { // parsing the sizes failed
+ // take the first
+ for (auto icon : obj) {
+ details.icon_file = icon.toString();
+ break;
+ }
+ }
+ } else if (icon.isString()) {
+ details.icon_file = icon.toString();
+ }
+ }
+
}
return details;
}
@@ -515,6 +646,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 {