diff options
Diffstat (limited to 'meshmc/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp')
| -rw-r--r-- | meshmc/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 876 |
1 files changed, 876 insertions, 0 deletions
diff --git a/meshmc/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/meshmc/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp new file mode 100644 index 0000000000..8c0e5507f0 --- /dev/null +++ b/meshmc/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -0,0 +1,876 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright 2021 Petr Mrazek <peterix@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ATLPackInstallTask.h" + +#include <QtConcurrent/QtConcurrent> +#include <QRegularExpression> + +#include "MMCZip.h" +#include "minecraft/OneSixVersionFormat.h" +#include "Version.h" +#include "net/ChecksumValidator.h" +#include "FileSystem.h" +#include "Json.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "settings/INISettingsObject.h" +#include "meta/Index.h" +#include "meta/Version.h" +#include "meta/VersionList.h" + +#include "BuildConfig.h" +#include "Application.h" + +namespace ATLauncher +{ + + PackInstallTask::PackInstallTask(UserInteractionSupport* support, + QString pack, QString version) + { + m_support = support; + m_pack = pack; + m_version_name = version; + } + + bool PackInstallTask::abort() + { + if (abortable) { + return jobPtr->abort(); + } + return false; + } + + void PackInstallTask::executeTask() + { + qDebug() << "PackInstallTask::executeTask: " + << QThread::currentThreadId(); + auto* netJob = + new NetJob("ATLauncher::VersionFetch", APPLICATION->network()); + auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + + "packs/%1/versions/%2/Configs.json") + .arg(m_pack) + .arg(m_version_name); + netJob->addNetAction( + Net::Download::makeByteArray(QUrl(searchUrl), &response)); + jobPtr = netJob; + jobPtr->start(); + + QObject::connect(netJob, &NetJob::succeeded, this, + &PackInstallTask::onDownloadSucceeded); + QObject::connect(netJob, &NetJob::failed, this, + &PackInstallTask::onDownloadFailed); + } + + void PackInstallTask::onDownloadSucceeded() + { + qDebug() << "PackInstallTask::onDownloadSucceeded: " + << QThread::currentThreadId(); + jobPtr.reset(); + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from FTB at " + << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + auto obj = doc.object(); + + ATLauncher::PackVersion version; + try { + ATLauncher::loadVersion(version, obj); + } catch (const JSONValidationError& e) { + emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); + return; + } + m_version = version; + + auto vlist = APPLICATION->metadataIndex()->get("net.minecraft"); + if (!vlist) { + emitFailed(tr("Failed to get local metadata index for %1") + .arg("net.minecraft")); + return; + } + + auto ver = vlist->getVersion(m_version.minecraft); + if (!ver) { + emitFailed(tr("Failed to get local metadata index for '%1' v%2") + .arg("net.minecraft") + .arg(m_version.minecraft)); + return; + } + ver->load(Net::Mode::Online); + minecraftVersion = ver; + + if (m_version.noConfigs) { + downloadMods(); + } else { + installConfigs(); + } + } + + void PackInstallTask::onDownloadFailed(QString reason) + { + qDebug() << "PackInstallTask::onDownloadFailed: " + << QThread::currentThreadId(); + emitFailed(reason); + jobPtr.reset(); + } + + QString PackInstallTask::getDirForModType(ModType type, QString raw) + { + switch (type) { + // Mod types that can either be ignored at this stage, or ignored + // completely. + case ModType::Root: + case ModType::Extract: + case ModType::Decomp: + case ModType::TexturePackExtract: + case ModType::ResourcePackExtract: + case ModType::MCPC: + return Q_NULLPTR; + case ModType::Forge: + // Forge detection happens later on, if it cannot be detected it + // will install a jarmod component. + case ModType::Jar: + return "jarmods"; + case ModType::Mods: + return "mods"; + case ModType::Flan: + return "Flan"; + case ModType::Dependency: + return FS::PathCombine("mods", m_version.minecraft); + case ModType::Ic2Lib: + return FS::PathCombine("mods", "ic2"); + case ModType::DenLib: + return FS::PathCombine("mods", "denlib"); + case ModType::Coremods: + return "coremods"; + case ModType::Plugins: + return "plugins"; + case ModType::TexturePack: + return "texturepacks"; + case ModType::ResourcePack: + return "resourcepacks"; + case ModType::ShaderPack: + return "shaderpacks"; + case ModType::Millenaire: + qWarning() << "Unsupported mod type: " + raw; + return Q_NULLPTR; + case ModType::Unknown: + emitFailed(tr("Unknown mod type: %1").arg(raw)); + return Q_NULLPTR; + } + + return Q_NULLPTR; + } + + QString PackInstallTask::getVersionForLoader(QString uid) + { + if (m_version.loader.recommended || m_version.loader.latest || + m_version.loader.choose) { + auto vlist = APPLICATION->metadataIndex()->get(uid); + if (!vlist) { + emitFailed( + tr("Failed to get local metadata index for %1").arg(uid)); + return Q_NULLPTR; + } + + if (!vlist->isLoaded()) { + vlist->load(Net::Mode::Online); + } + + if (m_version.loader.recommended || m_version.loader.latest) { + for (int i = 0; i < vlist->versions().size(); i++) { + auto version = vlist->versions().at(i); + auto reqs = version->requirements(); + + // filter by minecraft version, if the loader depends on a + // certain version. not all mod loaders depend on a given + // Minecraft version, so we won't do this filtering for + // those loaders. + if (m_version.loader.type != "fabric") { + auto iter = + std::find_if(reqs.begin(), reqs.end(), + [](const Meta::Require& req) { + return req.uid == "net.minecraft"; + }); + if (iter == reqs.end()) + continue; + if (iter->equalsVersion != m_version.minecraft) + continue; + } + + if (m_version.loader.recommended) { + // first recommended build we find, we use. + if (!version->isRecommended()) + continue; + } + + return version->descriptor(); + } + + emitFailed(tr("Failed to find version for %1 loader") + .arg(m_version.loader.type)); + return Q_NULLPTR; + } else if (m_version.loader.choose) { + // Fabric Loader doesn't depend on a given Minecraft version. + if (m_version.loader.type == "fabric") { + return m_support->chooseVersion(vlist, Q_NULLPTR); + } + + return m_support->chooseVersion(vlist, m_version.minecraft); + } + } + + if (m_version.loader.version == Q_NULLPTR || + m_version.loader.version.isEmpty()) { + emitFailed(tr("No loader version set for modpack!")); + return Q_NULLPTR; + } + + return m_version.loader.version; + } + + QString PackInstallTask::detectLibrary(VersionLibrary library) + { + // Try to detect what the library is + if (!library.server.isEmpty() && + library.server.split("/").length() >= 3) { + auto lastSlash = library.server.lastIndexOf("/"); + auto locationAndVersion = library.server.mid(0, lastSlash); + auto fileName = library.server.mid(lastSlash + 1); + + lastSlash = locationAndVersion.lastIndexOf("/"); + auto location = locationAndVersion.mid(0, lastSlash); + auto version = locationAndVersion.mid(lastSlash + 1); + + lastSlash = location.lastIndexOf("/"); + auto group = location.mid(0, lastSlash).replace("/", "."); + auto artefact = location.mid(lastSlash + 1); + + return group + ":" + artefact + ":" + version; + } + + if (library.file.contains("-")) { + auto lastSlash = library.file.lastIndexOf("-"); + auto name = library.file.mid(0, lastSlash); + auto version = library.file.mid(lastSlash + 1).remove(".jar"); + + if (name == QString("guava")) { + return "com.google.guava:guava:" + version; + } else if (name == QString("commons-lang3")) { + return "org.apache.commons:commons-lang3:" + version; + } + } + + return "org.projecttick.atlauncher:" + library.md5 + ":1"; + } + + bool PackInstallTask::createLibrariesComponent( + QString instanceRoot, std::shared_ptr<PackProfile> profile) + { + if (m_version.libraries.isEmpty()) { + return true; + } + + QList<GradleSpecifier> exempt; + for (const auto& componentUid : componentsToInstall.keys()) { + auto componentVersion = componentsToInstall.value(componentUid); + + for (const auto& library : componentVersion->data()->libraries) { + GradleSpecifier lib(library->rawName()); + exempt.append(lib); + } + } + + { + for (const auto& library : minecraftVersion->data()->libraries) { + GradleSpecifier lib(library->rawName()); + exempt.append(lib); + } + } + + auto uuid = QUuid::createUuid(); + auto id = uuid.toString().remove('{').remove('}'); + auto target_id = "org.projecttick.atlauncher." + id; + + auto patchDir = FS::PathCombine(instanceRoot, "patches"); + if (!FS::ensureFolderPathExists(patchDir)) { + return false; + } + auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + auto f = std::make_shared<VersionFile>(); + f->name = m_pack + " " + m_version_name + " (libraries)"; + + for (const auto& lib : m_version.libraries) { + auto libName = detectLibrary(lib); + GradleSpecifier libSpecifier(libName); + + bool libExempt = false; + for (const auto& existingLib : exempt) { + if (libSpecifier.matchName(existingLib)) { + // If the pack specifies a newer version of the lib, use + // that! + libExempt = Version(libSpecifier.version()) >= + Version(existingLib.version()); + } + } + if (libExempt) + continue; + + auto library = std::make_shared<Library>(); + library->setRawName(libName); + + switch (lib.download) { + case DownloadType::Server: + library->setAbsoluteUrl( + BuildConfig.ATL_DOWNLOAD_SERVER_URL + lib.url); + break; + case DownloadType::Direct: + library->setAbsoluteUrl(lib.url); + break; + case DownloadType::Browser: + case DownloadType::Unknown: + emitFailed(tr("Unknown or unsupported download type: %1") + .arg(lib.download_raw)); + return false; + } + + f->libraries.append(library); + } + + if (f->libraries.isEmpty()) { + return true; + } + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + profile->appendComponent(new Component(profile.get(), target_id, f)); + return true; + } + + bool + PackInstallTask::createPackComponent(QString instanceRoot, + std::shared_ptr<PackProfile> profile) + { + if (m_version.mainClass == QString() && + m_version.extraArguments == QString()) { + return true; + } + + auto uuid = QUuid::createUuid(); + auto id = uuid.toString().remove('{').remove('}'); + auto target_id = "org.projecttick.atlauncher." + id; + + auto patchDir = FS::PathCombine(instanceRoot, "patches"); + if (!FS::ensureFolderPathExists(patchDir)) { + return false; + } + auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QStringList mainClasses; + QStringList tweakers; + for (const auto& componentUid : componentsToInstall.keys()) { + auto componentVersion = componentsToInstall.value(componentUid); + + if (componentVersion->data()->mainClass != QString("")) { + mainClasses.append(componentVersion->data()->mainClass); + } + tweakers.append(componentVersion->data()->addTweakers); + } + + auto f = std::make_shared<VersionFile>(); + f->name = m_pack + " " + m_version_name; + if (m_version.mainClass != QString() && + !mainClasses.contains(m_version.mainClass)) { + f->mainClass = m_version.mainClass; + } + + // Parse out tweakers + auto args = m_version.extraArguments.split(" "); + QString previous; + for (auto arg : args) { + if (arg.startsWith("--tweakClass=") || previous == "--tweakClass") { + auto tweakClass = arg.remove("--tweakClass="); + if (tweakers.contains(tweakClass)) + continue; + + f->addTweakers.append(tweakClass); + } + previous = arg; + } + + if (f->mainClass == QString() && f->addTweakers.isEmpty()) { + return true; + } + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + profile->appendComponent(new Component(profile.get(), target_id, f)); + return true; + } + + void PackInstallTask::installConfigs() + { + qDebug() << "PackInstallTask::installConfigs: " + << QThread::currentThreadId(); + setStatus(tr("Downloading configs...")); + jobPtr = new NetJob(tr("Config download"), APPLICATION->network()); + + auto path = + QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name); + auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + + "packs/%1/versions/%2/Configs.zip") + .arg(m_pack) + .arg(m_version_name); + auto entry = + APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path); + entry->setStale(true); + + auto dl = Net::Download::makeCached(url, entry); + if (!m_version.configs.sha1.isEmpty()) { + auto rawSha1 = + QByteArray::fromHex(m_version.configs.sha1.toLatin1()); + dl->addValidator( + new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + } + jobPtr->addNetAction(dl); + archivePath = entry->getFullPath(); + + connect(jobPtr.get(), &NetJob::succeeded, this, [&]() { + abortable = false; + extractConfigs(); + jobPtr.reset(); + }); + connect(jobPtr.get(), &NetJob::failed, [&](QString reason) { + abortable = false; + emitFailed(reason); + jobPtr.reset(); + }); + connect(jobPtr.get(), &NetJob::progress, + [&](qint64 current, qint64 total) { + abortable = true; + setProgress(current, total); + }); + + jobPtr->start(); + } + + void PackInstallTask::extractConfigs() + { + qDebug() << "PackInstallTask::extractConfigs: " + << QThread::currentThreadId(); + setStatus(tr("Extracting configs...")); + + QDir extractDir(m_stagingPath); + + QString extractPath = extractDir.absolutePath() + "/minecraft"; + QString archivePathCopy = archivePath; + m_extractFuture = QtConcurrent::run( + QThreadPool::globalInstance(), [archivePathCopy, extractPath]() { + return MMCZip::extractDir(archivePathCopy, extractPath); + }); + connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, + this, [&]() { downloadMods(); }); + connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, + this, [&]() { emitAborted(); }); + m_extractFutureWatcher.setFuture(m_extractFuture); + } + + void PackInstallTask::downloadMods() + { + qDebug() << "PackInstallTask::installMods: " + << QThread::currentThreadId(); + + QVector<ATLauncher::VersionMod> optionalMods; + for (const auto& mod : m_version.mods) { + if (mod.optional) { + optionalMods.push_back(mod); + } + } + + // Select optional mods, if pack contains any + QVector<QString> selectedMods; + if (!optionalMods.isEmpty()) { + setStatus(tr("Selecting optional mods...")); + selectedMods = m_support->chooseOptionalMods(optionalMods); + } + + setStatus(tr("Downloading mods...")); + + jarmods.clear(); + jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& mod : m_version.mods) { + // skip non-client mods + if (!mod.client) + continue; + + // skip optional mods that were not selected + if (mod.optional && !selectedMods.contains(mod.name)) + continue; + + QString url; + switch (mod.download) { + case DownloadType::Server: + url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url; + break; + case DownloadType::Browser: + emitFailed(tr("Unsupported download type: %1") + .arg(mod.download_raw)); + return; + case DownloadType::Direct: + url = mod.url; + break; + case DownloadType::Unknown: + emitFailed( + tr("Unknown download type: %1").arg(mod.download_raw)); + return; + } + + QFileInfo fileName(mod.file); + auto cacheName = fileName.completeBaseName() + "-" + mod.md5 + "." + + fileName.suffix(); + + if (mod.type == ModType::Extract || + mod.type == ModType::TexturePackExtract || + mod.type == ModType::ResourcePackExtract) { + auto entry = APPLICATION->metacache()->resolveEntry( + "ATLauncherPacks", cacheName); + entry->setStale(true); + modsToExtract.insert(entry->getFullPath(), mod); + + auto dl = Net::Download::makeCached(url, entry); + if (!mod.md5.isEmpty()) { + auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); + dl->addValidator(new Net::ChecksumValidator( + QCryptographicHash::Md5, rawMd5)); + } + jobPtr->addNetAction(dl); + } else if (mod.type == ModType::Decomp) { + auto entry = APPLICATION->metacache()->resolveEntry( + "ATLauncherPacks", cacheName); + entry->setStale(true); + modsToDecomp.insert(entry->getFullPath(), mod); + + auto dl = Net::Download::makeCached(url, entry); + if (!mod.md5.isEmpty()) { + auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); + dl->addValidator(new Net::ChecksumValidator( + QCryptographicHash::Md5, rawMd5)); + } + jobPtr->addNetAction(dl); + } else { + auto relpath = getDirForModType(mod.type, mod.type_raw); + if (relpath == Q_NULLPTR) + continue; + + auto entry = APPLICATION->metacache()->resolveEntry( + "ATLauncherPacks", cacheName); + entry->setStale(true); + + auto dl = Net::Download::makeCached(url, entry); + if (!mod.md5.isEmpty()) { + auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); + dl->addValidator(new Net::ChecksumValidator( + QCryptographicHash::Md5, rawMd5)); + } + jobPtr->addNetAction(dl); + + auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, + mod.file); + qDebug() << "Will download" << url << "to" << path; + modsToCopy[entry->getFullPath()] = path; + + if (mod.type == ModType::Forge) { + auto vlist = + APPLICATION->metadataIndex()->get("net.minecraftforge"); + if (vlist) { + auto ver = vlist->getVersion(mod.version); + if (ver) { + ver->load(Net::Mode::Online); + componentsToInstall.insert("net.minecraftforge", + ver); + continue; + } + } + + qDebug() << "Jarmod: " + path; + jarmods.push_back(path); + } + + if (mod.type == ModType::Jar) { + qDebug() << "Jarmod: " + path; + jarmods.push_back(path); + } + } + } + + connect(jobPtr.get(), &NetJob::succeeded, this, + &PackInstallTask::onModsDownloaded); + connect(jobPtr.get(), &NetJob::failed, [&](QString reason) { + abortable = false; + emitFailed(reason); + jobPtr.reset(); + }); + connect(jobPtr.get(), &NetJob::progress, + [&](qint64 current, qint64 total) { + abortable = true; + setProgress(current, total); + }); + + jobPtr->start(); + } + + void PackInstallTask::onModsDownloaded() + { + abortable = false; + + qDebug() << "PackInstallTask::onModsDownloaded: " + << QThread::currentThreadId(); + + if (!modsToExtract.empty() || !modsToDecomp.empty() || + !modsToCopy.empty()) { + auto modsToExtractCopy = modsToExtract; + auto modsToDecompCopy = modsToDecomp; + auto modsToCopyCopy = modsToCopy; + m_modExtractFuture = QtConcurrent::run( + QThreadPool::globalInstance(), + [this, modsToExtractCopy, modsToDecompCopy, modsToCopyCopy]() { + return this->extractMods(modsToExtractCopy, + modsToDecompCopy, modsToCopyCopy); + }); + connect(&m_modExtractFutureWatcher, + &QFutureWatcher<QStringList>::finished, this, + &PackInstallTask::onModsExtracted); + connect(&m_modExtractFutureWatcher, + &QFutureWatcher<QStringList>::canceled, this, + [&]() { emitAborted(); }); + m_modExtractFutureWatcher.setFuture(m_modExtractFuture); + } else { + install(); + } + } + + void PackInstallTask::onModsExtracted() + { + qDebug() << "PackInstallTask::onModsExtracted: " + << QThread::currentThreadId(); + if (m_modExtractFuture.result()) { + install(); + } else { + emitFailed(tr("Failed to extract mods...")); + } + } + + bool + PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract, + const QMap<QString, VersionMod>& toDecomp, + const QMap<QString, QString>& toCopy) + { + qDebug() << "PackInstallTask::extractMods: " + << QThread::currentThreadId(); + + setStatus(tr("Extracting mods...")); + for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) { + auto& modPath = iter.key(); + auto& mod = iter.value(); + + QString extractToDir; + if (mod.type == ModType::Extract) { + extractToDir = + getDirForModType(mod.extractTo, mod.extractTo_raw); + } else if (mod.type == ModType::TexturePackExtract) { + extractToDir = FS::PathCombine("texturepacks", "extracted"); + } else if (mod.type == ModType::ResourcePackExtract) { + extractToDir = FS::PathCombine("resourcepacks", "extracted"); + } + + QDir extractDir(m_stagingPath); + auto extractToPath = FS::PathCombine(extractDir.absolutePath(), + "minecraft", extractToDir); + + QString folderToExtract = ""; + if (mod.type == ModType::Extract) { + folderToExtract = mod.extractFolder; + folderToExtract.remove(QRegularExpression("^/")); + } + + qDebug() << "Extracting " + mod.file + " to " + extractToDir; + if (!MMCZip::extractDir(modPath, folderToExtract, extractToPath)) { + // assume error + return false; + } + } + + for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) { + auto& modPath = iter.key(); + auto& mod = iter.value(); + auto extractToDir = + getDirForModType(mod.decompType, mod.decompType_raw); + + QDir extractDir(m_stagingPath); + auto extractToPath = + FS::PathCombine(extractDir.absolutePath(), "minecraft", + extractToDir, mod.decompFile); + + qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir; + if (!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) { + qWarning() << "Failed to extract" << mod.decompFile; + return false; + } + } + + for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { + auto& from = iter.key(); + auto& to = iter.value(); + FS::copy fileCopyOperation(from, to); + if (!fileCopyOperation()) { + qWarning() << "Failed to copy" << from << "to" << to; + return false; + } + } + return true; + } + + void PackInstallTask::install() + { + qDebug() << "PackInstallTask::install: " << QThread::currentThreadId(); + setStatus(tr("Installing modpack")); + + auto instanceConfigPath = + FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = + std::make_shared<INISettingsObject>(instanceConfigPath); + instanceSettings->suspendSave(); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + + MinecraftInstance instance(m_globalSettings, instanceSettings, + m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + + // Use a component to add libraries BEFORE Minecraft + if (!createLibrariesComponent(instance.instanceRoot(), components)) { + emitFailed(tr("Failed to create libraries component")); + return; + } + + // Minecraft + components->setComponentVersion("net.minecraft", m_version.minecraft, + true); + + // Loader + if (m_version.loader.type == QString("forge")) { + auto version = getVersionForLoader("net.minecraftforge"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.minecraftforge", version, + true); + } else if (m_version.loader.type == QString("fabric")) { + auto version = getVersionForLoader("net.fabricmc.fabric-loader"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.fabricmc.fabric-loader", + version, true); + } else if (m_version.loader.type == QString("neoforge")) { + auto version = getVersionForLoader("net.neoforged"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("net.neoforged", version, true); + } else if (m_version.loader.type == QString("quilt")) { + auto version = getVersionForLoader("org.quiltmc.quilt-loader"); + if (version == Q_NULLPTR) + return; + + components->setComponentVersion("org.quiltmc.quilt-loader", version, + true); + } else if (m_version.loader.type != QString()) { + emitFailed(tr("Unknown loader type: ") + m_version.loader.type); + return; + } + + for (const auto& componentUid : componentsToInstall.keys()) { + auto version = componentsToInstall.value(componentUid); + components->setComponentVersion(componentUid, version->version()); + } + + components->installJarMods(jarmods); + + // Use a component to fill in the rest of the data + // todo: use more detection + if (!createPackComponent(instance.instanceRoot(), components)) { + emitFailed(tr("Failed to create pack component")); + return; + } + + components->saveNow(); + + instance.setName(m_instName); + instance.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + + jarmods.clear(); + emitSucceeded(); + } + +} // namespace ATLauncher |
