diff options
Diffstat (limited to 'launcher/modplatform')
-rw-r--r-- | launcher/modplatform/ModAPI.h | 2 | ||||
-rw-r--r-- | launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 43 | ||||
-rw-r--r-- | launcher/modplatform/atlauncher/ATLPackManifest.cpp | 16 | ||||
-rw-r--r-- | launcher/modplatform/atlauncher/ATLPackManifest.h | 16 | ||||
-rw-r--r-- | launcher/modplatform/flame/FileResolvingTask.cpp | 124 | ||||
-rw-r--r-- | launcher/modplatform/flame/FileResolvingTask.h | 8 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlamePackIndex.cpp | 12 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlamePackIndex.h | 1 | ||||
-rw-r--r-- | launcher/modplatform/flame/PackManifest.cpp | 35 | ||||
-rw-r--r-- | launcher/modplatform/flame/PackManifest.h | 10 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthAPI.h | 8 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthPackManifest.cpp | 22 |
12 files changed, 222 insertions, 75 deletions
diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 4230df0b..eb0de3f0 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -68,7 +68,7 @@ class ModAPI { { QString s; for(auto& ver : mcVersions){ - s += QString("%1,").arg(ver.toString()); + s += QString("\"%1\",").arg(ver.toString()); } s.remove(s.length() - 1, 1); //remove last comma return s; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 9b14f355..62c7bf6d 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -414,7 +414,31 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile) { - if(m_version.mainClass == QString() && m_version.extraArguments == QString()) { + if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) { + return true; + } + + auto mainClass = m_version.mainClass.mainClass; + auto extraArguments = m_version.extraArguments.arguments; + + auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty(); + auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty(); + if (hasMainClassDepends || hasExtraArgumentsDepends) { + QSet<QString> mods; + for (const auto& item : m_version.mods) { + mods.insert(item.name); + } + + if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) { + mainClass = ""; + } + + if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) { + extraArguments = ""; + } + } + + if (mainClass.isEmpty() && extraArguments.isEmpty()) { return true; } @@ -442,12 +466,12 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr< auto f = std::make_shared<VersionFile>(); f->name = m_pack + " " + m_version_name; - if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) { - f->mainClass = m_version.mainClass; + if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) { + f->mainClass = mainClass; } // Parse out tweakers - auto args = m_version.extraArguments.split(" "); + auto args = extraArguments.split(" "); QString previous; for(auto arg : args) { if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") { @@ -757,6 +781,17 @@ bool PackInstallTask::extractMods( for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { auto &from = iter.key(); auto &to = iter.value(); + + // If the file already exists, assume the mod is the correct copy - and remove + // the copy from the Configs.zip + QFileInfo fileInfo(to); + if (fileInfo.exists()) { + if (!QFile::remove(to)) { + qWarning() << "Failed to delete" << to; + return false; + } + } + FS::copy fileCopyOperation(from, to); if(!fileCopyOperation()) { qWarning() << "Failed to copy" << from << "to" << to; diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index d01ec32c..3af02a09 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -212,6 +212,18 @@ static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj m.update = Json::ensureString(obj, "update", ""); } +static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj) +{ + m.mainClass = Json::ensureString(obj, "mainClass", ""); + m.depends = Json::ensureString(obj, "depends", ""); +} + +static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj) +{ + a.arguments = Json::ensureString(obj, "arguments", ""); + a.depends = Json::ensureString(obj, "depends", ""); +} + void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) { v.version = Json::requireString(obj, "version"); @@ -220,12 +232,12 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) if(obj.contains("mainClass")) { auto main = Json::requireObject(obj, "mainClass"); - v.mainClass = Json::ensureString(main, "mainClass", ""); + loadVersionMainClass(v.mainClass, main); } if(obj.contains("extraArguments")) { auto arguments = Json::requireObject(obj, "extraArguments"); - v.extraArguments = Json::ensureString(arguments, "arguments", ""); + loadVersionExtraArguments(v.extraArguments, arguments); } if(obj.contains("loader")) { diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 23e162e3..43510c50 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -150,13 +150,25 @@ struct VersionMessages QString update; }; +struct PackVersionMainClass +{ + QString mainClass; + QString depends; +}; + +struct PackVersionExtraArguments +{ + QString arguments; + QString depends; +}; + struct PackVersion { QString version; QString minecraft; bool noConfigs; - QString mainClass; - QString extraArguments; + PackVersionMainClass mainClass; + PackVersionExtraArguments extraArguments; VersionLoader loader; QVector<VersionLibrary> libraries; 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/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 6d48a3bf..bece7843 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -65,16 +65,12 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) // pick the latest version supported file.mcVersion = versionArray[0].toString(); file.version = Json::requireString(version, "displayName"); - file.fileName = Json::requireString(version, "fileName"); file.downloadUrl = Json::ensureString(version, "downloadUrl"); - if(file.downloadUrl.isEmpty()){ - //FIXME : HACK, MAY NOT WORK FOR LONG - file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3") - .arg(QString::number(QString::number(file.fileId).leftRef(4).toInt()) - ,QString::number(QString::number(file.fileId).rightRef(3).toInt()) - ,QUrl::toPercentEncoding(file.fileName)); + + // only add if we have a download URL (third party distribution is enabled) + if (!file.downloadUrl.isEmpty()) { + unsortedVersions.append(file); } - unsortedVersions.append(file); } auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; }; diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h index a8bb15be..7ffa29c3 100644 --- a/launcher/modplatform/flame/FlamePackIndex.h +++ b/launcher/modplatform/flame/FlamePackIndex.h @@ -18,7 +18,6 @@ struct IndexedVersion { QString version; QString mcVersion; QString downloadUrl; - QString fileName; }; struct IndexedPack 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/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index f9d35fcd..60ecbd32 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -80,11 +80,11 @@ class ModrinthAPI : public NetworkModAPI { { return QString(BuildConfig.MODRINTH_PROD_URL + "/project/%1/version?" - "game_versions=[%2]" + "game_versions=[%2]&" "loaders=[\"%3\"]") - .arg(args.addonId) - .arg(getGameVersionsString(args.mcVersions)) - .arg(getModLoaderStrings(args.loaders).join("\",\"")); + .arg(args.addonId, + getGameVersionsString(args.mcVersions), + getModLoaderStrings(args.loaders).join("\",\"")); }; auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString 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 |