Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into develop12
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
@ -38,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
|
||||
{
|
||||
QStringList l;
|
||||
for (auto loader : { Forge, Fabric, Quilt }) {
|
||||
for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) {
|
||||
if (types & loader) {
|
||||
l << getModLoaderString(loader);
|
||||
}
|
||||
@ -92,7 +92,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||
{
|
||||
if (args.loaders.has_value()) {
|
||||
if (!validateModLoaders(args.loaders.value())) {
|
||||
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
|
||||
qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@ -141,7 +141,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
||||
return s.isEmpty() ? QString() : s;
|
||||
}
|
||||
|
||||
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
|
||||
static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); }
|
||||
|
||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||
{
|
||||
|
@ -8,7 +8,10 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModrinthCheckUpdate(QList<Mod*>& mods, std::list<Version>& mcVersions, std::optional<ResourceAPI::ModLoaderTypes> loaders, std::shared_ptr<ModFolderModel> mods_folder)
|
||||
ModrinthCheckUpdate(QList<Mod*>& mods,
|
||||
std::list<Version>& mcVersions,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
|
||||
{}
|
||||
|
||||
|
@ -133,10 +133,10 @@ bool ModrinthCreationTask::updateInstance()
|
||||
}
|
||||
} 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);
|
||||
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;
|
||||
@ -144,7 +144,6 @@ bool ModrinthCreationTask::updateInstance()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setOverride(true, inst->id());
|
||||
qDebug() << "Will override instance!";
|
||||
|
||||
@ -233,7 +232,8 @@ bool ModrinthCreationTask::createInstance()
|
||||
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));
|
||||
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;
|
||||
}
|
||||
|
||||
@ -250,7 +250,8 @@ bool ModrinthCreationTask::createInstance()
|
||||
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
||||
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||
m_files_job->addNetAction(ndl);
|
||||
if (auto shared = param.lock()) shared->succeeded();
|
||||
if (auto shared = param.lock())
|
||||
shared->succeeded();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -263,11 +264,11 @@ bool ModrinthCreationTask::createInstance()
|
||||
setError(reason);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||
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);
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress);
|
||||
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);
|
||||
|
||||
setStatus(tr("Downloading mods..."));
|
||||
m_files_job->start();
|
||||
@ -293,7 +294,10 @@ bool ModrinthCreationTask::createInstance()
|
||||
return ended_well;
|
||||
}
|
||||
|
||||
bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector<Modrinth::File>& files, bool set_internal_data, bool show_optional_dialog)
|
||||
bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
std::vector<Modrinth::File>& files,
|
||||
bool set_internal_data,
|
||||
bool show_optional_dialog)
|
||||
{
|
||||
try {
|
||||
auto doc = Json::requireDocument(index_path);
|
||||
|
@ -20,10 +20,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
|
||||
QString id,
|
||||
QString version_id = {},
|
||||
QString original_instance_id = {})
|
||||
: InstanceCreationTask()
|
||||
, m_parent(parent)
|
||||
, m_managed_id(std::move(id))
|
||||
, m_managed_version_id(std::move(version_id))
|
||||
: InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
|
||||
{
|
||||
setStagingPath(staging_path);
|
||||
setParentSettings(global_settings);
|
||||
|
@ -55,19 +55,11 @@ void ModrinthPackExportTask::executeTask()
|
||||
|
||||
bool ModrinthPackExportTask::abort()
|
||||
{
|
||||
if (task != nullptr) {
|
||||
if (task) {
|
||||
task->abort();
|
||||
task = nullptr;
|
||||
emitAborted();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (buildZipFuture.isRunning()) {
|
||||
buildZipFuture.cancel();
|
||||
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -94,6 +86,7 @@ void ModrinthPackExportTask::collectFiles()
|
||||
|
||||
void ModrinthPackExportTask::collectHashes()
|
||||
{
|
||||
setStatus(tr("Finding file hashes..."));
|
||||
for (const QFileInfo& file : files) {
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
@ -157,6 +150,7 @@ void ModrinthPackExportTask::makeApiRequest()
|
||||
if (pendingHashes.isEmpty())
|
||||
buildZip();
|
||||
else {
|
||||
setStatus(tr("Finding versions for hashes..."));
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
task = api.currentVersions(pendingHashes.values(), "sha512", response);
|
||||
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); });
|
||||
@ -202,74 +196,47 @@ void ModrinthPackExportTask::buildZip()
|
||||
{
|
||||
setStatus(tr("Adding files..."));
|
||||
|
||||
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
|
||||
QuaZip zip(output);
|
||||
if (!zip.open(QuaZip::mdCreate)) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not create file"));
|
||||
}
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||
|
||||
if (buildZipFuture.isCanceled())
|
||||
return BuildZipResult();
|
||||
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||
|
||||
QuaZipFile indexFile(&zip);
|
||||
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not create index"));
|
||||
}
|
||||
indexFile.write(generateIndex());
|
||||
|
||||
size_t progress = 0;
|
||||
for (const QFileInfo& file : files) {
|
||||
if (buildZipFuture.isCanceled()) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult();
|
||||
}
|
||||
|
||||
setProgress(progress, files.length());
|
||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||
if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
|
||||
}
|
||||
progress++;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
if (zip.getZipError() != 0) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("A zip error occurred"));
|
||||
}
|
||||
|
||||
return BuildZipResult();
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||
progressStep->state = TaskStepState::Succeeded;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
|
||||
buildZipWatcher.setFuture(buildZipFuture);
|
||||
}
|
||||
|
||||
void ModrinthPackExportTask::finish()
|
||||
{
|
||||
if (buildZipFuture.isCanceled())
|
||||
emitAborted();
|
||||
else {
|
||||
const BuildZipResult result = buildZipFuture.result();
|
||||
if (result.has_value())
|
||||
emitFailed(result.value());
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded);
|
||||
connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
|
||||
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress);
|
||||
|
||||
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||
progressStep->update(current, total);
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
task.reset(zipTask);
|
||||
zipTask->start();
|
||||
}
|
||||
|
||||
QByteArray ModrinthPackExportTask::generateIndex()
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["formatVersion"] = 1;
|
||||
obj["game"] = "minecraft";
|
||||
obj["name"] = name;
|
||||
obj["versionId"] = version;
|
||||
QJsonObject out;
|
||||
out["formatVersion"] = 1;
|
||||
out["game"] = "minecraft";
|
||||
out["name"] = name;
|
||||
out["versionId"] = version;
|
||||
if (!summary.isEmpty())
|
||||
obj["summary"] = summary;
|
||||
out["summary"] = summary;
|
||||
|
||||
if (mcInstance) {
|
||||
auto profile = mcInstance->getPackProfile();
|
||||
@ -290,30 +257,40 @@ QByteArray ModrinthPackExportTask::generateIndex()
|
||||
if (forge != nullptr)
|
||||
dependencies["forge"] = forge->m_version;
|
||||
|
||||
obj["dependencies"] = dependencies;
|
||||
out["dependencies"] = dependencies;
|
||||
}
|
||||
|
||||
QJsonArray files;
|
||||
QMapIterator<QString, ResolvedFile> iterator(resolvedFiles);
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
QJsonArray filesOut;
|
||||
for (auto iterator = resolvedFiles.constBegin(); iterator != resolvedFiles.constEnd(); iterator++) {
|
||||
QJsonObject fileOut;
|
||||
|
||||
QString path = iterator.key();
|
||||
const ResolvedFile& value = iterator.value();
|
||||
|
||||
QJsonObject file;
|
||||
file["path"] = iterator.key();
|
||||
file["downloads"] = QJsonArray({ iterator.value().url });
|
||||
// detect disabled mod
|
||||
const QFileInfo pathInfo(path);
|
||||
if (pathInfo.suffix() == "disabled") {
|
||||
// rename it
|
||||
path = pathInfo.dir().filePath(pathInfo.completeBaseName());
|
||||
// ...and make it optional
|
||||
QJsonObject env;
|
||||
env["client"] = "optional";
|
||||
env["server"] = "optional";
|
||||
fileOut["env"] = env;
|
||||
}
|
||||
|
||||
fileOut["path"] = path;
|
||||
fileOut["downloads"] = QJsonArray{ iterator.value().url };
|
||||
|
||||
QJsonObject hashes;
|
||||
hashes["sha1"] = value.sha1;
|
||||
hashes["sha512"] = value.sha512;
|
||||
fileOut["hashes"] = hashes;
|
||||
|
||||
file["hashes"] = hashes;
|
||||
file["fileSize"] = value.size;
|
||||
|
||||
files << file;
|
||||
fileOut["fileSize"] = value.size;
|
||||
filesOut << fileOut;
|
||||
}
|
||||
obj["files"] = files;
|
||||
out["files"] = filesOut;
|
||||
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
return QJsonDocument(out).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {
|
||||
const QString output;
|
||||
const MMCZip::FilterFunction filter;
|
||||
|
||||
typedef std::optional<QString> BuildZipResult;
|
||||
|
||||
ModrinthAPI api;
|
||||
QFileInfoList files;
|
||||
QMap<QString, QString> pendingHashes;
|
||||
QMap<QString, ResolvedFile> resolvedFiles;
|
||||
Task::Ptr task;
|
||||
QFuture<BuildZipResult> buildZipFuture;
|
||||
QFutureWatcher<BuildZipResult> buildZipWatcher;
|
||||
|
||||
void collectFiles();
|
||||
void collectHashes();
|
||||
void makeApiRequest();
|
||||
void parseApiResponse(const std::shared_ptr<QByteArray> response);
|
||||
void buildZip();
|
||||
void finish();
|
||||
|
||||
QByteArray generateIndex();
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
*
|
||||
* 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -66,23 +66,23 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
|
||||
pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug"));
|
||||
|
||||
pack.extra.issuesUrl = Json::ensureString(obj, "issues_url");
|
||||
if(pack.extra.issuesUrl.endsWith('/'))
|
||||
if (pack.extra.issuesUrl.endsWith('/'))
|
||||
pack.extra.issuesUrl.chop(1);
|
||||
|
||||
pack.extra.sourceUrl = Json::ensureString(obj, "source_url");
|
||||
if(pack.extra.sourceUrl.endsWith('/'))
|
||||
if (pack.extra.sourceUrl.endsWith('/'))
|
||||
pack.extra.sourceUrl.chop(1);
|
||||
|
||||
pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url");
|
||||
if(pack.extra.wikiUrl.endsWith('/'))
|
||||
if (pack.extra.wikiUrl.endsWith('/'))
|
||||
pack.extra.wikiUrl.chop(1);
|
||||
|
||||
pack.extra.discordUrl = Json::ensureString(obj, "discord_url");
|
||||
if(pack.extra.discordUrl.endsWith('/'))
|
||||
if (pack.extra.discordUrl.endsWith('/'))
|
||||
pack.extra.discordUrl.chop(1);
|
||||
|
||||
auto donate_arr = Json::ensureArray(obj, "donation_urls");
|
||||
for(auto d : donate_arr){
|
||||
for (auto d : donate_arr) {
|
||||
auto d_obj = Json::requireObject(d);
|
||||
|
||||
DonationData donate;
|
||||
@ -107,7 +107,7 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
|
||||
auto obj = Json::requireObject(versionIter);
|
||||
auto file = loadIndexedVersion(obj);
|
||||
|
||||
if(!file.id.isEmpty()) // Heuristic to check if the returned value is valid
|
||||
if (!file.id.isEmpty()) // Heuristic to check if the returned value is valid
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
|
||||
@ -122,7 +122,7 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
|
||||
pack.versionsLoaded = true;
|
||||
}
|
||||
|
||||
auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
|
||||
auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion
|
||||
{
|
||||
ModpackVersion file;
|
||||
|
||||
@ -132,12 +132,11 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
|
||||
|
||||
file.id = Json::requireString(obj, "id");
|
||||
file.project_id = Json::requireString(obj, "project_id");
|
||||
|
||||
|
||||
file.date = Json::requireString(obj, "date_published");
|
||||
|
||||
auto files = Json::requireArray(obj, "files");
|
||||
|
||||
|
||||
for (auto file_iter : files) {
|
||||
File indexed_file;
|
||||
auto parent = Json::requireObject(file_iter);
|
||||
@ -146,21 +145,21 @@ auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
|
||||
auto filename = Json::ensureString(parent, "filename");
|
||||
// Checking suffix here is fine because it's the response from Modrinth,
|
||||
// so one would assume it will always be in English.
|
||||
if(!filename.endsWith("mrpack") && !filename.endsWith("zip"))
|
||||
if (!filename.endsWith("mrpack") && !filename.endsWith("zip"))
|
||||
continue;
|
||||
}
|
||||
|
||||
auto url = Json::requireString(parent, "url");
|
||||
|
||||
file.download_url = url;
|
||||
if(is_primary)
|
||||
if (is_primary)
|
||||
break;
|
||||
}
|
||||
|
||||
if(file.download_url.isEmpty())
|
||||
if (file.download_url.isEmpty())
|
||||
return {};
|
||||
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Modrinth
|
||||
|
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -74,7 +74,6 @@ struct ModpackExtra {
|
||||
QString discordUrl;
|
||||
|
||||
QList<DonationData> donate;
|
||||
|
||||
};
|
||||
|
||||
struct ModpackVersion {
|
||||
@ -97,10 +96,10 @@ struct Modpack {
|
||||
QString description;
|
||||
std::tuple<QString, QUrl> author;
|
||||
QString iconName;
|
||||
QUrl iconUrl;
|
||||
QUrl iconUrl;
|
||||
|
||||
bool versionsLoaded = false;
|
||||
bool extraInfoLoaded = false;
|
||||
bool versionsLoaded = false;
|
||||
bool extraInfoLoaded = false;
|
||||
|
||||
ModpackExtra extra;
|
||||
QVector<ModpackVersion> versions;
|
||||
@ -113,7 +112,7 @@ auto loadIndexedVersion(QJsonObject&) -> ModpackVersion;
|
||||
|
||||
auto validateDownloadUrl(QUrl) -> bool;
|
||||
|
||||
}
|
||||
} // namespace Modrinth
|
||||
|
||||
Q_DECLARE_METATYPE(Modrinth::Modpack)
|
||||
Q_DECLARE_METATYPE(Modrinth::ModpackVersion)
|
||||
|
Reference in New Issue
Block a user