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:
parent
e92b7bd25e
commit
62e099ace5
@ -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"));
|
|
||||||
QJsonObject hashes = Json::requireObject(obj, "hashes");
|
auto env = Json::ensureObject(obj, "env");
|
||||||
QString hash;
|
QString support = Json::ensureString(env, "client", "unsupported");
|
||||||
QCryptographicHash::Algorithm hashAlgorithm;
|
if (support == "unsupported") {
|
||||||
hash = Json::ensureString(hashes, "sha1");
|
continue;
|
||||||
hashAlgorithm = QCryptographicHash::Sha1;
|
} else if (support == "optional") {
|
||||||
if (hash.isEmpty())
|
// TODO: Make a review dialog for choosing which ones the user wants!
|
||||||
{
|
if (!had_optional) {
|
||||||
hash = Json::ensureString(hashes, "sha512");
|
had_optional = true;
|
||||||
hashAlgorithm = QCryptographicHash::Sha512;
|
auto info = CustomMessageBox::selectable(
|
||||||
if (hash.isEmpty())
|
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);
|
||||||
hash = Json::ensureString(hashes, "sha256");
|
info->exec();
|
||||||
hashAlgorithm = QCryptographicHash::Sha256;
|
}
|
||||||
if (hash.isEmpty())
|
|
||||||
{
|
if (file.path.endsWith(".jar"))
|
||||||
throw JSONValidationError("No hash found for: " + file.path);
|
file.path += ".disabled";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject hashes = Json::requireObject(obj, "hashes");
|
||||||
|
QString hash;
|
||||||
|
QCryptographicHash::Algorithm hashAlgorithm;
|
||||||
|
hash = Json::ensureString(hashes, "sha1");
|
||||||
|
hashAlgorithm = QCryptographicHash::Sha1;
|
||||||
|
if (hash.isEmpty()) {
|
||||||
|
hash = Json::ensureString(hashes, "sha512");
|
||||||
|
hashAlgorithm = QCryptographicHash::Sha512;
|
||||||
|
if (hash.isEmpty()) {
|
||||||
|
hash = Json::ensureString(hashes, "sha256");
|
||||||
|
hashAlgorithm = QCryptographicHash::Sha256;
|
||||||
|
if (hash.isEmpty()) {
|
||||||
|
throw JSONValidationError("No hash found for: " + file.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.hash = QByteArray::fromHex(hash.toLatin1());
|
}
|
||||||
file.hashAlgorithm = hashAlgorithm;
|
file.hash = QByteArray::fromHex(hash.toLatin1());
|
||||||
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode (as Modrinth seems to incorrectly handle spaces)
|
file.hashAlgorithm = hashAlgorithm;
|
||||||
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
|
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode (as Modrinth seems to incorrectly
|
||||||
if (!file.download.isValid() || !Modrinth::validadeDownloadUrl(file.download))
|
// handle spaces)
|
||||||
{
|
file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
|
||||||
throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
|
if (!file.download.isValid() || !Modrinth::validadeDownloadUrl(file.download)) {
|
||||||
}
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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); });
|
||||||
|
Loading…
Reference in New Issue
Block a user