diff options
Diffstat (limited to 'launcher')
20 files changed, 726 insertions, 151 deletions
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 15534c71..bbf80185 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -128,6 +128,8 @@ set(NET_SOURCES net/PasteUpload.h net/Sink.h net/Validator.h + net/Upload.cpp + net/Upload.h ) # Game launch logic @@ -837,6 +839,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h + ui/dialogs/ScrollMessageBox.cpp + ui/dialogs/ScrollMessageBox.h # GUI - widgets ui/widgets/Common.cpp @@ -940,6 +944,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/ReviewMessageBox.ui + ui/dialogs/ScrollMessageBox.ui ) qt5_add_resources(LAUNCHER_RESOURCES @@ -958,7 +963,7 @@ qt5_add_resources(LAUNCHER_RESOURCES ######## Windows resource files ######## if(WIN32) - set(LAUNCHER_RCS ../${Launcher_Branding_WindowsRC}) + set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) endif() # Add executable diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4acde16d..09c2a333 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -60,9 +60,9 @@ #include "net/ChecksumValidator.h" #include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/ScrollMessageBox.h" #include <algorithm> -#include <iterator> InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) { @@ -164,14 +164,14 @@ void InstanceImportTask::processZipPack() QString mmcRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); QString flameRoot = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); - if (!mmcRoot.isEmpty()) + if (!mmcRoot.isNull()) { // process as MultiMC instance/pack qDebug() << "MultiMC:" << mmcRoot; root = mmcRoot; m_modpackType = ModpackType::MultiMC; } - else if(!flameRoot.isEmpty()) + else if(!flameRoot.isNull()) { // process as Flame pack qDebug() << "Flame:" << flameRoot; @@ -394,61 +394,136 @@ void InstanceImportTask::processFlame() connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() { auto results = m_modIdResolver->getResults(); - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for(const auto& result: results.files) - { - QString filename = result.fileName; - if(!result.required) - { - filename += ".disabled"; + //first check for blocked mods + QString text; + auto anyBlocked = false; + for(const auto& result: results.files.values()) { + if (!result.resolved || result.url.isEmpty()) { + text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl); + anyBlocked = true; } + } + if(anyBlocked) { + qWarning() << "Blocked mods found, displaying mod list"; + + auto message_dialog = new ScrollMessageBox(m_parent, + tr("Blocked mods found"), + tr("The following mods were blocked on third party launchers.<br/>" + "You will need to manually download them and add them to the modpack"), + text); + message_dialog->setModal(true); + message_dialog->show(); + connect(message_dialog, &QDialog::rejected, [&]() { + m_modIdResolver.reset(); + emitFailed("Canceled"); + }); + connect(message_dialog, &QDialog::accepted, [&]() { + m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto &result: m_modIdResolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath , relpath); + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); - switch(result.type) - { - case Flame::File::Type::Folder: - { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + 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_filesNetJob->addNetAction(dl); + } + break; + } + 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; + } } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: - { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - break; + m_modIdResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); + }); + }else{ + //TODO extract to function ? + m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto &result: m_modIdResolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + 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_filesNetJob->addNetAction(dl); + } + break; + } + 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; } - 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; } + m_modIdResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) - { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); } ); connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) @@ -510,6 +585,7 @@ void InstanceImportTask::processMultiMC() void InstanceImportTask::processModrinth() { std::vector<Modrinth::File> files; + std::vector<Modrinth::File> non_whitelisted_files; QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; try { QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); @@ -524,11 +600,11 @@ void InstanceImportTask::processModrinth() auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json"); bool had_optional = false; - for (auto& obj : jsonFiles) { + for (auto& modInfo : jsonFiles) { Modrinth::File file; - file.path = Json::requireString(obj, "path"); + file.path = Json::requireString(modInfo, "path"); - auto env = Json::ensureObject(obj, "env"); + auto env = Json::ensureObject(modInfo, "env"); QString support = Json::ensureString(env, "client", "unsupported"); if (support == "unsupported") { continue; @@ -546,7 +622,7 @@ void InstanceImportTask::processModrinth() file.path += ".disabled"; } - QJsonObject hashes = Json::requireObject(obj, "hashes"); + QJsonObject hashes = Json::requireObject(modInfo, "hashes"); QString hash; QCryptographicHash::Algorithm hashAlgorithm; hash = Json::ensureString(hashes, "sha1"); @@ -566,13 +642,38 @@ void InstanceImportTask::processModrinth() file.hashAlgorithm = hashAlgorithm; // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) - file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path); - if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) { - throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL"); + + file.download = Json::requireString(Json::ensureArray(modInfo, "downloads").first(), "Download URL for " + file.path); + + if (!file.download.isValid()) { + qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(file.download.toString(), file.path); + throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } + else if (!Modrinth::validateDownloadUrl(file.download)) { + qDebug() << QString("Download URL (%1) for %2 is from a non-whitelisted by Modrinth domain").arg(file.download.toString(), file.path); + non_whitelisted_files.push_back(file); } + files.push_back(file); } + if (!non_whitelisted_files.empty()) { + QString text; + for (const auto& file : non_whitelisted_files) { + text += tr("Filepath: %1<br>URL: <a href='%2'>%2</a><br>").arg(file.path, file.download.toString()); + } + + auto message_dialog = new ScrollMessageBox(m_parent, tr("Non-whitelisted mods found"), + tr("The following mods have URLs that are not whitelisted by Modrinth.\n" + "Proceed with caution!"), + text); + message_dialog->setModal(true); + if (message_dialog->exec() == QDialog::Rejected) { + emitFailed("Aborted"); + return; + } + } + auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 5e4d3235..b67d48f3 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -42,6 +42,7 @@ #include <QFutureWatcher> #include "settings/SettingsObject.h" #include "QObjectPtr.h" +#include "modplatform/flame/PackManifest.h" #include <nonstd/optional> @@ -59,6 +60,10 @@ public: bool canAbort() const override { return true; } bool abort() override; + const QVector<Flame::File> &getBlockedFiles() const + { + return m_blockedMods; + } protected: //! Entry point for tasks. @@ -87,6 +92,7 @@ private: /* data */ std::unique_ptr<QuaZip> m_packZip; QFuture<nonstd::optional<QStringList>> m_extractFuture; QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher; + QVector<Flame::File> m_blockedMods; enum class ModpackType{ Unknown, MultiMC, diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index c269d10a..0ddfae55 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -273,7 +273,7 @@ void IconList::installIcons(const QStringList &iconFiles) QFileInfo fileinfo(file); if (!fileinfo.isReadable() || !fileinfo.isFile()) continue; - QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); + QString target = FS::PathCombine(getDirectory(), fileinfo.fileName()); QString suffix = fileinfo.suffix(); if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") @@ -290,7 +290,7 @@ void IconList::installIcon(const QString &file, const QString &name) if(!fileinfo.isReadable() || !fileinfo.isFile()) return; - QString target = FS::PathCombine(m_dir.dirName(), name); + QString target = FS::PathCombine(getDirectory(), name); QFile::copy(file, target); } diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 95924a68..a790ab9c 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -1,7 +1,9 @@ #include "FileResolvingTask.h" + #include "Json.h" +#include "net/Upload.h" -Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess) +Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) {} @@ -10,40 +12,116 @@ void Flame::FileResolvingTask::executeTask() setStatus(tr("Resolving mod IDs...")); setProgress(0, m_toProcess.files.size()); m_dljob = new NetJob("Mod id resolver", m_network); - results.resize(m_toProcess.files.size()); - int index = 0; - for (auto& file : m_toProcess.files) { - auto projectIdStr = QString::number(file.projectId); - auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(projectIdStr, fileIdStr); - auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); - m_dljob->addNetAction(dl); - index++; - } + result.reset(new QByteArray()); + //build json data to send + QJsonObject object; + + object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) { + l.push_back(s.fileId); + return l; + })); + QByteArray data = Json::toText(object); + auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data); + m_dljob->addNetAction(dl); connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); m_dljob->start(); } void Flame::FileResolvingTask::netJobFinished() { - bool failed = false; int index = 0; - for (auto& bytes : results) { - auto& out = m_toProcess.files[index]; + // job to check modrinth for blocked projects + auto job = new NetJob("Modrinth check", m_network); + blockedProjects = QMap<File *,QByteArray *>(); + auto doc = Json::requireDocument(*result); + auto array = Json::requireArray(doc.object()["data"]); + for (QJsonValueRef file : array) { + auto fileid = Json::requireInteger(Json::requireObject(file)["id"]); + auto& out = m_toProcess.files[fileid]; try { - failed &= (!out.parseFromBytes(bytes)); + out.parseFromObject(Json::requireObject(file)); } catch (const JSONValidationError& e) { - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; - qCritical() << e.cause(); - qCritical() << "JSON:"; - qCritical() << bytes; - failed = true; + qDebug() << "Blocked mod on curseforge" << out.fileName; + auto hash = out.hash; + if(!hash.isEmpty()) { + auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); + auto output = new QByteArray(); + auto dl = Net::Download::makeByteArray(QUrl(url), output); + QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { + out.resolved = true; + }); + + job->addNetAction(dl); + blockedProjects.insert(&out, output); + } } index++; } - if (!failed) { - emitSucceeded(); + connect(job, &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished); + + job->start(); +} + +void Flame::FileResolvingTask::modrinthCheckFinished() { + qDebug() << "Finished with blocked mods : " << blockedProjects.size(); + + for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { + auto &out = *it; + auto bytes = blockedProjects[out]; + if (!out->resolved) { + delete bytes; + continue; + } + QJsonDocument doc = QJsonDocument::fromJson(*bytes); + auto obj = doc.object(); + auto array = Json::requireArray(obj,"files"); + for (auto file: array) { + auto fileObj = Json::requireObject(file); + auto primary = Json::requireBoolean(fileObj,"primary"); + if (primary) { + out->url = Json::requireUrl(fileObj,"url"); + qDebug() << "Found alternative on modrinth " << out->fileName; + break; + } + } + delete bytes; + } + //copy to an output list and filter out projects found on modrinth + auto block = new QList<File *>(); + auto it = blockedProjects.keys(); + std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { + return !f->resolved; + }); + //Display not found mods early + if (!block->empty()) { + //blocked mods found, we need the slug for displaying.... we need another job :D ! + auto slugJob = new NetJob("Slug Job", m_network); + auto slugs = QVector<QByteArray>(block->size()); + auto index = 0; + for (auto fileInfo: *block) { + auto projectId = fileInfo->projectId; + slugs[index] = QByteArray(); + auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); + auto dl = Net::Download::makeByteArray(url, &slugs[index]); + slugJob->addNetAction(dl); + index++; + } + connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() { + slugJob->deleteLater(); + auto index = 0; + for (const auto &slugResult: slugs) { + auto json = QJsonDocument::fromJson(slugResult); + auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), + "websiteUrl"); + auto mod = block->at(index); + auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); + mod->websiteUrl = link; + index++; + } + emitSucceeded(); + }); + slugJob->start(); } else { - emitFailed(tr("Some mod ID resolving tasks failed.")); + emitSucceeded(); } } diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 5e5adcd7..87981f0a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -10,7 +10,7 @@ class FileResolvingTask : public Task { Q_OBJECT public: - explicit FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest &toProcess); + explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess); virtual ~FileResolvingTask() {}; const Flame::Manifest &getResults() const @@ -27,7 +27,11 @@ protected slots: private: /* data */ shared_qobject_ptr<QNetworkAccessManager> m_network; Flame::Manifest m_toProcess; - QVector<QByteArray> results; + std::shared_ptr<QByteArray> result; NetJob::Ptr m_dljob; + + void modrinthCheckFinished(); + + QMap<File *, QByteArray *> blockedProjects; }; } diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index e4f90c1a..12a4b990 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -41,7 +41,7 @@ static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest) auto obj = Json::requireObject(item); Flame::File file; loadFileV1(file, obj); - m.files.append(file); + m.files.insert(file.fileId,file); } m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } @@ -61,21 +61,9 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) loadManifestV1(m, obj); } -bool Flame::File::parseFromBytes(const QByteArray& bytes) +bool Flame::File::parseFromObject(const QJsonObject& obj) { - auto doc = Json::requireDocument(bytes); - if (!doc.isObject()) { - throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); - } - auto obj = Json::ensureObject(doc.object(), "data"); - fileName = Json::requireString(obj, "fileName"); - - QString rawUrl = Json::requireString(obj, "downloadUrl"); - url = QUrl(rawUrl, QUrl::TolerantMode); - if (!url.isValid()) { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience // It is also optional type = File::Type::SingleFile; @@ -87,6 +75,25 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes) // this is probably a mod, dunno what else could modpacks download targetFolder = "mods"; } + // get the hash + hash = QString(); + auto hashes = Json::ensureArray(obj, "hashes"); + for(QJsonValueRef item : hashes) { + auto hobj = Json::requireObject(item); + auto algo = Json::requireInteger(hobj, "algo"); + auto value = Json::requireString(hobj, "value"); + if (algo == 1) { + hash = value; + } + } + + + // may throw, if the project is blocked + QString rawUrl = Json::ensureString(obj, "downloadUrl"); + url = QUrl(rawUrl, QUrl::TolerantMode); + if (!url.isValid()) { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } resolved = true; return true; diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 02f39f0e..26a48d1c 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -2,19 +2,24 @@ #include <QString> #include <QVector> +#include <QMap> #include <QUrl> +#include <QJsonObject> namespace Flame { struct File { // NOTE: throws JSONValidationError - bool parseFromBytes(const QByteArray &bytes); + bool parseFromObject(const QJsonObject& object); int projectId = 0; int fileId = 0; // NOTE: the opposite to 'optional'. This is at the time of writing unused. bool required = true; + QString hash; + // NOTE: only set on blocked files ! Empty otherwise. + QString websiteUrl; // our bool resolved = false; @@ -54,7 +59,8 @@ struct Manifest QString name; QString version; QString author; - QVector<Flame::File> files; + //File id -> File + QMap<int,Flame::File> files; QString overrides; }; diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp index f1ad39ce..33116231 100644 --- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -42,6 +42,8 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include <QSet> + static ModrinthAPI api; namespace Modrinth { @@ -95,19 +97,15 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) auto validateDownloadUrl(QUrl url) -> bool { + static QSet<QString> domainWhitelist{ + "cdn.modrinth.com", + "github.com", + "raw.githubusercontent.com", + "gitlab.com" + }; + auto domain = url.host(); - if(domain == "cdn.modrinth.com") - return true; - if(domain == "edge.forgecdn.net") - return true; - if(domain == "media.forgecdn.net") - return true; - if(domain == "github.com") - return true; - if(domain == "raw.githubusercontent.com") - return true; - - return false; + return domainWhitelist.contains(domain); } auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp new file mode 100644 index 00000000..bbd27390 --- /dev/null +++ b/launcher/net/Upload.cpp @@ -0,0 +1,199 @@ +// +// Created by timoreo on 20/05/22. +// + +#include "Upload.h" + +#include <utility> +#include "ByteArraySink.h" +#include "BuildConfig.h" +#include "Application.h" + +namespace Net { + + void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + setProgress(bytesReceived, bytesTotal); + } + + void Upload::downloadError(QNetworkReply::NetworkError error) { + if (error == QNetworkReply::OperationCanceledError) { + qCritical() << "Aborted " << m_url.toString(); + m_state = State::AbortedByUser; + } else { + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_state = State::Failed; + } + } + + void Upload::sslErrors(const QList<QSslError> &errors) { + int i = 1; + for (const auto& error : errors) { + qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; |
