From 5e5e180feae749cecd6f12e17c8da06681ac481a Mon Sep 17 00:00:00 2001 From: Piplup <100526773+piplup55@users.noreply.github.com> Date: Thu, 6 Feb 2025 00:32:27 +0000 Subject: [PATCH 01/10] PlayReportAnalyzer: Added Pokemon Scarlet and Violet (#630) Every base game location excluding buildings are done, DLC locations will be added at a later point --- src/Ryujinx/Utilities/PlayReport.cs | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/Ryujinx/Utilities/PlayReport.cs b/src/Ryujinx/Utilities/PlayReport.cs index f518fb902..7ebf53482 100644 --- a/src/Ryujinx/Utilities/PlayReport.cs +++ b/src/Ryujinx/Utilities/PlayReport.cs @@ -32,6 +32,12 @@ namespace Ryujinx.Ava.Utilities .AddSpec( // Global & China IDs ["0100152000022000", "010075100e8ec000"], spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode) + ) + .AddSpec( + ["0100a3d008c5c000", "01008f6008c5e000"], + spec => spec + .AddValueFormatter("area_no", PokemonSVArea) + .AddValueFormatter("team_circle", PokemonSVUnionCircle) ); private static PlayReportFormattedValue BreathOfTheWild_MasterMode(PlayReportValue value) @@ -81,5 +87,42 @@ namespace Ryujinx.Ava.Utilities "Race" => "Racing", _ => PlayReportFormattedValue.ForceReset }; + + private static PlayReportFormattedValue PokemonSVUnionCircle(PlayReportValue value) + => value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group"; + + private static PlayReportFormattedValue PokemonSVArea(PlayReportValue value) + => value.StringValue switch + { + // Base Game Locations + "a_w01" => "South Area One", + "a_w02" => "Mesagoza", + "a_w03" => "The Pokemon League", + "a_w04" => "South Area Two", + "a_w05" => "South Area Four", + "a_w06" => "South Area Six", + "a_w07" => "South Area Five", + "a_w08" => "South Area Three", + "a_w09" => "West Area One", + "a_w10" => "Asado Desert", + "a_w11" => "West Area Two", + "a_w12" => "Medali", + "a_w13" => "Tagtree Thicket", + "a_w14" => "East Area Three", + "a_w15" => "Artazon", + "a_w16" => "East Area Two", + "a_w18" => "Casseroya Lake", + "a_w19" => "Glaseado Mountain", + "a_w20" => "North Area Three", + "a_w21" => "North Area One", + "a_w22" => "North Area Two", + "a_w23" => "The Great Crater of Paldea", + "a_w24" => "South Paldean Sea", + "a_w25" => "West Paldean Sea", + "a_w26" => "East Paldean Sea", + "a_w27" => "Nouth Paldean Sea", + //TODO DLC Locations + _ => PlayReportFormattedValue.ForceReset + }; } } From c638a7daf8c1f33daaef8e8cbf5141cafd7945b5 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 5 Feb 2025 19:27:44 -0600 Subject: [PATCH 02/10] misc: chore: Move Play Report analyzer into a dedicated namespace and remove the PlayReport name prefix on types --- src/Ryujinx/DiscordIntegrationModule.cs | 10 +-- .../Analyzer.cs} | 74 +++++++++---------- .../PlayReports.cs} | 32 ++++---- 3 files changed, 56 insertions(+), 60 deletions(-) rename src/Ryujinx/Utilities/{PlayReportAnalyzer.cs => PlayReport/Analyzer.cs} (78%) rename src/Ryujinx/Utilities/{PlayReport.cs => PlayReport/PlayReports.cs} (76%) diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index 20b296511..d95bb80dd 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -4,16 +4,12 @@ using MsgPack; using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration; +using Ryujinx.Ava.Utilities.PlayReport; using Ryujinx.Common; -using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; using Ryujinx.HLE; using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Horizon; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; using System.Text; namespace Ryujinx.Ava @@ -130,8 +126,8 @@ namespace Ryujinx.Ava if (!TitleIDs.CurrentApplication.Value.HasValue) return; if (_discordPresencePlaying is null) return; - PlayReportAnalyzer.FormattedValue formattedValue = - PlayReport.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport); + Analyzer.FormattedValue formattedValue = + PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport); if (!formattedValue.Handled) return; diff --git a/src/Ryujinx/Utilities/PlayReportAnalyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs similarity index 78% rename from src/Ryujinx/Utilities/PlayReportAnalyzer.cs rename to src/Ryujinx/Utilities/PlayReport/Analyzer.cs index 47c36a396..ae5abbf9d 100644 --- a/src/Ryujinx/Utilities/PlayReportAnalyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -6,27 +6,27 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -namespace Ryujinx.Ava.Utilities +namespace Ryujinx.Ava.Utilities.PlayReport { /// /// The entrypoint for the Play Report analysis system. /// - public class PlayReportAnalyzer + public class Analyzer { - private readonly List _specs = []; + private readonly List _specs = []; /// /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration. /// /// The ID of the game to listen to Play Reports in. /// The configuration function for the analysis spec. - /// The current , for chaining convenience. - public PlayReportAnalyzer AddSpec(string titleId, Func transform) + /// The current , for chaining convenience. + public Analyzer AddSpec(string titleId, Func transform) { Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _), - $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); + $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); - _specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] })); + _specs.Add(transform(new GameSpec { TitleIds = [titleId] })); return this; } @@ -35,13 +35,13 @@ namespace Ryujinx.Ava.Utilities /// /// The ID of the game to listen to Play Reports in. /// The configuration function for the analysis spec. - /// The current , for chaining convenience. - public PlayReportAnalyzer AddSpec(string titleId, Action transform) + /// The current , for chaining convenience. + public Analyzer AddSpec(string titleId, Action transform) { Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _), - $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); + $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); - _specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform)); + _specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform)); return this; } @@ -50,15 +50,15 @@ namespace Ryujinx.Ava.Utilities /// /// The IDs of the games to listen to Play Reports in. /// The configuration function for the analysis spec. - /// The current , for chaining convenience. - public PlayReportAnalyzer AddSpec(IEnumerable titleIds, - Func transform) + /// The current , for chaining convenience. + public Analyzer AddSpec(IEnumerable titleIds, + Func transform) { string[] tids = titleIds.ToArray(); Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)), - $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); + $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); - _specs.Add(transform(new PlayReportGameSpec { TitleIds = [..tids] })); + _specs.Add(transform(new GameSpec { TitleIds = [..tids] })); return this; } @@ -67,20 +67,20 @@ namespace Ryujinx.Ava.Utilities /// /// The IDs of the games to listen to Play Reports in. /// The configuration function for the analysis spec. - /// The current , for chaining convenience. - public PlayReportAnalyzer AddSpec(IEnumerable titleIds, Action transform) + /// The current , for chaining convenience. + public Analyzer AddSpec(IEnumerable titleIds, Action transform) { string[] tids = titleIds.ToArray(); Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)), - $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(PlayReportGameSpec)}."); + $"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}."); - _specs.Add(new PlayReportGameSpec { TitleIds = [..tids] }.Apply(transform)); + _specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform)); return this; } /// - /// Runs the configured for the specified game title ID. + /// Runs the configured for the specified game title ID. /// /// The game currently running. /// The Application metadata information, including localized game name and play time information. @@ -95,15 +95,15 @@ namespace Ryujinx.Ava.Utilities if (!playReport.IsDictionary) return FormattedValue.Unhandled; - if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec)) + if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) return FormattedValue.Unhandled; - foreach (PlayReportGameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority)) + foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority)) { if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) continue; - return formatSpec.ValueFormatter(new PlayReportValue + return formatSpec.ValueFormatter(new Value { Application = appMeta, PackedValue = valuePackObject }); @@ -123,7 +123,7 @@ namespace Ryujinx.Ava.Utilities public bool Handled { get; private init; } /// - /// Did the handler request the caller of the to reset the existing value? + /// Did the handler request the caller of the to reset the existing value? /// public bool Reset { get; private init; } @@ -151,7 +151,7 @@ namespace Ryujinx.Ava.Utilities public static FormattedValue Unhandled => default; /// - /// Return this to suggest the caller reset the value it's using the for. + /// Return this to suggest the caller reset the value it's using the for. /// public static FormattedValue ForceReset => new() { Handled = true, Reset = true }; @@ -172,21 +172,21 @@ namespace Ryujinx.Ava.Utilities /// /// A mapping of title IDs to value formatter specs. /// - /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself. + /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself. /// - public class PlayReportGameSpec + public class GameSpec { public required string[] TitleIds { get; init; } public List SimpleValueFormatters { get; } = []; /// - /// Add a value formatter to the current + /// Add a value formatter to the current /// matching a specific key that could exist in a Play Report for the previously specified title IDs. /// /// The key name to match. /// The function which can return a potential formatted value. - /// The current , for chaining convenience. - public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter) + /// The current , for chaining convenience. + public GameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter) { SimpleValueFormatters.Add(new FormatterSpec { @@ -196,14 +196,14 @@ namespace Ryujinx.Ava.Utilities } /// - /// Add a value formatter at a specific priority to the current + /// Add a value formatter at a specific priority to the current /// matching a specific key that could exist in a Play Report for the previously specified title IDs. /// /// The resolution priority of this value formatter. Higher resolves sooner. /// The key name to match. /// The function which can return a potential formatted value. - /// The current , for chaining convenience. - public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, + /// The current , for chaining convenience. + public GameSpec AddValueFormatter(int priority, string reportKey, PlayReportValueFormatter valueFormatter) { SimpleValueFormatters.Add(new FormatterSpec @@ -229,7 +229,7 @@ namespace Ryujinx.Ava.Utilities /// containing the currently running application's , /// and the matched from the Play Report. /// - public class PlayReportValue + public class Value { /// /// The currently running application's . @@ -276,7 +276,7 @@ namespace Ryujinx.Ava.Utilities ///
/// a signal that nothing was available to handle it, ///
- /// OR a signal to reset the value that the caller is using the for. + /// OR a signal to reset the value that the caller is using the for. ///
- public delegate PlayReportAnalyzer.FormattedValue PlayReportValueFormatter(PlayReportValue value); + public delegate Analyzer.FormattedValue PlayReportValueFormatter(Value value); } diff --git a/src/Ryujinx/Utilities/PlayReport.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs similarity index 76% rename from src/Ryujinx/Utilities/PlayReport.cs rename to src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 7ebf53482..ce35cae89 100644 --- a/src/Ryujinx/Utilities/PlayReport.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -1,16 +1,16 @@ -using PlayReportFormattedValue = Ryujinx.Ava.Utilities.PlayReportAnalyzer.FormattedValue; +using static Ryujinx.Ava.Utilities.PlayReport.Analyzer; -namespace Ryujinx.Ava.Utilities +namespace Ryujinx.Ava.Utilities.PlayReport { - public static class PlayReport + public static class PlayReports { - public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer() + public static Analyzer Analyzer { get; } = new Analyzer() .AddSpec( "01007ef00011e000", spec => spec .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) // reset to normal status when switching between normal & master mode in title screen - .AddValueFormatter("AoCVer", PlayReportFormattedValue.AlwaysResets) + .AddValueFormatter("AoCVer", FormattedValue.AlwaysResets) ) .AddSpec( "0100f2c0115b6000", @@ -40,10 +40,10 @@ namespace Ryujinx.Ava.Utilities .AddValueFormatter("team_circle", PokemonSVUnionCircle) ); - private static PlayReportFormattedValue BreathOfTheWild_MasterMode(PlayReportValue value) - => value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset; + private static FormattedValue BreathOfTheWild_MasterMode(Value value) + => value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset; - private static PlayReportFormattedValue TearsOfTheKingdom_CurrentField(PlayReportValue value) => + private static FormattedValue TearsOfTheKingdom_CurrentField(Value value) => value.DoubleValue switch { > 800d => "Exploring the Sky Islands", @@ -51,16 +51,16 @@ namespace Ryujinx.Ava.Utilities _ => "Roaming Hyrule" }; - private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(PlayReportValue value) + private static FormattedValue SuperMarioOdyssey_AssistMode(Value value) => value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode"; - private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(PlayReportValue value) + private static FormattedValue SuperMarioOdysseyChina_AssistMode(Value value) => value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式"; - private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(PlayReportValue value) + private static FormattedValue SuperMario3DWorldOrBowsersFury(Value value) => value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury"; - private static PlayReportFormattedValue MarioKart8Deluxe_Mode(PlayReportValue value) + private static FormattedValue MarioKart8Deluxe_Mode(Value value) => value.StringValue switch { // Single Player @@ -85,13 +85,13 @@ namespace Ryujinx.Ava.Utilities "Battle" => "Battle Mode", "RaceStart" => "Selecting a Course", "Race" => "Racing", - _ => PlayReportFormattedValue.ForceReset + _ => FormattedValue.ForceReset }; - private static PlayReportFormattedValue PokemonSVUnionCircle(PlayReportValue value) + private static FormattedValue PokemonSVUnionCircle(Value value) => value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group"; - private static PlayReportFormattedValue PokemonSVArea(PlayReportValue value) + private static FormattedValue PokemonSVArea(Value value) => value.StringValue switch { // Base Game Locations @@ -122,7 +122,7 @@ namespace Ryujinx.Ava.Utilities "a_w26" => "East Paldean Sea", "a_w27" => "Nouth Paldean Sea", //TODO DLC Locations - _ => PlayReportFormattedValue.ForceReset + _ => FormattedValue.ForceReset }; } } From e55629a90855e02c6f1ca517c63c3f9fb781b905 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 5 Feb 2025 19:42:36 -0600 Subject: [PATCH 03/10] misc: chore: [ci skip] Play Report Analyzer: Added Multi Value formatters --- src/Ryujinx/Assets/locales.json | 2 +- src/Ryujinx/Utilities/PlayReport/Analyzer.cs | 63 ++++++++++++++++++- .../Utilities/PlayReport/PlayReports.cs | 8 ++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 8cf5b0d7c..5afb46c13 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -23698,4 +23698,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index ae5abbf9d..3d0963b5a 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -108,6 +108,25 @@ namespace Ryujinx.Ava.Utilities.PlayReport Application = appMeta, PackedValue = valuePackObject }); } + + foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority)) + { + List packedObjects = []; + foreach (var reportKey in formatSpec.ReportKeys) + { + if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) + continue; + + packedObjects.Add(valuePackObject); + } + + if (packedObjects.Count != formatSpec.ReportKeys.Length) + return FormattedValue.Unhandled; + + return formatSpec.ValueFormatter(packedObjects + .Select(packObject => new Value { Application = appMeta, PackedValue = packObject }) + .ToArray()); + } return FormattedValue.Unhandled; } @@ -178,6 +197,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport { public required string[] TitleIds { get; init; } public List SimpleValueFormatters { get; } = []; + public List MultiValueFormatters { get; } = []; /// /// Add a value formatter to the current @@ -212,6 +232,25 @@ namespace Ryujinx.Ava.Utilities.PlayReport }); return this; } + + public GameSpec AddMultiValueFormatter(string[] reportKeys, PlayReportMultiValueFormatter valueFormatter) + { + MultiValueFormatters.Add(new MultiFormatterSpec + { + Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter + }); + return this; + } + + public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys, + PlayReportMultiValueFormatter valueFormatter) + { + MultiValueFormatters.Add(new MultiFormatterSpec + { + Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter + }); + return this; + } /// /// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value. @@ -222,6 +261,16 @@ namespace Ryujinx.Ava.Utilities.PlayReport public required string ReportKey { get; init; } public PlayReportValueFormatter ValueFormatter { get; init; } } + + /// + /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values. + /// + public struct MultiFormatterSpec + { + public required int Priority { get; init; } + public required string[] ReportKeys { get; init; } + public PlayReportMultiValueFormatter ValueFormatter { get; init; } + } } /// @@ -269,7 +318,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport } /// - /// The delegate type that powers the entire analysis system (as it currently is).
+ /// The delegate type that powers single value formatters.
/// Takes in the result value from the Play Report, and outputs: ///
/// a formatted string, @@ -279,4 +328,16 @@ namespace Ryujinx.Ava.Utilities.PlayReport /// OR a signal to reset the value that the caller is using the for. ///
public delegate Analyzer.FormattedValue PlayReportValueFormatter(Value value); + + /// + /// The delegate type that powers multiple value formatters.
+ /// Takes in the result value from the Play Report, and outputs: + ///
+ /// a formatted string, + ///
+ /// a signal that nothing was available to handle it, + ///
+ /// OR a signal to reset the value that the caller is using the for. + ///
+ public delegate Analyzer.FormattedValue PlayReportMultiValueFormatter(Value[] value); } diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index ce35cae89..068d6bb9a 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -14,7 +14,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport ) .AddSpec( "0100f2c0115b6000", - spec => spec.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) + spec => spec + .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) .AddSpec( "0100000000010000", spec => @@ -40,6 +41,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport .AddValueFormatter("team_circle", PokemonSVUnionCircle) ); + private static FormattedValue Botw(Value[] values) + { + return $"{values[0].BoxedValue}, {values[1].BoxedValue}"; + } + private static FormattedValue BreathOfTheWild_MasterMode(Value value) => value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset; From 4a8f98126f38a3f82aa6e7385f9c686f4ca70a90 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 5 Feb 2025 19:45:29 -0600 Subject: [PATCH 04/10] [ci skip] remove test --- src/Ryujinx/Utilities/PlayReport/PlayReports.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 068d6bb9a..25457744e 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -41,11 +41,6 @@ namespace Ryujinx.Ava.Utilities.PlayReport .AddValueFormatter("team_circle", PokemonSVUnionCircle) ); - private static FormattedValue Botw(Value[] values) - { - return $"{values[0].BoxedValue}, {values[1].BoxedValue}"; - } - private static FormattedValue BreathOfTheWild_MasterMode(Value value) => value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset; From d1da937fce8861ea8b5de073394dc68f8b6a6eba Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Wed, 5 Feb 2025 19:51:43 -0600 Subject: [PATCH 05/10] misc: chore: [ci skip] XMLdocs on new Play Report Analyzer members --- src/Ryujinx/Utilities/PlayReport/Analyzer.cs | 45 +++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index 3d0963b5a..84bdbf085 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -132,7 +132,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport } /// - /// A potential formatted value returned by a . + /// A potential formatted value returned by a . /// public readonly struct FormattedValue { @@ -175,16 +175,16 @@ namespace Ryujinx.Ava.Utilities.PlayReport public static FormattedValue ForceReset => new() { Handled = true, Reset = true }; /// - /// A delegate singleton you can use to always return in a . + /// A delegate singleton you can use to always return in a . /// - public static readonly PlayReportValueFormatter AlwaysResets = _ => ForceReset; + public static readonly ValueFormatter AlwaysResets = _ => ForceReset; /// /// A delegate factory you can use to always return the specified - /// in a . + /// in a . /// /// The string to always return for this delegate instance. - public static PlayReportValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue; + public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue; } } @@ -206,7 +206,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport /// The key name to match. /// The function which can return a potential formatted value. /// The current , for chaining convenience. - public GameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter) + public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter) { SimpleValueFormatters.Add(new FormatterSpec { @@ -224,7 +224,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport /// The function which can return a potential formatted value. /// The current , for chaining convenience. public GameSpec AddValueFormatter(int priority, string reportKey, - PlayReportValueFormatter valueFormatter) + ValueFormatter valueFormatter) { SimpleValueFormatters.Add(new FormatterSpec { @@ -233,7 +233,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport return this; } - public GameSpec AddMultiValueFormatter(string[] reportKeys, PlayReportMultiValueFormatter valueFormatter) + /// + /// Add a multi-value formatter to the current + /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. + /// + /// The key names to match. + /// The function which can format the values. + /// The current , for chaining convenience. + public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter) { MultiValueFormatters.Add(new MultiFormatterSpec { @@ -242,8 +249,16 @@ namespace Ryujinx.Ava.Utilities.PlayReport return this; } + /// + /// Add a multi-value formatter at a specific priority to the current + /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. + /// + /// The resolution priority of this value formatter. Higher resolves sooner. + /// The key names to match. + /// The function which can format the values. + /// The current , for chaining convenience. public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys, - PlayReportMultiValueFormatter valueFormatter) + MultiValueFormatter valueFormatter) { MultiValueFormatters.Add(new MultiFormatterSpec { @@ -259,7 +274,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport { public required int Priority { get; init; } public required string ReportKey { get; init; } - public PlayReportValueFormatter ValueFormatter { get; init; } + public ValueFormatter ValueFormatter { get; init; } } /// @@ -269,12 +284,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport { public required int Priority { get; init; } public required string[] ReportKeys { get; init; } - public PlayReportMultiValueFormatter ValueFormatter { get; init; } + public MultiValueFormatter ValueFormatter { get; init; } } } /// - /// The input data to a , + /// The input data to a , /// containing the currently running application's , /// and the matched from the Play Report. /// @@ -294,7 +309,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport /// Access the as its underlying .NET type.
/// /// Does not seem to work well with comparing numeric types, - /// so use and the AsX (where X is a numerical type name i.e. Int32) methods for that. + /// so use XValue properties for that. ///
public object BoxedValue => PackedValue.ToObject(); @@ -327,7 +342,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport ///
/// OR a signal to reset the value that the caller is using the for. ///
- public delegate Analyzer.FormattedValue PlayReportValueFormatter(Value value); + public delegate Analyzer.FormattedValue ValueFormatter(Value value); /// /// The delegate type that powers multiple value formatters.
@@ -339,5 +354,5 @@ namespace Ryujinx.Ava.Utilities.PlayReport ///
/// OR a signal to reset the value that the caller is using the for. ///
- public delegate Analyzer.FormattedValue PlayReportMultiValueFormatter(Value[] value); + public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value); } From 54b233dd7871c1b1483e49de7a5c638c90d75e5e Mon Sep 17 00:00:00 2001 From: Daenorth Date: Thu, 6 Feb 2025 11:46:23 +0100 Subject: [PATCH 06/10] Updated the compat list. (#618) --- docs/compatibility.csv | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/compatibility.csv b/docs/compatibility.csv index 53ad389b6..0fd8eadca 100644 --- a/docs/compatibility.csv +++ b/docs/compatibility.csv @@ -332,6 +332,7 @@ 0100E680149DC000,"Arcaea",,playable,2023-03-16 19:31:21 01003C2010C78000,"Archaica: The Path Of Light",crash,nothing,2020-10-16 13:22:26 01004DA012976000,"Area 86",,playable,2020-12-16 16:45:52 +01008d8006a6a000,"Arena of Valor",crash,boots,2025-02-03 22:19:34 0100691013C46000,"ARIA CHRONICLE",,playable,2022-11-16 13:50:55 0100D4A00B284000,"ARK: Survival Evolved",gpu;nvdec;online-broken;UE4;ldn-untested,ingame,2024-04-16 00:53:56 0100C56012C96000,"Arkanoid vs. Space Invaders",services,ingame,2021-01-21 12:50:30 @@ -426,6 +427,7 @@ 0100E48013A34000,"Balan Wonderworld Demo",gpu;services;UE4;demo,ingame,2023-02-16 20:05:07 0100CD801CE5E000,"Balatro",,ingame,2024-04-21 02:01:53 010010A00DA48000,"Baldur's Gate and Baldur's Gate II: Enhanced Editions",32-bit,playable,2022-09-12 23:52:15 +0100fd1014726000,"Baldur's Gate: Dark Alliance",ldn-untested,ingame,2025-02-03 22:21:00 0100BC400FB64000,"Balthazar's Dream",,playable,2022-09-13 00:13:22 01008D30128E0000,"Bamerang",,playable,2022-10-26 00:29:39 010013C010C5C000,"Banner of the Maid",,playable,2021-06-14 15:23:37 @@ -528,6 +530,7 @@ 01005950022EC000,"Blade Strangers",nvdec,playable,2022-07-17 19:02:43 0100DF0011A6A000,"Bladed Fury",,playable,2022-10-26 11:36:26 0100CFA00CC74000,"Blades of Time",deadlock;online,boots,2022-07-17 19:19:58 +01003d700dd8a000,"Blades",,boots,2025-02-03 22:22:00 01006CC01182C000,"Blair Witch",nvdec;UE4,playable,2022-10-01 14:06:16 010039501405E000,"Blanc",gpu;slow,ingame,2023-02-22 14:00:13 0100698009C6E000,"Blasphemous",nvdec,playable,2021-03-01 12:15:31 @@ -955,7 +958,7 @@ 010012800EBAE000,"Disney TSUM TSUM FESTIVAL",crash,menus,2020-07-14 14:05:28 01009740120FE000,"DISTRAINT 2",,playable,2020-09-03 16:08:12 010075B004DD2000,"DISTRAINT: Deluxe Edition",,playable,2020-06-15 23:42:24 -010027400CDC6000,"Divinity: Original Sin 2 - Definitive Edition",services;crash;online-broken;regression,menus,2023-08-13 17:20:03 +010027400CDC6000,"Divinity: Original Sin 2 - Definitive Edition",services;crash;online-broken;regression,ingame,2025-02-03 22:12:30 01001770115C8000,"Dodo Peak",nvdec;UE4,playable,2022-10-04 16:13:05 010077B0100DA000,"Dogurai",,playable,2020-10-04 02:40:16 010048100D51A000,"Dokapon Up! Mugen no Roulette",gpu;Needs Update,menus,2022-12-08 19:39:10 @@ -1654,7 +1657,7 @@ 0100A73006E74000,"Legendary Eleven",,playable,2021-06-08 12:09:03 0100A7700B46C000,"Legendary Fishing",online,playable,2021-04-14 15:08:46 0100739018020000,"LEGO® 2K Drive",gpu;ldn-works,ingame,2024-04-09 02:05:12 -01003A30012C0000,"LEGO® CITY Undercover",nvdec,playable,2024-09-30 08:44:27 +010085500130a000,"LEGO® CITY Undercover",nvdec,playable,2024-09-30 08:44:27 010070D009FEC000,"LEGO® DC Super-Villains",,playable,2021-05-27 18:10:37 010052A00B5D2000,"LEGO® Harry Potter™ Collection",crash,ingame,2024-01-31 10:28:07 010073C01AF34000,"LEGO® Horizon Adventures™",vulkan-backend-bug;opengl-backend-bug;UE4,ingame,2025-01-07 04:24:56 @@ -1913,6 +1916,7 @@ 010073E008E6E000,"Mugsters",,playable,2021-01-28 17:57:17 0100A8400471A000,"MUJO",,playable,2020-05-08 16:31:04 0100211005E94000,"Mulaka",,playable,2021-01-28 18:07:20 +01008e2013fb4000,"Multi Quiz",ldn-untested,ingame,2025-02-03 22:26:00 010038B00B9AE000,"Mummy Pinball",,playable,2022-08-05 16:08:11 01008E200C5C2000,"Muse Dash",,playable,2020-06-06 14:41:29 010035901046C000,"Mushroom Quest",,playable,2020-05-17 13:07:08 @@ -2028,6 +2032,7 @@ 010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26 0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29 0100C9A00ECE6000,"Nintendo 64™ – Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07 +0100e0601c632000,"Nintendo 64™ – Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00 0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06 0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07 01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11 @@ -2532,7 +2537,7 @@ 0100C3E00B700000,"SEGA AGES Space Harrier",,playable,2021-01-11 12:57:40 010054400D2E6000,"SEGA AGES Virtua Racing",online-broken,playable,2023-01-29 17:08:39 01001E700AC60000,"SEGA AGES Wonder Boy: Monster Land",online,playable,2021-05-05 16:28:25 -0100B3C014BDA000,"SEGA Genesis™ – Nintendo Switch Online",crash;regression,nothing,2022-04-11 07:27:21 +0100B3C014BDA000,"SEGA Genesis™ – Nintendo Switch Online",crash;regression,ingame,2025-02-03 22:13:30 0100F7300B24E000,"SEGA Mega Drive Classics",online,playable,2021-01-05 11:08:00 01009840046BC000,"Semispheres",,playable,2021-01-06 23:08:31 0100D1800D902000,"SENRAN KAGURA Peach Ball",,playable,2021-06-03 15:12:10 @@ -2964,6 +2969,7 @@ 0100C38004DCC000,"The Flame In The Flood: Complete Edition",gpu;nvdec;UE4,ingame,2022-08-22 16:23:49 010007700D4AC000,"The Forbidden Arts",,playable,2021-01-26 16:26:24 010030700CBBC000,"The friends of Ringo Ishikawa",,playable,2022-08-22 16:33:17 +0100b620139d8000,"The Game of Life 2",ldn-untested,ingame,2025-02-03 22:30:00 01006350148DA000,"The Gardener and the Wild Vines",gpu,ingame,2024-04-29 16:32:10 0100B13007A6A000,"The Gardens Between",,playable,2021-01-29 16:16:53 010036E00FB20000,"The Great Ace Attorney Chronicles",,playable,2023-06-22 21:26:29 @@ -2981,6 +2987,8 @@ 010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40 0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06 0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34 +01006fe0096ac000,"The Jackbox Party Pack 5",ldn-untested,boots,2025-02-03 22:32:00 +01005a400db52000,"The Jackbox Party Pack 6",ldn-untested,boots,2025-02-03 22:32:00 010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41 01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27 01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13 @@ -3159,6 +3167,7 @@ 010055E00CA68000,"Trine 4: The Nightmare Prince",gpu,nothing,2025-01-07 05:47:46 0100D9000A930000,"Trine Enchanted Edition",ldn-untested;nvdec,playable,2021-06-03 11:28:15 01002D7010A54000,"Trinity Trigger",crash,ingame,2023-03-03 03:09:09 +010020700a5e0000,"TRIVIAL PURSUIT Live!",ldn-untested,ingame,2025-02-03 22:35:00 0100868013FFC000,"TRIVIAL PURSUIT Live! 2",,boots,2022-12-19 00:04:33 0100F78002040000,"Troll and I™",gpu;nvdec,ingame,2021-06-04 16:58:50 0100145011008000,"Trollhunters: Defenders of Arcadia",gpu;nvdec,ingame,2020-11-30 13:27:09 @@ -3208,6 +3217,7 @@ 0100AB2010B4C000,"Unlock The King",,playable,2020-09-01 13:58:27 0100A3E011CB0000,"Unlock the King 2",,playable,2021-06-15 20:43:55 01005AA00372A000,"UNO® for Nintendo Switch",nvdec;ldn-untested,playable,2022-07-28 14:49:47 +0100b6e012ebe000,"UNO",ldn-untested,ingame,2025-02-03 22:40:00 0100E5D00CC0C000,"Unravel Two",nvdec,playable,2024-05-23 15:45:05 010001300CC4A000,"Unruly Heroes",,playable,2021-01-07 18:09:31 0100B410138C0000,"Unspottable",,playable,2022-10-25 19:28:49 @@ -3372,6 +3382,7 @@ 0100F47016F26000,"Yomawari 3",,playable,2022-05-10 08:26:51 010012F00B6F2000,"Yomawari: The Long Night Collection",,playable,2022-09-03 14:36:59 0100CC600ABB2000,"Yonder: The Cloud Catcher Chronicles (Retail Only)",,playable,2021-01-28 14:06:25 +0100534009ff2000,"Yonder: The Cloud Catcher Chronicles",,playable,2025-02-03 22:19:13 0100BE50042F6000,"Yono and the Celestial Elephants",,playable,2021-01-28 18:23:58 0100F110029C8000,"Yooka-Laylee",,playable,2021-01-28 14:21:45 010022F00DA66000,"Yooka-Laylee and the Impossible Lair",,playable,2021-03-05 17:32:21 From a4211fec33afd135958238f903484c024a39211a Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Thu, 6 Feb 2025 22:56:25 -0600 Subject: [PATCH 07/10] UI: Properly space the play time & last play date in the game info popup --- src/Ryujinx/Assets/locales.json | 72 +++++++++---------- .../UI/Controls/ApplicationDataView.axaml | 45 +++++++----- .../UI/ViewModels/ApplicationDataViewModel.cs | 3 - 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 5afb46c13..f30aac3bc 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -1576,50 +1576,50 @@ "ID": "GameListHeaderTimePlayed", "Translations": { "ar_SA": "", - "de_DE": "Spielzeit: {0}", - "el_GR": "Χρόνος: {0}", - "en_US": "Play Time: {0}", - "es_ES": "Tiempo jugado: {0}", - "fr_FR": "Temps de jeu: {0}", + "de_DE": "Spielzeit:", + "el_GR": "Χρόνος:", + "en_US": "Play Time:", + "es_ES": "Tiempo jugado:", + "fr_FR": "Temps de jeu:", "he_IL": "", - "it_IT": "Tempo di gioco: {0}", - "ja_JP": "プレイ時間: {0}", - "ko_KR": "플레이 타임: {0}", - "no_NO": "Spilletid: {0}", - "pl_PL": "Czas w grze: {0}", - "pt_BR": "Tempo de jogo: {0}", - "ru_RU": "Время в игре: {0}", - "sv_SE": "Speltid: {0}", - "th_TH": "เล่นไปแล้ว: {0}", - "tr_TR": "Oynama Süresi: {0}", - "uk_UA": "Зіграно часу: {0}", - "zh_CN": "游玩时长: {0}", - "zh_TW": "遊玩時數: {0}" + "it_IT": "Tempo di gioco:", + "ja_JP": "プレイ時間:", + "ko_KR": "플레이 타임:", + "no_NO": "Spilletid:", + "pl_PL": "Czas w grze:", + "pt_BR": "Tempo de jogo:", + "ru_RU": "Время в игре:", + "sv_SE": "Speltid:", + "th_TH": "เล่นไปแล้ว:", + "tr_TR": "Oynama Süresi:", + "uk_UA": "Зіграно часу:", + "zh_CN": "游玩时长:", + "zh_TW": "遊玩時數:" } }, { "ID": "GameListHeaderLastPlayed", "Translations": { "ar_SA": "", - "de_DE": "Zuletzt gespielt: {0}", - "el_GR": "Παίχτηκε: {0}", - "en_US": "Last Played: {0}", - "es_ES": "Jugado por última vez: {0}", - "fr_FR": "Dernière partie jouée: {0}", + "de_DE": "Zuletzt gespielt: ", + "el_GR": "Παίχτηκε: ", + "en_US": "Last Played:", + "es_ES": "Jugado por última vez:", + "fr_FR": "Dernière partie jouée:", "he_IL": "", - "it_IT": "Ultima partita: {0}", - "ja_JP": "最終プレイ日時: {0}", - "ko_KR": "마지막 플레이: {0}", - "no_NO": "Sist Spilt: {0}", - "pl_PL": "Ostatnio grane: {0}", - "pt_BR": "Último jogo: {0}", - "ru_RU": "Последний запуск: {0}", - "sv_SE": "Senast spelad: {0}", - "th_TH": "เล่นล่าสุด: {0}", - "tr_TR": "Son Oynama Tarihi: {0}", - "uk_UA": "Востаннє зіграно: {0}", - "zh_CN": "最近游玩: {0}", - "zh_TW": "最近遊玩: {0}" + "it_IT": "Ultima partita:", + "ja_JP": "最終プレイ日時:", + "ko_KR": "마지막 플레이:", + "no_NO": "Sist Spilt:", + "pl_PL": "Ostatnio grane:", + "pt_BR": "Último jogo:", + "ru_RU": "Последний запуск:", + "sv_SE": "Senast spelad:", + "th_TH": "เล่นล่าสุด:", + "tr_TR": "Son Oynama Tarihi:", + "uk_UA": "Востаннє зіграно:", + "zh_CN": "最近游玩:", + "zh_TW": "最近遊玩:" } }, { diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml index 45ae75639..a0b6ad7b3 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml @@ -92,22 +92,35 @@ TextAlignment="Start" TextWrapping="Wrap" /> - - - + + + + + + + + + diff --git a/src/Ryujinx/UI/ViewModels/ApplicationDataViewModel.cs b/src/Ryujinx/UI/ViewModels/ApplicationDataViewModel.cs index 9e0a3554a..592d5f81a 100644 --- a/src/Ryujinx/UI/ViewModels/ApplicationDataViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/ApplicationDataViewModel.cs @@ -12,10 +12,7 @@ namespace Ryujinx.Ava.UI.ViewModels 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); - public string FormattedLastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed].Format(AppData.LastPlayedString); - public string FormattedPlayTime => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed].Format(AppData.TimePlayedString); public string FormattedFileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize].Format(AppData.FileSizeString); public string FormattedLdnInfo => From 2c8edaf89e29b33c5f3021269b58345f47cec24f Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 7 Feb 2025 15:43:50 -0600 Subject: [PATCH 08/10] PlayReport: Add Sparse Multi Value formatters --- src/Ryujinx/DiscordIntegrationModule.cs | 10 +- src/Ryujinx/Utilities/PlayReport/Analyzer.cs | 259 ++---------------- src/Ryujinx/Utilities/PlayReport/Delegates.cs | 42 +++ .../Utilities/PlayReport/PlayReports.cs | 6 +- src/Ryujinx/Utilities/PlayReport/Specs.cs | 140 ++++++++++ src/Ryujinx/Utilities/PlayReport/Value.cs | 130 +++++++++ 6 files changed, 343 insertions(+), 244 deletions(-) create mode 100644 src/Ryujinx/Utilities/PlayReport/Delegates.cs create mode 100644 src/Ryujinx/Utilities/PlayReport/Specs.cs create mode 100644 src/Ryujinx/Utilities/PlayReport/Value.cs diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index d95bb80dd..abdd9fed1 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -126,14 +126,16 @@ namespace Ryujinx.Ava if (!TitleIDs.CurrentApplication.Value.HasValue) return; if (_discordPresencePlaying is null) return; - Analyzer.FormattedValue formattedValue = + FormattedValue formattedValue = PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport); if (!formattedValue.Handled) return; - _discordPresencePlaying.Details = formattedValue.Reset - ? $"Playing {_currentApp.Title}" - : formattedValue.FormattedString; + _discordPresencePlaying.Details = TruncateToByteLength( + formattedValue.Reset + ? $"Playing {_currentApp.Title}" + : formattedValue.FormattedString + ); if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details)) return; //don't trigger an update if the set presence Details are identical to current diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index 84bdbf085..390e06d28 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -78,7 +78,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport return this; } - + /// /// Runs the configured for the specified game title ID. /// @@ -98,261 +98,48 @@ namespace Ryujinx.Ava.Utilities.PlayReport if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) return FormattedValue.Unhandled; - foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority)) + foreach (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority)) { if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) continue; - return formatSpec.ValueFormatter(new Value - { - Application = appMeta, PackedValue = valuePackObject - }); + return formatSpec.Formatter(new Value { Application = appMeta, PackedValue = valuePackObject }); } - - foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority)) + + foreach (MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority)) { List packedObjects = []; foreach (var reportKey in formatSpec.ReportKeys) { if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) continue; - + packedObjects.Add(valuePackObject); } - + if (packedObjects.Count != formatSpec.ReportKeys.Length) return FormattedValue.Unhandled; - - return formatSpec.ValueFormatter(packedObjects + + return formatSpec.Formatter(packedObjects .Select(packObject => new Value { Application = appMeta, PackedValue = packObject }) .ToArray()); } + foreach (SparseMultiFormatterSpec formatSpec in spec.SparseMultiValueFormatters.OrderBy(x => x.Priority)) + { + Dictionary packedObjects = []; + foreach (var reportKey in formatSpec.ReportKeys) + { + if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) + continue; + + packedObjects.Add(reportKey, new Value { Application = appMeta, PackedValue = valuePackObject }); + } + + return formatSpec.Formatter(packedObjects); + } + return FormattedValue.Unhandled; } - - /// - /// A potential formatted value returned by a . - /// - public readonly struct FormattedValue - { - /// - /// Was any handler able to match anything in the Play Report? - /// - public bool Handled { get; private init; } - - /// - /// Did the handler request the caller of the to reset the existing value? - /// - public bool Reset { get; private init; } - - /// - /// The formatted value, only present if is true, and is false. - /// - public string FormattedString { get; private init; } - - /// - /// The intended path of execution for having a string to return: simply return the string. - /// This implicit conversion will make the struct for you.

- /// - /// If the input is null, is returned. - ///
- /// The formatted string value. - /// The automatically constructed struct. - public static implicit operator FormattedValue(string formattedValue) - => formattedValue is not null - ? new FormattedValue { Handled = true, FormattedString = formattedValue } - : Unhandled; - - /// - /// Return this to tell the caller there is no value to return. - /// - public static FormattedValue Unhandled => default; - - /// - /// Return this to suggest the caller reset the value it's using the for. - /// - public static FormattedValue ForceReset => new() { Handled = true, Reset = true }; - - /// - /// A delegate singleton you can use to always return in a . - /// - public static readonly ValueFormatter AlwaysResets = _ => ForceReset; - - /// - /// A delegate factory you can use to always return the specified - /// in a . - /// - /// The string to always return for this delegate instance. - public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue; - } } - - /// - /// A mapping of title IDs to value formatter specs. - /// - /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself. - /// - public class GameSpec - { - public required string[] TitleIds { get; init; } - public List SimpleValueFormatters { get; } = []; - public List MultiValueFormatters { get; } = []; - - /// - /// Add a value formatter to the current - /// matching a specific key that could exist in a Play Report for the previously specified title IDs. - /// - /// The key name to match. - /// The function which can return a potential formatted value. - /// The current , for chaining convenience. - public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter) - { - SimpleValueFormatters.Add(new FormatterSpec - { - Priority = SimpleValueFormatters.Count, ReportKey = reportKey, ValueFormatter = valueFormatter - }); - return this; - } - - /// - /// Add a value formatter at a specific priority to the current - /// matching a specific key that could exist in a Play Report for the previously specified title IDs. - /// - /// The resolution priority of this value formatter. Higher resolves sooner. - /// The key name to match. - /// The function which can return a potential formatted value. - /// The current , for chaining convenience. - public GameSpec AddValueFormatter(int priority, string reportKey, - ValueFormatter valueFormatter) - { - SimpleValueFormatters.Add(new FormatterSpec - { - Priority = priority, ReportKey = reportKey, ValueFormatter = valueFormatter - }); - return this; - } - - /// - /// Add a multi-value formatter to the current - /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. - /// - /// The key names to match. - /// The function which can format the values. - /// The current , for chaining convenience. - public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter) - { - MultiValueFormatters.Add(new MultiFormatterSpec - { - Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter - }); - return this; - } - - /// - /// Add a multi-value formatter at a specific priority to the current - /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. - /// - /// The resolution priority of this value formatter. Higher resolves sooner. - /// The key names to match. - /// The function which can format the values. - /// The current , for chaining convenience. - public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys, - MultiValueFormatter valueFormatter) - { - MultiValueFormatters.Add(new MultiFormatterSpec - { - Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter - }); - return this; - } - - /// - /// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value. - /// - public struct FormatterSpec - { - public required int Priority { get; init; } - public required string ReportKey { get; init; } - public ValueFormatter ValueFormatter { get; init; } - } - - /// - /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values. - /// - public struct MultiFormatterSpec - { - public required int Priority { get; init; } - public required string[] ReportKeys { get; init; } - public MultiValueFormatter ValueFormatter { get; init; } - } - } - - /// - /// The input data to a , - /// containing the currently running application's , - /// and the matched from the Play Report. - /// - public class Value - { - /// - /// The currently running application's . - /// - public ApplicationMetadata Application { get; init; } - - /// - /// The matched value from the Play Report. - /// - public MessagePackObject PackedValue { get; init; } - - /// - /// Access the as its underlying .NET type.
- /// - /// Does not seem to work well with comparing numeric types, - /// so use XValue properties for that. - ///
- public object BoxedValue => PackedValue.ToObject(); - - #region AsX accessors - - public bool BooleanValue => PackedValue.AsBoolean(); - public byte ByteValye => PackedValue.AsByte(); - public sbyte SByteValye => PackedValue.AsSByte(); - public short ShortValye => PackedValue.AsInt16(); - public ushort UShortValye => PackedValue.AsUInt16(); - public int IntValye => PackedValue.AsInt32(); - public uint UIntValye => PackedValue.AsUInt32(); - public long LongValye => PackedValue.AsInt64(); - public ulong ULongValye => PackedValue.AsUInt64(); - public float FloatValue => PackedValue.AsSingle(); - public double DoubleValue => PackedValue.AsDouble(); - public string StringValue => PackedValue.AsString(); - public Span BinaryValue => PackedValue.AsBinary(); - - #endregion - } - - /// - /// The delegate type that powers single value formatters.
- /// Takes in the result value from the Play Report, and outputs: - ///
- /// a formatted string, - ///
- /// a signal that nothing was available to handle it, - ///
- /// OR a signal to reset the value that the caller is using the for. - ///
- public delegate Analyzer.FormattedValue ValueFormatter(Value value); - - /// - /// The delegate type that powers multiple value formatters.
- /// Takes in the result value from the Play Report, and outputs: - ///
- /// a formatted string, - ///
- /// a signal that nothing was available to handle it, - ///
- /// OR a signal to reset the value that the caller is using the for. - ///
- public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value); } diff --git a/src/Ryujinx/Utilities/PlayReport/Delegates.cs b/src/Ryujinx/Utilities/PlayReport/Delegates.cs new file mode 100644 index 000000000..7c8952e18 --- /dev/null +++ b/src/Ryujinx/Utilities/PlayReport/Delegates.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace Ryujinx.Ava.Utilities.PlayReport +{ + /// + /// The delegate type that powers single value formatters.
+ /// Takes in the result value from the Play Report, and outputs: + ///
+ /// a formatted string, + ///
+ /// a signal that nothing was available to handle it, + ///
+ /// OR a signal to reset the value that the caller is using the for. + ///
+ public delegate FormattedValue ValueFormatter(Value value); + + /// + /// The delegate type that powers multiple value formatters.
+ /// Takes in the result values from the Play Report, and outputs: + ///
+ /// a formatted string, + ///
+ /// a signal that nothing was available to handle it, + ///
+ /// OR a signal to reset the value that the caller is using the for. + ///
+ public delegate FormattedValue MultiValueFormatter(Value[] value); + + /// + /// The delegate type that powers multiple value formatters. + /// The dictionary passed to this delegate is sparsely populated; + /// that is, not every key specified in the Play Report needs to match for this to be used.
+ /// Takes in the result values from the Play Report, and outputs: + ///
+ /// a formatted string, + ///
+ /// a signal that nothing was available to handle it, + ///
+ /// OR a signal to reset the value that the caller is using the for. + ///
+ public delegate FormattedValue SparseMultiValueFormatter(Dictionary values); +} diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 25457744e..ae954c81c 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -1,6 +1,4 @@ -using static Ryujinx.Ava.Utilities.PlayReport.Analyzer; - -namespace Ryujinx.Ava.Utilities.PlayReport +namespace Ryujinx.Ava.Utilities.PlayReport { public static class PlayReports { @@ -10,7 +8,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport spec => spec .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) // reset to normal status when switching between normal & master mode in title screen - .AddValueFormatter("AoCVer", FormattedValue.AlwaysResets) + .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) ) .AddSpec( "0100f2c0115b6000", diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs new file mode 100644 index 000000000..649813b7a --- /dev/null +++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs @@ -0,0 +1,140 @@ +using FluentAvalonia.Core; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Ava.Utilities.PlayReport +{ + /// + /// A mapping of title IDs to value formatter specs. + /// + /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself. + /// + public class GameSpec + { + public required string[] TitleIds { get; init; } + public List SimpleValueFormatters { get; } = []; + public List MultiValueFormatters { get; } = []; + public List SparseMultiValueFormatters { get; } = []; + + + /// + /// Add a value formatter to the current + /// matching a specific key that could exist in a Play Report for the previously specified title IDs. + /// + /// The key name to match. + /// The function which can return a potential formatted value. + /// The current , for chaining convenience. + public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter) + => AddValueFormatter(SimpleValueFormatters.Count, reportKey, valueFormatter); + + /// + /// Add a value formatter at a specific priority to the current + /// matching a specific key that could exist in a Play Report for the previously specified title IDs. + /// + /// The resolution priority of this value formatter. Higher resolves sooner. + /// The key name to match. + /// The function which can return a potential formatted value. + /// The current , for chaining convenience. + public GameSpec AddValueFormatter(int priority, string reportKey, + ValueFormatter valueFormatter) + { + SimpleValueFormatters.Add(new FormatterSpec + { + Priority = priority, ReportKey = reportKey, Formatter = valueFormatter + }); + return this; + } + + /// + /// Add a multi-value formatter to the current + /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. + /// + /// The key names to match. + /// The function which can format the values. + /// The current , for chaining convenience. + public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter) + => AddMultiValueFormatter(MultiValueFormatters.Count, reportKeys, valueFormatter); + + /// + /// Add a multi-value formatter at a specific priority to the current + /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. + /// + /// The resolution priority of this value formatter. Higher resolves sooner. + /// The key names to match. + /// The function which can format the values. + /// The current , for chaining convenience. + public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys, + MultiValueFormatter valueFormatter) + { + MultiValueFormatters.Add(new MultiFormatterSpec + { + Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter + }); + return this; + } + + /// + /// Add a multi-value formatter to the current + /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. + ///

+ /// The 'Sparse' multi-value formatters do not require every key to be present. + /// If you need this requirement, use . + ///
+ /// The key names to match. + /// The function which can format the values. + /// The current , for chaining convenience. + public GameSpec AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter) + => AddSparseMultiValueFormatter(SparseMultiValueFormatters.Count, reportKeys, valueFormatter); + + /// + /// Add a multi-value formatter at a specific priority to the current + /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs. + ///

+ /// The 'Sparse' multi-value formatters do not require every key to be present. + /// If you need this requirement, use . + ///
+ /// The resolution priority of this value formatter. Higher resolves sooner. + /// The key names to match. + /// The function which can format the values. + /// The current , for chaining convenience. + public GameSpec AddSparseMultiValueFormatter(int priority, string[] reportKeys, + SparseMultiValueFormatter valueFormatter) + { + SparseMultiValueFormatters.Add(new SparseMultiFormatterSpec + { + Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter + }); + return this; + } + } + + /// + /// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value. + /// + public struct FormatterSpec + { + public required int Priority { get; init; } + public required string ReportKey { get; init; } + public ValueFormatter Formatter { get; init; } + } + + /// + /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values. + /// + public struct MultiFormatterSpec + { + public required int Priority { get; init; } + public required string[] ReportKeys { get; init; } + public MultiValueFormatter Formatter { get; init; } + } + + /// + /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their sparsely populated potential values. + /// + public struct SparseMultiFormatterSpec + { + public required int Priority { get; init; } + public required string[] ReportKeys { get; init; } + public SparseMultiValueFormatter Formatter { get; init; } + } +} diff --git a/src/Ryujinx/Utilities/PlayReport/Value.cs b/src/Ryujinx/Utilities/PlayReport/Value.cs new file mode 100644 index 000000000..46d47366d --- /dev/null +++ b/src/Ryujinx/Utilities/PlayReport/Value.cs @@ -0,0 +1,130 @@ +using MsgPack; +using Ryujinx.Ava.Utilities.AppLibrary; +using System; + +namespace Ryujinx.Ava.Utilities.PlayReport +{ + /// + /// The input data to a , + /// containing the currently running application's , + /// and the matched from the Play Report. + /// + public class Value + { + /// + /// The currently running application's . + /// + public ApplicationMetadata Application { get; init; } + + /// + /// The matched value from the Play Report. + /// + public MessagePackObject PackedValue { get; init; } + + /// + /// Access the as its underlying .NET type.
+ /// + /// Does not seem to work well with comparing numeric types, + /// so use XValue properties for that. + ///
+ public object BoxedValue => PackedValue.ToObject(); + + public override string ToString() + { + object boxed = BoxedValue; + return boxed == null + ? "null" + : boxed.ToString(); + } + + #region AsX accessors + + public bool BooleanValue => PackedValue.AsBoolean(); + public byte ByteValue => PackedValue.AsByte(); + public sbyte SByteValue => PackedValue.AsSByte(); + public short ShortValue => PackedValue.AsInt16(); + public ushort UShortValue => PackedValue.AsUInt16(); + public int IntValue => PackedValue.AsInt32(); + public uint UIntValue => PackedValue.AsUInt32(); + public long LongValue => PackedValue.AsInt64(); + public ulong ULongValue => PackedValue.AsUInt64(); + public float FloatValue => PackedValue.AsSingle(); + public double DoubleValue => PackedValue.AsDouble(); + public string StringValue => PackedValue.AsString(); + public Span BinaryValue => PackedValue.AsBinary(); + + #endregion + } + + /// + /// A potential formatted value returned by a . + /// + public readonly struct FormattedValue + { + /// + /// Was any handler able to match anything in the Play Report? + /// + public bool Handled { get; private init; } + + /// + /// Did the handler request the caller of the to reset the existing value? + /// + public bool Reset { get; private init; } + + /// + /// The formatted value, only present if is true, and is false. + /// + public string FormattedString { get; private init; } + + /// + /// The intended path of execution for having a string to return: simply return the string. + /// This implicit conversion will make the struct for you.

+ /// + /// If the input is null, is returned. + ///
+ /// The formatted string value. + /// The automatically constructed struct. + public static implicit operator FormattedValue(string formattedValue) + => formattedValue is not null + ? new FormattedValue { Handled = true, FormattedString = formattedValue } + : Unhandled; + + public override string ToString() + { + if (!Handled) + return ""; + + if (Reset) + return ""; + + return FormattedString; + } + + /// + /// Return this to tell the caller there is no value to return. + /// + public static FormattedValue Unhandled => default; + + /// + /// Return this to suggest the caller reset the value it's using the for. + /// + public static FormattedValue ForceReset => new() { Handled = true, Reset = true }; + + /// + /// A delegate singleton you can use to always return in a . + /// + public static readonly ValueFormatter SingleAlwaysResets = _ => ForceReset; + + /// + /// A delegate singleton you can use to always return in a . + /// + public static readonly MultiValueFormatter MultiAlwaysResets = _ => ForceReset; + + /// + /// A delegate factory you can use to always return the specified + /// in a . + /// + /// The string to always return for this delegate instance. + public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue; + } +} From 5085af0050fde71db9fb0fc6d886e38ffdb60e25 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 7 Feb 2025 18:28:32 -0600 Subject: [PATCH 09/10] UI: Changed the color of "Ingame" from yellow to orange to stand out better in light mode --- src/Ryujinx/UI/Helpers/Converters/PlayabilityStatusConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ryujinx/UI/Helpers/Converters/PlayabilityStatusConverter.cs b/src/Ryujinx/UI/Helpers/Converters/PlayabilityStatusConverter.cs index 99c6a0fce..c8082ec62 100644 --- a/src/Ryujinx/UI/Helpers/Converters/PlayabilityStatusConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/PlayabilityStatusConverter.cs @@ -18,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers LocaleKeys.CompatibilityListNothing or LocaleKeys.CompatibilityListBoots or LocaleKeys.CompatibilityListMenus => Brushes.Red, - LocaleKeys.CompatibilityListIngame => Brushes.Yellow, + LocaleKeys.CompatibilityListIngame => Brushes.DarkOrange, _ => Brushes.ForestGreen }; From 4e8157688eb08ab651f641dc40723188cffdf820 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Fri, 7 Feb 2025 18:34:11 -0600 Subject: [PATCH 10/10] UI: See what games do/don't have an image & dynamic RPC support in the Game Info popup --- src/Ryujinx/Assets/locales.json | 50 +++++++++++++++++++ src/Ryujinx/DiscordIntegrationModule.cs | 4 ++ .../UI/Controls/ApplicationDataView.axaml | 44 ++++++++++++++++ .../Utilities/AppLibrary/ApplicationData.cs | 3 ++ src/Ryujinx/Utilities/PlayReport/Analyzer.cs | 5 ++ 5 files changed, 106 insertions(+) diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index f30aac3bc..25a40b29e 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -23696,6 +23696,56 @@ "zh_CN": "选择一个要解压的 DLC", "zh_TW": "" } + }, + { + "ID": "GameInfoRpcImage", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Rich Presence Image", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "GameInfoRpcDynamic", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Dynamic Rich Presence", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } } ] } \ No newline at end of file diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index abdd9fed1..229b6ee09 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -10,6 +10,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE; using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Horizon; +using System.Linq; using System.Text; namespace Ryujinx.Ava @@ -37,6 +38,9 @@ namespace Ryujinx.Ava private static RichPresence _discordPresencePlaying; private static ApplicationMetadata _currentApp; + public static bool HasAssetImage(string titleId) => TitleIDs.DiscordGameAssetKeys.ContainsIgnoreCase(titleId); + public static bool HasAnalyzer(string titleId) => PlayReports.Analyzer.TitleIds.ContainsIgnoreCase(titleId); + public static void Initialize() { _discordPresenceMain = new RichPresence diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml index a0b6ad7b3..c40b6e192 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml @@ -4,6 +4,7 @@ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:ext="using:Ryujinx.Ava.Common.Markup" xmlns:viewModels="using:Ryujinx.Ava.UI.ViewModels" + xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Ryujinx.Ava.UI.Controls.ApplicationDataView" @@ -85,6 +86,49 @@ + + + + + + + + + + + + + + + + + + + PlayerCount != 0 && GameCount != 0; + + public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString); + public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString); 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 390e06d28..338c198a1 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -3,6 +3,7 @@ using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; @@ -15,6 +16,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport { private readonly List _specs = []; + public string[] TitleIds => Specs.SelectMany(x => x.TitleIds).ToArray(); + + public IReadOnlyList Specs => new ReadOnlyCollection(_specs); + /// /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration. ///