diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml index aee8f7b36..92e4d1ac3 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml @@ -119,17 +119,23 @@ TextWrapping="Wrap" > - - + + + TextWrapping="Wrap"> - + AppData = appData; + public string DynamicRichPresenceDescription => + AppData.HasDynamicRichPresenceSupport + ? AppData.RichPresenceSpec.Value.Description + : GameSpec.DefaultDescription; + public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version); public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer); public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension); diff --git a/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs b/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs index 747ead8ca..2658352d7 100644 --- a/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs +++ b/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs @@ -10,6 +10,7 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Utilities.Compat; +using Ryujinx.Ava.Utilities.PlayReport; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.Loaders.Processes.Extensions; @@ -35,9 +36,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary { _id = value; - Compatibility = CompatibilityCsv.Find(Id); + Compatibility = CompatibilityCsv.Find(value); + RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec) + ? gameSpec + : default(Optional); } } + public Optional RichPresenceSpec { get; set; } + public string Developer { get; set; } = "Unknown"; public string Version { get; set; } = "0"; public int PlayerCount { get; set; } @@ -46,7 +52,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public bool HasLdnGames => PlayerCount != 0 && GameCount != 0; public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString); - public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString); + public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue; public TimeSpan TimePlayed { get; set; } public DateTime? LastPlayed { get; set; } diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index cd4021bc4..8faf4fb31 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -20,6 +20,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport public IReadOnlyList Specs => new ReadOnlyCollection(_specs); + public GameSpec GetSpec(string titleId) => _specs.First(x => x.TitleIds.ContainsIgnoreCase(titleId)); + + public bool TryGetSpec(string titleId, out GameSpec gameSpec) + => (gameSpec = _specs.FirstOrDefault(x => x.TitleIds.ContainsIgnoreCase(titleId))) != null; + /// /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration. /// @@ -128,13 +133,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport { if (!playReport.ReportData.IsDictionary) return FormattedValue.Unhandled; - - if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) + + if (!TryGetSpec(runningGameId, out GameSpec spec)) return FormattedValue.Unhandled; foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority)) { - if (!formatSpec.Format(appMeta, playReport, out FormattedValue value)) + if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value)) continue; return value; diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 27330c808..7602c1de8 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -17,6 +17,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport .AddSpec( "01007ef00011e000", spec => spec + .WithDescription("based on being in Master Mode.") .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) // reset to normal status when switching between normal & master mode in title screen .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) @@ -24,34 +25,48 @@ namespace Ryujinx.Ava.Utilities.PlayReport .AddSpec( "0100f2c0115b6000", spec => spec + .WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).") .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) + .AddSpec( + "01002da013484000", + spec => spec + .WithDescription("based on how many Rupees you have.") + .AddValueFormatter("rupees", SkywardSwordHD_Rupees)) .AddSpec( "0100000000010000", - spec => - spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) + spec => spec + .WithDescription("based on if you're playing with Assist Mode.") + .AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) ) .AddSpec( "010075000ecbe000", - spec => - spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) + spec => spec + .WithDescription("based on if you're playing with Assist Mode.") + .AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) ) .AddSpec( "010028600ebda000", - spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) + spec => spec + .WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.") + .AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) ) .AddSpec( // Global & China IDs ["0100152000022000", "010075100e8ec000"], - spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode) + spec => spec + .WithDescription("based on what modes you're selecting in the menu & whether or not you're in a race.") + .AddValueFormatter("To", MarioKart8Deluxe_Mode) ) .AddSpec( ["0100a3d008c5c000", "01008f6008c5e000"], spec => spec + .WithDescription("based on what area of Paldea you're exploring.") .AddValueFormatter("area_no", PokemonSVArea) .AddValueFormatter("team_circle", PokemonSVUnionCircle) ) .AddSpec( "01006a800016e000", spec => spec + .WithDescription("based on what mode you're playing, who won, and what characters were present.") .AddSparseMultiValueFormatter( [ // Metadata to figure out what PlayReport we have. @@ -72,9 +87,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000", "010012f017576000", "0100c62011050000", "0100b3c014bda000" ], - spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) + spec => spec + .WithDescription("based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.") + .AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) ) - .AddSpec("01002da013484000", spec => spec.AddValueFormatter("rupees", SkywardSwordHD_Rupees)) ); private static string Playing(string game) => $"Playing {game}"; diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs index f1b94de68..c162d4c2c 100644 --- a/src/Ryujinx/Utilities/PlayReport/Specs.cs +++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs @@ -23,6 +23,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport public required string[] TitleIds { get; init; } + public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game."; + + private string _valueDescription; + + public string Description => _valueDescription ?? DefaultDescription; + + public GameSpec WithDescription(string description) + { + _valueDescription = description != null + ? $"Formats the details on your Discord presence {description}" + : null; + return this; + } + public List ValueFormatters { get; } = []; @@ -197,7 +211,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport public string[] ReportKeys { get; init; } public Delegate Formatter { get; init; } - public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, + public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, out FormattedValue formattedValue) { formattedValue = default;