mirror of
https://github.com/MultiMC/MultiMC5.git
synced 2025-03-31 16:20:53 +00:00
Merge pull request #5052 from arthomnix/feature/mrpack_export
GH-4699 Modrinth pack exporter
This commit is contained in:
commit
e50cb5caa4
@ -527,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
|
||||
@ -784,6 +786,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/dialogs/SkinUploadDialog.h
|
||||
ui/dialogs/CreateShortcutDialog.cpp
|
||||
ui/dialogs/CreateShortcutDialog.h
|
||||
ui/dialogs/ModrinthExportDialog.cpp
|
||||
ui/dialogs/ModrinthExportDialog.h
|
||||
|
||||
# GUI - widgets
|
||||
ui/widgets/Common.cpp
|
||||
@ -882,6 +886,7 @@ qt5_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/LoginDialog.ui
|
||||
ui/dialogs/EditAccountDialog.ui
|
||||
ui/dialogs/CreateShortcutDialog.ui
|
||||
ui/dialogs/ModrinthExportDialog.ui
|
||||
)
|
||||
|
||||
qt5_add_resources(LAUNCHER_RESOURCES
|
||||
|
247
launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp
Normal file
247
launcher/modplatform/modrinth/ModrinthInstanceExportTask.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2023 arthomnix
|
||||
*
|
||||
* This source is subject to the Microsoft Public License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QCryptographicHash>
|
||||
#include <QMap>
|
||||
#include "Json.h"
|
||||
#include "ModrinthInstanceExportTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "Application.h"
|
||||
#include "ui/dialogs/ModrinthExportDialog.h"
|
||||
#include "JlCompress.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace Modrinth
|
||||
{
|
||||
|
||||
InstanceExportTask::InstanceExportTask(InstancePtr instance, ExportSettings settings) : m_instance(instance), m_settings(settings) {}
|
||||
|
||||
void InstanceExportTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Finding files to look up on Modrinth..."));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
qDebug() << "Attempting to resolve file hash from Modrinth API: " << filePath;
|
||||
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(HashLookupData{
|
||||
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,
|
||||
Net::Download::Options(Net::Download::Option::AllowNotFound)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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 InstanceExportTask::lookupSucceeded()
|
||||
{
|
||||
setStatus(tr("Creating modpack metadata..."));
|
||||
QList<ExportFile> 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<QJsonObject>(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");
|
||||
|
||||
ExportFile 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.absoluteFilePath() << " 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);
|
||||
|
||||
setStatus(tr("Copying files to modpack..."));
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
setStatus(tr("Zipping modpack..."));
|
||||
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;
|
||||
}
|
||||
|
||||
qDebug() << "Successfully exported Modrinth pack to " << m_settings.exportPath;
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
void InstanceExportTask::lookupFailed(const QString &reason)
|
||||
{
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void InstanceExportTask::lookupProgress(qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
}
|
||||
|
||||
}
|
77
launcher/modplatform/modrinth/ModrinthInstanceExportTask.h
Normal file
77
launcher/modplatform/modrinth/ModrinthInstanceExportTask.h
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
namespace Modrinth
|
||||
{
|
||||
|
||||
struct ExportSettings
|
||||
{
|
||||
QString version;
|
||||
QString name;
|
||||
QString description;
|
||||
|
||||
bool includeGameConfig;
|
||||
bool includeModConfigs;
|
||||
bool includeResourcePacks;
|
||||
bool includeShaderPacks;
|
||||
QString datapacksPath;
|
||||
|
||||
QString gameVersion;
|
||||
QString forgeVersion;
|
||||
QString fabricVersion;
|
||||
QString quiltVersion;
|
||||
|
||||
QString exportPath;
|
||||
};
|
||||
|
||||
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 ExportFile
|
||||
{
|
||||
QString path;
|
||||
QString sha512;
|
||||
QString sha1;
|
||||
QString download;
|
||||
qint64 fileSize;
|
||||
};
|
||||
|
||||
class InstanceExportTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InstanceExportTask(InstancePtr instance, ExportSettings settings);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void lookupSucceeded();
|
||||
void lookupFailed(const QString &reason);
|
||||
void lookupProgress(qint64 current, qint64 total);
|
||||
|
||||
private:
|
||||
InstancePtr m_instance;
|
||||
ExportSettings m_settings;
|
||||
QList<HashLookupData> m_responses;
|
||||
NetJob::Ptr m_netJob;
|
||||
};
|
||||
|
||||
}
|
@ -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.
|
||||
@ -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)
|
||||
|
@ -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.
|
||||
@ -32,7 +32,8 @@ public: /* types */
|
||||
enum class Option
|
||||
{
|
||||
NoOptions = 0,
|
||||
AcceptLocalFiles = 1
|
||||
AcceptLocalFiles = 1,
|
||||
AllowNotFound = 2
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
/* Copyright 2013-2023 MultiMC Contributors
|
||||
*
|
||||
* Authors: Andrew Okin
|
||||
* Peterix
|
||||
@ -85,6 +85,7 @@
|
||||
#include "ui/dialogs/NotificationDialog.h"
|
||||
#include "ui/dialogs/CreateShortcutDialog.h"
|
||||
#include "ui/dialogs/ExportInstanceDialog.h"
|
||||
#include "ui/dialogs/ModrinthExportDialog.h"
|
||||
|
||||
#include "UpdateController.h"
|
||||
#include "KonamiCode.h"
|
||||
@ -974,6 +975,33 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
|
||||
|
||||
void MainWindow::updateToolsMenu()
|
||||
{
|
||||
QToolButton *exportButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionExportInstance));
|
||||
exportButton->setPopupMode(QToolButton::MenuButtonPopup);
|
||||
|
||||
QMenu *exportMenu = ui->actionExportInstance->menu();
|
||||
|
||||
if (exportMenu) {
|
||||
exportMenu->clear();
|
||||
} else {
|
||||
exportMenu = new QMenu();
|
||||
}
|
||||
|
||||
exportMenu->addSeparator()->setText(tr("Format"));
|
||||
|
||||
QAction *mmcExport = exportMenu->addAction(BuildConfig.LAUNCHER_NAME);
|
||||
QAction *modrinthExport = exportMenu->addAction(tr("Modrinth (WIP)"));
|
||||
|
||||
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<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
|
||||
QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));
|
||||
|
||||
|
142
launcher/ui/dialogs/ModrinthExportDialog.cpp
Normal file
142
launcher/ui/dialogs/ModrinthExportDialog.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2023 arthomnix
|
||||
*
|
||||
* This source is subject to the Microsoft Public License (MS-PL).
|
||||
* Please see the COPYING.md file for more information.
|
||||
*/
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QProgressDialog>
|
||||
#include <QMessageBox>
|
||||
#include "ModrinthExportDialog.h"
|
||||
#include "ui_ModrinthExportDialog.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "modplatform/modrinth/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().endsWith(".mrpack")
|
||||
&& (
|
||||
!ui->includeDatapacks->isChecked()
|
||||
|| (!ui->datapacksPath->text().isEmpty() && QDir(m_instance->gameRoot() + "/" + ui->datapacksPath->text()).exists())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
if (dialog.exec()) {
|
||||
ui->file->setText(dialog.selectedFiles().at(0));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
Modrinth::ExportSettings 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();
|
||||
|
||||
if (ui->includeDatapacks->isChecked()) {
|
||||
settings.datapacksPath = ui->datapacksPath->text();
|
||||
}
|
||||
|
||||
MinecraftInstancePtr minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(m_instance);
|
||||
minecraftInstance->getPackProfile()->reload(Net::Mode::Offline);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
auto *task = new Modrinth::InstanceExportTask(m_instance, settings);
|
||||
|
||||
connect(task, &Task::failed, [this](QString reason)
|
||||
{
|
||||
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]()
|
||||
{
|
||||
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;
|
||||
}
|
38
launcher/ui/dialogs/ModrinthExportDialog.h
Normal file
38
launcher/ui/dialogs/ModrinthExportDialog.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 <QDialog>
|
||||
#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 on_datapackPathBrowse_clicked();
|
||||
void accept() override;
|
||||
void updateDialogState();
|
||||
|
||||
private:
|
||||
Ui::ModrinthExportDialog *ui;
|
||||
InstancePtr m_instance;
|
||||
};
|
333
launcher/ui/dialogs/ModrinthExportDialog.ui
Normal file
333
launcher/ui/dialogs/ModrinthExportDialog.ui
Normal file
@ -0,0 +1,333 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ModrinthExportDialog</class>
|
||||
<widget class="QDialog" name="ModrinthExportDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>835</width>
|
||||
<height>559</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>ModrinthExportDialog</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>821</width>
|
||||
<height>541</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Export Modrinth modpack</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="metadataGroupBox">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Metadata</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>801</width>
|
||||
<height>151</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="metadataVLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="metadataFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="version"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="description"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="optionsGroupBox">
|
||||
<property name="title">
|
||||
<string>Export Options</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>9</x>
|
||||
<y>29</y>
|
||||
<width>801</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="optionsLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="fileLabel">
|
||||
<property name="text">
|
||||
<string>File</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="file"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="fileBrowseButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeGameConfig">
|
||||
<property name="text">
|
||||
<string>Include Minecraft config</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeModConfigs">
|
||||
<property name="text">
|
||||
<string>Include mod configs</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeResourcePacks">
|
||||
<property name="text">
|
||||
<string>Include resource packs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeShaderPacks">
|
||||
<property name="text">
|
||||
<string>Include shader packs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeDatapacks">
|
||||
<property name="toolTip">
|
||||
<string>Use this if your modpack contains a mod which adds global datapacks.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Include global datapacks folder:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="datapacksPath"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="datapackPathBrowse">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>340</x>
|
||||
<y>532</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>338</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>340</x>
|
||||
<y>532</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>338</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>name</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>updateDialogState()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>395</x>
|
||||
<y>90</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>339</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>version</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>updateDialogState()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>395</x>
|
||||
<y>129</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>339</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>file</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>updateDialogState()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>309</x>
|
||||
<y>329</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>339</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>datapacksPath</sender>
|
||||
<signal>textChanged(QString)</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>updateDialogState()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>532</x>
|
||||
<y>472</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>417</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>includeDatapacks</sender>
|
||||
<signal>stateChanged(int)</signal>
|
||||
<receiver>ModrinthExportDialog</receiver>
|
||||
<slot>updateDialogState()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>183</x>
|
||||
<y>472</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>417</x>
|
||||
<y>279</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>updateDialogState()</slot>
|
||||
</slots>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user