2022-12-06 12:18:50 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
/*
|
|
|
|
* Prism Launcher - Minecraft Launcher
|
|
|
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
|
|
*
|
|
|
|
* 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, version 3.
|
|
|
|
*
|
|
|
|
* 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 2013-2021 MultiMC Contributors
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
#include "FlameInstanceCreationTask.h"
|
|
|
|
|
2023-03-31 07:50:29 +01:00
|
|
|
#include "modplatform/flame/FileResolvingTask.h"
|
2022-07-14 20:41:49 +01:00
|
|
|
#include "modplatform/flame/FlameAPI.h"
|
2022-07-08 22:44:43 +01:00
|
|
|
#include "modplatform/flame/PackManifest.h"
|
|
|
|
|
|
|
|
#include "Application.h"
|
|
|
|
#include "FileSystem.h"
|
2022-07-14 20:13:23 +01:00
|
|
|
#include "InstanceList.h"
|
2022-07-08 22:44:43 +01:00
|
|
|
#include "Json.h"
|
|
|
|
|
|
|
|
#include "minecraft/MinecraftInstance.h"
|
|
|
|
#include "minecraft/PackProfile.h"
|
|
|
|
|
2022-07-31 20:45:01 +01:00
|
|
|
#include "modplatform/helpers/OverrideUtils.h"
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
#include "settings/INISettingsObject.h"
|
|
|
|
|
|
|
|
#include "ui/dialogs/BlockedModsDialog.h"
|
2022-09-11 17:16:25 +01:00
|
|
|
#include "ui/dialogs/CustomMessageBox.h"
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2022-12-26 21:29:13 +00:00
|
|
|
#include <QDebug>
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
|
|
|
#include "minecraft/World.h"
|
2022-12-30 02:47:19 +00:00
|
|
|
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
|
|
|
|
2022-12-25 03:38:29 +00:00
|
|
|
|
2022-07-14 20:41:49 +01:00
|
|
|
const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" },
|
|
|
|
{ "1.4.2", "6.0.1.355" },
|
|
|
|
{ "1.4.7", "6.6.2.534" },
|
|
|
|
{ "1.5.2", "7.8.1.737" } };
|
|
|
|
|
|
|
|
static const FlameAPI api;
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
bool FlameCreationTask::abort()
|
|
|
|
{
|
2022-07-31 22:21:59 +01:00
|
|
|
if (!canAbort())
|
|
|
|
return false;
|
|
|
|
|
2022-08-06 01:25:21 +01:00
|
|
|
m_abort = true;
|
2022-07-14 20:41:49 +01:00
|
|
|
if (m_process_update_file_info_job)
|
|
|
|
m_process_update_file_info_job->abort();
|
2022-07-08 22:44:43 +01:00
|
|
|
if (m_files_job)
|
|
|
|
m_files_job->abort();
|
|
|
|
if (m_mod_id_resolver)
|
|
|
|
m_mod_id_resolver->abort();
|
|
|
|
|
2022-07-31 22:21:59 +01:00
|
|
|
return Task::abort();
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
|
|
|
|
2022-07-14 20:41:49 +01:00
|
|
|
bool FlameCreationTask::updateInstance()
|
|
|
|
{
|
|
|
|
auto instance_list = APPLICATION->instances();
|
|
|
|
|
|
|
|
// FIXME: How to handle situations when there's more than one install already for a given modpack?
|
2022-12-03 13:15:38 +00:00
|
|
|
InstancePtr inst;
|
|
|
|
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
|
|
|
|
inst = instance_list->getInstanceById(original_id);
|
|
|
|
Q_ASSERT(inst);
|
|
|
|
} else {
|
|
|
|
inst = instance_list->getInstanceByManagedName(originalName());
|
2022-07-14 20:41:49 +01:00
|
|
|
|
2022-12-03 13:15:38 +00:00
|
|
|
if (!inst) {
|
|
|
|
inst = instance_list->getInstanceById(originalName());
|
2022-07-14 20:41:49 +01:00
|
|
|
|
2022-12-03 13:15:38 +00:00
|
|
|
if (!inst)
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-14 20:41:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
|
|
|
|
|
|
|
|
try {
|
|
|
|
Flame::loadManifest(m_pack, index_path);
|
|
|
|
} catch (const JSONValidationError& e) {
|
|
|
|
setError(tr("Could not understand pack manifest:\n") + e.cause());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto version_id = inst->getManagedPackVersionName();
|
|
|
|
auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : "";
|
|
|
|
|
2022-10-14 18:23:55 +01:00
|
|
|
if (shouldConfirmUpdate()) {
|
|
|
|
auto should_update = askIfShouldUpdate(m_parent, version_str);
|
|
|
|
if (should_update == ShouldUpdate::SkipUpdating)
|
|
|
|
return false;
|
|
|
|
if (should_update == ShouldUpdate::Cancel) {
|
|
|
|
m_abort = true;
|
|
|
|
return false;
|
|
|
|
}
|
2022-08-01 00:29:12 +01:00
|
|
|
}
|
2022-07-14 20:41:49 +01:00
|
|
|
|
|
|
|
QDir old_inst_dir(inst->instanceRoot());
|
|
|
|
|
2022-07-31 20:45:01 +01:00
|
|
|
QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame"));
|
|
|
|
QString old_index_path(FS::PathCombine(old_index_folder, "manifest.json"));
|
|
|
|
|
2022-07-14 20:41:49 +01:00
|
|
|
QFileInfo old_index_file(old_index_path);
|
|
|
|
if (old_index_file.exists()) {
|
|
|
|
Flame::Manifest old_pack;
|
|
|
|
Flame::loadManifest(old_pack, old_index_path);
|
|
|
|
|
|
|
|
auto& old_files = old_pack.files;
|
|
|
|
|
|
|
|
auto& files = m_pack.files;
|
|
|
|
|
2022-07-31 20:45:01 +01:00
|
|
|
// Remove repeated files, we don't need to download them!
|
2022-07-14 20:41:49 +01:00
|
|
|
auto files_iterator = files.begin();
|
|
|
|
while (files_iterator != files.end()) {
|
|
|
|
auto const& file = files_iterator;
|
|
|
|
|
|
|
|
auto old_file = old_files.find(file.key());
|
|
|
|
if (old_file != old_files.end()) {
|
|
|
|
// We found a match, but is it a different version?
|
|
|
|
if (old_file->fileId == file->fileId) {
|
|
|
|
qDebug() << "Removed file at" << file->targetFolder << "with id" << file->fileId << "from list of downloads";
|
|
|
|
|
|
|
|
old_files.remove(file.key());
|
|
|
|
files_iterator = files.erase(files_iterator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
files_iterator++;
|
|
|
|
}
|
2022-07-31 20:45:01 +01:00
|
|
|
|
fix: move file deletion to the end of the instance update
This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.
Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.
What a long commit message o.O
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-08-06 01:26:02 +01:00
|
|
|
QDir old_minecraft_dir(inst->gameRoot());
|
2022-07-31 20:45:01 +01:00
|
|
|
|
|
|
|
// We will remove all the previous overrides, to prevent duplicate files!
|
|
|
|
// TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides?
|
|
|
|
// FIXME: We may want to do something about disabled mods.
|
|
|
|
auto old_overrides = Override::readOverrides("overrides", old_index_folder);
|
2022-09-26 10:50:31 +01:00
|
|
|
for (const auto& entry : old_overrides) {
|
fix: move file deletion to the end of the instance update
This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.
Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.
What a long commit message o.O
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-08-06 01:26:02 +01:00
|
|
|
if (entry.isEmpty())
|
|
|
|
continue;
|
|
|
|
qDebug() << "Scheduling" << entry << "for removal";
|
|
|
|
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry));
|
2022-07-31 20:45:01 +01:00
|
|
|
}
|
|
|
|
|
2022-07-14 20:41:49 +01:00
|
|
|
// Remove remaining old files (we need to do an API request to know which ids are which files...)
|
|
|
|
QStringList fileIds;
|
|
|
|
|
|
|
|
for (auto& file : old_files) {
|
|
|
|
fileIds.append(QString::number(file.fileId));
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* raw_response = new QByteArray;
|
|
|
|
auto job = api.getFiles(fileIds, raw_response);
|
|
|
|
|
|
|
|
QEventLoop loop;
|
|
|
|
|
2023-01-03 16:58:27 +00:00
|
|
|
connect(job.get(), &Task::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] {
|
2022-07-14 20:41:49 +01:00
|
|
|
// Parse the API response
|
|
|
|
QJsonParseError parse_error{};
|
|
|
|
auto doc = QJsonDocument::fromJson(*raw_response, &parse_error);
|
|
|
|
if (parse_error.error != QJsonParseError::NoError) {
|
|
|
|
qWarning() << "Error while parsing JSON response from Flame files task at " << parse_error.offset
|
|
|
|
<< " reason: " << parse_error.errorString();
|
|
|
|
qWarning() << *raw_response;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
QJsonArray entries;
|
|
|
|
if (fileIds.size() == 1)
|
|
|
|
entries = { Json::requireObject(Json::requireObject(doc), "data") };
|
|
|
|
else
|
|
|
|
entries = Json::requireArray(Json::requireObject(doc), "data");
|
|
|
|
|
|
|
|
for (auto entry : entries) {
|
|
|
|
auto entry_obj = Json::requireObject(entry);
|
|
|
|
|
|
|
|
Flame::File file;
|
|
|
|
// We don't care about blocked mods, we just need local data to delete the file
|
|
|
|
file.parseFromObject(entry_obj, false);
|
|
|
|
|
|
|
|
auto id = Json::requireInteger(entry_obj, "id");
|
|
|
|
old_files.insert(id, file);
|
|
|
|
}
|
|
|
|
} catch (Json::JsonException& e) {
|
|
|
|
qCritical() << e.cause() << e.what();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the files
|
|
|
|
for (auto& file : old_files) {
|
|
|
|
if (file.fileName.isEmpty() || file.targetFolder.isEmpty())
|
|
|
|
continue;
|
|
|
|
|
fix: move file deletion to the end of the instance update
This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.
Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.
What a long commit message o.O
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-08-06 01:26:02 +01:00
|
|
|
QString relative_path(FS::PathCombine(file.targetFolder, file.fileName));
|
|
|
|
qDebug() << "Scheduling" << relative_path << "for removal";
|
|
|
|
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
|
2022-07-14 20:41:49 +01:00
|
|
|
}
|
|
|
|
});
|
2023-01-03 16:58:27 +00:00
|
|
|
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
|
2022-07-14 20:41:49 +01:00
|
|
|
|
|
|
|
m_process_update_file_info_job = job;
|
|
|
|
job->start();
|
|
|
|
|
|
|
|
loop.exec();
|
|
|
|
|
|
|
|
m_process_update_file_info_job = nullptr;
|
2022-08-21 14:00:23 +01:00
|
|
|
} else {
|
|
|
|
// We don't have an old index file, so we may duplicate stuff!
|
2022-12-06 12:16:27 +00:00
|
|
|
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
|
|
|
|
tr("We couldn't find a suitable index file for the older version. This may cause some "
|
|
|
|
"of the files to be duplicated. Do you want to continue?"),
|
|
|
|
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
|
2022-08-21 14:00:23 +01:00
|
|
|
|
|
|
|
if (dialog->exec() == QDialog::DialogCode::Rejected) {
|
|
|
|
m_abort = true;
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-14 20:41:49 +01:00
|
|
|
}
|
|
|
|
|
2022-12-03 13:15:38 +00:00
|
|
|
setOverride(true, inst->id());
|
2022-07-14 20:41:49 +01:00
|
|
|
qDebug() << "Will override instance!";
|
|
|
|
|
2022-07-31 23:56:14 +01:00
|
|
|
m_instance = inst;
|
|
|
|
|
2022-07-14 20:41:49 +01:00
|
|
|
// We let it go through the createInstance() stage, just with a couple modifications for updating
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-08 22:44:43 +01:00
|
|
|
|
|
|
|
bool FlameCreationTask::createInstance()
|
|
|
|
{
|
|
|
|
QEventLoop loop;
|
|
|
|
|
2022-07-31 20:45:01 +01:00
|
|
|
QString parent_folder(FS::PathCombine(m_stagingPath, "flame"));
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
try {
|
2022-07-14 20:41:49 +01:00
|
|
|
QString index_path(FS::PathCombine(m_stagingPath, "manifest.json"));
|
|
|
|
if (!m_pack.is_loaded)
|
|
|
|
Flame::loadManifest(m_pack, index_path);
|
|
|
|
|
|
|
|
// Keep index file in case we need it some other time (like when changing versions)
|
2022-07-31 20:45:01 +01:00
|
|
|
QString new_index_place(FS::PathCombine(parent_folder, "manifest.json"));
|
2022-07-14 20:41:49 +01:00
|
|
|
FS::ensureFilePathExists(new_index_place);
|
|
|
|
QFile::rename(index_path, new_index_place);
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
} catch (const JSONValidationError& e) {
|
|
|
|
setError(tr("Could not understand pack manifest:\n") + e.cause());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-07-14 20:41:49 +01:00
|
|
|
if (!m_pack.overrides.isEmpty()) {
|
|
|
|
QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides);
|
2022-07-08 22:44:43 +01:00
|
|
|
if (QFile::exists(overridePath)) {
|
2022-07-31 20:45:01 +01:00
|
|
|
// Create a list of overrides in "overrides.txt" inside flame/
|
|
|
|
Override::createOverrides("overrides", parent_folder, overridePath);
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
|
|
|
|
if (!QFile::rename(overridePath, mcPath)) {
|
2022-07-14 20:41:49 +01:00
|
|
|
setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides);
|
2022-07-08 22:44:43 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logWarning(
|
2022-07-14 20:41:49 +01:00
|
|
|
tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(m_pack.overrides));
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString forgeVersion;
|
|
|
|
QString fabricVersion;
|
|
|
|
// TODO: is Quilt relevant here?
|
2022-07-14 20:41:49 +01:00
|
|
|
for (auto& loader : m_pack.minecraft.modLoaders) {
|
2022-07-08 22:44:43 +01:00
|
|
|
auto id = loader.id;
|
|
|
|
if (id.startsWith("forge-")) {
|
|
|
|
id.remove("forge-");
|
|
|
|
forgeVersion = id;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (id.startsWith("fabric-")) {
|
|
|
|
id.remove("fabric-");
|
|
|
|
fabricVersion = id;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
|
|
|
}
|
|
|
|
|
|
|
|
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
|
|
|
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
|
|
|
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
2022-07-14 20:41:49 +01:00
|
|
|
auto mcVersion = m_pack.minecraft.version;
|
2022-07-08 22:44:43 +01:00
|
|
|
|
|
|
|
// Hack to correct some 'special sauce'...
|
|
|
|
if (mcVersion.endsWith('.')) {
|
|
|
|
mcVersion.remove(QRegularExpression("[.]+$"));
|
|
|
|
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
|
|
|
|
}
|
|
|
|
|
|
|
|
auto components = instance.getPackProfile();
|
|
|
|
components->buildingFromScratch();
|
|
|
|
components->setComponentVersion("net.minecraft", mcVersion, true);
|
|
|
|
if (!forgeVersion.isEmpty()) {
|
|
|
|
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
|
|
|
|
if (forgeVersion == "recommended") {
|
|
|
|
if (forgemap.contains(mcVersion)) {
|
|
|
|
forgeVersion = forgemap[mcVersion];
|
|
|
|
} else {
|
|
|
|
logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
|
|
|
}
|
|
|
|
if (!fabricVersion.isEmpty())
|
|
|
|
components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
|
|
|
|
|
|
|
|
if (m_instIcon != "default") {
|
|
|
|
instance.setIconKey(m_instIcon);
|
|
|
|
} else {
|
2022-07-14 20:41:49 +01:00
|
|
|
if (m_pack.name.contains("Direwolf20")) {
|
2022-07-08 22:44:43 +01:00
|
|
|
instance.setIconKey("steve");
|
2022-07-14 20:41:49 +01:00
|
|
|
} else if (m_pack.name.contains("FTB") || m_pack.name.contains("Feed The Beast")) {
|
2022-07-08 22:44:43 +01:00
|
|
|
instance.setIconKey("ftb_logo");
|
|
|
|
} else {
|
|
|
|
instance.setIconKey("flame");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
|
|
|
QFileInfo jarmodsInfo(jarmodsPath);
|
|
|
|
if (jarmodsInfo.isDir()) {
|
|
|
|
// install all the jar mods
|
|
|
|
qDebug() << "Found jarmods:";
|
|
|
|
QDir jarmodsDir(jarmodsPath);
|
|
|
|
QStringList jarMods;
|
2022-09-26 10:50:31 +01:00
|
|
|
for (const auto& info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
|
2022-07-08 22:44:43 +01:00
|
|
|
qDebug() << info.fileName();
|
|
|
|
jarMods.push_back(info.absoluteFilePath());
|
|
|
|
}
|
|
|
|
auto profile = instance.getPackProfile();
|
|
|
|
profile->installJarMods(jarMods);
|
|
|
|
// nuke the original files
|
|
|
|
FS::deletePath(jarmodsPath);
|
|
|
|
}
|
|
|
|
|
2022-12-13 16:43:27 +00:00
|
|
|
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
|
|
|
if (!m_managed_id.isEmpty())
|
|
|
|
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
|
2022-07-14 20:13:23 +01:00
|
|
|
instance.setName(name());
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2023-01-24 19:52:09 +00:00
|
|
|
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
|
2022-07-14 20:41:49 +01:00
|
|
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
|
2022-07-08 22:44:43 +01:00
|
|
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
|
|
|
|
m_mod_id_resolver.reset();
|
|
|
|
setError(tr("Unable to resolve mod IDs:\n") + reason);
|
2022-12-01 18:33:35 +00:00
|
|
|
loop.quit();
|
2022-07-08 22:44:43 +01:00
|
|
|
});
|
|
|
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
|
|
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
2023-03-31 07:50:29 +01:00
|
|
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress);
|
2023-05-21 09:46:28 +01:00
|
|
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
|
2022-07-08 22:44:43 +01:00
|
|
|
m_mod_id_resolver->start();
|
|
|
|
|
|
|
|
loop.exec();
|
|
|
|
|
fix: move file deletion to the end of the instance update
This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.
Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.
What a long commit message o.O
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-08-06 01:26:02 +01:00
|
|
|
bool did_succeed = getError().isEmpty();
|
|
|
|
|
2022-09-11 17:16:25 +01:00
|
|
|
// Update information of the already installed instance, if any.
|
fix: move file deletion to the end of the instance update
This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.
Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.
What a long commit message o.O
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-08-06 01:26:02 +01:00
|
|
|
if (m_instance && did_succeed) {
|
2022-08-21 13:26:27 +01:00
|
|
|
setAbortable(false);
|
2022-07-31 23:56:14 +01:00
|
|
|
auto inst = m_instance.value();
|
|
|
|
|
|
|
|
inst->copyManagedPack(instance);
|
|
|
|
}
|
|
|
|
|
fix: move file deletion to the end of the instance update
This makes it harder for problems in the updating process to affect the
current instance. Network issues, for instance, will no longer put the
instance in an invalid state.
Still, a possible improvement to this would be passing that logic to
InstanceStaging instead, to be handled with the instance commiting
directly. However, as it is now, the code would become very spaguetti-y,
and given that the override operation in the commiting could also put
the instance into an invalid state, it seems to me that, in order to
fully error-proof this, we would need to do a copy operation on the
whole instance, in order to modify the copy, and only in the end
override everything an once with a rename. That also has the possibility
of corrupting the instance if done without super care, however, so I
think we may need to instead create an automatic backup system, with an
undo command of sorts, or something like that. This doesn't seem very
trivial though, so it'll probably need to wait until another PR. In the
meantime, the user is advised to always backup their instances before
doing this kind of action, as always.
What a long commit message o.O
Signed-off-by: flow <flowlnlnln@gmail.com>
2022-08-06 01:26:02 +01:00
|
|
|
return did_succeed;
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|
|
|
{
|
|
|
|
auto results = m_mod_id_resolver->getResults();
|
2022-07-21 20:43:08 +01:00
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
// first check for blocked mods
|
2022-10-24 12:08:38 +01:00
|
|
|
QList<BlockedMod> blocked_mods;
|
2022-07-08 22:44:43 +01:00
|
|
|
auto anyBlocked = false;
|
|
|
|
for (const auto& result : results.files.values()) {
|
2022-12-26 21:29:13 +00:00
|
|
|
if (result.fileName.endsWith(".zip")) {
|
2022-12-25 03:38:29 +00:00
|
|
|
m_ZIP_resources.append(std::make_pair(result.fileName, result.targetFolder));
|
|
|
|
}
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
if (!result.resolved || result.url.isEmpty()) {
|
2022-10-24 12:08:38 +01:00
|
|
|
BlockedMod blocked_mod;
|
|
|
|
blocked_mod.name = result.fileName;
|
|
|
|
blocked_mod.websiteUrl = result.websiteUrl;
|
|
|
|
blocked_mod.hash = result.hash;
|
|
|
|
blocked_mod.matched = false;
|
|
|
|
blocked_mod.localPath = "";
|
2022-12-09 01:42:51 +00:00
|
|
|
blocked_mod.targetFolder = result.targetFolder;
|
2022-10-24 12:08:38 +01:00
|
|
|
|
|
|
|
blocked_mods.append(blocked_mod);
|
|
|
|
|
2022-07-08 22:44:43 +01:00
|
|
|
anyBlocked = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (anyBlocked) {
|
|
|
|
qWarning() << "Blocked mods found, displaying mod list";
|
|
|
|
|
2022-12-05 20:33:42 +00:00
|
|
|
BlockedModsDialog message_dialog(m_parent, tr("Blocked mods found"),
|
2022-12-06 12:16:27 +00:00
|
|
|
tr("The following files are not available for download in third party launchers.<br/>"
|
|
|
|
"You will need to manually download them and add them to the instance."),
|
|
|
|
blocked_mods);
|
2022-11-12 01:05:19 +00:00
|
|
|
|
2022-12-05 20:33:42 +00:00
|
|
|
message_dialog.setModal(true);
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2022-12-05 20:33:42 +00:00
|
|
|
if (message_dialog.exec()) {
|
2022-11-11 12:41:32 +00:00
|
|
|
qDebug() << "Post dialog blocked mods list: " << blocked_mods;
|
2022-10-25 18:59:37 +01:00
|
|
|
copyBlockedMods(blocked_mods);
|
2022-07-21 20:43:08 +01:00
|
|
|
setupDownloadJob(loop);
|
2022-07-08 22:44:43 +01:00
|
|
|
} else {
|
|
|
|
m_mod_id_resolver.reset();
|
|
|
|
setError("Canceled");
|
2022-07-31 20:45:01 +01:00
|
|
|
loop.quit();
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
|
|
|
} else {
|
2022-07-21 20:43:08 +01:00
|
|
|
setupDownloadJob(loop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|
|
|
{
|
2023-04-01 03:25:01 +01:00
|
|
|
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
2022-07-21 20:43:08 +01:00
|
|
|
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
|
|
|
QString filename = result.fileName;
|
|
|
|
if (!result.required) {
|
|
|
|
filename += ".disabled";
|
|
|
|
}
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2022-07-21 20:43:08 +01:00
|
|
|
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
|
|
|
auto path = FS::PathCombine(m_stagingPath, relpath);
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2022-07-21 20:43:08 +01:00
|
|
|
switch (result.type) {
|
|
|
|
case Flame::File::Type::Folder: {
|
|
|
|
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
2023-06-08 00:37:54 +01:00
|
|
|
// fallthrough intentional, we treat these as plain old mods and dump them wherever.
|
2022-07-21 20:43:08 +01:00
|
|
|
}
|
2023-06-08 00:37:54 +01:00
|
|
|
/* fallthrough */
|
2022-07-21 20:43:08 +01:00
|
|
|
case Flame::File::Type::SingleFile:
|
|
|
|
case Flame::File::Type::Mod: {
|
|
|
|
if (!result.url.isEmpty()) {
|
|
|
|
qDebug() << "Will download" << result.url << "to" << path;
|
|
|
|
auto dl = Net::Download::makeFile(result.url, path);
|
|
|
|
m_files_job->addNetAction(dl);
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
2022-07-21 20:43:08 +01:00
|
|
|
break;
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
2022-07-21 20:43:08 +01:00
|
|
|
case Flame::File::Type::Modpack:
|
|
|
|
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
|
|
|
|
break;
|
|
|
|
case Flame::File::Type::Cmod2:
|
|
|
|
case Flame::File::Type::Ctoc:
|
|
|
|
case Flame::File::Type::Unknown:
|
|
|
|
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
|
|
|
|
break;
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
2022-07-21 20:43:08 +01:00
|
|
|
}
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2022-07-21 20:43:08 +01:00
|
|
|
m_mod_id_resolver.reset();
|
2022-12-26 21:29:13 +00:00
|
|
|
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
|
|
|
m_files_job.reset();
|
2022-12-25 03:38:29 +00:00
|
|
|
validateZIPResouces();
|
|
|
|
});
|
2022-07-21 20:43:08 +01:00
|
|
|
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
|
|
|
m_files_job.reset();
|
|
|
|
setError(reason);
|
|
|
|
});
|
2023-04-01 03:25:01 +01:00
|
|
|
connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){
|
|
|
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
|
|
|
setProgress(current, total);
|
|
|
|
});
|
2023-03-31 07:50:29 +01:00
|
|
|
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress);
|
2022-07-21 20:43:08 +01:00
|
|
|
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
2022-07-08 22:44:43 +01:00
|
|
|
|
2022-07-21 20:43:08 +01:00
|
|
|
setStatus(tr("Downloading mods..."));
|
|
|
|
m_files_job->start();
|
2022-07-08 22:44:43 +01:00
|
|
|
}
|
2022-12-25 03:38:29 +00:00
|
|
|
|
|
|
|
/// @brief copy the matched blocked mods to the instance staging area
|
|
|
|
/// @param blocked_mods list of the blocked mods and their matched paths
|
|
|
|
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
|
|
|
{
|
|
|
|
setStatus(tr("Copying Blocked Mods..."));
|
|
|
|
setAbortable(false);
|
|
|
|
int i = 0;
|
|
|
|
int total = blocked_mods.length();
|
|
|
|
setProgress(i, total);
|
|
|
|
for (auto const& mod : blocked_mods) {
|
|
|
|
if (!mod.matched) {
|
|
|
|
qDebug() << mod.name << "was not matched to a local file, skipping copy";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", mod.targetFolder, mod.name);
|
|
|
|
|
|
|
|
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
|
|
|
|
|
|
|
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
|
|
|
|
|
|
|
|
if (!FS::copy(mod.localPath, destPath)()) {
|
|
|
|
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
|
|
|
|
}
|
|
|
|
|
|
|
|
i++;
|
|
|
|
setProgress(i, total);
|
|
|
|
}
|
|
|
|
|
|
|
|
setAbortable(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FlameCreationTask::validateZIPResouces()
|
|
|
|
{
|
2022-12-26 21:29:13 +00:00
|
|
|
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
2022-12-25 03:38:29 +00:00
|
|
|
for (auto [fileName, targetFolder] : m_ZIP_resources) {
|
|
|
|
qDebug() << "Checking" << fileName << "...";
|
|
|
|
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
|
2022-12-25 23:49:56 +00:00
|
|
|
|
2022-12-26 21:29:13 +00:00
|
|
|
/// @brief check the target and move the the file
|
|
|
|
/// @return path where file can now be found
|
2022-12-25 23:49:56 +00:00
|
|
|
auto validatePath = [&localPath, this](QString fileName, QString targetFolder, QString realTarget) {
|
2022-12-26 00:16:26 +00:00
|
|
|
if (targetFolder != realTarget) {
|
|
|
|
qDebug() << "Target folder of" << fileName << "is incorrect, it belongs in" << realTarget;
|
|
|
|
auto destPath = FS::PathCombine(m_stagingPath, "minecraft", realTarget, fileName);
|
2022-12-25 23:49:56 +00:00
|
|
|
qDebug() << "Moving" << localPath << "to" << destPath;
|
2022-12-30 00:21:54 +00:00
|
|
|
if (FS::move(localPath, destPath)) {
|
2022-12-25 23:49:56 +00:00
|
|
|
return destPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return localPath;
|
|
|
|
};
|
|
|
|
|
2022-12-30 02:47:19 +00:00
|
|
|
auto installWorld = [this](QString worldPath){
|
|
|
|
qDebug() << "Installing World from" << worldPath;
|
|
|
|
QFileInfo worldFileInfo(worldPath);
|
|
|
|
World w(worldFileInfo);
|
|
|
|
if (!w.isValid()) {
|
|
|
|
qDebug() << "World at" << worldPath << "is not valid, skipping install.";
|
|
|
|
} else {
|
|
|
|
w.install(FS::PathCombine(m_stagingPath, "minecraft", "saves"));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-12-25 03:38:29 +00:00
|
|
|
QFileInfo localFileInfo(localPath);
|
2022-12-30 02:47:19 +00:00
|
|
|
auto type = ResourceUtils::identify(localFileInfo);
|
|
|
|
|
|
|
|
QString worldPath;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case PackedResourceType::ResourcePack :
|
2022-12-25 23:49:56 +00:00
|
|
|
validatePath(fileName, targetFolder, "resourcepacks");
|
2022-12-30 02:47:19 +00:00
|
|
|
break;
|
|
|
|
case PackedResourceType::TexturePack :
|
2022-12-25 23:49:56 +00:00
|
|
|
validatePath(fileName, targetFolder, "texturepacks");
|
2022-12-30 02:47:19 +00:00
|
|
|
break;
|
|
|
|
case PackedResourceType::DataPack :
|
2022-12-25 23:49:56 +00:00
|
|
|
validatePath(fileName, targetFolder, "datapacks");
|
2022-12-30 02:47:19 +00:00
|
|
|
break;
|
|
|
|
case PackedResourceType::Mod :
|
2022-12-25 23:49:56 +00:00
|
|
|
validatePath(fileName, targetFolder, "mods");
|
2022-12-30 02:47:19 +00:00
|
|
|
break;
|
|
|
|
case PackedResourceType::ShaderPack :
|
2022-12-25 23:49:56 +00:00
|
|
|
// in theroy flame API can't do this but who knows, that *may* change ?
|
|
|
|
// better to handle it if it *does* occure in the future
|
|
|
|
validatePath(fileName, targetFolder, "shaderpacks");
|
2022-12-30 02:47:19 +00:00
|
|
|
break;
|
|
|
|
case PackedResourceType::WorldSave :
|
|
|
|
worldPath = validatePath(fileName, targetFolder, "saves");
|
|
|
|
installWorld(worldPath);
|
|
|
|
break;
|
|
|
|
case PackedResourceType::UNKNOWN :
|
|
|
|
default :
|
2022-12-25 03:38:29 +00:00
|
|
|
qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is.";
|
2022-12-30 02:47:19 +00:00
|
|
|
break;
|
2022-12-25 03:38:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|