diff options
20 files changed, 325 insertions, 384 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1ede3f74..b387f46a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -25,6 +25,13 @@ body: - Other - type: textarea attributes: + label: Version of PolyMC + description: The version of PolyMC used in the bug report. + placeholder: PolyMC 1.2.2 + validations: + required: true +- type: textarea + attributes: label: Description of bug description: What did you expect to happen, what happened, and why is it incorrect? placeholder: The cat button should show a cat, but it showed a dog instead! @@ -14,6 +14,8 @@ CMakeLists.txt.user.* /.project /.settings /.idea +/.vscode +.clang-format cmake-build-*/ Debug diff --git a/CMakeLists.txt b/CMakeLists.txt index a7824a99..4d3683d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL ######## Set version numbers ######## set(Launcher_VERSION_MAJOR 1) set(Launcher_VERSION_MINOR 2) -set(Launcher_VERSION_HOTFIX 1) +set(Launcher_VERSION_HOTFIX 2) # Build number set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") @@ -89,6 +89,10 @@ set(Launcher_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can g # MSA Client ID set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application") +# CurseForge API Key +# CHANGE THIS IF YOU FORK THIS PROJECT! +set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key") + # Bug tracker URL set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.") diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 7360d964..70f8f7f0 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -90,6 +90,7 @@ Config::Config() HELP_URL = "@Launcher_HELP_URL@"; IMGUR_CLIENT_ID = "@Launcher_IMGUR_CLIENT_ID@"; MSA_CLIENT_ID = "@Launcher_MSA_CLIENT_ID@"; + CURSEFORGE_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; META_URL = "@Launcher_META_URL@"; BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index c1d34708..a920a3d4 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -40,9 +40,8 @@ /** * \brief The Config class holds all the build-time information passed from the build system. */ -class Config -{ -public: +class Config { + public: Config(); QString LAUNCHER_NAME; QString LAUNCHER_DISPLAYNAME; @@ -75,7 +74,6 @@ public: /// URL for the updater's channel QString UPDATER_BASE; - /// User-Agent to use. QString USER_AGENT; @@ -118,6 +116,11 @@ public: QString MSA_CLIENT_ID; /** + * Client API key for CurseForge + */ + QString CURSEFORGE_API_KEY; + + /** * Metadata repository URL prefix */ QString META_URL; @@ -156,4 +159,3 @@ public: }; extern const Config BuildConfig; - diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 3889a935..95924a68 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -1,14 +1,9 @@ #include "FileResolvingTask.h" #include "Json.h" -namespace { - const char * metabase = "https://cursemeta.dries007.net"; -} - Flame::FileResolvingTask::FileResolvingTask(shared_qobject_ptr<QNetworkAccessManager> network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) -{ -} +{} void Flame::FileResolvingTask::executeTask() { @@ -17,14 +12,13 @@ void Flame::FileResolvingTask::executeTask() m_dljob = new NetJob("Mod id resolver", m_network); results.resize(m_toProcess.files.size()); int index = 0; - for(auto & file: m_toProcess.files) - { + for (auto& file : m_toProcess.files) { auto projectIdStr = QString::number(file.projectId); auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); + 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 ++; + index++; } connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); m_dljob->start(); @@ -34,16 +28,11 @@ void Flame::FileResolvingTask::netJobFinished() { bool failed = false; int index = 0; - for(auto & bytes: results) - { - auto & out = m_toProcess.files[index]; - try - { + for (auto& bytes : results) { + auto& out = m_toProcess.files[index]; + try { failed &= (!out.parseFromBytes(bytes)); - } - catch (const JSONValidationError &e) - { - + } catch (const JSONValidationError& e) { qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; qCritical() << e.cause(); qCritical() << "JSON:"; @@ -52,12 +41,9 @@ void Flame::FileResolvingTask::netJobFinished() } index++; } - if(!failed) - { + if (!failed) { emitSucceeded(); - } - else - { + } else { emitFailed(tr("Some mod ID resolving tasks failed.")); } } diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index ce02df65..61628e60 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -4,32 +4,52 @@ class FlameAPI : public NetworkModAPI { private: + inline auto getSortFieldInt(QString sortString) const -> int + { + return sortString == "Featured" ? 1 + : sortString == "Popularity" ? 2 + : sortString == "LastUpdated" ? 3 + : sortString == "Name" ? 4 + : sortString == "Author" ? 5 + : sortString == "TotalDownloads" ? 6 + : sortString == "Category" ? 7 + : sortString == "GameVersion" ? 8 + : 1; + } + + private: inline auto getModSearchURL(SearchArgs& args) const -> QString override { auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); return QString( - "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "https://api.curseforge.com/v1/mods/search?" "gameId=432&" - "categoryId=0&" - "sectionId=6&" + "classId=6&" "index=%1&" "pageSize=25&" "searchFilter=%2&" - "sort=%3&" + "sortField=%3&" + "sortOrder=desc&" "modLoaderType=%4&" "%5") .arg(args.offset) .arg(args.search) - .arg(args.sorting) + .arg(getSortFieldInt(args.sorting)) .arg(getMappedModLoader(args.mod_loader)) .arg(gameVersionStr); }; inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { - return QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(args.addonId); + QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; + QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader)); + + return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") + .arg(args.addonId) + .arg(gameVersionQuery) + .arg(modLoaderQuery); }; public: diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index c7b86b5c..ba0824cf 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -10,23 +10,12 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); - pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); + pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); - bool thumbnailFound = false; - auto attachments = Json::requireArray(obj, "attachments"); - for (auto attachmentRaw : attachments) { - auto attachmentObj = Json::requireObject(attachmentRaw); - bool isDefault = attachmentObj.value("isDefault").toBool(false); - if (isDefault) { - thumbnailFound = true; - pack.logoName = Json::requireString(attachmentObj, "title"); - pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); - break; - } - } - - if (!thumbnailFound) { throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); } + QJsonObject logo = Json::requireObject(obj, "logo"); + pack.logoName = Json::requireString(logo, "title"); + pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); auto authors = Json::requireArray(obj, "authors"); for (auto authorIter : authors) { @@ -45,18 +34,22 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, { QVector<ModPlatform::IndexedVersion> unsortedVersions; auto profile = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile(); - bool hasFabric = FlameAPI::getMappedModLoader(profile->getModLoader()) == ModAPI::Fabric; QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); - auto versionArray = Json::requireArray(obj, "gameVersion"); - if (versionArray.isEmpty()) { continue; } + auto versionArray = Json::requireArray(obj, "gameVersions"); + if (versionArray.isEmpty()) { + continue; + } ModPlatform::IndexedVersion file; for (auto mcVer : versionArray) { - file.mcVersion.append(mcVer.toString()); + auto str = mcVer.toString(); + + if (str.contains('.')) + file.mcVersion.append(str); } file.addonId = pack.addonId; @@ -66,28 +59,9 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, file.downloadUrl = Json::requireString(obj, "downloadUrl"); file.fileName = Json::requireString(obj, "fileName"); - auto modules = Json::requireArray(obj, "modules"); - bool is_valid_fabric_version = false; - for (auto m : modules) { - auto fname = Json::requireString(m.toObject(), "foldername"); - // FIXME: This does not work properly when a mod supports more than one mod loader, since - // FIXME: This also doesn't deal with Quilt mods at the moment - // they bundle the meta files for all of them in the same arquive, even when that version - // doesn't support the given mod loader. - if (hasFabric) { - if (fname == "fabric.mod.json") { - is_valid_fabric_version = true; - break; - } - } else - break; - // NOTE: Since we're not validating forge versions, we can just skip this loop. - } - - if (hasFabric && !is_valid_fabric_version) continue; - unsortedVersions.append(file); } + auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { // dates are in RFC 3339 format return a.date > b.date; diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 3d8ea22a..549cace6 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -2,76 +2,63 @@ #include "Json.h" -void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj) +void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); - bool thumbnailFound = false; - auto attachments = Json::requireArray(obj, "attachments"); - for(auto attachmentRaw: attachments) { - auto attachmentObj = Json::requireObject(attachmentRaw); - bool isDefault = attachmentObj.value("isDefault").toBool(false); - if(isDefault) { - thumbnailFound = true; - pack.logoName = Json::requireString(attachmentObj, "title"); - pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl"); - break; - } - } - - if(!thumbnailFound) { - throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name)); - } + auto logo = Json::requireObject(obj, "logo"); + pack.logoName = Json::requireString(logo, "title"); + pack.logoUrl = Json::requireString(logo, "thumbnailUrl"); auto authors = Json::requireArray(obj, "authors"); - for(auto authorIter: authors) { + for (auto authorIter : authors) { auto author = Json::requireObject(authorIter); Flame::ModpackAuthor packAuthor; packAuthor.name = Json::requireString(author, "name"); packAuthor.url = Json::requireString(author, "url"); pack.authors.append(packAuthor); } - int defaultFileId = Json::requireInteger(obj, "defaultFileId"); + int defaultFileId = Json::requireInteger(obj, "mainFileId"); bool found = false; // check if there are some files before adding the pack auto files = Json::requireArray(obj, "latestFiles"); - for(auto fileIter: files) { + for (auto fileIter : files) { auto file = Json::requireObject(fileIter); int id = Json::requireInteger(file, "id"); // NOTE: for now, ignore everything that's not the default... - if(id != defaultFileId) { + if (id != defaultFileId) { continue; } - auto versionArray = Json::requireArray(file, "gameVersion"); - if(versionArray.size() < 1) { + auto versionArray = Json::requireArray(file, "gameVersions"); + if (versionArray.size() < 1) { continue; } found = true; break; } - if(!found) { + if (!found) { throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name)); } } -void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) +void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr) { QVector<Flame::IndexedVersion> unsortedVersions; - for(auto versionIter: arr) { + for (auto versionIter : arr) { auto version = Json::requireObject(versionIter); - Flame::IndexedVersion file; + Flame::IndexedVersion file; file.addonId = pack.addonId; file.fileId = Json::requireInteger(version, "id"); - auto versionArray = Json::requireArray(version, "gameVersion"); - if(versionArray.size() < 1) { + auto versionArray = Json::requireArray(version, "gameVersions"); + if (versionArray.size() < 1) { continue; } @@ -82,10 +69,7 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr) unsortedVersions.append(file); } - auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool - { - return a.fileId > b.fileId; - }; + auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; }; std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); pack.versions = unsortedVersions; pack.versionsLoaded = true; diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index b928fd16..e4f90c1a 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -1,28 +1,27 @@ #include "PackManifest.h" #include "Json.h" -static void loadFileV1(Flame::File & f, QJsonObject & file) +static void loadFileV1(Flame::File& f, QJsonObject& file) { f.projectId = Json::requireInteger(file, "projectID"); f.fileId = Json::requireInteger(file, "fileID"); f.required = Json::ensureBoolean(file, QString("required"), true); } -static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader) +static void loadModloaderV1(Flame::Modloader& m, QJsonObject& modLoader) { m.id = Json::requireString(modLoader, "id"); m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); } -static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) +static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft) { m.version = Json::requireString(minecraft, "version"); // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); - for (QJsonValueRef item : arr) - { + for (QJsonValueRef item : arr) { auto obj = Json::requireObject(item); Flame::Modloader loader; loadModloaderV1(loader, obj); @@ -30,16 +29,15 @@ static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) } } -static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) +static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest) { auto mc = Json::requireObject(manifest, "minecraft"); loadMinecraftV1(m.minecraft, mc); m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); + m.author = Json::ensureString(manifest, QString("author"), "Anonymous"); auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (QJsonValueRef item : arr) - { + for (QJsonValueRef item : arr) { auto obj = Json::requireObject(item); Flame::File file; loadFileV1(file, obj); @@ -48,18 +46,16 @@ static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } -void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) +void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) { auto doc = Json::requireDocument(filepath); auto obj = Json::requireObject(doc); m.manifestType = Json::requireString(obj, "manifestType"); - if(m.manifestType != "minecraftModpack") - { + if (m.manifestType != "minecraftModpack") { throw JSONValidationError("Not a modpack manifest!"); } m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); - if(m.manifestVersion != 1) - { + if (m.manifestVersion != 1) { throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); } loadManifestV1(m, obj); @@ -68,59 +64,30 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) bool Flame::File::parseFromBytes(const QByteArray& bytes) { auto doc = Json::requireDocument(bytes); - auto obj = Json::requireObject(doc); - // result code signifies true failure. - if(obj.contains("code")) - { - qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:"; - qCritical() << bytes; - return false; + if (!doc.isObject()) { + throw JSONValidationError(QString("data is not an object? that's not supposed to happen")); } - fileName = Json::requireString(obj, "FileNameOnDisk"); - QString rawUrl = Json::requireString(obj, "DownloadURL"); + 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()) - { + 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 - QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); - if(!projObj.isEmpty()) - { - QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); - if(strType == "singlefile") - { - type = File::Type::SingleFile; - } - else if(strType == "ctoc") - { - type = File::Type::Ctoc; - } - else if(strType == "cmod2") - { - type = File::Type::Cmod2; - } - else if(strType == "mod") - { - type = File::Type::Mod; - } - else if(strType == "folder") - { - type = File::Type::Folder; - } - else if(strType == "modpack") - { - type = File::Type::Modpack; - } - else - { - qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType; - type = File::Type::Unknown; - return false; - } - targetFolder = Json::ensureString(projObj, "Path", "mods"); + type = File::Type::SingleFile; + + if (fileName.endsWith(".zip")) { + // this is probably a resource pack + targetFolder = "resourcepacks"; + } else { + // this is probably a mod, dunno what else could modpacks download + targetFolder = "mods"; } + resolved = true; return true; } diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index b314573f..65cc8f67 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -15,27 +15,27 @@ #include "Download.h" -#include <QFileInfo> #include <QDateTime> #include <QDebug> +#include <QFileInfo> -#include "FileSystem.h" +#include "ByteArraySink.h" #include "ChecksumValidator.h" +#include "FileSystem.h" #include "MetaCacheSink.h" -#include "ByteArraySink.h" #include "BuildConfig.h" namespace Net { -Download::Download():NetAction() +Download::Download() : NetAction() { m_status = Job_NotStarted; } Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) { - Download * dl = new Download(); + Download* dl = new Download(); dl->m_url = url; dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); @@ -45,9 +45,9 @@ Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options return dl; } -Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options options) +Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options) { - Download * dl = new Download(); + Download* dl = new Download(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new ByteArraySink(output)); @@ -56,30 +56,28 @@ Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options opti Download::Ptr Download::makeFile(QUrl url, QString path, Options options) { - Download * dl = new Download(); + Download* dl = new Download(); dl->m_url = url; dl->m_options = options; dl->m_sink.reset(new FileSink(path)); return dl; } -void Download::addValidator(Validator * v) +void Download::addValidator(Validator* v) { m_sink->addValidator(v); } void Download::startImpl() { - if(m_status == Job_Aborted) - { + if (m_status == Job_Aborted) { qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); emit aborted(m_index_within_job); return; } QNetworkRequest request(m_url); m_status = m_sink->init(request); - switch(m_status) - { + switch (m_status) { case Job_Finished: emit succeeded(m_index_within_job); qDebug() << "Download cache hit " << m_url.toString(); @@ -87,7 +85,7 @@ void Download::startImpl() case Job_InProgress: qDebug() << "Downloading " << m_url.toString(); break; - case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. + case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. case Job_NotStarted: case Job_Failed: emit failed(m_index_within_job); @@ -97,8 +95,11 @@ void Download::startImpl() } request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT); + if (request.url().host().contains("api.curseforge.com")) { + request.setRawHeader("x-api-key", BuildConfig.CURSEFORGE_API_KEY.toUtf8()); + }; - QNetworkReply *rep = m_network->get(request); + QNetworkReply* rep = m_network->get(request); m_reply.reset(rep); connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); @@ -117,17 +118,12 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Download::downloadError(QNetworkReply::NetworkError error) { - if(error == QNetworkReply::OperationCanceledError) - { + if (error == QNetworkReply::OperationCanceledError) { qCritical() << "Aborted " << m_url.toString(); m_status = Job_Aborted; - } - else - { - if(m_options & Option::AcceptLocalFiles) - { - if(m_sink->hasLocalData()) - { + } else { + if (m_options & Option::AcceptLocalFiles) { + if (m_sink->hasLocalData()) { m_status = Job_Failed_Proceed; return; } @@ -138,11 +134,10 @@ void Download::downloadError(QNetworkReply::NetworkError error) } } -void Download::sslErrors(const QList<QSslError> & errors) +void Download::sslErrors(const QList<QSslError>& errors) { int i = 1; - for (auto error : errors) - { + for (auto error : errors) { qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); auto cert = error.certificate(); qCritical() << "Certificate in question:\n" << cert.toText(); @@ -153,33 +148,27 @@ void Download::sslErrors(const QList<QSslError> & errors) bool Download::handleRedirect() { QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if(!redirect.isValid()) - { - if(!m_reply->hasRawHeader("Location")) - { + if (!redirect.isValid()) { + if (!m_reply->hasRawHeader("Location")) { // no redirect -> it's fine to continue return false; } // there is a Location header, but it's not correct. we need to apply some workarounds... QByteArray redirectBA = m_reply->rawHeader("Location"); - if(redirectBA.size() == 0) - { + if (redirectBA.size() == 0) { // empty, yet present redirect header? WTF? return false; } QString redirectStr = QString::fromUtf8(redirectBA); - if(redirectStr.startsWith("//")) - { + if (redirectStr.startsWith("//")) { /* * IF the URL begins with //, we need to insert the URL scheme. * See: https://bugreports.qt.io/browse/QTBUG-41061 * See: http://tools.ietf.org/html/rfc3986#section-4.2 */ redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } - else if(redirectStr.startsWith("/")) - { + } else if (redirectStr.startsWith("/")) { /* * IF the URL begins with /, we need to process it as a relative URL */ @@ -193,16 +182,13 @@ bool Download::handleRedirect() * FIXME: report Qt bug for this */ redirect = QUrl(redirectStr, QUrl::TolerantMode); - if(!redirect.isValid()) - { + if (!redirect.isValid()) { qWarning() << "Failed to parse redirect URL:" << redirectStr; downloadError(QNetworkReply::ProtocolFailure); return false; } qDebug() << "Fixed location header:" << redirect; - } - else - { + } else { qDebug() << "Location header:" << redirect; } @@ -212,35 +198,28 @@ bool Download::handleRedirect() return true; } - void Download::downloadFinished() { // handle HTTP redirection first - if(handleRedirect()) - { + if (handleRedirect()) { qDebug() << "Download redirected:" << m_url.toString(); return; } // if the download failed before this point ... - if (m_status == Job_Failed_Proceed) - { + if (m_status == Job_Failed_Proceed) { qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit succeeded(m_index_within_job); return; - } - else if (m_status == Job_Failed) - { + } else if (m_status == Job_Failed) { qDebug() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(m_index_within_job); return; - } - else if(m_status == Job_Aborted) - { + } else if (m_status == Job_Aborted) { qDebug() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); @@ -250,16 +229,14 @@ void Download::downloadFinished() // make sure we got all the remaining data, if any auto data = m_reply->readAll(); - if(data.size()) - { + if (data.size()) { qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; m_status = m_sink->write(data); } // otherwise, finalize the whole graph m_status = m_sink->finalize(*m_reply.get()); - if (m_status != Job_Finished) - { + if (m_status != Job_Finished) { qDebug() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); @@ -273,32 +250,25 @@ void Download::downloadFinished() void Download::downloadReadyRead() { - if(m_status == Job_InProgress) - { + if (m_status == Job_InProgress) { auto data = m_reply->readAll(); m_status = m_sink->write(data); - if(m_status == Job_Failed) - { + if (m_status == Job_Failed) { qCritical() << "Failed to process response chunk for " << m_target_path; } // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; - } - else - { + } else { qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; } } -} +} // namespace Net bool Net::Download::abort() { - if(m_reply) - { + if (m_reply) { m_reply->abort(); - } - else - { + } else { m_status = Job_Aborted; } return true; diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 6a5a324f..8d137afc 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -100,7 +100,8 @@ void SkinUploadDialog::on_buttonBox_accepted() void SkinUploadDialog::on_skinBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png"); + auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { return; diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 487bf77b..1b53dd40 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -143,7 +143,8 @@ void ImportPage::setUrl(const QString& url) void ImportPage::on_modpackBtn_clicked() { - const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)")); + auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); + const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); if (url.isValid()) { if (url.isLocalFile()) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index e82e1cdb..540ee2fd 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -21,7 +21,8 @@ auto ListModel::debugName() const -> QString void ListModel::fetchMore(const QModelIndex& parent) { - if (parent.isValid()) return; + if (parent.isValid()) + return; if (nextSearchOffset == 0) { qWarning() << "fetchMore with 0 offset is wrong..."; return; @@ -32,7 +33,9 @@ void ListModel::fetchMore(const QModelIndex& parent) auto ListModel::data(const QModelIndex& index, int role) const -> QVariant { int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } ModPlatform::IndexedPack pack = modpacks.at(pos); if (role == Qt::DisplayRole) { @@ -46,7 +49,9 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant } return pack.description; } else if (role == Qt::DecorationRole) { - if (m_logoMap.contains(pack.logoName)) { return (m_logoMap.value(pack.logoName)); } + if (m_logoMap.contains(pack.logoName)) { + return (m_logoMap.value(pack.logoName)); + } QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; @@ -63,16 +68,15 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile(); - m_parent->apiProvider()->getVersions(this, - { current.addonId.toString(), getMineVersions(), profile->getModLoader() }); + m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoader() }); } void ListModel::performPaginatedSearch() { auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile(); - m_parent->apiProvider()->searchMods(this, - { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); + m_parent->apiProvider()->searchMods( + this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); } void ListModel::refresh() @@ -93,11 +97,9 @@ void ListModel::refresh() void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) { - if (currentSearchTerm == term - && currentSearchTerm.isNull() == term.isNull() - && currentSort == sort - && !filter_changed) - { return; } + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filter_changed) { + return; + } currentSearchTerm = term; currentSort = sort; @@ -118,7 +120,9 @@ void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallbac void ListModel::requestLogo(QString logo, QString url) { - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { return; } + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { + return; + } MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); @@ -129,7 +133,9 @@ void ListModel::requestLogo(QString logo, QString url) QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { waitingCallbacks.value(logo)(fullPath); } + if (waitingCallbacks.contains(logo)) { + waitingCallbacks.value(logo)(fullPath); + } }); QObject::connect(job, &NetJob::failed, this, [this, logo, job] { @@ -148,7 +154,9 @@ void ListModel::logoLoaded(QString logo, QIcon out) m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); for (int i = 0; i < modpacks.size(); i++) { - if (modpacks[i].logoName == logo) { emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } + if (modpacks[i].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); + } } } @@ -199,7 +207,9 @@ void ListModel::searchRequestFailed(QString reason) // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself - QString("%1 %2").arg(m_parent->displayName()).arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); + QString("%1 %2") + .arg(m_parent->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); } jobPtr.reset(); @@ -218,9 +228,12 @@ void ListModel::searchRequestFailed(QString reason) void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) { auto& current = m_parent->getCurrent(); - if (addonId != current.addonId) { return; } + if (addonId != current.addonId) { + return; + } + + auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array(); - QJsonArray arr = doc.array(); try { loadIndexedPackVersions(current, arr); } catch (const JSONValidationError& e) { diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 905fb2dd..8de2e545 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -1,5 +1,5 @@ #include "FlameModModel.h" - +#include "Json.h" #include "modplatform/flame/FlameModIndex.h" namespace FlameMod { @@ -19,7 +19,7 @@ void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray { - return obj.array(); + return Json::ensureArray(obj.object(), "data"); } } // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index fe163cae..f97536e8 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -1,6 +1,6 @@ #include "FlameModel.h" -#include "Application.h" #include <Json.h> +#include "Application.h" #include <MMCStrings.h> #include <Version.h> @@ -9,61 +9,46 @@ namespace Flame { -ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) -{ -} +ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} -ListModel::~ListModel() -{ -} +ListModel::~ListModel() {} -int ListModel::rowCount(const QModelIndex &parent) const +int ListModel::rowCount(const QModelIndex& parent) const { return modpacks.size(); } -int ListModel::columnCount(const QModelIndex &parent) const +int ListModel::columnCount(const QModelIndex& parent) const { return 1; } -QVariant ListModel::data(const QModelIndex &index, int role) const +QVariant ListModel::data(const QModelIndex& index, int role) const { int pos = index.row(); - if(pos >= modpacks.size() || pos < 0 || !index.isValid()) - { + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { return QString("INVALID INDEX %1").arg(pos); } IndexedPack pack = modpacks.at(pos); - if(role == Qt::DisplayRole) - { + if (role == Qt::DisplayRole) { return pack.name; - } - else if (role == Qt::ToolTipRole) - { - if(pack.description.length() > 100) - { - //some magic to prevent to long tooltips and replace html linebreaks + } else if (role == Qt::ToolTipRole) { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks QString edit = pack.description.left(97); edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); return edit; - } return pack.description; - } - else if(role == Qt::DecorationRole) - { - if(m_logoMap.contains(pack.logoName)) - { + } else if (role == Qt::DecorationRole) { + if (m_logoMap.contains(pack.logoName)) { return (m_logoMap.value(pack.logoName)); } QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); + ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; - } - else if(role == Qt::UserRole) - { + } else if (role == Qt::UserRole) { QVariant v; v.setValue(pack); return v; @@ -76,9 +61,9 @@ void ListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); - for(int i = 0; i < modpacks.size(); i++) { - if(modpacks[i].logoName == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + for (int i = 0; i < modpacks.size(); i++) { + if (modpacks[i].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); } } } @@ -91,8 +76,7 @@ void ListModel::logoFailed(QString logo) void ListModel::requestLogo(QString logo, QString url) { - if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) - { + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { return; } @@ -101,18 +85,15 @@ void ListModel::requestLogo(QString logo, QString url) job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] - { + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { job->deleteLater(); emit logoLoaded(logo, QIcon(fullPath)); - if(waitingCallbacks.contains(logo)) - { + if (waitingCallbacks.contains(logo)) { waitingCallbacks.value(logo)(fullPath); } }); - QObject::connect(job, &NetJob::failed, this, [this, logo, job] - { + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { job->deleteLater(); emit logoFailed(logo); }); @@ -122,19 +103,16 @@ void ListModel::requestLogo(QString logo, QString url) m_loadingLogos.append(logo); } -void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) +void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) { - if(m_logoMap.contains(logo)) - { + if (m_logoMap.contains(logo)) { callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); - } - else - { + } else { requestLogo(logo, logoUrl); } } -Qt::ItemFlags ListModel::flags(const QModelIndex &index) const +Qt::ItemFlags ListModel::flags(const QModelIndex& index) const { return QAbstractListModel::flags(index); } @@ -148,7 +126,7 @@ void ListModel::fetchMore(const QModelIndex& parent) { if (parent.isValid()) return; - if(nextSearchOffset == 0) { + if (nextSearchOffset == 0) { qWarning() << "fetchMore with 0 offset is wrong..."; return; } @@ -157,17 +135,20 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { - NetJob *netJob = new NetJob("Flame::Search", APPLICATION->network()); + NetJob* netJob = new NetJob("Flame::Search", APPLICATION->network()); auto searchUrl = QString( - "https://addons-ecs.forgesvc.net/api/v2/addon/search?" - "categoryId=0&" - "gameId=432&" - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sectionId=4471&" - "sort=%3" - ).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort); + "https://api.curseforge.com/v1/mods/search?" + "gameId=432&" + "classId=4471&" + "index=%1&" + "pageSize=25&" + "searchFilter=%2&" + "sortField=%3&" + "sortOrder=desc") + .arg(nextSearchOffset) + .arg(currentSearchTerm) + .arg(currentSort + 1); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -177,17 +158,16 @@ void ListModel::performPaginatedSearch() void ListModel::searchWithTerm(const QString& term, int sort) { - if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; } currentSearchTerm = term; currentSort = sort; - if(jobPtr) { + if (jobPtr) { jobPtr->abort(); searchState = ResetRequested; return; - } - else { + } else { beginResetModel(); modpacks.clear(); endResetModel(); @@ -203,30 +183,28 @@ void Flame::ListModel::searchRequestFinished() QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset + << " reason: " << parse_error.errorString(); qWarning() << response; return; } QList<Flame::IndexedPack> newList; - auto packs = doc.array(); - for(auto packRaw : packs) { + auto packs = Json::ensureArray(doc.object(), "data"); + for (auto packRaw : packs) { auto packObj = packRaw.toObject(); Flame::IndexedPack pack; - try - { + try { Flame::loadIndexedPack(pack, packObj); newList.append(pack); - } - catch(const JSONValidationError &e) - { + } catch (const JSONValidationError& e) { qWarning() << "Error while loading pack from CurseForge: " << e.cause(); continue; } } - if(packs.size() < 25) { + if (packs.size() < 25) { searchState = Finished; } else { nextSearchOffset += 25; @@ -241,7 +219,7 @@ void Flame::ListModel::searchRequestFailed(QString reason) { jobPtr.reset(); - if(searchState == ResetRequested) { + if (searchState == ResetRequested) { beginResetModel(); modpacks.clear(); endResetModel(); @@ -253,5 +231,4 @@ void Flame::ListModel::searchRequestFailed(QString reason) } } -} - +} // namespace Flame diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index c90294ce..ec774621 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -39,13 +39,12 @@ #include <QKeyEvent> #include "Application.h" +#include "FlameModel.h" +#include "InstanceImportTask.h" #include "Json.h" #include "ui/dialogs/NewInstanceDialog.h" -#include "InstanceImportTask.h" -#include "FlameModel.h" -FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) +FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) { ui->setupUi(this); connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch); @@ -112,10 +111,8 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) { ui->versionSelectionBox->clear(); - if(!first.isValid()) - { - if(isOpened) - { + if (!first.isValid()) { + if (isOpened) { dialog->setSuggestedPack(); } return; @@ -130,14 +127,14 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) else text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; if (!current.authors.empty()) { - auto authorToStr = [](Flame::ModpackAuthor & author) { - if(author.url.isEmpty()) { + auto authorToStr = [](Flame::ModpackAuthor& author) { + if (author.url.isEmpty()) { return author.name; } return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); }; QStringList authorStrs; - for(auto & author: current.authors) { + for (auto& author : current.authors) { authorStrs.push_back(authorToStr(author)); } text += "<br>" + tr(" by ") + authorStrs.join(", "); @@ -146,53 +143,46 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) ui->packDescription->setHtml(text + current.description); - if (current.versionsLoaded == false) - { + if (current.versionsLoaded == false) { qDebug() << "Loading flame modpack versions"; auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto response = new QByteArray(); int addonId = current.addonId; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response)); + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] - { - if(addonId != current.addonId){ - return; //wrong request + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] { + if (addonId != current.addonId) { + return; // wrong request } QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset + << " reason: " << parse_error.errorString(); qWarning() << *response; return; } - QJsonArray arr = doc.array(); - try - { + auto arr = Json::ensureArray(doc.object(), "data"); + try { Flame::loadIndexedPackVersions(current, arr); - } - catch(const JSONValidationError &e) - { + } catch (const JSONValidationError& e) { qDebug() << *response; qWarning() << "Error while reading flame modpack version: " << e.cause(); } - for(auto version : current.versions) { + for (auto version : current.versions) { ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } suggestCurrent(); }); - QObject::connect(netJob, &NetJob::finished, this, [response, netJob] - { + QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); delete response; }); netJob->start(); - } - else - { - for(auto version : current.versions) { + } else { + for (auto version : current.versions) { ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); } @@ -202,13 +192,11 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) void FlamePage::suggestCurrent() { - if(!isOpened) - { + if (!isOpened) { return; } - if (selectedVersion.isEmpty()) - { + if (selectedVersion.isEmpty()) { dialog->setSuggestedPack(); return; } @@ -216,16 +204,13 @@ void FlamePage::suggestCurrent() dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion)); QString editedLogoName; editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0); - listModel->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); + listModel->getLogo(current.logoName, current.logoUrl, + [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); } void FlamePage::onVersionSelectionChanged(QString data) { - if(data.isNull() || data.isEmpty()) - { + if (data.isNull() || data.isEmpty()) { selectedVersion = ""; return; } diff --git a/program_info/genicons.sh b/program_info/genicons.sh index b553edb5..313bdb53 100755 --- a/program_info/genicons.sh +++ b/program_info/genicons.sh @@ -1,5 +1,7 @@ #/bin/bash +# ICO + inkscape -w 16 -h 16 -o polymc_16.png org.polymc.PolyMC.svg inkscape -w 24 -h 24 -o polymc_24.png org.polymc.PolyMC.svg inkscape -w 32 -h 32 -o polymc_32.png org.polymc.PolyMC.svg @@ -9,11 +11,24 @@ inkscape -w 128 -h 128 -o polymc_128.png org.polymc.PolyMC.svg convert polymc_128.png polymc_64.png polymc_48.png polymc_32.png polymc_24.png polymc_16.png polymc.ico -inkscape -w 256 -h 256 -o polymc_256.png org.polymc.PolyMC.svg -inkscape -w 512 -h 512 -o polymc_512.png org.polymc.PolyMC.svg -inkscape -w 1024 -h 1024 -o polymc_1024.png org.polymc.PolyMC.svg +rm -f polymc_*.png + +inkscape -w 1024 -h 1024 -o polymc_1024.png org.polymc.PolyMC.bigsur.svg + +mkdir polymc.iconset + +sips -z 16 16 polymc_1024.png --out polymc.iconset/icon_16x16.png +sips -z 32 32 polymc_1024.png --out polymc.iconset/icon_16x16@2x.png +sips -z 32 32 polymc_1024.png --out polymc.iconset/icon_32x32.png +sips -z 64 64 polymc_1024.png --out polymc.iconset/icon_32x32@2x.png +sips -z 128 128 polymc_1024.png --out polymc.iconset/icon_128x128.png +sips -z 256 256 polymc_1024.png --out polymc.iconset/icon_128x128@2x.png +sips -z 256 256 polymc_1024.png --out polymc.iconset/icon_256x256.png +sips -z 512 512 polymc_1024.png --out polymc.iconset/icon_256x256@2x.png +sips -z 512 512 polymc_1024.png --out polymc.iconset/icon_512x512.png +cp polymc_1024.png polymc.iconset/icon_512x512@2x.png -png2icns polymc.icns polymc_1024.png polymc_512.png polymc_256.png polymc_128.png polymc_32.png polymc_16.png +iconutil -c icns polymc.iconset rm -f polymc_*.png rm -rf polymc.iconset diff --git a/program_info/org.polymc.PolyMC.bigsur.svg b/program_info/org.polymc.PolyMC.bigsur.svg new file mode 100644 index 00000000..1d680032 --- /dev/null +++ b/program_info/org.polymc.PolyMC.bigsur.svg @@ -0,0 +1,32 @@ +<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g filter="url(#filter0_d_68_227)"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M924 356.627C924 346.845 924.004 337.062 923.944 327.279C923.895 319.038 923.8 310.799 923.576 302.562C923.092 284.609 922.033 266.502 918.84 248.749C915.602 230.741 910.314 213.98 901.981 197.617C893.789 181.534 883.088 166.817 870.32 154.058C857.555 141.299 842.834 130.605 826.746 122.418C810.366 114.083 793.587 108.797 775.558 105.56C757.803 102.372 739.691 101.315 721.738 100.83C713.495 100.607 705.253 100.513 697.008 100.462C687.22 100.402 677.432 100.407 667.644 100.407L553.997 100H468.997L357.361 100.407C347.554 100.407 337.747 100.402 327.94 100.462C319.678 100.513 311.42 100.607 303.161 100.83C285.167 101.315 267.014 102.373 249.217 105.565C231.164 108.801 214.36 114.085 197.958 122.414C181.835 130.602 167.083 141.297 154.291 154.058C141.501 166.816 130.78 181.53 122.573 197.61C114.217 213.981 108.919 230.752 105.673 248.77C102.477 266.516 101.418 284.617 100.931 302.562C100.709 310.8 100.613 319.039 100.563 327.279C100.503 337.063 100 349.216 100 358.999L100.003 469.089L100 554.998L100.508 667.427C100.508 677.223 100.504 687.019 100.563 696.815C100.613 705.067 100.709 713.317 100.932 721.566C101.418 739.542 102.479 757.675 105.678 775.452C108.923 793.484 114.22 810.269 122.569 826.653C130.777 842.759 141.5 857.495 154.291 870.272C167.082 883.049 181.83 893.757 197.95 901.956C214.362 910.302 231.174 915.595 249.238 918.836C267.027 922.029 285.174 923.088 303.161 923.573C311.42 923.796 319.679 923.891 327.941 923.941C337.748 924.001 347.554 923.997 357.361 923.997L470.006 924H555.217L667.644 923.996C677.432 923.996 687.22 924.001 697.008 923.941C705.253 923.891 713.495 923.796 721.738 923.573C739.698 923.087 757.816 922.027 775.579 918.832C793.597 915.591 810.368 910.3 826.739 901.959C842.831 893.761 857.554 883.051 870.32 870.272C883.086 857.497 893.786 842.763 901.978 826.66C910.316 810.268 915.604 793.475 918.844 775.431C922.034 757.661 923.092 739.535 923.577 721.566C923.8 713.316 923.895 705.066 923.944 696.815C924.005 687.019 924 677.223 924 667.427C924 667.427 923.994 556.983 923.994 554.998V468.999C923.994 467.533 924 356.627 924 356.627Z" fill="url(#paint0_linear_68_227)"/> +</g> +<path d="M338.18 779.507C338.18 779.507 338.18 653.214 512.004 653.214C685.874 653.214 685.827 779.507 685.827 779.507H338.18Z" fill="#765338"/> +<path d="M512.007 653.221L338.183 779.514L230.752 448.878L512.007 653.221Z" fill="#B7835A"/> +<path d="M512.007 653.221L793.263 448.878L685.831 779.514L512.007 653.221Z" fill="#5B422D"/> +<path d="M524.909 662.576L512.005 671.951L499.101 662.576C499.101 653.201 512.005 653.201 512.005 653.201C512.005 653.201 524.909 653.201 524.909 662.576Z" fill="#72B147"/> +<path d="M512.007 653.221C512.007 653.221 512.007 448.878 793.263 448.878L785.288 473.423L752.741 515.819L720.194 520.716L687.647 563.113L655.1 568.009L622.553 610.406L590.006 615.302L557.459 657.699L524.912 662.595L512.007 653.221Z" fill="#5A9A30"/> +<path d="M499.102 662.576L466.555 657.679L434.008 615.283L401.461 610.386L368.914 567.99L336.367 563.093L303.82 520.697L271.273 515.8L238.726 473.403L230.751 448.859C512.007 448.859 512.007 653.202 512.007 653.202L499.102 662.576Z" fill="#88B858"/> +<path d="M230.75 448.861L512.006 653.204L793.262 448.861L512.006 244.518L230.75 448.861Z" fill="url(#paint1_linear_68_227)"/> +<defs> +<filter id="filter0_d_68_227" x="90" y="100" width="844" height="844" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="10"/> +<feGaussianBlur stdDeviation="5"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0"/> +<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_68_227"/> +<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_68_227" result="shape"/> +</filter> +<linearGradient id="paint0_linear_68_227" x1="512" y1="100" x2="512" y2="924" gradientUnits="userSpaceOnUse"> +<stop stop-color="#292929"/> +<stop offset="1" stop-color="#171717"/> +</linearGradient> +<linearGradient id="paint1_linear_68_227" x1="371.378" y1="346.687" x2="652.619" y2="551.034" gradientUnits="userSpaceOnUse"> +<stop stop-color="#88B858"/> +<stop offset="0.5" stop-color="#72B147"/> +<stop offset="1" stop-color="#5A9A30"/> +</linearGradient> +</defs> +</svg> diff --git a/program_info/polymc.icns b/program_info/polymc.icns Binary files differindex 84148d1a..a090c1b0 100644 --- a/program_info/polymc.icns +++ b/program_info/polymc.icns |