diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 2b24ec090..87bd1b703 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -522,7 +522,8 @@ QStringList MinecraftInstance::javaArguments() if (javaVersion.isModular() && shouldApplyOnlineFixes()) // allow reflective access to java.net - required by the skin fix - args << "--add-opens" << "java.base/java.net=ALL-UNNAMED"; + args << "--add-opens" + << "java.base/java.net=ALL-UNNAMED"; return args; } @@ -796,8 +797,10 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) { QStringList out; - out << "Main Class:" << " " + getMainClass() << ""; - out << "Native path:" << " " + getNativePath() << ""; + out << "Main Class:" + << " " + getMainClass() << ""; + out << "Native path:" + << " " + getNativePath() << ""; auto profile = m_components->getProfile(); @@ -1227,6 +1230,11 @@ std::shared_ptr MinecraftInstance::shaderPackList() return m_shader_pack_list; } +QList> MinecraftInstance::resourceLists() +{ + return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() }; +} + std::shared_ptr MinecraftInstance::worldList() { if (!m_world_list) { diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 75e97ae45..5d9bb45ef 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -116,6 +116,7 @@ class MinecraftInstance : public BaseInstance { std::shared_ptr resourcePackList(); std::shared_ptr texturePackList(); std::shared_ptr shaderPackList(); + QList> resourceLists(); std::shared_ptr worldList(); std::shared_ptr gameOptionsModel(); diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index a3b8d904c..1db7ca453 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -115,7 +115,7 @@ QString hash(QIODevice* device, Algorithm type) QCryptographicHash hash(alg); if (!hash.addData(device)) - qCritical() << "Failed to read JAR to create hash!"; + qCritical() << "Failed to read file to create hash!"; Q_ASSERT(hash.result().length() == hash.hashLength(alg)); auto result = hash.result().toHex(); diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index d103170af..8df6405ad 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -34,22 +34,21 @@ const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" }); const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" }); -ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, - const QString& version, - const QString& summary, +ModrinthPackExportTask::ModrinthPackExportTask(QString name, + QString version, + QString summary, bool optionalFiles, - InstancePtr instance, - const QString& output, + MinecraftInstancePtr instance, + QString output, MMCZip::FilterFunction filter) - : name(name) - , version(version) - , summary(summary) + : name(std::move(name)) + , version(std::move(version)) + , summary(std::move(summary)) , optionalFiles(optionalFiles) - , instance(instance) - , mcInstance(dynamic_cast(instance.get())) - , gameRoot(instance->gameRoot()) - , output(output) - , filter(filter) + , instance(std::move(instance)) + , gameRoot(this->instance->gameRoot()) + , output(std::move(output)) + , filter(std::move(filter)) {} void ModrinthPackExportTask::executeTask() @@ -83,20 +82,86 @@ void ModrinthPackExportTask::collectFiles() pendingHashes.clear(); resolvedFiles.clear(); - if (mcInstance) { - mcInstance->loaderModList()->update(); - connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes); - } else - collectHashes(); + collectHashes(); } void ModrinthPackExportTask::collectHashes() { setStatus(tr("Finding file hashes...")); + + for (const auto& model : instance->resourceLists()) { + QCoreApplication::processEvents(); // TODO: maybe don't do this? + + QEventLoop loop; + connect(model.get(), &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); + model->update(); + loop.exec(); + + for (const Resource* resource : model->allResources()) { + QCoreApplication::processEvents(); + + if (resource->metadata() == nullptr) + continue; + + const QUrl& url = resource->metadata()->url; + + if (url.isEmpty() || !BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) + continue; + + const QFileInfo& fileInfo = resource->fileinfo(); + const QString relativePath = gameRoot.relativeFilePath(fileInfo.absoluteFilePath()); + + if (filter(relativePath)) + continue; + + qDebug() << "Resolving" << relativePath << "from index"; + + QString sha1; + QString sha512; + qint64 size; + + if (resource->metadata()->hash_format == "sha1") + sha1 = resource->metadata()->hash; + else if (resource->metadata()->hash_format == "sha512") + sha512 = resource->metadata()->hash; + + { + QFile file(fileInfo.absoluteFilePath()); + + if (!file.open(QFile::ReadOnly)) { + qWarning() << "Could not open" << relativePath << "for hashing"; + continue; + } + + const QByteArray data = file.readAll(); + + if (file.error() != QFileDevice::NoError) { + qWarning() << "Could not read" << relativePath; + continue; + } + + if (sha1.isEmpty()) + sha1 = Hashing::hash(data, Hashing::Algorithm::Sha1); + + if (sha512.isEmpty()) + sha512 = Hashing::hash(data, Hashing::Algorithm::Sha512); + + size = file.size(); + } + + ResolvedFile resolvedFile{ sha1, sha512, url.toEncoded(), size, resource->metadata()->side }; + resolvedFiles[relativePath] = resolvedFile; + } + } + for (const QFileInfo& file : files) { QCoreApplication::processEvents(); const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); + + if (resolvedFiles.contains(relative)) + continue; + // require sensible file types if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix); })) continue; @@ -105,42 +170,8 @@ void ModrinthPackExportTask::collectHashes() })) continue; - QFile openFile(file.absoluteFilePath()); - if (!openFile.open(QFile::ReadOnly)) { - qWarning() << "Could not open" << file << "for hashing"; - continue; - } - - const QByteArray data = openFile.readAll(); - if (openFile.error() != QFileDevice::NoError) { - qWarning() << "Could not read" << file; - continue; - } - auto sha512 = Hashing::hash(data, Hashing::Algorithm::Sha512); - - auto allMods = mcInstance->loaderModList()->allMods(); - if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); - modIter != allMods.end()) { - const Mod* mod = *modIter; - if (mod->metadata() != nullptr) { - const QUrl& url = mod->metadata()->url; - // ensure the url is permitted on modrinth.com - if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) { - qDebug() << "Resolving" << relative << "from index"; - - auto sha1 = Hashing::hash(data, Hashing::Algorithm::Sha1); - - ResolvedFile resolvedFile{ sha1, sha512, url.toEncoded(), openFile.size(), mod->metadata()->side }; - resolvedFiles[relative] = resolvedFile; - - // nice! we've managed to resolve based on local metadata! - // no need to enqueue it - continue; - } - } - } - qDebug() << "Enqueueing" << relative << "for Modrinth query"; + auto sha512 = Hashing::hash(file.absoluteFilePath(), Hashing::Algorithm::Sha512); pendingHashes[relative] = sha512; } @@ -241,30 +272,28 @@ QByteArray ModrinthPackExportTask::generateIndex() if (!summary.isEmpty()) out["summary"] = summary; - if (mcInstance) { - auto profile = mcInstance->getPackProfile(); - // collect all supported components - const ComponentPtr minecraft = profile->getComponent("net.minecraft"); - const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); - const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); - const ComponentPtr forge = profile->getComponent("net.minecraftforge"); - const ComponentPtr neoForge = profile->getComponent("net.neoforged"); + auto profile = instance->getPackProfile(); + // collect all supported components + const ComponentPtr minecraft = profile->getComponent("net.minecraft"); + const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + const ComponentPtr forge = profile->getComponent("net.minecraftforge"); + const ComponentPtr neoForge = profile->getComponent("net.neoforged"); - // convert all available components to mrpack dependencies - QJsonObject dependencies; - if (minecraft != nullptr) - dependencies["minecraft"] = minecraft->m_version; - if (quilt != nullptr) - dependencies["quilt-loader"] = quilt->m_version; - if (fabric != nullptr) - dependencies["fabric-loader"] = fabric->m_version; - if (forge != nullptr) - dependencies["forge"] = forge->m_version; - if (neoForge != nullptr) - dependencies["neoforge"] = neoForge->m_version; + // convert all available components to mrpack dependencies + QJsonObject dependencies; + if (minecraft != nullptr) + dependencies["minecraft"] = minecraft->m_version; + if (quilt != nullptr) + dependencies["quilt-loader"] = quilt->m_version; + if (fabric != nullptr) + dependencies["fabric-loader"] = fabric->m_version; + if (forge != nullptr) + dependencies["forge"] = forge->m_version; + if (neoForge != nullptr) + dependencies["neoforge"] = neoForge->m_version; - out["dependencies"] = dependencies; - } + out["dependencies"] = dependencies; QJsonArray filesOut; for (auto iterator = resolvedFiles.constBegin(); iterator != resolvedFiles.constEnd(); iterator++) { diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index ee740a456..14bc36009 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -29,12 +29,12 @@ class ModrinthPackExportTask : public Task { Q_OBJECT public: - ModrinthPackExportTask(const QString& name, - const QString& version, - const QString& summary, + ModrinthPackExportTask(QString name, + QString version, + QString summary, bool optionalFiles, - InstancePtr instance, - const QString& output, + MinecraftInstancePtr instance, + QString output, MMCZip::FilterFunction filter); protected: @@ -54,8 +54,7 @@ class ModrinthPackExportTask : public Task { // inputs const QString name, version, summary; const bool optionalFiles; - const InstancePtr instance; - MinecraftInstance* mcInstance; + const MinecraftInstancePtr instance; const QDir gameRoot; const QString output; const MMCZip::FilterFunction filter; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 96f1348d2..6134d9d31 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1403,27 +1403,26 @@ void MainWindow::on_actionExportInstanceZip_triggered() void MainWindow::on_actionExportInstanceMrPack_triggered() { - if (m_selectedInstance) { - ExportPackDialog dlg(m_selectedInstance, this); + auto instance = std::dynamic_pointer_cast(m_selectedInstance); + if (instance) { + ExportPackDialog dlg(std::move(instance), this); dlg.exec(); } } void MainWindow::on_actionExportInstanceFlamePack_triggered() { - if (m_selectedInstance) { - auto instance = dynamic_cast(m_selectedInstance.get()); - if (instance) { - if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft"); - cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") { - QMessageBox msgBox(this); - msgBox.setText("Snapshots are currently not supported by CurseForge modpacks."); - msgBox.exec(); - return; - } - ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME); - dlg.exec(); + auto instance = std::dynamic_pointer_cast(m_selectedInstance); + if (instance) { + if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft"); + cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") { + QMessageBox msgBox(this); + msgBox.setText("Snapshots are currently not supported by CurseForge modpacks."); + msgBox.exec(); + return; } + ExportPackDialog dlg(std::move(instance), this, ModPlatform::ResourceProvider::FLAME); + dlg.exec(); } } diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 0278c6cb0..c01b83f5c 100644 --- a/launcher/ui/dialogs/ExportPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -34,7 +34,7 @@ #include "MMCZip.h" #include "modplatform/modrinth/ModrinthPackExportTask.h" -ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) +ExportPackDialog::ExportPackDialog(const MinecraftInstancePtr& instance, QWidget* parent, ModPlatform::ResourceProvider provider) : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider) { Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME); @@ -86,12 +86,9 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla proxy->blockedPaths().insert(file); } - MinecraftInstance* mcInstance = dynamic_cast(instance.get()); - if (mcInstance) { - const QDir index = mcInstance->loaderModList()->indexDir(); - if (index.exists()) - proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath())); - } + for (auto& resourceModel : instance->resourceLists()) + if (resourceModel->indexDir().exists()) + proxy->ignoreFilesWithPath().insert(root.relativeFilePath(resourceModel->indexDir().absolutePath())); ui->files->setModel(proxy); ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); diff --git a/launcher/ui/dialogs/ExportPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h index 830c24d25..ab20f44bd 100644 --- a/launcher/ui/dialogs/ExportPackDialog.h +++ b/launcher/ui/dialogs/ExportPackDialog.h @@ -18,6 +18,7 @@ #pragma once +#include #include #include "BaseInstance.h" #include "FastFileIconProvider.h" @@ -32,7 +33,7 @@ class ExportPackDialog : public QDialog { Q_OBJECT public: - explicit ExportPackDialog(InstancePtr instance, + explicit ExportPackDialog(const MinecraftInstancePtr& instance, QWidget* parent = nullptr, ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH); ~ExportPackDialog(); @@ -41,7 +42,7 @@ class ExportPackDialog : public QDialog { void validate(); private: - const InstancePtr instance; + const MinecraftInstancePtr instance; Ui::ExportPackDialog* ui; FileIgnoreProxy* proxy; FastFileIconProvider icons;