diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14c5b5e5..b6e179e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -546,11 +546,10 @@ jobs: submodules: 'true' - name: Build Flatpak (Linux) if: inputs.build_type == 'Debug' - uses: flatpak/flatpak-github-actions/flatpak-builder@v4 + uses: flatpak/flatpak-github-actions/flatpak-builder@v5 with: bundle: "Prism Launcher.flatpak" manifest-path: flatpak/org.prismlauncher.PrismLauncher.yml - cache-key: flatpak-${{ github.sha }}-x86_64 nix: runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index de9b6fe1..c7ba9e9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,6 +268,8 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) find_package(ghc_filesystem QUIET) endif() +include(ECMQtDeclareLoggingCategory) + ####################################### Program Info ####################################### set(Launcher_APP_BINARY_NAME "prismlauncher" CACHE STRING "Name of the Launcher binary") diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a0d92b6e..6ca88ec6 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -551,6 +551,24 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) +######## Logging categories ######## + +ecm_qt_declare_logging_category(CORE_SOURCES + HEADER Logging.h + IDENTIFIER authCredentials + CATEGORY_NAME "launcher.auth.credentials" + DEFAULT_SEVERITY Warning + DESCRIPTION "Secrets and credentials for debugging purposes" + EXPORT "${Launcher_Name}" +) + +if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this + ecm_qt_install_logging_categories( + EXPORT "${Launcher_Name}" + DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}" + ) +endif() + ################################ COMPILE ################################ set(LOGIC_SOURCES diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp index 47473899..f3d9ad56 100644 --- a/launcher/minecraft/auth/Parsers.cpp +++ b/launcher/minecraft/auth/Parsers.cpp @@ -1,5 +1,6 @@ #include "Parsers.h" #include "Json.h" +#include "Logging.h" #include #include @@ -75,9 +76,7 @@ bool getBool(QJsonValue value, bool & out) { bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString name) { qDebug() << "Parsing" << name <<":"; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if(jsonError.error) { @@ -137,9 +136,7 @@ bool parseXTokenResponse(QByteArray & data, Katabasis::Token &output, QString na bool parseMinecraftProfile(QByteArray & data, MinecraftProfile &output) { qDebug() << "Parsing Minecraft profile..."; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); @@ -275,9 +272,7 @@ decoded base64 "value": bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) { qDebug() << "Parsing Minecraft profile..."; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); @@ -389,9 +384,7 @@ bool parseMinecraftProfileMojang(QByteArray & data, MinecraftProfile &output) { bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) { qDebug() << "Parsing Minecraft entitlements..."; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); @@ -424,9 +417,7 @@ bool parseMinecraftEntitlements(QByteArray & data, MinecraftEntitlement &output) bool parseRolloutResponse(QByteArray & data, bool& result) { qDebug() << "Parsing Rollout response..."; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); @@ -455,9 +446,7 @@ bool parseRolloutResponse(QByteArray & data, bool& result) { bool parseMojangResponse(QByteArray & data, Katabasis::Token &output) { QJsonParseError jsonError; qDebug() << "Parsing Mojang response..."; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); if(jsonError.error) { qWarning() << "Failed to parse response from api.minecraftservices.com/launcher/login as JSON: " << jsonError.errorString(); diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.cpp b/launcher/minecraft/auth/steps/EntitlementsStep.cpp index f726244f..bd604292 100644 --- a/launcher/minecraft/auth/steps/EntitlementsStep.cpp +++ b/launcher/minecraft/auth/steps/EntitlementsStep.cpp @@ -3,6 +3,7 @@ #include #include +#include "Logging.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" @@ -41,9 +42,7 @@ void EntitlementsStep::onRequestDone( auto requestor = qobject_cast(QObject::sender()); requestor->deleteLater(); -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; // TODO: check presence of same entitlementsRequestId? // TODO: validate JWTs? diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 8c53f037..8a26cbe7 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -2,9 +2,10 @@ #include +#include "Logging.h" +#include "minecraft/auth/AccountTask.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" -#include "minecraft/auth/AccountTask.h" #include "net/NetUtils.h" LauncherLoginStep::LauncherLoginStep(AccountData* data) : AuthStep(data) { @@ -51,14 +52,10 @@ void LauncherLoginStep::onRequestDone( auto requestor = qobject_cast(QObject::sender()); requestor->deleteLater(); -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; if (error != QNetworkReply::NoError) { qWarning() << "Reply error:" << error; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; if (Net::isApplicationError(error)) { emit finished( AccountTaskState::STATE_FAILED_SOFT, @@ -76,9 +73,7 @@ void LauncherLoginStep::onRequestDone( if(!Parsers::parseMojangResponse(data, m_data->yggdrasilToken)) { qWarning() << "Could not parse login_with_xbox response..."; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; emit finished( AccountTaskState::STATE_FAILED_SOFT, tr("Failed to parse the Minecraft access token response.") diff --git a/launcher/minecraft/auth/steps/MSAStep.cpp b/launcher/minecraft/auth/steps/MSAStep.cpp index 16afcb42..6fc8d468 100644 --- a/launcher/minecraft/auth/steps/MSAStep.cpp +++ b/launcher/minecraft/auth/steps/MSAStep.cpp @@ -42,6 +42,7 @@ #include "minecraft/auth/Parsers.h" #include "Application.h" +#include "Logging.h" using OAuth2 = Katabasis::DeviceFlow; using Activity = Katabasis::Activity; @@ -117,14 +118,12 @@ void MSAStep::onOAuthActivityChanged(Katabasis::Activity activity) { // Succeeded or did not invalidate tokens emit hideVerificationUriAndCode(); QVariantMap extraTokens = m_oauth2->extraTokens(); -#ifndef NDEBUG if (!extraTokens.isEmpty()) { - qDebug() << "Extra tokens in response:"; + qCDebug(authCredentials()) << "Extra tokens in response:"; foreach (QString key, extraTokens.keys()) { - qDebug() << "\t" << key << ":" << extraTokens.value(key); + qCDebug(authCredentials()) << "\t" << key << ":" << extraTokens.value(key); } } -#endif emit finished(AccountTaskState::STATE_WORKING, tr("Got ")); return; } diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index b39b9326..6cfa7c1c 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -2,6 +2,7 @@ #include +#include "Logging.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" @@ -40,9 +41,7 @@ void MinecraftProfileStep::onRequestDone( auto requestor = qobject_cast(QObject::sender()); requestor->deleteLater(); -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; if (error == QNetworkReply::ContentNotFoundError) { // NOTE: Succeed even if we do not have a profile. This is a valid account state. if(m_data->type == AccountType::Mojang) { diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp index 6a1eb7a0..8c378588 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp @@ -2,6 +2,7 @@ #include +#include "Logging.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" @@ -43,9 +44,7 @@ void MinecraftProfileStepMojang::onRequestDone( auto requestor = qobject_cast(QObject::sender()); requestor->deleteLater(); -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; if (error == QNetworkReply::ContentNotFoundError) { // NOTE: Succeed even if we do not have a profile. This is a valid account state. if(m_data->type == AccountType::Mojang) { diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 14bde47e..b397b734 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -4,6 +4,7 @@ #include #include +#include "Logging.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" @@ -58,9 +59,7 @@ void XboxAuthorizationStep::onRequestDone( auto requestor = qobject_cast(QObject::sender()); requestor->deleteLater(); -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; if (error != QNetworkReply::NoError) { qWarning() << "Reply error:" << error; if (Net::isApplicationError(error)) { diff --git a/launcher/minecraft/auth/steps/XboxProfileStep.cpp b/launcher/minecraft/auth/steps/XboxProfileStep.cpp index 738fe1db..644c419b 100644 --- a/launcher/minecraft/auth/steps/XboxProfileStep.cpp +++ b/launcher/minecraft/auth/steps/XboxProfileStep.cpp @@ -3,7 +3,7 @@ #include #include - +#include "Logging.h" #include "minecraft/auth/AuthRequest.h" #include "minecraft/auth/Parsers.h" #include "net/NetUtils.h" @@ -56,9 +56,7 @@ void XboxProfileStep::onRequestDone( if (error != QNetworkReply::NoError) { qWarning() << "Reply error:" << error; -#ifndef NDEBUG - qDebug() << data; -#endif + qCDebug(authCredentials()) << data; if (Net::isApplicationError(error)) { emit finished( AccountTaskState::STATE_FAILED_SOFT, @@ -74,9 +72,7 @@ void XboxProfileStep::onRequestDone( return; } -#ifndef NDEBUG - qDebug() << "XBox profile: " << data; -#endif + qCDebug(authCredentials()) << "XBox profile: " << data; emit finished(AccountTaskState::STATE_WORKING, tr("Got Xbox profile")); } diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 39023f69..9cd0056c 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -44,6 +44,8 @@ #include "MetadataHandler.h" #include "Version.h" +static ModPlatform::ProviderCapabilities ProviderCaps; + Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() { m_enabled = (file.suffix() != "disabled"); @@ -91,6 +93,11 @@ std::pair Mod::compare(const Resource& other, SortType type) const if (this_ver < other_ver) return { -1, type == SortType::VERSION }; } + case SortType::PROVIDER: { + auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); + if (compare_result != 0) + return { compare_result, type == SortType::PROVIDER }; + } } return { 0, false }; } @@ -189,4 +196,11 @@ void Mod::finishResolvingWithDetails(ModDetails&& details) m_local_details = std::move(details); if (metadata) setMetadata(std::move(metadata)); +}; + +auto Mod::provider() const -> std::optional +{ + if (metadata()) + return ProviderCaps.readableName(metadata()->provider); + return {}; } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index f336bec4..8185c8fc 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -39,6 +39,8 @@ #include #include +#include + #include "Resource.h" #include "ModDetails.h" @@ -61,6 +63,7 @@ public: auto description() const -> QString; auto authors() const -> QStringList; auto status() const -> ModStatus; + auto provider() const -> std::optional; auto metadata() -> std::shared_ptr; auto metadata() const -> const std::shared_ptr; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 4ccc5d4d..f258ad69 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -48,10 +48,11 @@ #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" +#include "modplatform/ModIndex.h" ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed) { - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER }; } QVariant ModFolderModel::data(const QModelIndex &index, int role) const @@ -82,7 +83,15 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const } case DateColumn: return m_resources[row]->dateTimeChanged(); + case ProviderColumn: { + auto provider = at(row)->provider(); + if (!provider.has_value()) { + //: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...) + return tr("Unknown"); + } + return provider.value(); + } default: return QVariant(); } @@ -118,6 +127,8 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in return tr("Version"); case DateColumn: return tr("Last changed"); + case ProviderColumn: + return tr("Provider"); default: return QVariant(); } @@ -133,6 +144,8 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in return tr("The version of the mod."); case DateColumn: return tr("The date and time this mod was last changed (or added)."); + case ProviderColumn: + return tr("Where the mod was downloaded from."); default: return QVariant(); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 93980319..6898f6eb 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -67,6 +67,7 @@ public: NameColumn, VersionColumn, DateColumn, + ProviderColumn, NUM_COLUMNS }; enum ModStatusAction { diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index f9bd811e..0c37f3a3 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -20,7 +20,8 @@ enum class SortType { DATE, VERSION, ENABLED, - PACK_FORMAT + PACK_FORMAT, + PROVIDER }; enum class EnableAction { diff --git a/launcher/ui/widgets/ModListView.cpp b/launcher/ui/widgets/ModListView.cpp index d1860f57..09b03a76 100644 --- a/launcher/ui/widgets/ModListView.cpp +++ b/launcher/ui/widgets/ModListView.cpp @@ -14,6 +14,9 @@ */ #include "ModListView.h" + +#include "minecraft/mod/ModFolderModel.h" + #include #include #include @@ -62,4 +65,17 @@ void ModListView::setModel ( QAbstractItemModel* model ) for(int i = 1; i < head->count(); i++) head->setSectionResizeMode(i, QHeaderView::ResizeToContents); } + + auto real_model = model; + if (auto proxy_model = dynamic_cast(model); proxy_model) + real_model = proxy_model->sourceModel(); + + if (auto mod_model = dynamic_cast(real_model); mod_model) { + connect(mod_model, &ModFolderModel::updateFinished, this, [this, mod_model]{ + auto mods = mod_model->allMods(); + // Hide the 'Provider' column if no mod has a defined provider! + setColumnHidden(ModFolderModel::Columns::ProviderColumn, + std::none_of(mods.constBegin(), mods.constEnd(), [](auto const mod){ return mod->provider().has_value(); })); + }); + } } diff --git a/libraries/katabasis/CMakeLists.txt b/libraries/katabasis/CMakeLists.txt index f764feb6..643244ed 100644 --- a/libraries/katabasis/CMakeLists.txt +++ b/libraries/katabasis/CMakeLists.txt @@ -38,6 +38,15 @@ set( katabasis_PUBLIC include/katabasis/RequestParameter.h ) +ecm_qt_declare_logging_category(katabasis_PRIVATE + HEADER KatabasisLogging.h # NOTE: this won't be in src/, but CMAKE_BINARY_DIR/src isn't included by default so this should be fine + IDENTIFIER katabasisCredentials + CATEGORY_NAME "katabasis.credentials" + DEFAULT_SEVERITY Warning + DESCRIPTION "Secrets and credentials from Katabasis" + EXPORT "Katabasis" +) + add_library( Katabasis STATIC ${katabasis_PRIVATE} ${katabasis_PUBLIC} ) target_link_libraries(Katabasis Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network) diff --git a/libraries/katabasis/include/katabasis/DeviceFlow.h b/libraries/katabasis/include/katabasis/DeviceFlow.h index b68c92e0..0401df3c 100644 --- a/libraries/katabasis/include/katabasis/DeviceFlow.h +++ b/libraries/katabasis/include/katabasis/DeviceFlow.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/libraries/katabasis/src/DeviceFlow.cpp b/libraries/katabasis/src/DeviceFlow.cpp index f78fd620..f49fcb7d 100644 --- a/libraries/katabasis/src/DeviceFlow.cpp +++ b/libraries/katabasis/src/DeviceFlow.cpp @@ -19,9 +19,11 @@ #include "katabasis/PollServer.h" #include "katabasis/Globals.h" +#include "KatabasisLogging.h" #include "JsonResponse.h" namespace { + // ref: https://tools.ietf.org/html/rfc8628#section-3.2 // Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both. bool hasMandatoryDeviceAuthParams(const QVariantMap& params) @@ -333,9 +335,7 @@ QString DeviceFlow::refreshToken() { } void DeviceFlow::setRefreshToken(const QString &v) { -#ifndef NDEBUG - qDebug() << "DeviceFlow::setRefreshToken" << v << "..."; -#endif + qCDebug(katabasisCredentials) << "new refresh token:" << v; token_.refresh_token = v; } diff --git a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in index 13a860d9..96708960 100644 --- a/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in +++ b/program_info/org.prismlauncher.PrismLauncher.metainfo.xml.in @@ -19,38 +19,49 @@

Features:

  • Easily install game modifications, such as Fabric, Forge and Quilt
  • -
  • Control your Java settings
  • +
  • Easily install and update modpacks from the Launcher
  • +
  • Control your Java settings, and enable Mangohud or Gamemode with a toggle
  • Manage worlds and resource packs from the launcher
  • -
  • See logs and other details easily
  • +
  • See logs and other details easily through a dashboard
  • Kill Minecraft in case of a crash/freeze
  • Isolate Minecraft instances to keep everything clean
  • Install and update mods directly from the launcher
  • +
  • Customize the launcher with themes, and more
  • +
  • And cat :3
The main Prism Launcher window - https://prismlauncher.org/img/screenshots/LauncherDark.png + https://prismlauncher.org/img/screenshots/LauncherDark.png Modpack installation - https://prismlauncher.org/img/screenshots/ModpackInstallDark.png + https://prismlauncher.org/img/screenshots/ModpackInstallDark.png + + + Modpack updating + https://prismlauncher.org/img/screenshots/ModpackUpdateDark.png Mod installation - https://prismlauncher.org/img/screenshots/ModInstallDark.png + https://prismlauncher.org/img/screenshots/ModInstallDark.png Mod updating - https://prismlauncher.org/img/screenshots/ModUpdateDark.png + https://prismlauncher.org/img/screenshots/ModUpdateDark.png Instance management - https://prismlauncher.org/img/screenshots/PropertiesDark.png + https://prismlauncher.org/img/screenshots/PropertiesDark.png - Cat :) - https://prismlauncher.org/img/screenshots/LauncherCatDark.png + Cat :3 + https://prismlauncher.org/img/screenshots/LauncherCatDark.png + + + Customization + https://prismlauncher.org/img/screenshots/CustomizeDark.png