Merge branch 'develop' into data-packs
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
@@ -50,6 +51,12 @@ class BasicFolderLoadTask : public Task {
|
||||
|
||||
m_dir.refresh();
|
||||
for (auto entry : m_dir.entryInfoList()) {
|
||||
auto filePath = entry.absoluteFilePath();
|
||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||
if (newFilePath != filePath) {
|
||||
FS::move(filePath, newFilePath);
|
||||
entry = QFileInfo(newFilePath);
|
||||
}
|
||||
auto resource = m_create_func(entry);
|
||||
resource->moveToThread(m_thread_to_spawn_into);
|
||||
m_result->resources.insert(resource->internal_id(), resource);
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
#include <memory>
|
||||
#include "Json.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
#include "ui/pages/modplatform/ModModel.h"
|
||||
#include "ui/pages/modplatform/flame/FlameResourceModels.h"
|
||||
@@ -44,6 +44,14 @@ static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
|
||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value();
|
||||
}
|
||||
|
||||
static bool checkDependencies(std::shared_ptr<GetModDependenciesTask::PackDependency> sel,
|
||||
Version mcVersion,
|
||||
ModPlatform::ModLoaderTypes loaders)
|
||||
{
|
||||
return (sel->pack->versions.isEmpty() || sel->version.mcVersion.contains(mcVersion.toString())) &&
|
||||
(!loaders || !sel->version.loaders || sel->version.loaders & loaders);
|
||||
}
|
||||
|
||||
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
||||
BaseInstance* instance,
|
||||
ModFolderModel* folder,
|
||||
@@ -57,18 +65,21 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
||||
, m_version(mcVersion(instance))
|
||||
, m_loaderType(mcLoaders(instance))
|
||||
{
|
||||
for (auto mod : folder->allMods())
|
||||
for (auto mod : folder->allMods()) {
|
||||
m_mods_file_names << mod->fileinfo().fileName();
|
||||
if (auto meta = mod->metadata(); meta)
|
||||
m_mods.append(meta);
|
||||
}
|
||||
prepare();
|
||||
}
|
||||
|
||||
void GetModDependenciesTask::prepare()
|
||||
{
|
||||
for (auto sel : m_selected) {
|
||||
for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) {
|
||||
addTask(prepareDependencyTask(dep, sel->pack->provider, 20));
|
||||
}
|
||||
if (checkDependencies(sel, m_version, m_loaderType))
|
||||
for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) {
|
||||
addTask(prepareDependencyTask(dep, sel->pack->provider, 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,10 +148,11 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDepende
|
||||
auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||
auto responseInfo = std::make_shared<QByteArray>();
|
||||
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
|
||||
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
|
||||
QObject::connect(info.get(), &NetJob::succeeded, [this, responseInfo, provider, pDep] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
removePack(pDep->pack->addonId);
|
||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qDebug() << *responseInfo;
|
||||
@@ -151,6 +163,7 @@ Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDepende
|
||||
: Json::requireObject(doc);
|
||||
provider.mod->loadIndexedPack(*pDep->pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
removePack(pDep->pack->addonId);
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod info: " << e.cause();
|
||||
}
|
||||
@@ -180,7 +193,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
|
||||
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
|
||||
ResourceAPI::DependencySearchCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [](QString reason, int) {
|
||||
qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason);
|
||||
};
|
||||
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
|
||||
try {
|
||||
QJsonArray arr;
|
||||
@@ -211,11 +226,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
pDep->pack->versionsLoaded = true;
|
||||
|
||||
} catch (const JSONValidationError& e) {
|
||||
removePack(dep.addonId);
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod version: " << e.cause();
|
||||
return;
|
||||
}
|
||||
if (level == 0) {
|
||||
removePack(dep.addonId);
|
||||
qWarning() << "Dependency cycle exceeded";
|
||||
return;
|
||||
}
|
||||
@@ -225,8 +242,13 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
if (dep_.addonId != pDep->version.addonId) {
|
||||
removePack(pDep->version.addonId);
|
||||
addTask(prepareDependencyTask(dep_, provider.name, level));
|
||||
} else
|
||||
} else {
|
||||
addTask(getProjectInfoTask(pDep));
|
||||
}
|
||||
}
|
||||
if (isLocalyInstalled(pDep)) {
|
||||
removePack(pDep->version.addonId);
|
||||
return;
|
||||
}
|
||||
for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) {
|
||||
addTask(prepareDependencyTask(dep_, provider.name, level - 1));
|
||||
@@ -238,7 +260,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
|
||||
return tasks;
|
||||
}
|
||||
|
||||
void GetModDependenciesTask::removePack(const QVariant addonId)
|
||||
void GetModDependenciesTask::removePack(const QVariant& addonId)
|
||||
{
|
||||
auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
|
||||
@@ -252,9 +274,9 @@ void GetModDependenciesTask::removePack(const QVariant addonId)
|
||||
#endif
|
||||
}
|
||||
|
||||
QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
|
||||
auto GetModDependenciesTask::getExtraInfo() -> QHash<QString, PackDependencyExtraInfo>
|
||||
{
|
||||
QHash<QString, QStringList> rby;
|
||||
QHash<QString, PackDependencyExtraInfo> rby;
|
||||
auto fullList = m_selected + m_pack_dependencies;
|
||||
for (auto& mod : fullList) {
|
||||
auto addonId = mod->pack->addonId;
|
||||
@@ -276,7 +298,61 @@ QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
|
||||
req.append(smod->pack->name);
|
||||
}
|
||||
}
|
||||
rby[addonId.toString()] = req;
|
||||
rby[addonId.toString()] = { maybeInstalled(mod), req };
|
||||
}
|
||||
return rby;
|
||||
}
|
||||
}
|
||||
|
||||
// super lax compare (but not fuzzy)
|
||||
// convert to lowercase
|
||||
// convert all speratores to whitespace
|
||||
// simplify sequence of internal whitespace to a single space
|
||||
// efectivly compare two strings ignoring all separators and case
|
||||
auto laxCompare = [](QString fsfilename, QString metadataFilename, bool excludeDigits = false) {
|
||||
// allowed character seperators
|
||||
QList<QChar> allowedSeperators = { '-', '+', '.', '_' };
|
||||
if (excludeDigits)
|
||||
allowedSeperators.append({ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
|
||||
|
||||
// copy in lowercase
|
||||
auto fsName = fsfilename.toLower();
|
||||
auto metaName = metadataFilename.toLower();
|
||||
|
||||
// replace all potential allowed seperatores with whitespace
|
||||
for (auto sep : allowedSeperators) {
|
||||
fsName = fsName.replace(sep, ' ');
|
||||
metaName = metaName.replace(sep, ' ');
|
||||
}
|
||||
|
||||
// remove extraneous whitespace
|
||||
fsName = fsName.simplified();
|
||||
metaName = metaName.simplified();
|
||||
|
||||
return fsName.compare(metaName) == 0;
|
||||
};
|
||||
|
||||
bool GetModDependenciesTask::isLocalyInstalled(std::shared_ptr<PackDependency> pDep)
|
||||
{
|
||||
return pDep->version.fileName.isEmpty() ||
|
||||
|
||||
std::find_if(m_selected.begin(), m_selected.end(),
|
||||
[pDep](std::shared_ptr<PackDependency> i) {
|
||||
return !i->version.fileName.isEmpty() && laxCompare(i->version.fileName, pDep->version.fileName);
|
||||
}) != m_selected.end() || // check the selected versions
|
||||
|
||||
std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(),
|
||||
[pDep](QString i) { return !i.isEmpty() && laxCompare(i, pDep->version.fileName); }) !=
|
||||
m_mods_file_names.end() || // check the existing mods
|
||||
|
||||
std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(), [pDep](std::shared_ptr<PackDependency> i) {
|
||||
return pDep->pack->addonId != i->pack->addonId && !i->version.fileName.isEmpty() &&
|
||||
laxCompare(pDep->version.fileName, i->version.fileName);
|
||||
}) != m_pack_dependencies.end(); // check loaded dependencies
|
||||
}
|
||||
|
||||
bool GetModDependenciesTask::maybeInstalled(std::shared_ptr<PackDependency> pDep)
|
||||
{
|
||||
return std::find_if(m_mods_file_names.begin(), m_mods_file_names.end(), [pDep](QString i) {
|
||||
return !i.isEmpty() && laxCompare(i, pDep->version.fileName, true);
|
||||
}) != m_mods_file_names.end(); // check the existing mods
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ class GetModDependenciesTask : public SequentialTask {
|
||||
}
|
||||
};
|
||||
|
||||
struct PackDependencyExtraInfo {
|
||||
bool maybe_installed;
|
||||
QStringList required_by;
|
||||
};
|
||||
|
||||
struct Provider {
|
||||
ModPlatform::ResourceProvider name;
|
||||
std::shared_ptr<ResourceDownload::ModModel> mod;
|
||||
@@ -62,21 +67,25 @@ class GetModDependenciesTask : public SequentialTask {
|
||||
QList<std::shared_ptr<PackDependency>> selected);
|
||||
|
||||
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
||||
QHash<QString, QStringList> getRequiredBy();
|
||||
QHash<QString, PackDependencyExtraInfo> getExtraInfo();
|
||||
|
||||
protected slots:
|
||||
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
|
||||
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, ModPlatform::ResourceProvider, int);
|
||||
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
|
||||
const ModPlatform::ResourceProvider providerName);
|
||||
ModPlatform::ResourceProvider providerName);
|
||||
void prepare();
|
||||
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
|
||||
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
|
||||
void removePack(const QVariant addonId);
|
||||
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, ModPlatform::ResourceProvider providerName);
|
||||
void removePack(const QVariant& addonId);
|
||||
|
||||
bool isLocalyInstalled(std::shared_ptr<PackDependency> pDep);
|
||||
bool maybeInstalled(std::shared_ptr<PackDependency> pDep);
|
||||
|
||||
private:
|
||||
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
|
||||
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
|
||||
QList<std::shared_ptr<PackDependency>> m_selected;
|
||||
QStringList m_mods_file_names;
|
||||
Provider m_flame_provider;
|
||||
Provider m_modrinth_provider;
|
||||
|
||||
|
||||
@@ -469,7 +469,7 @@ bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile("META-INF/mods.toml")) {
|
||||
if (zip.setCurrentFile("META-INF/mods.toml") || zip.setCurrentFile("META-INF/neoforge.mods.toml")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
zip.close();
|
||||
return false;
|
||||
@@ -746,7 +746,7 @@ void LocalModParseTask::executeTask()
|
||||
m_result->details = mod.details();
|
||||
|
||||
if (m_aborted)
|
||||
emit finished();
|
||||
emitAborted();
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@@ -178,6 +178,88 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
|
||||
return true;
|
||||
}
|
||||
|
||||
QString buildStyle(const QJsonObject& obj)
|
||||
{
|
||||
QStringList styles;
|
||||
if (auto color = Json::ensureString(obj, "color"); !color.isEmpty()) {
|
||||
styles << QString("color: %1;").arg(color);
|
||||
}
|
||||
if (obj.contains("bold")) {
|
||||
QString weight = "normal";
|
||||
if (Json::ensureBoolean(obj, "bold", false)) {
|
||||
weight = "bold";
|
||||
}
|
||||
styles << QString("font-weight: %1;").arg(weight);
|
||||
}
|
||||
if (obj.contains("italic")) {
|
||||
QString style = "normal";
|
||||
if (Json::ensureBoolean(obj, "italic", false)) {
|
||||
style = "italic";
|
||||
}
|
||||
styles << QString("font-style: %1;").arg(style);
|
||||
}
|
||||
|
||||
return styles.isEmpty() ? "" : QString("style=\"%1\"").arg(styles.join(" "));
|
||||
}
|
||||
|
||||
QString processComponent(const QJsonArray& value, bool strikethrough, bool underline)
|
||||
{
|
||||
QString result;
|
||||
for (auto current : value)
|
||||
result += processComponent(current, strikethrough, underline);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString processComponent(const QJsonObject& obj, bool strikethrough, bool underline)
|
||||
{
|
||||
underline = Json::ensureBoolean(obj, "underlined", underline);
|
||||
strikethrough = Json::ensureBoolean(obj, "strikethrough", strikethrough);
|
||||
|
||||
QString result = Json::ensureString(obj, "text");
|
||||
if (underline) {
|
||||
result = QString("<u>%1</u>").arg(result);
|
||||
}
|
||||
if (strikethrough) {
|
||||
result = QString("<s>%1</s>").arg(result);
|
||||
}
|
||||
// the extra needs to be a array
|
||||
result += processComponent(Json::ensureArray(obj, "extra"), strikethrough, underline);
|
||||
if (auto style = buildStyle(obj); !style.isEmpty()) {
|
||||
result = QString("<span %1>%2</span>").arg(style, result);
|
||||
}
|
||||
if (obj.contains("clickEvent")) {
|
||||
auto click_event = Json::ensureObject(obj, "clickEvent");
|
||||
auto action = Json::ensureString(click_event, "action");
|
||||
auto value = Json::ensureString(click_event, "value");
|
||||
if (action == "open_url" && !value.isEmpty()) {
|
||||
result = QString("<a href=\"%1\">%2</a>").arg(value, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString processComponent(const QJsonValue& value, bool strikethrough, bool underline)
|
||||
{
|
||||
if (value.isString()) {
|
||||
return value.toString();
|
||||
}
|
||||
if (value.isBool()) {
|
||||
return value.toBool() ? "true" : "false";
|
||||
}
|
||||
if (value.isDouble()) {
|
||||
return QString::number(value.toDouble());
|
||||
}
|
||||
if (value.isArray()) {
|
||||
return processComponent(value.toArray(), strikethrough, underline);
|
||||
}
|
||||
if (value.isObject()) {
|
||||
return processComponent(value.toObject(), strikethrough, underline);
|
||||
}
|
||||
qWarning() << "Invalid component type!";
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://minecraft.wiki/w/Raw_JSON_text_format
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
||||
{
|
||||
@@ -186,7 +268,9 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
||||
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
||||
|
||||
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
|
||||
|
||||
pack.setDescription(processComponent(pack_obj.value("description")));
|
||||
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << "JsonException: " << e.what() << e.cause();
|
||||
return false;
|
||||
@@ -286,8 +370,10 @@ bool LocalResourcePackParseTask::abort()
|
||||
|
||||
void LocalResourcePackParseTask::executeTask()
|
||||
{
|
||||
if (!ResourcePackUtils::process(m_resource_pack))
|
||||
if (!ResourcePackUtils::process(m_resource_pack)) {
|
||||
emitFailed("this is not a resource pack");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
|
||||
@@ -34,6 +34,7 @@ bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
|
||||
|
||||
|
||||
@@ -103,8 +103,10 @@ bool LocalShaderPackParseTask::abort()
|
||||
|
||||
void LocalShaderPackParseTask::executeTask()
|
||||
{
|
||||
if (!ShaderPackUtils::process(m_shader_pack))
|
||||
if (!ShaderPackUtils::process(m_shader_pack)) {
|
||||
emitFailed("this is not a shader pack");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
|
||||
@@ -241,8 +241,10 @@ bool LocalTexturePackParseTask::abort()
|
||||
|
||||
void LocalTexturePackParseTask::executeTask()
|
||||
{
|
||||
if (!TexturePackUtils::process(m_texture_pack))
|
||||
if (!TexturePackUtils::process(m_texture_pack)) {
|
||||
emitFailed("this is not a texture pack");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "ModFolderLoadTask.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
#include <QThread>
|
||||
@@ -63,6 +64,12 @@ void ModFolderLoadTask::executeTask()
|
||||
// Read JAR files that don't have metadata
|
||||
m_mods_dir.refresh();
|
||||
for (auto entry : m_mods_dir.entryInfoList()) {
|
||||
auto filePath = entry.absoluteFilePath();
|
||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||
if (newFilePath != filePath) {
|
||||
FS::move(filePath, newFilePath);
|
||||
entry = QFileInfo(newFilePath);
|
||||
}
|
||||
Mod* mod(new Mod(entry));
|
||||
|
||||
if (mod->enabled()) {
|
||||
|
||||
Reference in New Issue
Block a user