feat: better handling of optional mods

This disables the optional mods by default and tell the user about it.
Pretty hackish, but a better solution would involve the modrinth
metadata to have the mod names...

Also sorry for the diffs, my clangd went rogue x.x
This commit is contained in:
flow 2022-05-15 22:16:52 -03:00
parent e92b7bd25e
commit 62e099ace5
No known key found for this signature in database
GPG Key ID: 8D0F221F0A59F469
3 changed files with 80 additions and 80 deletions

View File

@ -35,35 +35,38 @@
*/ */
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include <QtConcurrentRun>
#include "Application.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Application.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "settings/INISettingsObject.h"
#include "icons/IconUtils.h" #include "icons/IconUtils.h"
#include <QtConcurrentRun> #include "settings/INISettingsObject.h"
// FIXME: this does not belong here, it's Minecraft/Flame specific // FIXME: this does not belong here, it's Minecraft/Flame specific
#include <quazip/quazipdir.h>
#include "Json.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h" #include "modplatform/flame/PackManifest.h"
#include "Json.h"
#include <quazip/quazipdir.h>
#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/modrinth/ModrinthPackManifest.h"
#include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/technic/TechnicPackProcessor.h"
#include "icons/IconList.h"
#include "Application.h" #include "Application.h"
#include "icons/IconList.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{ {
m_sourceUrl = sourceUrl; m_sourceUrl = sourceUrl;
m_parent = parent;
} }
bool InstanceImportTask::abort() bool InstanceImportTask::abort()
@ -476,124 +479,118 @@ void InstanceImportTask::processMultiMC()
instance.setName(m_instName); instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack // if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon != "default") if (m_instIcon != "default") {
{
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
} } else {
else
{
m_instIcon = instance.iconKey(); m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
if (!importIconPath.isNull() && QFile::exists(importIconPath)) if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
{
// import icon // import icon
auto iconList = APPLICATION->icons(); auto iconList = APPLICATION->icons();
if (iconList->iconFileExists(m_instIcon)) if (iconList->iconFileExists(m_instIcon)) {
{
iconList->deleteIcon(m_instIcon); iconList->deleteIcon(m_instIcon);
} }
iconList->installIcons({importIconPath}); iconList->installIcons({ importIconPath });
} }
} }
emitSucceeded(); emitSucceeded();
} }
void InstanceImportTask::processModrinth() { void InstanceImportTask::processModrinth()
{
std::vector<Modrinth::File> files; std::vector<Modrinth::File> files;
QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
try try {
{
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
auto doc = Json::requireDocument(indexPath); auto doc = Json::requireDocument(indexPath);
auto obj = Json::requireObject(doc, "modrinth.index.json"); auto obj = Json::requireObject(doc, "modrinth.index.json");
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
if (formatVersion == 1) if (formatVersion == 1) {
{
auto game = Json::requireString(obj, "game", "modrinth.index.json"); auto game = Json::requireString(obj, "game", "modrinth.index.json");
if (game != "minecraft") if (game != "minecraft") {
{
throw JSONValidationError("Unknown game: " + game); throw JSONValidationError("Unknown game: " + game);
} }
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json"); auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
std::transform(jsonFiles.begin(), jsonFiles.end(), std::back_inserter(files), [](const QJsonObject& obj) bool had_optional = false;
{ for (auto& obj : jsonFiles) {
Modrinth::File file; Modrinth::File file;
file.path = Json::requireString(obj, "path"); file.path = Json::requireString(obj, "path");
QString supported = Json::ensureString(Json::ensureObject(obj, "env"));
auto env = Json::ensureObject(obj, "env");
QString support = Json::ensureString(env, "client", "unsupported");
if (support == "unsupported") {
continue;
} else if (support == "optional") {
// TODO: Make a review dialog for choosing which ones the user wants!
if (!had_optional) {
had_optional = true;
auto info = CustomMessageBox::selectable(
m_parent, tr("Optional mod detected!"),
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information);
info->exec();
}
if (file.path.endsWith(".jar"))
file.path += ".disabled";
}
QJsonObject hashes = Json::requireObject(obj, "hashes"); QJsonObject hashes = Json::requireObject(obj, "hashes");
QString hash; QString hash;
QCryptographicHash::Algorithm hashAlgorithm; QCryptographicHash::Algorithm hashAlgorithm;
hash = Json::ensureString(hashes, "sha1"); hash = Json::ensureString(hashes, "sha1");
hashAlgorithm = QCryptographicHash::Sha1; hashAlgorithm = QCryptographicHash::Sha1;
if (hash.isEmpty()) if (hash.isEmpty()) {
{
hash = Json::ensureString(hashes, "sha512"); hash = Json::ensureString(hashes, "sha512");
hashAlgorithm = QCryptographicHash::Sha512; hashAlgorithm = QCryptographicHash::Sha512;
if (hash.isEmpty()) if (hash.isEmpty()) {
{
hash = Json::ensureString(hashes, "sha256"); hash = Json::ensureString(hashes, "sha256");
hashAlgorithm = QCryptographicHash::Sha256; hashAlgorithm = QCryptographicHash::Sha256;
if (hash.isEmpty()) if (hash.isEmpty()) {
{
throw JSONValidationError("No hash found for: " + file.path); throw JSONValidationError("No hash found for: " + file.path);
} }
} }
} }
file.hash = QByteArray::fromHex(hash.toLatin1()); file.hash = QByteArray::fromHex(hash.toLatin1());
file.hashAlgorithm = hashAlgorithm; file.hashAlgorithm = hashAlgorithm;
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode (as Modrinth seems to incorrectly handle spaces) // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode (as Modrinth seems to incorrectly
// handle spaces)
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path); file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
if (!file.download.isValid() || !Modrinth::validadeDownloadUrl(file.download)) if (!file.download.isValid() || !Modrinth::validadeDownloadUrl(file.download)) {
{
throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL"); throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
} }
return file; files.push_back(file);
}); }
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
{
QString name = it.key(); QString name = it.key();
if (name == "minecraft") if (name == "minecraft") {
{
if (!minecraftVersion.isEmpty()) if (!minecraftVersion.isEmpty())
throw JSONValidationError("Duplicate Minecraft version"); throw JSONValidationError("Duplicate Minecraft version");
minecraftVersion = Json::requireString(*it, "Minecraft version"); minecraftVersion = Json::requireString(*it, "Minecraft version");
} } else if (name == "fabric-loader") {
else if (name == "fabric-loader")
{
if (!fabricVersion.isEmpty()) if (!fabricVersion.isEmpty())
throw JSONValidationError("Duplicate Fabric Loader version"); throw JSONValidationError("Duplicate Fabric Loader version");
fabricVersion = Json::requireString(*it, "Fabric Loader version"); fabricVersion = Json::requireString(*it, "Fabric Loader version");
} } else if (name == "quilt-loader") {
else if (name == "quilt-loader")
{
if (!quiltVersion.isEmpty()) if (!quiltVersion.isEmpty())
throw JSONValidationError("Duplicate Quilt Loader version"); throw JSONValidationError("Duplicate Quilt Loader version");
quiltVersion = Json::requireString(*it, "Quilt Loader version"); quiltVersion = Json::requireString(*it, "Quilt Loader version");
} } else if (name == "forge") {
else if (name == "forge")
{
if (!forgeVersion.isEmpty()) if (!forgeVersion.isEmpty())
throw JSONValidationError("Duplicate Forge version"); throw JSONValidationError("Duplicate Forge version");
forgeVersion = Json::requireString(*it, "Forge version"); forgeVersion = Json::requireString(*it, "Forge version");
} } else {
else
{
throw JSONValidationError("Unknown dependency type: " + name); throw JSONValidationError("Unknown dependency type: " + name);
} }
} }
} } else {
else
{
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
} }
QFile::remove(indexPath); QFile::remove(indexPath);
} } catch (const JSONValidationError& e) {
catch (const JSONValidationError &e)
{
emitFailed(tr("Could not understand pack index:\n") + e.cause()); emitFailed(tr("Could not understand pack index:\n") + e.cause());
return; return;
} }

View File

@ -55,7 +55,7 @@ class InstanceImportTask : public InstanceTask
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceImportTask(const QUrl sourceUrl); explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
@ -94,4 +94,7 @@ private: /* data */
Flame, Flame,
Modrinth, Modrinth,
} m_modpackType = ModpackType::Unknown; } m_modpackType = ModpackType::Unknown;
//FIXME: nuke
QWidget* m_parent;
}; };

View File

@ -251,7 +251,7 @@ void ModrinthPage::suggestCurrent()
for (auto& ver : current.versions) { for (auto& ver : current.versions) {
if (ver.id == selectedVersion) { if (ver.id == selectedVersion) {
dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url)); dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url, this));
auto iconName = current.iconName; auto iconName = current.iconName;
m_model->getLogo(iconName, current.iconUrl.toString(), m_model->getLogo(iconName, current.iconUrl.toString(),
[this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); }); [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); });