diff options
Diffstat (limited to 'launcher/ui/pages/modplatform')
53 files changed, 1036 insertions, 298 deletions
diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index 4ac21b01..068fb3a3 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged() ui->loaderVersionList->setEmptyString(tr("No mod loader is selected.")); ui->loaderVersionList->setEmptyMode(VersionListView::String); return; + } else if (ui->neoForgeFilter->isChecked()) { + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); + m_selectedLoader = "net.neoforged"; } else if (ui->forgeFilter->isChecked()) { ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); m_selectedLoader = "net.minecraftforge"; diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui index 0d89b595..23351ccd 100644 --- a/launcher/ui/pages/modplatform/CustomPage.ui +++ b/launcher/ui/pages/modplatform/CustomPage.ui @@ -195,6 +195,16 @@ </widget> </item> <item> + <widget class="QRadioButton" name="neoForgeFilter"> + <property name="text"> + <string>NeoForge</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">loaderBtnGroup</string> + </attribute> + </widget> + </item> + <item> <widget class="QRadioButton" name="forgeFilter"> <property name="text"> <string>Forge</string> diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index ba53d033..3e3c36b7 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -35,20 +35,28 @@ */ #include "ImportPage.h" + +#include "ui/dialogs/ProgressDialog.h" #include "ui_ImportPage.h" #include <QFileDialog> #include <QValidator> +#include <utility> +#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/NewInstanceDialog.h" +#include "modplatform/flame/FlameAPI.h" + +#include "Json.h" + #include "InstanceImportTask.h" class UrlValidator : public QValidator { public: using QValidator::QValidator; - State validate(QString& in, int& pos) const + State validate(QString& in, [[maybe_unused]] int& pos) const { const QUrl url(in); if (url.isValid() && !url.isRelative() && !url.isEmpty()) { @@ -106,10 +114,61 @@ void ImportPage::updateState() bool isMRPack = fi.suffix() == "mrpack"; if (fi.exists() && (isZip || isMRPack)) { - QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this)); + auto extra_info = QMap(m_extra_info); + qDebug() << "Pack Extra Info" << extra_info << m_extra_info; + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info))); dialog->setSuggestedIcon("default"); } + } else if (url.scheme() == "curseforge") { + // need to find the download link for the modpack + // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE + QUrlQuery query(url); + auto addonId = query.allQueryItemValues("addonId")[0]; + auto fileId = query.allQueryItemValues("fileId")[0]; + auto array = std::make_shared<QByteArray>(); + + auto api = FlameAPI(); + auto job = api.getFile(addonId, fileId, array); + + connect(job.get(), &NetJob::failed, this, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); + connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] { + qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); + auto doc = Json::requireDocument(*array); + auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data"); + // No way to find out if it's a mod or a modpack before here + // And also we need to check if it ends with .zip, instead of any better way + auto fileName = Json::ensureString(data, "fileName"); + if (fileName.endsWith(".zip")) { + // Have to use ensureString then use QUrl to get proper url encoding + auto dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl")); + if (!dl_url.isValid()) { + CustomMessageBox::selectable( + this, tr("Error"), + tr("The modpack %1 is blocked for third-parties! Please download it manually.").arg(fileName), + QMessageBox::Critical) + ->show(); + return; + } + + QFileInfo dl_file(dl_url.fileName()); + QString pack_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName"); + + QMap<QString, QString> extra_info; + extra_info.insert("pack_id", addonId); + extra_info.insert("pack_version_id", fileId); + + dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info))); + dialog->setSuggestedIcon("default"); + + } else { + CustomMessageBox::selectable(this, tr("Error"), tr("This url isn't a valid modpack !"), QMessageBox::Critical)->show(); + } + }); + ProgressDialog dlUrlDialod(this); + dlUrlDialod.setSkipButton(true, tr("Abort")); + dlUrlDialod.execWithTask(job.get()); + return; } else { if (input.endsWith("?client=y")) { input.chop(9); @@ -118,7 +177,8 @@ void ImportPage::updateState() } // hook, line and sinker. QFileInfo fi(url.fileName()); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this)); + auto extra_info = QMap(m_extra_info); + dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info))); dialog->setSuggestedIcon("default"); } } else { @@ -132,6 +192,12 @@ void ImportPage::setUrl(const QString& url) updateState(); } +void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info) +{ + m_extra_info = extra_info; + updateState(); +} + void ImportPage::on_modpackBtn_clicked() { auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); diff --git a/launcher/ui/pages/modplatform/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h index d846d566..70d7736e 100644 --- a/launcher/ui/pages/modplatform/ImportPage.h +++ b/launcher/ui/pages/modplatform/ImportPage.h @@ -62,7 +62,7 @@ class ImportPage : public QWidget, public BasePage { void setUrl(const QString& url); void openedImpl() override; - + void setExtraInfo(const QMap<QString, QString>& extra_info); private slots: void on_modpackBtn_clicked(); void updateState(); @@ -73,4 +73,5 @@ class ImportPage : public QWidget, public BasePage { private: Ui::ImportPage* ui = nullptr; NewInstanceDialog* dialog = nullptr; + QMap<QString, QString> m_extra_info = {}; }; diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui index 3583cf90..9a9736b8 100644 --- a/launcher/ui/pages/modplatform/ImportPage.ui +++ b/launcher/ui/pages/modplatform/ImportPage.ui @@ -40,7 +40,7 @@ <item> <widget class="QLabel" name="label_5"> <property name="text"> - <string>- CurseForge modpacks (ZIP)</string> + <string>- CurseForge modpacks (ZIP / curseforge:// URL)</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index b7537890..c628f74a 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments() auto sort = getCurrentSortingMethodByIndex(); - return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions }; + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions }; } ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) @@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en if (!m_filter->versions.empty()) versions = m_filter->versions; - return { pack, versions, profile->getModLoaders() }; + return { pack, versions, profile->getSupportedModLoaders() }; } ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 9b178048..d6cc1fdc 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -124,8 +124,7 @@ void ModPage::updateVersionList() auto version = current_pack->versions[i]; bool valid = false; for (auto& mcVer : m_filter->versions) { - // NOTE: Flame doesn't care about loader, so passing it changes nothing. - if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) { + if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) { valid = true; break; } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 5510c191..5a43e49a 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -55,7 +55,7 @@ class ModPage : public ResourcePage { virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, - std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0; + std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0; [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 49405a02..cb8f1920 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -17,7 +17,7 @@ #include "BuildConfig.h" #include "Json.h" -#include "net/Download.h" +#include "net/ApiDownload.h" #include "net/NetJob.h" #include "modplatform/ModIndex.h" @@ -102,7 +102,7 @@ QHash<int, QByteArray> ResourceModel::roleNames() const return roles; } -bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role) +bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); if (pos >= m_packs.size() || pos < 0 || !index.isValid()) @@ -132,6 +132,32 @@ void ResourceModel::search() if (hasActiveSearchJob()) return; + if (m_search_term.startsWith("#")) { + auto projectId = m_search_term.mid(1); + if (!projectId.isEmpty()) { + ResourceAPI::ProjectInfoCallbacks callbacks; + + callbacks.on_fail = [this](QString reason) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFailed(reason, -1); + }; + callbacks.on_abort = [this] { + if (!s_running_models.constFind(this).value()) + return; + searchRequestAborted(); + }; + + callbacks.on_succeed = [this](auto& doc, auto& pack) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestForOneSucceeded(doc); + }; + if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job) + runSearchJob(job); + return; + } + } auto args{ createSearchArguments() }; auto callbacks{ createSearchCallbacks() }; @@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry) // Use default if no callbacks are set if (!callbacks.on_succeed) - callbacks.on_succeed = [this, entry](auto& doc, auto pack) { + callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) { if (!s_running_models.constFind(this).value()) return; + auto pack = newpack; infoRequestSucceeded(doc, pack, entry); }; + if (!callbacks.on_fail) + callbacks.on_fail = [this](QString reason) { + if (!s_running_models.constFind(this).value()) + return; + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason)); + }; if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) runInfoJob(job); @@ -281,7 +314,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url) auto cache_entry = APPLICATION->metacache()->resolveEntry( metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); - auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); + auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); auto full_file_path = cache_entry->getFullPath(); connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { @@ -310,7 +343,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url) #define NEED_FOR_CALLBACK_ASSERT(name) \ Q_ASSERT_X(0 != 0, #name, "You NEED to re-implement this if you intend on using the default callbacks.") -QJsonArray ResourceModel::documentToArray(QJsonDocument& doc) const +QJsonArray ResourceModel::documentToArray([[maybe_unused]] QJsonDocument& doc) const { NEED_FOR_CALLBACK_ASSERT("documentToArray"); return {}; @@ -372,7 +405,28 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) endInsertRows(); } -void ResourceModel::searchRequestFailed(QString reason, int network_error_code) +void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc) +{ + ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>(); + + try { + auto obj = Json::requireObject(doc); + if (obj.contains("data")) + obj = Json::requireObject(obj, "data"); + loadIndexedPack(*pack, obj); + } catch (const JSONValidationError& e) { + qDebug() << doc; + qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); + } + + m_search_state = SearchState::Finished; + + beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1); + m_packs.append(pack); + endInsertRows(); +} + +void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) { switch (network_error_code) { default: diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 6533d9c6..ecf4f8f7 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -42,7 +42,10 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] virtual auto debugName() const -> QString; [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; - [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } + [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override + { + return parent.isValid() ? 0 : static_cast<int>(m_packs.size()); + } [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } @@ -146,6 +149,7 @@ class ResourceModel : public QAbstractListModel { private: /* Default search request callbacks */ void searchRequestSucceeded(QJsonDocument&); + void searchRequestForOneSucceeded(QJsonDocument&); void searchRequestFailed(QString reason, int network_error_code); void searchRequestAborted(); diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index 18c14bf8..d436f320 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -9,7 +9,8 @@ namespace ResourceDownload { ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) - : ResourceModel(api), m_base_instance(base_inst){}; + : ResourceModel(api), m_base_instance(base_inst) +{} /******** Make data requests ********/ diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 2925ec87..44a91003 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -4,7 +4,7 @@ /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> + * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,9 +44,6 @@ #include <QKeyEvent> #include "Markdown.h" -#include "ResourceDownloadTask.h" - -#include "minecraft/MinecraftInstance.h" #include "ui/dialogs/ResourceDownloadDialog.h" #include "ui/pages/modplatform/ResourceModel.h" @@ -283,7 +280,7 @@ void ResourcePage::updateVersionList() updateSelectionButton(); } -void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev) { if (!curr.isValid()) { return; @@ -310,9 +307,9 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) updateUi(); } -void ResourcePage::onVersionSelectionChanged(QString data) +void ResourcePage::onVersionSelectionChanged(QString versionData) { - if (data.isNull() || data.isEmpty()) { + if (versionData.isNull() || versionData.isEmpty()) { m_selected_version_index = -1; return; } @@ -398,7 +395,7 @@ void ResourcePage::openUrl(const QUrl& url) if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) { m_parent_dialog->selectPage(page); - auto newPage = m_parent_dialog->getSelectedPage(); + auto newPage = m_parent_dialog->selectedPage(); QLineEdit* searchEdit = newPage->m_ui->searchEdit; auto model = newPage->m_model; diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index b4a87f57..7bec0a37 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -97,7 +97,11 @@ class ResourcePage : public QWidget, public BasePage { virtual void openUrl(const QUrl&); /** Whether the version is opted out or not. Currently only makes sense in CF. */ - virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; + virtual bool optedOut(ModPlatform::IndexedVersion& ver) const + { + Q_UNUSED(ver); + return false; + }; public: BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index aabd3be6..8c913657 100644 --- a/launcher/ui |
