2022-05-10 23:57:47 +01:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
/*
|
2022-09-23 22:06:36 +01:00
|
|
|
* PolyMC - Minecraft Launcher
|
|
|
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, version 3.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2022-05-10 23:57:47 +01:00
|
|
|
|
2022-04-13 23:16:36 +01:00
|
|
|
#include "Packwiz.h"
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QObject>
|
2023-04-11 22:45:44 +01:00
|
|
|
#include <algorithm>
|
|
|
|
#include <iterator>
|
2022-04-13 23:16:36 +01:00
|
|
|
|
2022-11-03 19:59:50 +00:00
|
|
|
#include "FileSystem.h"
|
|
|
|
#include "StringUtils.h"
|
|
|
|
|
2022-04-16 02:37:10 +01:00
|
|
|
#include "minecraft/mod/Mod.h"
|
2022-04-20 00:19:51 +01:00
|
|
|
#include "modplatform/ModIndex.h"
|
2022-04-16 02:37:10 +01:00
|
|
|
|
2022-11-03 19:59:50 +00:00
|
|
|
#include <toml++/toml.h>
|
|
|
|
|
2022-04-16 17:27:29 +01:00
|
|
|
namespace Packwiz {
|
|
|
|
|
2022-05-18 09:46:07 +01:00
|
|
|
auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
|
|
|
|
{
|
|
|
|
QFile index_file(index_dir.absoluteFilePath(normalized_fname));
|
|
|
|
|
|
|
|
QString real_fname = normalized_fname;
|
|
|
|
if (!index_file.exists()) {
|
|
|
|
// Tries to get similar entries
|
|
|
|
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
|
|
|
|
if (!QString::compare(normalized_fname, file_name, Qt::CaseInsensitive)) {
|
|
|
|
real_fname = file_name;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
if (should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)) {
|
2022-05-18 09:46:07 +01:00
|
|
|
qCritical() << "Could not find a match for a valid metadata file!";
|
|
|
|
qCritical() << "File: " << normalized_fname;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return real_fname;
|
|
|
|
}
|
|
|
|
|
2022-04-15 02:02:41 +01:00
|
|
|
// Helpers
|
2022-06-19 18:26:15 +01:00
|
|
|
static inline auto indexFileName(QString const& mod_slug) -> QString
|
2022-04-15 02:02:41 +01:00
|
|
|
{
|
2022-09-23 22:06:36 +01:00
|
|
|
if (mod_slug.endsWith(".pw.toml"))
|
2022-06-19 18:26:15 +01:00
|
|
|
return mod_slug;
|
|
|
|
return QString("%1.pw.toml").arg(mod_slug);
|
2022-04-15 02:02:41 +01:00
|
|
|
}
|
|
|
|
|
2022-04-20 01:10:12 +01:00
|
|
|
static ModPlatform::ProviderCapabilities ProviderCaps;
|
|
|
|
|
2022-05-18 09:46:07 +01:00
|
|
|
// Helper functions for extracting data from the TOML file
|
2022-11-03 19:59:50 +00:00
|
|
|
auto stringEntry(toml::table table, QString entry_name) -> QString
|
2022-05-18 09:46:07 +01:00
|
|
|
{
|
2022-11-03 19:59:50 +00:00
|
|
|
auto node = table[StringUtils::toStdString(entry_name)];
|
2022-09-23 22:06:36 +01:00
|
|
|
if (!node) {
|
2022-11-03 19:59:50 +00:00
|
|
|
qCritical() << "Failed to read str property '" + entry_name + "' in mod metadata.";
|
2022-05-18 09:46:07 +01:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-11-03 19:59:50 +00:00
|
|
|
return node.value_or("");
|
2022-05-18 09:46:07 +01:00
|
|
|
}
|
|
|
|
|
2022-11-03 19:59:50 +00:00
|
|
|
auto intEntry(toml::table table, QString entry_name) -> int
|
2022-05-18 09:46:07 +01:00
|
|
|
{
|
2022-11-03 19:59:50 +00:00
|
|
|
auto node = table[StringUtils::toStdString(entry_name)];
|
2022-09-23 22:06:36 +01:00
|
|
|
if (!node) {
|
2022-11-03 19:59:50 +00:00
|
|
|
qCritical() << "Failed to read int property '" + entry_name + "' in mod metadata.";
|
2022-05-18 09:46:07 +01:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
return node.value_or(0);
|
2022-05-18 09:46:07 +01:00
|
|
|
}
|
|
|
|
|
2022-04-16 17:27:29 +01:00
|
|
|
auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod
|
2022-04-13 23:16:36 +01:00
|
|
|
{
|
|
|
|
Mod mod;
|
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
mod.slug = mod_pack.slug;
|
2022-04-13 23:16:36 +01:00
|
|
|
mod.name = mod_pack.name;
|
|
|
|
mod.filename = mod_version.fileName;
|
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
if (mod_pack.provider == ModPlatform::ResourceProvider::FLAME) {
|
2022-05-07 23:39:00 +01:00
|
|
|
mod.mode = "metadata:curseforge";
|
2022-09-23 22:06:36 +01:00
|
|
|
} else {
|
2022-05-07 23:39:00 +01:00
|
|
|
mod.mode = "url";
|
|
|
|
mod.url = mod_version.downloadUrl;
|
|
|
|
}
|
|
|
|
|
2022-05-06 16:42:01 +01:00
|
|
|
mod.hash_format = mod_version.hash_type;
|
2022-04-21 19:45:20 +01:00
|
|
|
mod.hash = mod_version.hash;
|
2022-04-13 23:16:36 +01:00
|
|
|
|
|
|
|
mod.provider = mod_pack.provider;
|
2022-05-13 15:42:08 +01:00
|
|
|
mod.file_id = mod_version.fileId;
|
|
|
|
mod.project_id = mod_pack.addonId;
|
2022-04-13 23:16:36 +01:00
|
|
|
|
|
|
|
return mod;
|
|
|
|
}
|
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod
|
2022-04-16 02:37:10 +01:00
|
|
|
{
|
|
|
|
// Try getting metadata if it exists
|
2022-09-23 22:06:36 +01:00
|
|
|
Mod mod{ getIndexForMod(index_dir, slug) };
|
|
|
|
if (mod.isValid())
|
2022-04-16 02:37:10 +01:00
|
|
|
return mod;
|
|
|
|
|
2022-04-20 22:45:39 +01:00
|
|
|
qWarning() << QString("Tried to create mod metadata with a Mod without metadata!");
|
2022-04-16 02:37:10 +01:00
|
|
|
|
2022-04-20 22:45:39 +01:00
|
|
|
return {};
|
2022-04-16 02:37:10 +01:00
|
|
|
}
|
|
|
|
|
2022-04-16 17:27:29 +01:00
|
|
|
void V1::updateModIndex(QDir& index_dir, Mod& mod)
|
2022-04-13 23:16:36 +01:00
|
|
|
{
|
2022-09-23 22:06:36 +01:00
|
|
|
if (!mod.isValid()) {
|
2022-04-16 02:37:10 +01:00
|
|
|
qCritical() << QString("Tried to update metadata of an invalid mod!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-13 23:16:36 +01:00
|
|
|
// Ensure the corresponding mod's info exists, and create it if not
|
2022-05-18 09:46:07 +01:00
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
auto normalized_fname = indexFileName(mod.slug);
|
2022-05-18 09:46:07 +01:00
|
|
|
auto real_fname = getRealIndexName(index_dir, normalized_fname);
|
|
|
|
|
|
|
|
QFile index_file(index_dir.absoluteFilePath(real_fname));
|
2022-04-13 23:16:36 +01:00
|
|
|
|
2022-06-11 21:19:34 +01:00
|
|
|
if (real_fname != normalized_fname)
|
|
|
|
index_file.rename(normalized_fname);
|
|
|
|
|
2022-04-13 23:16:36 +01:00
|
|
|
// There's already data on there!
|
2022-04-16 02:37:10 +01:00
|
|
|
// TODO: We should do more stuff here, as the user is likely trying to
|
|
|
|
// override a file. In this case, check versions and ask the user what
|
|
|
|
// they want to do!
|
2022-09-23 22:06:36 +01:00
|
|
|
if (index_file.exists()) {
|
|
|
|
index_file.remove();
|
2022-11-03 19:59:50 +00:00
|
|
|
} else {
|
|
|
|
FS::ensureFilePathExists(index_file.fileName());
|
2022-09-23 22:06:36 +01:00
|
|
|
}
|
2022-04-13 23:16:36 +01:00
|
|
|
|
|
|
|
if (!index_file.open(QIODevice::ReadWrite)) {
|
2022-04-15 02:02:41 +01:00
|
|
|
qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name));
|
2022-04-13 23:16:36 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put TOML data into the file
|
|
|
|
QTextStream in_stream(&index_file);
|
|
|
|
auto addToStream = [&in_stream](QString&& key, QString value) { in_stream << QString("%1 = \"%2\"\n").arg(key, value); };
|
|
|
|
|
|
|
|
{
|
|
|
|
addToStream("name", mod.name);
|
|
|
|
addToStream("filename", mod.filename);
|
|
|
|
addToStream("side", mod.side);
|
|
|
|
|
|
|
|
in_stream << QString("\n[download]\n");
|
2022-05-07 23:39:00 +01:00
|
|
|
addToStream("mode", mod.mode);
|
2022-04-13 23:16:36 +01:00
|
|
|
addToStream("url", mod.url.toString());
|
|
|
|
addToStream("hash-format", mod.hash_format);
|
|
|
|
addToStream("hash", mod.hash);
|
|
|
|
|
|
|
|
in_stream << QString("\n[update]\n");
|
2022-04-20 01:10:12 +01:00
|
|
|
in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider));
|
2022-09-23 22:06:36 +01:00
|
|
|
switch (mod.provider) {
|
2022-11-25 12:23:46 +00:00
|
|
|
case (ModPlatform::ResourceProvider::FLAME):
|
2022-09-23 22:06:36 +01:00
|
|
|
in_stream << QString("file-id = %1\n").arg(mod.file_id.toString());
|
|
|
|
in_stream << QString("project-id = %1\n").arg(mod.project_id.toString());
|
|
|
|
break;
|
2022-11-25 12:23:46 +00:00
|
|
|
case (ModPlatform::ResourceProvider::MODRINTH):
|
2022-09-23 22:06:36 +01:00
|
|
|
addToStream("mod-id", mod.mod_id().toString());
|
|
|
|
addToStream("version", mod.version().toString());
|
|
|
|
break;
|
2022-04-17 15:40:41 +01:00
|
|
|
}
|
2022-04-13 23:16:36 +01:00
|
|
|
}
|
2022-05-18 09:46:07 +01:00
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
index_file.flush();
|
2022-05-18 09:46:07 +01:00
|
|
|
index_file.close();
|
2022-04-13 23:16:36 +01:00
|
|
|
}
|
2022-04-14 01:25:08 +01:00
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
|
2022-04-15 02:02:41 +01:00
|
|
|
{
|
2022-06-19 18:26:15 +01:00
|
|
|
auto normalized_fname = indexFileName(mod_slug);
|
2022-05-18 09:46:07 +01:00
|
|
|
auto real_fname = getRealIndexName(index_dir, normalized_fname);
|
|
|
|
if (real_fname.isEmpty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QFile index_file(index_dir.absoluteFilePath(real_fname));
|
2022-04-15 02:02:41 +01:00
|
|
|
|
2022-06-11 21:19:34 +01:00
|
|
|
if (!index_file.exists()) {
|
2022-06-19 18:26:15 +01:00
|
|
|
qWarning() << QString("Tried to delete non-existent mod metadata for %1!").arg(mod_slug);
|
2022-04-15 02:02:41 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-11 21:19:34 +01:00
|
|
|
if (!index_file.remove()) {
|
2022-06-19 18:26:15 +01:00
|
|
|
qWarning() << QString("Failed to remove metadata for mod %1!").arg(mod_slug);
|
2022-04-15 02:02:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-11 21:19:34 +01:00
|
|
|
void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
|
|
|
|
{
|
|
|
|
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
|
|
|
|
auto mod = getIndexForMod(index_dir, file_name);
|
|
|
|
|
|
|
|
if (mod.mod_id() == mod_id) {
|
|
|
|
deleteModIndex(index_dir, mod.name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
2022-04-14 01:25:08 +01:00
|
|
|
{
|
|
|
|
Mod mod;
|
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
auto normalized_fname = indexFileName(slug);
|
2022-05-18 09:46:07 +01:00
|
|
|
auto real_fname = getRealIndexName(index_dir, normalized_fname, true);
|
|
|
|
if (real_fname.isEmpty())
|
2022-04-16 02:37:10 +01:00
|
|
|
return {};
|
2022-05-18 09:46:07 +01:00
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
toml::table table;
|
|
|
|
#if TOML_EXCEPTIONS
|
|
|
|
try {
|
2022-11-03 19:59:50 +00:00
|
|
|
table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname)));
|
2022-09-23 22:06:36 +01:00
|
|
|
} catch (const toml::parse_error& err) {
|
|
|
|
qWarning() << QString("Could not open file %1!").arg(normalized_fname);
|
|
|
|
qWarning() << "Reason: " << QString(err.what());
|
2022-04-16 02:37:10 +01:00
|
|
|
return {};
|
2022-04-15 02:02:41 +01:00
|
|
|
}
|
2022-09-23 22:06:36 +01:00
|
|
|
#else
|
2022-11-03 19:59:50 +00:00
|
|
|
table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname)));
|
2022-04-14 01:25:08 +01:00
|
|
|
if (!table) {
|
2022-06-19 18:26:15 +01:00
|
|
|
qWarning() << QString("Could not open file %1!").arg(normalized_fname);
|
2022-09-23 22:06:36 +01:00
|
|
|
qWarning() << "Reason: " << QString(table.error().what());
|
2022-04-16 02:37:10 +01:00
|
|
|
return {};
|
2022-04-14 01:25:08 +01:00
|
|
|
}
|
2022-09-23 22:06:36 +01:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// index_file.close();
|
2022-05-18 09:46:07 +01:00
|
|
|
|
2022-06-19 18:26:15 +01:00
|
|
|
mod.slug = slug;
|
|
|
|
|
2022-05-18 09:46:07 +01:00
|
|
|
{ // Basic info
|
2022-04-14 01:25:08 +01:00
|
|
|
mod.name = stringEntry(table, "name");
|
|
|
|
mod.filename = stringEntry(table, "filename");
|
|
|
|
mod.side = stringEntry(table, "side");
|
|
|
|
}
|
|
|
|
|
2022-05-18 09:46:07 +01:00
|
|
|
{ // [download] info
|
2022-09-23 22:06:36 +01:00
|
|
|
auto download_table = table["download"].as_table();
|
2022-04-14 01:25:08 +01:00
|
|
|
if (!download_table) {
|
|
|
|
qCritical() << QString("No [download] section found on mod metadata!");
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
mod.mode = stringEntry(*download_table, "mode");
|
|
|
|
mod.url = stringEntry(*download_table, "url");
|
|
|
|
mod.hash_format = stringEntry(*download_table, "hash-format");
|
|
|
|
mod.hash = stringEntry(*download_table, "hash");
|
2022-04-14 01:25:08 +01:00
|
|
|
}
|
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
{ // [update] info
|
2022-11-25 12:23:46 +00:00
|
|
|
using Provider = ModPlatform::ResourceProvider;
|
2022-04-14 01:25:08 +01:00
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
auto update_table = table["update"];
|
|
|
|
if (!update_table || !update_table.is_table()) {
|
2022-04-14 01:25:08 +01:00
|
|
|
qCritical() << QString("No [update] section found on mod metadata!");
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-09-23 22:06:36 +01:00
|
|
|
toml::table* mod_provider_table = nullptr;
|
|
|
|
if ((mod_provider_table = update_table[ProviderCaps.name(Provider::FLAME)].as_table())) {
|
2022-04-14 01:25:08 +01:00
|
|
|
mod.provider = Provider::FLAME;
|
2022-09-23 22:06:36 +01:00
|
|
|
mod.file_id = intEntry(*mod_provider_table, "file-id");
|
|
|
|
mod.project_id = intEntry(*mod_provider_table, "project-id");
|
|
|
|
} else if ((mod_provider_table = update_table[ProviderCaps.name(Provider::MODRINTH)].as_table())) {
|
2022-04-14 01:25:08 +01:00
|
|
|
mod.provider = Provider::MODRINTH;
|
2022-09-23 22:06:36 +01:00
|
|
|
mod.mod_id() = stringEntry(*mod_provider_table, "mod-id");
|
|
|
|
mod.version() = stringEntry(*mod_provider_table, "version");
|
2022-04-14 01:25:08 +01:00
|
|
|
} else {
|
2022-04-15 02:02:41 +01:00
|
|
|
qCritical() << QString("No mod provider on mod metadata!");
|
2022-04-14 01:25:08 +01:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mod;
|
|
|
|
}
|
2022-04-16 17:27:29 +01:00
|
|
|
|
2022-06-11 21:19:34 +01:00
|
|
|
auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod
|
|
|
|
{
|
|
|
|
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
|
|
|
|
auto mod = getIndexForMod(index_dir, file_name);
|
|
|
|
|
|
|
|
if (mod.mod_id() == mod_id)
|
|
|
|
return mod;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-04-11 22:45:44 +01:00
|
|
|
auto V1::getAllMods(QDir& index_dir) -> QList<Mod>
|
|
|
|
{
|
|
|
|
auto files = index_dir.entryList(QDir::Filter::Files);
|
|
|
|
auto mods = QList<Mod>();
|
|
|
|
std::transform(files.begin(), files.end(), std::back_inserter(mods),
|
2023-04-11 22:49:50 +01:00
|
|
|
[&index_dir](auto file_name) { return getIndexForMod(index_dir, file_name); });
|
2023-04-11 22:45:44 +01:00
|
|
|
return mods;
|
|
|
|
}
|
|
|
|
|
2022-06-11 21:19:34 +01:00
|
|
|
} // namespace Packwiz
|