2022-07-08 01:10:41 +01:00
|
|
|
#include "ModrinthInstanceCreationTask.h"
|
|
|
|
|
|
|
|
#include "Application.h"
|
|
|
|
#include "FileSystem.h"
|
2022-07-08 17:00:44 +01:00
|
|
|
#include "InstanceList.h"
|
2022-07-08 01:10:41 +01:00
|
|
|
#include "Json.h"
|
|
|
|
|
|
|
|
#include "minecraft/PackProfile.h"
|
|
|
|
|
2022-07-29 02:35:40 +01:00
|
|
|
#include "modplatform/helpers/OverrideUtils.h"
|
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
#include "net/ChecksumValidator.h"
|
|
|
|
|
2023-03-31 07:50:29 +01:00
|
|
|
#include "net/NetJob.h"
|
2023-06-02 00:39:04 +01:00
|
|
|
#include "net/ApiDownload.h"
|
2022-07-08 01:10:41 +01:00
|
|
|
#include "settings/INISettingsObject.h"
|
|
|
|
|
|
|
|
#include "ui/dialogs/CustomMessageBox.h"
|
|
|
|
|
2022-07-08 17:00:44 +01:00
|
|
|
#include <QAbstractButton>
|
|
|
|
|
|
|
|
bool ModrinthCreationTask::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-08 17:00:44 +01:00
|
|
|
if (m_files_job)
|
2022-08-06 01:25:21 +01:00
|
|
|
m_files_job->abort();
|
2022-07-31 22:21:59 +01:00
|
|
|
return Task::abort();
|
2022-07-08 17:00:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool ModrinthCreationTask::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-08 17:00:44 +01:00
|
|
|
|
2022-12-03 13:15:38 +00:00
|
|
|
if (!inst) {
|
|
|
|
inst = instance_list->getInstanceById(originalName());
|
2022-07-08 17:00:44 +01:00
|
|
|
|
2022-12-03 13:15:38 +00:00
|
|
|
if (!inst)
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-08 17:00:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
2022-08-01 00:29:12 +01:00
|
|
|
if (!parseManifest(index_path, m_files, true, false))
|
2022-07-08 17:00:44 +01:00
|
|
|
return false;
|
|
|
|
|
2022-07-31 23:50:09 +01:00
|
|
|
auto version_name = inst->getManagedPackVersionName();
|
|
|
|
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
|
2022-07-08 17:00:44 +01:00
|
|
|
|
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-08 17:00:44 +01:00
|
|
|
|
|
|
|
// Remove repeated files, we don't need to download them!
|
|
|
|
QDir old_inst_dir(inst->instanceRoot());
|
|
|
|
|
2022-07-29 02:35:40 +01:00
|
|
|
QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack"));
|
|
|
|
|
|
|
|
QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json"));
|
2022-07-08 17:00:44 +01:00
|
|
|
QFileInfo old_index_file(old_index_path);
|
|
|
|
if (old_index_file.exists()) {
|
|
|
|
std::vector<Modrinth::File> old_files;
|
2022-08-01 00:29:12 +01:00
|
|
|
parseManifest(old_index_path, old_files, false, false);
|
2022-07-08 17:00:44 +01:00
|
|
|
|
|
|
|
// Let's remove all duplicated, identical resources!
|
|
|
|
auto files_iterator = m_files.begin();
|
2022-07-29 02:35:40 +01:00
|
|
|
begin:
|
2022-07-08 17:00:44 +01:00
|
|
|
while (files_iterator != m_files.end()) {
|
|
|
|
auto const& file = *files_iterator;
|
|
|
|
|
|
|
|
auto old_files_iterator = old_files.begin();
|
|
|
|
while (old_files_iterator != old_files.end()) {
|
|
|
|
auto const& old_file = *old_files_iterator;
|
|
|
|
|
|
|
|
if (old_file.hash == file.hash) {
|
|
|
|
qDebug() << "Removed file at" << file.path << "from list of downloads";
|
|
|
|
files_iterator = m_files.erase(files_iterator);
|
|
|
|
old_files_iterator = old_files.erase(old_files_iterator);
|
2022-07-29 02:35:40 +01:00
|
|
|
goto begin; // Sorry :c
|
2022-07-08 17:00:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
old_files_iterator++;
|
|
|
|
}
|
|
|
|
|
|
|
|
files_iterator++;
|
|
|
|
}
|
|
|
|
|
2022-07-29 02:35:40 +01:00
|
|
|
QDir old_minecraft_dir(inst->gameRoot());
|
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
|
|
|
|
2022-07-08 17:00:44 +01:00
|
|
|
// Some files were removed from the old version, and some will be downloaded in an updated version,
|
|
|
|
// so we're fine removing them!
|
|
|
|
if (!old_files.empty()) {
|
|
|
|
for (auto const& file : old_files) {
|
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 (file.path.isEmpty())
|
|
|
|
continue;
|
|
|
|
qDebug() << "Scheduling" << file.path << "for removal";
|
|
|
|
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path));
|
2022-07-08 17:00:44 +01:00
|
|
|
}
|
|
|
|
}
|
2022-07-29 02:35:40 +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-29 02:35:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
auto old_client_overrides = Override::readOverrides("client-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-29 02:35:40 +01:00
|
|
|
}
|
2022-08-21 14:00:23 +01:00
|
|
|
} else {
|
|
|
|
// We don't have an old index file, so we may duplicate stuff!
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (dialog->exec() == QDialog::DialogCode::Rejected) {
|
|
|
|
m_abort = true;
|
|
|
|
return false;
|
|
|
|
}
|
2022-07-08 17:00:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-03 13:15:38 +00:00
|
|
|
setOverride(true, inst->id());
|
2022-07-08 17:00:44 +01:00
|
|
|
qDebug() << "Will override instance!";
|
|
|
|
|
2022-07-31 23:50:09 +01:00
|
|
|
m_instance = inst;
|
|
|
|
|
2022-07-08 17:00:44 +01:00
|
|
|
// We let it go through the createInstance() stage, just with a couple modifications for updating
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://docs.modrinth.com/docs/modpacks/format_definition/
|
2022-07-08 01:10:41 +01:00
|
|
|
bool ModrinthCreationTask::createInstance()
|
|
|
|
{
|
|
|
|
QEventLoop loop;
|
|
|
|
|
2022-07-29 02:35:40 +01:00
|
|
|
QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack"));
|
|
|
|
|
2022-07-08 17:00:44 +01:00
|
|
|
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
|
2022-08-01 00:29:12 +01:00
|
|
|
if (m_files.empty() && !parseManifest(index_path, m_files, true, true))
|
2022-07-08 01:10:41 +01:00
|
|
|
return false;
|
|
|
|
|
2022-07-08 17:00:44 +01:00
|
|
|
// Keep index file in case we need it some other time (like when changing versions)
|
2022-07-29 02:35:40 +01:00
|
|
|
QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json"));
|
2022-07-08 17:00:44 +01:00
|
|
|
FS::ensureFilePathExists(new_index_place);
|
|
|
|
QFile::rename(index_path, new_index_place);
|
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
|
|
|
|
|
|
|
|
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
|
|
|
|
if (QFile::exists(override_path)) {
|
2022-07-29 02:35:40 +01:00
|
|
|
// Create a list of overrides in "overrides.txt" inside mrpack/
|
|
|
|
Override::createOverrides("overrides", parent_folder, override_path);
|
|
|
|
|
|
|
|
// Apply the overrides
|
2022-07-08 01:10:41 +01:00
|
|
|
if (!QFile::rename(override_path, mcPath)) {
|
|
|
|
setError(tr("Could not rename the overrides folder:\n") + "overrides");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do client overrides
|
|
|
|
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
|
|
|
|
if (QFile::exists(client_override_path)) {
|
2022-07-29 02:35:40 +01:00
|
|
|
// Create a list of overrides in "client-overrides.txt" inside mrpack/
|
|
|
|
Override::createOverrides("client-overrides", parent_folder, client_override_path);
|
|
|
|
|
|
|
|
// Apply the overrides
|
2022-07-08 01:10:41 +01:00
|
|
|
if (!FS::overrideFolder(mcPath, client_override_path)) {
|
|
|
|
setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
|
|
|
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
|
|
|
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
2022-07-08 17:00:44 +01:00
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
auto components = instance.getPackProfile();
|
|
|
|
components->buildingFromScratch();
|
2022-12-17 15:41:10 +00:00
|
|
|
components->setComponentVersion("net.minecraft", m_minecraft_version, true);
|
2022-07-08 01:10:41 +01:00
|
|
|
|
2022-12-17 15:41:10 +00:00
|
|
|
if (!m_fabric_version.isEmpty())
|
|
|
|
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
|
|
|
|
if (!m_quilt_version.isEmpty())
|
|
|
|
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
|
|
|
|
if (!m_forge_version.isEmpty())
|
|
|
|
components->setComponentVersion("net.minecraftforge", m_forge_version);
|
2022-07-08 17:00:44 +01:00
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
if (m_instIcon != "default") {
|
|
|
|
instance.setIconKey(m_instIcon);
|
2023-06-20 12:00:13 +01:00
|
|
|
} else if (!m_managed_id.isEmpty()) {
|
2022-07-08 01:10:41 +01:00
|
|
|
instance.setIconKey("modrinth");
|
|
|
|
}
|
2022-07-14 20:13:23 +01:00
|
|
|
|
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("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
|
2022-07-14 20:13:23 +01:00
|
|
|
instance.setName(name());
|
2022-07-08 01:10:41 +01:00
|
|
|
instance.saveNow();
|
|
|
|
|
2023-04-01 03:25:01 +01:00
|
|
|
m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network()));
|
2022-07-08 01:10:41 +01:00
|
|
|
|
2023-01-31 13:28:39 +00:00
|
|
|
auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft");
|
|
|
|
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
for (auto file : m_files) {
|
2023-01-31 13:28:39 +00:00
|
|
|
auto file_path = FS::PathCombine(root_modpack_path, file.path);
|
|
|
|
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
|
|
|
|
// This means we somehow got out of the root folder, so abort here to prevent exploits
|
|
|
|
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.").arg(file.path));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
|
2023-06-02 00:39:04 +01:00
|
|
|
auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
2022-07-08 01:10:41 +01:00
|
|
|
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
|
|
|
m_files_job->addNetAction(dl);
|
|
|
|
|
2022-09-26 10:50:31 +01:00
|
|
|
if (!file.downloads.empty()) {
|
2022-07-08 01:10:41 +01:00
|
|
|
// FIXME: This really needs to be put into a ConcurrentTask of
|
|
|
|
// MultipleOptionsTask's , once those exist :)
|
2022-09-26 10:50:55 +01:00
|
|
|
auto param = dl.toWeakRef();
|
2023-01-31 13:28:39 +00:00
|
|
|
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
2023-06-02 00:39:04 +01:00
|
|
|
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
2022-09-26 10:50:55 +01:00
|
|
|
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
|
|
|
m_files_job->addNetAction(ndl);
|
|
|
|
if (auto shared = param.lock()) shared->succeeded();
|
2022-07-08 01:10:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ended_well = false;
|
|
|
|
|
|
|
|
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { ended_well = true; });
|
|
|
|
connect(m_files_job.get(), &NetJob::failed, [&](const QString& reason) {
|
|
|
|
ended_well = false;
|
|
|
|
setError(reason);
|
|
|
|
});
|
|
|
|
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
2023-04-01 03:25:01 +01:00
|
|
|
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
|
|
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
|
|
|
setProgress(current, total);
|
|
|
|
});
|
2023-07-26 21:20:30 +01:00
|
|
|
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
|
2022-07-08 01:10:41 +01:00
|
|
|
|
|
|
|
setStatus(tr("Downloading mods..."));
|
|
|
|
m_files_job->start();
|
|
|
|
|
|
|
|
loop.exec();
|
|
|
|
|
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 && ended_well) {
|
2022-08-21 13:26:27 +01:00
|
|
|
setAbortable(false);
|
2022-07-31 23:50:09 +01:00
|
|
|
auto inst = m_instance.value();
|
|
|
|
|
2022-09-11 17:16:25 +01:00
|
|
|
// Only change the name if it didn't use a custom name, so that the previous custom name
|
|
|
|
// is preserved, but if we're using the original one, we update the version string.
|
|
|
|
// NOTE: This needs to come before the copyManagedPack call!
|
|
|
|
if (inst->name().contains(inst->getManagedPackVersionName())) {
|
|
|
|
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
|
|
|
|
inst->setName(instance.name());
|
|
|
|
}
|
|
|
|
|
2022-07-31 23:50:09 +01:00
|
|
|
inst->copyManagedPack(instance);
|
|
|
|
}
|
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
return ended_well;
|
|
|
|
}
|
|
|
|
|
2022-12-17 15:34:27 +00:00
|
|
|
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_internal_data, bool show_optional_dialog)
|
2022-07-08 01:10:41 +01:00
|
|
|
{
|
|
|
|
try {
|
2022-07-08 17:00:44 +01:00
|
|
|
auto doc = Json::requireDocument(index_path);
|
2022-07-08 01:10:41 +01:00
|
|
|
auto obj = Json::requireObject(doc, "modrinth.index.json");
|
|
|
|
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
|
|
|
|
if (formatVersion == 1) {
|
|
|
|
auto game = Json::requireString(obj, "game", "modrinth.index.json");
|
|
|
|
if (game != "minecraft") {
|
|
|
|
throw JSONValidationError("Unknown game: " + game);
|
|
|
|
}
|
|
|
|
|
2022-12-17 15:34:27 +00:00
|
|
|
if (set_internal_data) {
|
2022-11-12 14:42:07 +00:00
|
|
|
if (m_managed_version_id.isEmpty())
|
|
|
|
m_managed_version_id = Json::ensureString(obj, "versionId", {}, "Managed ID");
|
2022-07-31 23:50:09 +01:00
|
|
|
m_managed_name = Json::ensureString(obj, "name", {}, "Managed Name");
|
|
|
|
}
|
2022-07-08 01:10:41 +01:00
|
|
|
|
|
|
|
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
|
|
|
bool had_optional = false;
|
2022-09-26 10:50:31 +01:00
|
|
|
for (const auto& modInfo : jsonFiles) {
|
2022-07-08 01:10:41 +01:00
|
|
|
Modrinth::File file;
|
|
|
|
file.path = Json::requireString(modInfo, "path");
|
|
|
|
|
|
|
|
auto env = Json::ensureObject(modInfo, "env");
|
|
|
|
// 'env' field is optional
|
|
|
|
if (!env.isEmpty()) {
|
|
|
|
QString support = Json::ensureString(env, "client", "unsupported");
|
|
|
|
if (support == "unsupported") {
|
|
|
|
continue;
|
|
|
|
} else if (support == "optional") {
|
|
|
|
// TODO: Make a review dialog for choosing which ones the user wants!
|
2022-08-01 00:29:12 +01:00
|
|
|
if (!had_optional && show_optional_dialog) {
|
2022-07-08 01:10:41 +01:00
|
|
|
had_optional = true;
|
|
|
|
auto info = CustomMessageBox::selectable(
|
|
|
|
m_parent, tr("Optional mod detected!"),
|
|
|
|
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
|
|
|
|
QMessageBox::Information);
|
|
|
|
info->exec();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file.path.endsWith(".jar"))
|
|
|
|
file.path += ".disabled";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonObject hashes = Json::requireObject(modInfo, "hashes");
|
|
|
|
QString hash;
|
|
|
|
QCryptographicHash::Algorithm hashAlgorithm;
|
|
|
|
hash = Json::ensureString(hashes, "sha1");
|
|
|
|
hashAlgorithm = QCryptographicHash::Sha1;
|
|
|
|
if (hash.isEmpty()) {
|
|
|
|
hash = Json::ensureString(hashes, "sha512");
|
|
|
|
hashAlgorithm = QCryptographicHash::Sha512;
|
|
|
|
if (hash.isEmpty()) {
|
|
|
|
hash = Json::ensureString(hashes, "sha256");
|
|
|
|
hashAlgorithm = QCryptographicHash::Sha256;
|
|
|
|
if (hash.isEmpty()) {
|
|
|
|
throw JSONValidationError("No hash found for: " + file.path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file.hash = QByteArray::fromHex(hash.toLatin1());
|
|
|
|
file.hashAlgorithm = hashAlgorithm;
|
|
|
|
|
|
|
|
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
|
|
|
|
// (as Modrinth seems to incorrectly handle spaces)
|
|
|
|
|
|
|
|
auto download_arr = Json::ensureArray(modInfo, "downloads");
|
|
|
|
for (auto download : download_arr) {
|
|
|
|
qWarning() << download.toString();
|
|
|
|
bool is_last = download.toString() == download_arr.last().toString();
|
|
|
|
|
|
|
|
auto download_url = QUrl(download.toString());
|
|
|
|
|
|
|
|
if (!download_url.isValid()) {
|
|
|
|
qDebug()
|
|
|
|
<< QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path);
|
|
|
|
if (is_last && file.downloads.isEmpty())
|
|
|
|
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
|
|
|
|
} else {
|
|
|
|
file.downloads.push_back(download_url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-08 17:00:44 +01:00
|
|
|
files.push_back(file);
|
2022-07-08 01:10:41 +01:00
|
|
|
}
|
|
|
|
|
2022-12-17 15:34:27 +00:00
|
|
|
if (set_internal_data) {
|
|
|
|
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
|
|
|
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
|
|
|
QString name = it.key();
|
|
|
|
if (name == "minecraft") {
|
2022-12-17 15:41:10 +00:00
|
|
|
m_minecraft_version = Json::requireString(*it, "Minecraft version");
|
2022-12-17 15:34:27 +00:00
|
|
|
} else if (name == "fabric-loader") {
|
2022-12-17 15:41:10 +00:00
|
|
|
m_fabric_version = Json::requireString(*it, "Fabric Loader version");
|
2022-12-17 15:34:27 +00:00
|
|
|
} else if (name == "quilt-loader") {
|
2022-12-17 15:41:10 +00:00
|
|
|
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
|
2022-12-17 15:34:27 +00:00
|
|
|
} else if (name == "forge") {
|
2022-12-17 15:41:10 +00:00
|
|
|
m_forge_version = Json::requireString(*it, "Forge version");
|
2022-12-17 15:34:27 +00:00
|
|
|
} else {
|
|
|
|
throw JSONValidationError("Unknown dependency type: " + name);
|
|
|
|
}
|
2022-07-08 01:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
|
|
|
|
}
|
2022-07-08 17:00:44 +01:00
|
|
|
|
2022-07-08 01:10:41 +01:00
|
|
|
} catch (const JSONValidationError& e) {
|
|
|
|
setError(tr("Could not understand pack index:\n") + e.cause());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|