From 16cf56b7a49e1affbad3aa2fc2d60ff690823a79 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sat, 4 Feb 2023 21:41:24 +0000 Subject: [PATCH 1/9] GH-4699 Modrinth pack exporter (WIP) --- launcher/CMakeLists.txt | 8 + launcher/ModrinthInstanceExportTask.cpp | 221 ++++++++++++++ launcher/ModrinthInstanceExportTask.h | 69 +++++ launcher/ui/MainWindow.cpp | 4 +- launcher/ui/dialogs/ModrinthExportDialog.cpp | 114 +++++++ launcher/ui/dialogs/ModrinthExportDialog.h | 37 +++ launcher/ui/dialogs/ModrinthExportDialog.ui | 277 ++++++++++++++++++ .../SelectInstanceExportFormatDialog.cpp | 37 +++ .../SelectInstanceExportFormatDialog.h | 36 +++ .../SelectInstanceExportFormatDialog.ui | 95 ++++++ 10 files changed, 896 insertions(+), 2 deletions(-) create mode 100644 launcher/ModrinthInstanceExportTask.cpp create mode 100644 launcher/ModrinthInstanceExportTask.h create mode 100644 launcher/ui/dialogs/ModrinthExportDialog.cpp create mode 100644 launcher/ui/dialogs/ModrinthExportDialog.h create mode 100644 launcher/ui/dialogs/ModrinthExportDialog.ui create mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp create mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.h create mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 85f669eb..be12d08c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -36,6 +36,8 @@ set(CORE_SOURCES InstanceCopyTask.cpp InstanceImportTask.h InstanceImportTask.cpp + ModrinthInstanceExportTask.h + ModrinthInstanceExportTask.cpp # Use tracking separate from memory management Usable.h @@ -784,6 +786,10 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/CreateShortcutDialog.cpp ui/dialogs/CreateShortcutDialog.h + ui/dialogs/SelectInstanceExportFormatDialog.cpp + ui/dialogs/SelectInstanceExportFormatDialog.h + ui/dialogs/ModrinthExportDialog.cpp + ui/dialogs/ModrinthExportDialog.h # GUI - widgets ui/widgets/Common.cpp @@ -882,6 +888,8 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/CreateShortcutDialog.ui + ui/dialogs/SelectInstanceExportFormatDialog.ui + ui/dialogs/ModrinthExportDialog.ui ) qt5_add_resources(LAUNCHER_RESOURCES diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp new file mode 100644 index 00000000..943294c6 --- /dev/null +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -0,0 +1,221 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include +#include +#include +#include +#include "Json.h" +#include "ModrinthInstanceExportTask.h" +#include "net/NetJob.h" +#include "Application.h" +#include "ui/dialogs/ModrinthExportDialog.h" +#include "JlCompress.h" +#include "FileSystem.h" + +ModrinthInstanceExportTask::ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings) : m_instance(instance), m_settings(settings) {} + +void ModrinthInstanceExportTask::executeTask() +{ + QDir modsDir(m_instance->gameRoot() + "/mods"); + modsDir.setFilter(QDir::Files); + modsDir.setNameFilters(QStringList() << "*.jar"); + + QDir resourcePacksDir(m_instance->gameRoot() + "/resourcepacks"); + resourcePacksDir.setFilter(QDir::Files); + resourcePacksDir.setNameFilters(QStringList() << "*.zip"); + + QDir shaderPacksDir(m_instance->gameRoot() + "/shaderpacks"); + shaderPacksDir.setFilter(QDir::Files); + shaderPacksDir.setNameFilters(QStringList() << "*.zip"); + + QStringList filesToResolve; + + if (modsDir.exists()) { + QDirIterator modsIterator(modsDir); + while (modsIterator.hasNext()) { + filesToResolve << modsIterator.next(); + } + } + + if (m_settings.includeResourcePacks && resourcePacksDir.exists()) { + QDirIterator resourcePacksIterator(resourcePacksDir); + while (resourcePacksIterator.hasNext()) { + filesToResolve << resourcePacksIterator.next(); + } + } + + if (m_settings.includeShaderPacks && shaderPacksDir.exists()) { + QDirIterator shaderPacksIterator(shaderPacksDir); + while (shaderPacksIterator.hasNext()) { + filesToResolve << shaderPacksIterator.next(); + } + } + + m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); + + for (QString filePath: filesToResolve) { + QFile file(filePath); + + if (file.open(QFile::ReadOnly)) { + QByteArray contents = file.readAll(); + QCryptographicHash hasher(QCryptographicHash::Sha512); + hasher.addData(contents); + QString hash = hasher.result().toHex(); + + m_responses.append(ModrinthLookupData { + QFileInfo(file), + QByteArray() + }); + + m_netJob->addNetAction(Net::Download::makeByteArray( + QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha512").arg(hash), + &m_responses.last().response + )); + } + } + + connect(m_netJob.get(), &NetJob::succeeded, this, &ModrinthInstanceExportTask::lookupSucceeded); + connect(m_netJob.get(), &NetJob::failed, this, &ModrinthInstanceExportTask::lookupFailed); + connect(m_netJob.get(), &NetJob::progress, this, &ModrinthInstanceExportTask::lookupProgress); + + m_netJob->start(); +} + +void ModrinthInstanceExportTask::lookupSucceeded() +{ + QList resolvedFiles; + QFileInfoList failedFiles; + + for (const auto &data : m_responses) { + try { + auto document = Json::requireDocument(data.response); + auto object = Json::requireObject(document); + auto file = Json::requireIsArrayOf(object, "files").first(); + auto url = Json::requireString(file, "url"); + auto hashes = Json::requireObject(file, "hashes"); + + QString sha512Hash = Json::requireString(hashes, "sha512"); + QString sha1Hash = Json::requireString(hashes, "sha1"); + + ModrinthFile fileData; + + QDir gameDir(m_instance->gameRoot()); + + fileData.path = gameDir.relativeFilePath(data.fileInfo.absoluteFilePath()); + fileData.download = url; + fileData.sha512 = sha512Hash; + fileData.sha1 = sha1Hash; + fileData.fileSize = data.fileInfo.size(); + + resolvedFiles << fileData; + } catch (const Json::JsonException &e) { + qDebug() << "File " << data.fileInfo.path() << " failed to process for reason " << e.cause() << ", adding to overrides"; + failedFiles << data.fileInfo; + } + } + + qDebug() << "Failed files: " << failedFiles; + + QJsonObject indexJson; + indexJson.insert("formatVersion", QJsonValue(1)); + indexJson.insert("game", QJsonValue("minecraft")); + indexJson.insert("versionId", QJsonValue(m_settings.version)); + indexJson.insert("name", QJsonValue(m_settings.name)); + + if (!m_settings.description.isEmpty()) { + indexJson.insert("summary", QJsonValue(m_settings.description)); + } + + QJsonArray files; + + for (const auto &file : resolvedFiles) { + QJsonObject fileObj; + fileObj.insert("path", file.path); + + QJsonObject hashes; + hashes.insert("sha512", file.sha512); + hashes.insert("sha1", file.sha1); + fileObj.insert("hashes", hashes); + + QJsonArray downloads; + downloads.append(file.download); + fileObj.insert("downloads", downloads); + + fileObj.insert("fileSize", QJsonValue(file.fileSize)); + + files.append(fileObj); + } + + indexJson.insert("files", files); + + QJsonObject dependencies; + dependencies.insert("minecraft", m_settings.gameVersion); + if (!m_settings.forgeVersion.isEmpty()) { + dependencies.insert("forge", m_settings.forgeVersion); + } + if (!m_settings.fabricVersion.isEmpty()) { + dependencies.insert("fabric-loader", m_settings.fabricVersion); + } + if (!m_settings.quiltVersion.isEmpty()) { + dependencies.insert("quilt-loader", m_settings.quiltVersion); + } + + indexJson.insert("dependencies", dependencies); + + QTemporaryDir tmp; + if (tmp.isValid()) { + Json::write(indexJson, tmp.filePath("modrinth.index.json")); + + if (!failedFiles.isEmpty()) { + QDir tmpDir(tmp.path()); + QDir gameDir(m_instance->gameRoot()); + for (const auto &file : failedFiles) { + QString src = file.absoluteFilePath(); + tmpDir.mkpath("overrides/" + gameDir.relativeFilePath(file.absolutePath())); + QString dest = tmpDir.path() + "/overrides/" + gameDir.relativeFilePath(src); + qDebug() << dest; + if (!QFile::copy(file.absoluteFilePath(), dest)) { + emitFailed(tr("Failed to copy file %1 to overrides").arg(src)); + return; + } + } + + if (m_settings.includeGameConfig) { + tmpDir.mkdir("overrides"); + QFile::copy(gameDir.absoluteFilePath("options.txt"), tmpDir.absoluteFilePath("overrides/options.txt")); + } + + if (m_settings.includeModConfigs) { + tmpDir.mkdir("overrides"); + FS::copy copy(m_instance->gameRoot() + "/config", tmpDir.absoluteFilePath("overrides/config")); + copy(); + } + } + + if (!JlCompress::compressDir(m_settings.exportPath, tmp.path())) { + emitFailed(tr("Failed to create zip file")); + return; + } + } else { + emitFailed(tr("Failed to create temporary directory")); + return; + } + + emitSucceeded(); +} + +void ModrinthInstanceExportTask::lookupFailed(const QString &) +{ + lookupSucceeded(); // the NetJob will fail if some files were not found on Modrinth, we still want to continue in that case + // FIXME: the NetJob will retry each download 3 times if it fails, we should probably stop it from doing that +} + +void ModrinthInstanceExportTask::lookupProgress(qint64 current, qint64 total) +{ + setProgress(current, total); +} \ No newline at end of file diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/ModrinthInstanceExportTask.h new file mode 100644 index 00000000..8749ded4 --- /dev/null +++ b/launcher/ModrinthInstanceExportTask.h @@ -0,0 +1,69 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include "tasks/Task.h" +#include "BaseInstance.h" +#include "net/NetJob.h" +#include "ui/dialogs/ModrinthExportDialog.h" + +struct ModrinthExportSettings { + QString version; + QString name; + QString description; + + bool includeGameConfig; + bool includeModConfigs; + bool includeResourcePacks; + bool includeShaderPacks; + + QString gameVersion; + QString forgeVersion; + QString fabricVersion; + QString quiltVersion; + + QString exportPath; +}; + +struct ModrinthLookupData { + QFileInfo fileInfo; + QByteArray response; +}; + +// Using the existing Modrinth::File struct from the importer doesn't actually make much sense here (doesn't support multiple hashes, hash is a byte array rather than a string, no file size, etc) +struct ModrinthFile +{ + QString path; + QString sha512; + QString sha1; + QString download; + qint64 fileSize; +}; + +class ModrinthInstanceExportTask : public Task +{ +Q_OBJECT + +public: + explicit ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings); + +protected: + //! Entry point for tasks. + virtual void executeTask() override; + +private slots: + void lookupSucceeded(); + void lookupFailed(const QString &); + void lookupProgress(qint64 current, qint64 total); + +private: + InstancePtr m_instance; + ModrinthExportSettings m_settings; + QList m_responses; + NetJob::Ptr m_netJob; +}; \ No newline at end of file diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 44cb504b..9dcbabea 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -84,7 +84,7 @@ #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/NotificationDialog.h" #include "ui/dialogs/CreateShortcutDialog.h" -#include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/SelectInstanceExportFormatDialog.h" #include "UpdateController.h" #include "KonamiCode.h" @@ -1756,7 +1756,7 @@ void MainWindow::on_actionExportInstance_triggered() { if (m_selectedInstance) { - ExportInstanceDialog dlg(m_selectedInstance, this); + SelectInstanceExportFormatDialog dlg(m_selectedInstance, this); dlg.exec(); } } diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp new file mode 100644 index 00000000..decde9ec --- /dev/null +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include +#include +#include +#include +#include "ModrinthExportDialog.h" +#include "ui_ModrinthExportDialog.h" +#include "BaseInstance.h" +#include "ModrinthInstanceExportTask.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "ProgressDialog.h" +#include "CustomMessageBox.h" + + +ModrinthExportDialog::ModrinthExportDialog(InstancePtr instance, QWidget *parent) : + QDialog(parent), ui(new Ui::ModrinthExportDialog), m_instance(instance) +{ + ui->setupUi(this); + ui->name->setText(m_instance->name()); + ui->version->setText("1.0"); +} + +void ModrinthExportDialog::updateDialogState() +{ + ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled( + !ui->name->text().isEmpty() + && !ui->version->text().isEmpty() + && !ui->file->text().isEmpty() + ); +} + +void ModrinthExportDialog::on_fileBrowseButton_clicked() +{ + QFileDialog dialog(this, tr("Select modpack file"), QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); + dialog.setDefaultSuffix("mrpack"); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.selectFile(ui->name->text() + ".mrpack"); + + if (dialog.exec()) { + ui->file->setText(dialog.selectedFiles().at(0)); + } + + updateDialogState(); +} + +void ModrinthExportDialog::accept() +{ + ModrinthExportSettings settings; + + settings.name = ui->name->text(); + settings.version = ui->version->text(); + settings.description = ui->description->text(); + + settings.includeGameConfig = ui->includeGameConfig->isChecked(); + settings.includeModConfigs = ui->includeModConfigs->isChecked(); + settings.includeResourcePacks = ui->includeResourcePacks->isChecked(); + settings.includeShaderPacks = ui->includeShaderPacks->isChecked(); + + MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast(m_instance); + minecraftInstance->getPackProfile()->reload(Net::Mode::Offline); + + auto minecraftComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraft"); + auto forgeComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraftforge"); + auto fabricComponent = minecraftInstance->getPackProfile()->getComponent("net.fabricmc.fabric-loader"); + auto quiltComponent = minecraftInstance->getPackProfile()->getComponent("org.quiltmc.quilt-loader"); + + if (minecraftComponent) { + settings.gameVersion = minecraftComponent->getVersion(); + } + if (forgeComponent) { + settings.forgeVersion = forgeComponent->getVersion(); + } + if (fabricComponent) { + settings.fabricVersion = fabricComponent->getVersion(); + } + if (quiltComponent) { + settings.quiltVersion = quiltComponent->getVersion(); + } + + settings.exportPath = ui->file->text(); + + auto *task = new ModrinthInstanceExportTask(m_instance, settings); + + connect(task, &Task::failed, [this](QString reason) + { + CustomMessageBox::selectable(parentWidget(), tr("Error"), reason, QMessageBox::Critical)->show(); + }); + connect(task, &Task::succeeded, [this, task]() + { + QStringList warnings = task->warnings(); + if(warnings.count()) + { + CustomMessageBox::selectable(parentWidget(), tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + }); + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(task); + + QDialog::accept(); +} + +ModrinthExportDialog::~ModrinthExportDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/ModrinthExportDialog.h b/launcher/ui/dialogs/ModrinthExportDialog.h new file mode 100644 index 00000000..3ebc9436 --- /dev/null +++ b/launcher/ui/dialogs/ModrinthExportDialog.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include "ExportInstanceDialog.h" + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class ModrinthExportDialog; +} +QT_END_NAMESPACE + +class ModrinthExportDialog : public QDialog +{ +Q_OBJECT + +public: + explicit ModrinthExportDialog(InstancePtr instance, QWidget *parent = nullptr); + + ~ModrinthExportDialog() override; + +private slots: + void on_fileBrowseButton_clicked(); + void accept() override; + void updateDialogState(); + +private: + Ui::ModrinthExportDialog *ui; + InstancePtr m_instance; +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/ModrinthExportDialog.ui b/launcher/ui/dialogs/ModrinthExportDialog.ui new file mode 100644 index 00000000..7aa18bfc --- /dev/null +++ b/launcher/ui/dialogs/ModrinthExportDialog.ui @@ -0,0 +1,277 @@ + + + ModrinthExportDialog + + + + 0 + 0 + 679 + 559 + + + + + 0 + 0 + + + + ModrinthExportDialog + + + + + 10 + 10 + 661 + 541 + + + + + + + + 16777215 + 25 + + + + Export Modrinth modpack + + + + + + + + 16777215 + 200 + + + + Metadata + + + + + 10 + 30 + 641 + 151 + + + + + + + + + Name + + + + + + + + + + Version + + + + + + + Description + + + + + + + + + + + + + + + + + + + Export Options + + + + + 9 + 29 + 641 + 221 + + + + + + + QLayout::SetFixedSize + + + + + File + + + + + + + + + + Browse... + + + + + + + + + Include Minecraft config + + + true + + + + + + + Include mod configs + + + true + + + + + + + Include resource packs + + + + + + + Include shader packs + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + buttonBox + accepted() + ModrinthExportDialog + accept() + + + 340 + 532 + + + 338 + 279 + + + + + buttonBox + rejected() + ModrinthExportDialog + reject() + + + 340 + 532 + + + 338 + 279 + + + + + name + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 395 + 90 + + + 339 + 279 + + + + + version + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 395 + 129 + + + 339 + 279 + + + + + file + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 309 + 329 + + + 339 + 279 + + + + + + updateDialogState() + + diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp new file mode 100644 index 00000000..2a3c6f4b --- /dev/null +++ b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#include "SelectInstanceExportFormatDialog.h" +#include "ui_SelectInstanceExportFormatDialog.h" +#include "BuildConfig.h" +#include "ModrinthExportDialog.h" + + +SelectInstanceExportFormatDialog::SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent) : + QDialog(parent), ui(new Ui::SelectInstanceExportFormatDialog), m_instance(instance) +{ + ui->setupUi(this); + ui->mmcFormat->setText(BuildConfig.LAUNCHER_NAME); +} + +void SelectInstanceExportFormatDialog::accept() +{ + if (ui->mmcFormat->isChecked()) { + ExportInstanceDialog dlg(m_instance, parentWidget()); + QDialog::accept(); + dlg.exec(); + } else if (ui->modrinthFormat->isChecked()) { + ModrinthExportDialog dlg(m_instance, parentWidget()); + QDialog::accept(); + dlg.exec(); + } +} + +SelectInstanceExportFormatDialog::~SelectInstanceExportFormatDialog() +{ + delete ui; +} diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h new file mode 100644 index 00000000..2c286fad --- /dev/null +++ b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h @@ -0,0 +1,36 @@ +/* + * Copyright 2023 arthomnix + * + * This source is subject to the Microsoft Public License (MS-PL). + * Please see the COPYING.md file for more information. + */ + +#pragma once + +#include +#include "ExportInstanceDialog.h" + + +QT_BEGIN_NAMESPACE +namespace Ui +{ + class SelectInstanceExportFormatDialog; +} +QT_END_NAMESPACE + +class SelectInstanceExportFormatDialog : public QDialog +{ +Q_OBJECT + +public: + explicit SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent = nullptr); + + ~SelectInstanceExportFormatDialog() override; + +private slots: + void accept() override; + +private: + Ui::SelectInstanceExportFormatDialog *ui; + InstancePtr m_instance; +}; \ No newline at end of file diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui new file mode 100644 index 00000000..5b779a4e --- /dev/null +++ b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui @@ -0,0 +1,95 @@ + + + SelectInstanceExportFormatDialog + + + + 0 + 0 + 446 + 181 + + + + Select Instance Export Format + + + + + 10 + 10 + 421 + 161 + + + + + + + Select export format + + + + + + + Launcher + + + true + + + + + + + Modrinth (WIP) + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + buttonBox + accepted() + SelectInstanceExportFormatDialog + accept() + + + 220 + 152 + + + 222 + 90 + + + + + buttonBox + rejected() + SelectInstanceExportFormatDialog + reject() + + + 220 + 152 + + + 222 + 90 + + + + + From 74addfb78ba66b9ddf0ef916b950cfc88872b43e Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 10:13:13 +0000 Subject: [PATCH 2/9] GH-4699 Clean some things up Add a menu to select between MMC/Modrinth format packs instead of the custom dialog Treat 404s on requests to the Modrinth API as success, as the API returns a 404 if a hash was not found, and we don't want to retry the download in this case Improve logging --- launcher/CMakeLists.txt | 5 +- launcher/ModrinthInstanceExportTask.cpp | 15 +-- launcher/net/Download.cpp | 7 ++ launcher/net/Download.h | 3 +- launcher/ui/MainWindow.cpp | 30 +++++- .../SelectInstanceExportFormatDialog.cpp | 37 -------- .../SelectInstanceExportFormatDialog.h | 36 ------- .../SelectInstanceExportFormatDialog.ui | 95 ------------------- 8 files changed, 46 insertions(+), 182 deletions(-) delete mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp delete mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.h delete mode 100644 launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index be12d08c..c8e363eb 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -786,9 +786,7 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/CreateShortcutDialog.cpp ui/dialogs/CreateShortcutDialog.h - ui/dialogs/SelectInstanceExportFormatDialog.cpp - ui/dialogs/SelectInstanceExportFormatDialog.h - ui/dialogs/ModrinthExportDialog.cpp + ui/dialogs/ModrinthExportDialog.cpp ui/dialogs/ModrinthExportDialog.h # GUI - widgets @@ -888,7 +886,6 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/CreateShortcutDialog.ui - ui/dialogs/SelectInstanceExportFormatDialog.ui ui/dialogs/ModrinthExportDialog.ui ) diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp index 943294c6..50841d28 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -58,7 +58,8 @@ void ModrinthInstanceExportTask::executeTask() m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); - for (QString filePath: filesToResolve) { + for (const QString &filePath: filesToResolve) { + qDebug() << "Attempting to resolve file hash from Modrinth API: " << filePath; QFile file(filePath); if (file.open(QFile::ReadOnly)) { @@ -74,7 +75,8 @@ void ModrinthInstanceExportTask::executeTask() m_netJob->addNetAction(Net::Download::makeByteArray( QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha512").arg(hash), - &m_responses.last().response + &m_responses.last().response, + Net::Download::Options(Net::Download::Option::AllowNotFound) )); } } @@ -114,7 +116,7 @@ void ModrinthInstanceExportTask::lookupSucceeded() resolvedFiles << fileData; } catch (const Json::JsonException &e) { - qDebug() << "File " << data.fileInfo.path() << " failed to process for reason " << e.cause() << ", adding to overrides"; + qDebug() << "File " << data.fileInfo.absoluteFilePath() << " failed to process for reason " << e.cause() << ", adding to overrides"; failedFiles << data.fileInfo; } } @@ -178,7 +180,6 @@ void ModrinthInstanceExportTask::lookupSucceeded() QString src = file.absoluteFilePath(); tmpDir.mkpath("overrides/" + gameDir.relativeFilePath(file.absolutePath())); QString dest = tmpDir.path() + "/overrides/" + gameDir.relativeFilePath(src); - qDebug() << dest; if (!QFile::copy(file.absoluteFilePath(), dest)) { emitFailed(tr("Failed to copy file %1 to overrides").arg(src)); return; @@ -206,13 +207,13 @@ void ModrinthInstanceExportTask::lookupSucceeded() return; } + qDebug() << "Successfully exported Modrinth pack to " << m_settings.exportPath; emitSucceeded(); } -void ModrinthInstanceExportTask::lookupFailed(const QString &) +void ModrinthInstanceExportTask::lookupFailed(const QString &reason) { - lookupSucceeded(); // the NetJob will fail if some files were not found on Modrinth, we still want to continue in that case - // FIXME: the NetJob will retry each download 3 times if it fails, we should probably stop it from doing that + emitFailed(reason); } void ModrinthInstanceExportTask::lookupProgress(qint64 current, qint64 total) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index b314573f..2150fb14 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -122,6 +122,13 @@ void Download::downloadError(QNetworkReply::NetworkError error) qCritical() << "Aborted " << m_url.toString(); m_status = Job_Aborted; } + else if(error == QNetworkReply::ContentNotFoundError && (m_options & Option::AllowNotFound)) + { + // The Modrinth API returns a 404 when a hash was not found when performing reverse hash lookup, we don't want to treat this as a failure + qDebug() << "Received 404 from " << m_url.toString() << ", continuing..."; + m_status = Job_Finished; + return; + } else { if(m_options & Option::AcceptLocalFiles) diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 0f9bfe7f..08523e34 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -32,7 +32,8 @@ public: /* types */ enum class Option { NoOptions = 0, - AcceptLocalFiles = 1 + AcceptLocalFiles = 1, + AllowNotFound =2 }; Q_DECLARE_FLAGS(Options, Option) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 9dcbabea..607c1045 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -84,7 +84,8 @@ #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/NotificationDialog.h" #include "ui/dialogs/CreateShortcutDialog.h" -#include "ui/dialogs/SelectInstanceExportFormatDialog.h" +#include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/ModrinthExportDialog.h" #include "UpdateController.h" #include "KonamiCode.h" @@ -974,6 +975,31 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) void MainWindow::updateToolsMenu() { + QToolButton *exportButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionExportInstance)); + exportButton->setPopupMode(QToolButton::MenuButtonPopup); + + QMenu *exportMenu = ui->actionExportInstance->menu(); + + if (exportMenu) { + exportMenu->clear(); + } else { + exportMenu = new QMenu(); + } + + QAction *mmcExport = exportMenu->addAction(BuildConfig.LAUNCHER_NAME); + QAction *modrinthExport = exportMenu->addAction(tr("Modrinth")); + + connect(mmcExport, &QAction::triggered, this, &MainWindow::on_actionExportInstance_triggered); + connect(modrinthExport, &QAction::triggered, [this]() + { + if (m_selectedInstance) { + ModrinthExportDialog dlg(m_selectedInstance, this); + dlg.exec(); + } + }); + + ui->actionExportInstance->setMenu(exportMenu); + QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); @@ -1756,7 +1782,7 @@ void MainWindow::on_actionExportInstance_triggered() { if (m_selectedInstance) { - SelectInstanceExportFormatDialog dlg(m_selectedInstance, this); + ExportInstanceDialog dlg(m_selectedInstance, this); dlg.exec(); } } diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp deleted file mode 100644 index 2a3c6f4b..00000000 --- a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 arthomnix - * - * This source is subject to the Microsoft Public License (MS-PL). - * Please see the COPYING.md file for more information. - */ - -#include "SelectInstanceExportFormatDialog.h" -#include "ui_SelectInstanceExportFormatDialog.h" -#include "BuildConfig.h" -#include "ModrinthExportDialog.h" - - -SelectInstanceExportFormatDialog::SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent) : - QDialog(parent), ui(new Ui::SelectInstanceExportFormatDialog), m_instance(instance) -{ - ui->setupUi(this); - ui->mmcFormat->setText(BuildConfig.LAUNCHER_NAME); -} - -void SelectInstanceExportFormatDialog::accept() -{ - if (ui->mmcFormat->isChecked()) { - ExportInstanceDialog dlg(m_instance, parentWidget()); - QDialog::accept(); - dlg.exec(); - } else if (ui->modrinthFormat->isChecked()) { - ModrinthExportDialog dlg(m_instance, parentWidget()); - QDialog::accept(); - dlg.exec(); - } -} - -SelectInstanceExportFormatDialog::~SelectInstanceExportFormatDialog() -{ - delete ui; -} diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h deleted file mode 100644 index 2c286fad..00000000 --- a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 arthomnix - * - * This source is subject to the Microsoft Public License (MS-PL). - * Please see the COPYING.md file for more information. - */ - -#pragma once - -#include -#include "ExportInstanceDialog.h" - - -QT_BEGIN_NAMESPACE -namespace Ui -{ - class SelectInstanceExportFormatDialog; -} -QT_END_NAMESPACE - -class SelectInstanceExportFormatDialog : public QDialog -{ -Q_OBJECT - -public: - explicit SelectInstanceExportFormatDialog(InstancePtr instance, QWidget *parent = nullptr); - - ~SelectInstanceExportFormatDialog() override; - -private slots: - void accept() override; - -private: - Ui::SelectInstanceExportFormatDialog *ui; - InstancePtr m_instance; -}; \ No newline at end of file diff --git a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui b/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui deleted file mode 100644 index 5b779a4e..00000000 --- a/launcher/ui/dialogs/SelectInstanceExportFormatDialog.ui +++ /dev/null @@ -1,95 +0,0 @@ - - - SelectInstanceExportFormatDialog - - - - 0 - 0 - 446 - 181 - - - - Select Instance Export Format - - - - - 10 - 10 - 421 - 161 - - - - - - - Select export format - - - - - - - Launcher - - - true - - - - - - - Modrinth (WIP) - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - buttonBox - accepted() - SelectInstanceExportFormatDialog - accept() - - - 220 - 152 - - - 222 - 90 - - - - - buttonBox - rejected() - SelectInstanceExportFormatDialog - reject() - - - 220 - 152 - - - 222 - 90 - - - - - From a6dff61ff7b03f979f367a4399a0b78488193b7d Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 10:15:29 +0000 Subject: [PATCH 3/9] GH-4699 Formatting --- launcher/CMakeLists.txt | 2 +- launcher/ModrinthInstanceExportTask.h | 2 +- launcher/net/Download.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c8e363eb..47e37df5 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -786,7 +786,7 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/CreateShortcutDialog.cpp ui/dialogs/CreateShortcutDialog.h - ui/dialogs/ModrinthExportDialog.cpp + ui/dialogs/ModrinthExportDialog.cpp ui/dialogs/ModrinthExportDialog.h # GUI - widgets diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/ModrinthInstanceExportTask.h index 8749ded4..fbcdb0f5 100644 --- a/launcher/ModrinthInstanceExportTask.h +++ b/launcher/ModrinthInstanceExportTask.h @@ -58,7 +58,7 @@ protected: private slots: void lookupSucceeded(); - void lookupFailed(const QString &); + void lookupFailed(const QString &reason); void lookupProgress(qint64 current, qint64 total); private: diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 08523e34..c5102421 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -33,7 +33,7 @@ public: /* types */ { NoOptions = 0, AcceptLocalFiles = 1, - AllowNotFound =2 + AllowNotFound = 2 }; Q_DECLARE_FLAGS(Options, Option) From a1f256a7451223546e6dcda3f8e427a38e01273a Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 12:03:00 +0000 Subject: [PATCH 4/9] GH-4699 Add global datapacks support to Modrinth exporter --- launcher/ModrinthInstanceExportTask.cpp | 13 ++++ launcher/ModrinthInstanceExportTask.h | 1 + launcher/ui/dialogs/ModrinthExportDialog.cpp | 24 +++++++- launcher/ui/dialogs/ModrinthExportDialog.h | 1 + launcher/ui/dialogs/ModrinthExportDialog.ui | 64 ++++++++++++++++++-- 5 files changed, 98 insertions(+), 5 deletions(-) diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp index 50841d28..b0d46b37 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -56,6 +56,19 @@ void ModrinthInstanceExportTask::executeTask() } } + if (!m_settings.datapacksPath.isEmpty()) { + QDir datapacksDir(m_instance->gameRoot() + "/" + m_settings.datapacksPath); + datapacksDir.setFilter(QDir::Files); + datapacksDir.setNameFilters(QStringList() << "*.zip"); + + if (datapacksDir.exists()) { + QDirIterator datapacksIterator(datapacksDir); + while (datapacksIterator.hasNext()) { + filesToResolve << datapacksIterator.next(); + } + } + } + m_netJob = new NetJob(tr("Modrinth pack export"), APPLICATION->network()); for (const QString &filePath: filesToResolve) { diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/ModrinthInstanceExportTask.h index fbcdb0f5..d5e103c2 100644 --- a/launcher/ModrinthInstanceExportTask.h +++ b/launcher/ModrinthInstanceExportTask.h @@ -21,6 +21,7 @@ struct ModrinthExportSettings { bool includeModConfigs; bool includeResourcePacks; bool includeShaderPacks; + QString datapacksPath; QString gameVersion; QString forgeVersion; diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index decde9ec..ac01616e 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -32,7 +32,11 @@ void ModrinthExportDialog::updateDialogState() ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)->setEnabled( !ui->name->text().isEmpty() && !ui->version->text().isEmpty() - && !ui->file->text().isEmpty() + && ui->file->text().endsWith(".mrpack") + && ( + !ui->includeDatapacks->isChecked() + || (!ui->datapacksPath->text().isEmpty() && QDir(m_instance->gameRoot() + "/" + ui->datapacksPath->text()).exists()) + ) ); } @@ -40,6 +44,7 @@ void ModrinthExportDialog::on_fileBrowseButton_clicked() { QFileDialog dialog(this, tr("Select modpack file"), QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); dialog.setDefaultSuffix("mrpack"); + dialog.setNameFilter("Modrinth modpacks (*.mrpack)"); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.selectFile(ui->name->text() + ".mrpack"); @@ -51,6 +56,19 @@ void ModrinthExportDialog::on_fileBrowseButton_clicked() updateDialogState(); } +void ModrinthExportDialog::on_datapackPathBrowse_clicked() +{ + QFileDialog dialog(this, tr("Select global datapacks folder"), m_instance->gameRoot()); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::DirectoryOnly); + + if (dialog.exec()) { + ui->datapacksPath->setText(QDir(m_instance->gameRoot()).relativeFilePath(dialog.selectedFiles().at(0))); + } + + updateDialogState(); +} + void ModrinthExportDialog::accept() { ModrinthExportSettings settings; @@ -64,6 +82,10 @@ void ModrinthExportDialog::accept() settings.includeResourcePacks = ui->includeResourcePacks->isChecked(); settings.includeShaderPacks = ui->includeShaderPacks->isChecked(); + if (ui->includeDatapacks->isChecked()) { + settings.datapacksPath = ui->datapacksPath->text(); + } + MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast(m_instance); minecraftInstance->getPackProfile()->reload(Net::Mode::Offline); diff --git a/launcher/ui/dialogs/ModrinthExportDialog.h b/launcher/ui/dialogs/ModrinthExportDialog.h index 3ebc9436..08a244c9 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.h +++ b/launcher/ui/dialogs/ModrinthExportDialog.h @@ -28,6 +28,7 @@ public: private slots: void on_fileBrowseButton_clicked(); + void on_datapackPathBrowse_clicked(); void accept() override; void updateDialogState(); diff --git a/launcher/ui/dialogs/ModrinthExportDialog.ui b/launcher/ui/dialogs/ModrinthExportDialog.ui index 7aa18bfc..c386f881 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.ui +++ b/launcher/ui/dialogs/ModrinthExportDialog.ui @@ -6,7 +6,7 @@ 0 0 - 679 + 835 559 @@ -24,7 +24,7 @@ 10 10 - 661 + 821 541 @@ -58,7 +58,7 @@ 10 30 - 641 + 801 151 @@ -111,7 +111,7 @@ 9 29 - 641 + 801 221 @@ -174,6 +174,30 @@ + + + + + + Use this if your modpack contains a mod which adds global datapacks. + + + Include global datapacks folder: + + + + + + + + + + Browse... + + + + + @@ -270,6 +294,38 @@ + + datapacksPath + textChanged(QString) + ModrinthExportDialog + updateDialogState() + + + 532 + 472 + + + 417 + 279 + + + + + includeDatapacks + stateChanged(int) + ModrinthExportDialog + updateDialogState() + + + 183 + 472 + + + 417 + 279 + + + updateDialogState() From 01f3511e888a23de1707ba5b5dcd96db8117172f Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 12:11:27 +0000 Subject: [PATCH 5/9] GH-4699 Improve export instance menu --- launcher/ui/MainWindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 607c1045..8a6688d2 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -986,8 +986,10 @@ void MainWindow::updateToolsMenu() exportMenu = new QMenu(); } + exportMenu->addSeparator()->setText(tr("Format")); + QAction *mmcExport = exportMenu->addAction(BuildConfig.LAUNCHER_NAME); - QAction *modrinthExport = exportMenu->addAction(tr("Modrinth")); + QAction *modrinthExport = exportMenu->addAction(tr("Modrinth (WIP)")); connect(mmcExport, &QAction::triggered, this, &MainWindow::on_actionExportInstance_triggered); connect(modrinthExport, &QAction::triggered, [this]() From a452b7ee962268704acd0a9b81a3b068d32af3f7 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 12:33:56 +0000 Subject: [PATCH 6/9] GH-4699 Set Modrinth exporter task status --- launcher/ModrinthInstanceExportTask.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/ModrinthInstanceExportTask.cpp index b0d46b37..b1d62085 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/ModrinthInstanceExportTask.cpp @@ -21,6 +21,8 @@ ModrinthInstanceExportTask::ModrinthInstanceExportTask(InstancePtr instance, Mod void ModrinthInstanceExportTask::executeTask() { + setStatus(tr("Finding files to look up on Modrinth...")); + QDir modsDir(m_instance->gameRoot() + "/mods"); modsDir.setFilter(QDir::Files); modsDir.setNameFilters(QStringList() << "*.jar"); @@ -99,10 +101,12 @@ void ModrinthInstanceExportTask::executeTask() connect(m_netJob.get(), &NetJob::progress, this, &ModrinthInstanceExportTask::lookupProgress); m_netJob->start(); + setStatus(tr("Looking up files on Modrinth...")); } void ModrinthInstanceExportTask::lookupSucceeded() { + setStatus(tr("Creating modpack metadata...")); QList resolvedFiles; QFileInfoList failedFiles; @@ -182,6 +186,8 @@ void ModrinthInstanceExportTask::lookupSucceeded() indexJson.insert("dependencies", dependencies); + setStatus(tr("Copying files to modpack...")); + QTemporaryDir tmp; if (tmp.isValid()) { Json::write(indexJson, tmp.filePath("modrinth.index.json")); @@ -211,6 +217,7 @@ void ModrinthInstanceExportTask::lookupSucceeded() } } + setStatus(tr("Zipping modpack...")); if (!JlCompress::compressDir(m_settings.exportPath, tmp.path())) { emitFailed(tr("Failed to create zip file")); return; From aae2f23eb6f974f4986fd019b7e4d17713eb44ae Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 14:28:18 +0000 Subject: [PATCH 7/9] GH-4699 Move ModrinthInstanceExportTask to modplatform/modrinth; truncate error messages in dialog --- launcher/CMakeLists.txt | 4 +-- .../modrinth}/ModrinthInstanceExportTask.cpp | 31 +++++++++++-------- .../modrinth}/ModrinthInstanceExportTask.h | 23 +++++++++----- launcher/ui/dialogs/ModrinthExportDialog.cpp | 14 ++++++--- 4 files changed, 45 insertions(+), 27 deletions(-) rename launcher/{ => modplatform/modrinth}/ModrinthInstanceExportTask.cpp (89%) rename launcher/{ => modplatform/modrinth}/ModrinthInstanceExportTask.h (80%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 47e37df5..e813835d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -36,8 +36,6 @@ set(CORE_SOURCES InstanceCopyTask.cpp InstanceImportTask.h InstanceImportTask.cpp - ModrinthInstanceExportTask.h - ModrinthInstanceExportTask.cpp # Use tracking separate from memory management Usable.h @@ -529,6 +527,8 @@ set(ATLAUNCHER_SOURCES set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackManifest.cpp modplatform/modrinth/ModrinthPackManifest.h + modplatform/modrinth/ModrinthInstanceExportTask.h + modplatform/modrinth/ModrinthInstanceExportTask.cpp ) add_unit_test(Index diff --git a/launcher/ModrinthInstanceExportTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp similarity index 89% rename from launcher/ModrinthInstanceExportTask.cpp rename to launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp index b1d62085..1def5152 100644 --- a/launcher/ModrinthInstanceExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp @@ -17,9 +17,12 @@ #include "JlCompress.h" #include "FileSystem.h" -ModrinthInstanceExportTask::ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings) : m_instance(instance), m_settings(settings) {} +namespace Modrinth +{ -void ModrinthInstanceExportTask::executeTask() +InstanceExportTask::InstanceExportTask(InstancePtr instance, ExportSettings settings) : m_instance(instance), m_settings(settings) {} + +void InstanceExportTask::executeTask() { setStatus(tr("Finding files to look up on Modrinth...")); @@ -83,9 +86,9 @@ void ModrinthInstanceExportTask::executeTask() hasher.addData(contents); QString hash = hasher.result().toHex(); - m_responses.append(ModrinthLookupData { - QFileInfo(file), - QByteArray() + m_responses.append(HashLookupData{ + QFileInfo(file), + QByteArray() }); m_netJob->addNetAction(Net::Download::makeByteArray( @@ -96,18 +99,18 @@ void ModrinthInstanceExportTask::executeTask() } } - connect(m_netJob.get(), &NetJob::succeeded, this, &ModrinthInstanceExportTask::lookupSucceeded); - connect(m_netJob.get(), &NetJob::failed, this, &ModrinthInstanceExportTask::lookupFailed); - connect(m_netJob.get(), &NetJob::progress, this, &ModrinthInstanceExportTask::lookupProgress); + connect(m_netJob.get(), &NetJob::succeeded, this, &InstanceExportTask::lookupSucceeded); + connect(m_netJob.get(), &NetJob::failed, this, &InstanceExportTask::lookupFailed); + connect(m_netJob.get(), &NetJob::progress, this, &InstanceExportTask::lookupProgress); m_netJob->start(); setStatus(tr("Looking up files on Modrinth...")); } -void ModrinthInstanceExportTask::lookupSucceeded() +void InstanceExportTask::lookupSucceeded() { setStatus(tr("Creating modpack metadata...")); - QList resolvedFiles; + QList resolvedFiles; QFileInfoList failedFiles; for (const auto &data : m_responses) { @@ -121,7 +124,7 @@ void ModrinthInstanceExportTask::lookupSucceeded() QString sha512Hash = Json::requireString(hashes, "sha512"); QString sha1Hash = Json::requireString(hashes, "sha1"); - ModrinthFile fileData; + ExportFile fileData; QDir gameDir(m_instance->gameRoot()); @@ -231,12 +234,14 @@ void ModrinthInstanceExportTask::lookupSucceeded() emitSucceeded(); } -void ModrinthInstanceExportTask::lookupFailed(const QString &reason) +void InstanceExportTask::lookupFailed(const QString &reason) { emitFailed(reason); } -void ModrinthInstanceExportTask::lookupProgress(qint64 current, qint64 total) +void InstanceExportTask::lookupProgress(qint64 current, qint64 total) { setProgress(current, total); +} + } \ No newline at end of file diff --git a/launcher/ModrinthInstanceExportTask.h b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h similarity index 80% rename from launcher/ModrinthInstanceExportTask.h rename to launcher/modplatform/modrinth/ModrinthInstanceExportTask.h index d5e103c2..ff59f45b 100644 --- a/launcher/ModrinthInstanceExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceExportTask.h @@ -12,7 +12,11 @@ #include "net/NetJob.h" #include "ui/dialogs/ModrinthExportDialog.h" -struct ModrinthExportSettings { +namespace Modrinth +{ + +struct ExportSettings +{ QString version; QString name; QString description; @@ -31,13 +35,14 @@ struct ModrinthExportSettings { QString exportPath; }; -struct ModrinthLookupData { +struct HashLookupData +{ QFileInfo fileInfo; QByteArray response; }; // Using the existing Modrinth::File struct from the importer doesn't actually make much sense here (doesn't support multiple hashes, hash is a byte array rather than a string, no file size, etc) -struct ModrinthFile +struct ExportFile { QString path; QString sha512; @@ -46,12 +51,12 @@ struct ModrinthFile qint64 fileSize; }; -class ModrinthInstanceExportTask : public Task +class InstanceExportTask : public Task { Q_OBJECT public: - explicit ModrinthInstanceExportTask(InstancePtr instance, ModrinthExportSettings settings); + explicit InstanceExportTask(InstancePtr instance, ExportSettings settings); protected: //! Entry point for tasks. @@ -64,7 +69,9 @@ private slots: private: InstancePtr m_instance; - ModrinthExportSettings m_settings; - QList m_responses; + ExportSettings m_settings; + QList m_responses; NetJob::Ptr m_netJob; -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index ac01616e..4b0d3682 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -12,7 +12,7 @@ #include "ModrinthExportDialog.h" #include "ui_ModrinthExportDialog.h" #include "BaseInstance.h" -#include "ModrinthInstanceExportTask.h" +#include "modplatform/modrinth/ModrinthInstanceExportTask.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ProgressDialog.h" @@ -71,7 +71,7 @@ void ModrinthExportDialog::on_datapackPathBrowse_clicked() void ModrinthExportDialog::accept() { - ModrinthExportSettings settings; + Modrinth::ExportSettings settings; settings.name = ui->name->text(); settings.version = ui->version->text(); @@ -109,11 +109,17 @@ void ModrinthExportDialog::accept() settings.exportPath = ui->file->text(); - auto *task = new ModrinthInstanceExportTask(m_instance, settings); + auto *task = new Modrinth::InstanceExportTask(m_instance, settings); connect(task, &Task::failed, [this](QString reason) { - CustomMessageBox::selectable(parentWidget(), tr("Error"), reason, QMessageBox::Critical)->show(); + QString text; + if (reason.length() > 1000) { + text = reason.left(1000) + "..."; + } else { + text = reason; + } + CustomMessageBox::selectable(parentWidget(), tr("Error"), text, QMessageBox::Critical)->show(); }); connect(task, &Task::succeeded, [this, task]() { From 82dde9f426c9e6377fa25083593dd35c149e88f3 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 14:48:28 +0000 Subject: [PATCH 8/9] Update copyright years --- launcher/net/Download.cpp | 2 +- launcher/net/Download.h | 2 +- launcher/ui/MainWindow.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 2150fb14..f92d4de7 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2023 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/launcher/net/Download.h b/launcher/net/Download.h index c5102421..343ce7cd 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2023 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8a6688d2..0a747734 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2021 MultiMC Contributors +/* Copyright 2013-2023 MultiMC Contributors * * Authors: Andrew Okin * Peterix From e463edb185b0a65e0e170e96a8ea6842073aebc7 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Sun, 5 Feb 2023 15:55:49 +0000 Subject: [PATCH 9/9] GH-4699 Warn when exporting instance with custom components --- launcher/ui/dialogs/ModrinthExportDialog.cpp | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/launcher/ui/dialogs/ModrinthExportDialog.cpp b/launcher/ui/dialogs/ModrinthExportDialog.cpp index 4b0d3682..58db2322 100644 --- a/launcher/ui/dialogs/ModrinthExportDialog.cpp +++ b/launcher/ui/dialogs/ModrinthExportDialog.cpp @@ -89,23 +89,23 @@ void ModrinthExportDialog::accept() MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast(m_instance); minecraftInstance->getPackProfile()->reload(Net::Mode::Offline); - auto minecraftComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraft"); - auto forgeComponent = minecraftInstance->getPackProfile()->getComponent("net.minecraftforge"); - auto fabricComponent = minecraftInstance->getPackProfile()->getComponent("net.fabricmc.fabric-loader"); - auto quiltComponent = minecraftInstance->getPackProfile()->getComponent("org.quiltmc.quilt-loader"); + for (int i = 0; i < minecraftInstance->getPackProfile()->rowCount(); i++) { + auto component = minecraftInstance->getPackProfile()->getComponent(i); + if (component->isCustom()) { + CustomMessageBox::selectable( + this, + tr("Warning"), + tr("Instance contains a custom component: %1\nThis cannot be exported to a Modrinth pack; the exported pack may not work correctly!") + .arg(component->getName()), + QMessageBox::Warning + )->exec(); + } + } - if (minecraftComponent) { - settings.gameVersion = minecraftComponent->getVersion(); - } - if (forgeComponent) { - settings.forgeVersion = forgeComponent->getVersion(); - } - if (fabricComponent) { - settings.fabricVersion = fabricComponent->getVersion(); - } - if (quiltComponent) { - settings.quiltVersion = quiltComponent->getVersion(); - } + settings.gameVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.minecraft"); + settings.forgeVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.minecraftforge"); + settings.fabricVersion = minecraftInstance->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader"); + settings.forgeVersion = minecraftInstance->getPackProfile()->getComponentVersion("org.quiltmc.quilt-loader"); settings.exportPath = ui->file->text();