diff --git a/src/Ryujinx.Common/Helpers/PlayReportAnalyzer.cs b/src/Ryujinx.Common/Helpers/PlayReportAnalyzer.cs new file mode 100644 index 000000000..b69b18f57 --- /dev/null +++ b/src/Ryujinx.Common/Helpers/PlayReportAnalyzer.cs @@ -0,0 +1,80 @@ +using Gommon; +using MsgPack; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Common.Helper +{ + public class PlayReportAnalyzer + { + private readonly List _specs = []; + + public PlayReportAnalyzer AddSpec(string titleId, Func transform) + { + _specs.Add(transform(new PlayReportGameSpec { TitleIdStr = titleId })); + return this; + } + + public PlayReportAnalyzer AddSpec(string titleId, Action transform) + { + _specs.Add(new PlayReportGameSpec { TitleIdStr = titleId }.Apply(transform)); + return this; + } + + public Optional Run(string runningGameId, MessagePackObject playReport) + { + if (!playReport.IsDictionary) + return Optional.None; + + if (!_specs.TryGetFirst(s => s.TitleIdStr.EqualsIgnoreCase(runningGameId), out PlayReportGameSpec spec)) + return Optional.None; + + foreach (PlayReportValueFormatterSpec formatSpec in spec.Analyses.OrderBy(x => x.Priority)) + { + if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) + continue; + + return formatSpec.ValueFormatter(valuePackObject.ToObject()); + } + + return Optional.None; + } + + } + + public class PlayReportGameSpec + { + public required string TitleIdStr { get; init; } + public List Analyses { get; } = []; + + public PlayReportGameSpec AddValueFormatter(string reportKey, Func valueFormatter) + { + Analyses.Add(new PlayReportValueFormatterSpec + { + Priority = Analyses.Count, + ReportKey = reportKey, + ValueFormatter = valueFormatter + }); + return this; + } + + public PlayReportGameSpec AddValueFormatter(int priority, string reportKey, Func valueFormatter) + { + Analyses.Add(new PlayReportValueFormatterSpec + { + Priority = priority, + ReportKey = reportKey, + ValueFormatter = valueFormatter + }); + return this; + } + } + + public struct PlayReportValueFormatterSpec + { + public required int Priority { get; init; } + public required string ReportKey { get; init; } + public required Func ValueFormatter { get; init; } + } +} diff --git a/src/Ryujinx.Horizon/HorizonStatic.cs b/src/Ryujinx.Horizon/HorizonStatic.cs index 6de6c4d05..f08ddb3c0 100644 --- a/src/Ryujinx.Horizon/HorizonStatic.cs +++ b/src/Ryujinx.Horizon/HorizonStatic.cs @@ -7,7 +7,7 @@ namespace Ryujinx.Horizon { public static class HorizonStatic { - internal static void HandlePlayReport(MessagePackObject report) => PlayReportPrinted.Invoke(report); + internal static void HandlePlayReport(MessagePackObject report) => PlayReportPrinted?.Invoke(report); public static event Action PlayReportPrinted; diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs index 8d1a55582..add46bda4 100644 --- a/src/Ryujinx/DiscordIntegrationModule.cs +++ b/src/Ryujinx/DiscordIntegrationModule.cs @@ -5,6 +5,7 @@ using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Common; +using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; using Ryujinx.HLE; using Ryujinx.HLE.Loaders.Processes; @@ -23,12 +24,12 @@ namespace Ryujinx.Ava public static Timestamps GuestAppStartedAt { get; set; } private static string VersionString - => (ReleaseInformation.IsCanaryBuild ? "Canary " : string.Empty) + $"v{ReleaseInformation.Version}"; + => (ReleaseInformation.IsCanaryBuild ? "Canary " : string.Empty) + $"v{ReleaseInformation.Version}"; - private static readonly string _description = - ReleaseInformation.IsValid - ? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}" - : "dev build"; + private static readonly string _description = + ReleaseInformation.IsValid + ? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}" + : "dev build"; private const string ApplicationId = "1293250299716173864"; @@ -45,8 +46,7 @@ namespace Ryujinx.Ava { Assets = new Assets { - LargeImageKey = "ryujinx", - LargeImageText = TruncateToByteLength(_description) + LargeImageKey = "ryujinx", LargeImageText = TruncateToByteLength(_description) }, Details = "Main Menu", State = "Idling", @@ -86,10 +86,10 @@ namespace Ryujinx.Ava { if (titleId.TryGet(out string tid)) SwitchToPlayingState( - ApplicationLibrary.LoadAndSaveMetaData(tid), + ApplicationLibrary.LoadAndSaveMetaData(tid), Switch.Shared.Processes.ActiveApplication ); - else + else SwitchToMainState(); } @@ -114,7 +114,7 @@ namespace Ryujinx.Ava { _discordClient?.SetPresence(_discordPresencePlaying ??= CreatePlayingState(appMeta, procRes)); } - + private static void UpdatePlayingState() { _discordClient?.SetPresence(_discordPresencePlaying); @@ -126,37 +126,27 @@ namespace Ryujinx.Ava _discordPresencePlaying = null; } + private static readonly PlayReportAnalyzer _playReportAnalyzer = new PlayReportAnalyzer() + .AddSpec( // Breath of the Wild + "01007ef00011e000", + gameSpec => + gameSpec.AddValueFormatter("IsHardMode", val => val is 1 ? "Playing Master Mode" : "Playing Normal Mode") + ); + private static void HandlePlayReport(MessagePackObject playReport) { if (!TitleIDs.CurrentApplication.Value.HasValue) return; if (_discordPresencePlaying is null) return; - if (!playReport.IsDictionary) return; - foreach ((string titleId, (string reportKey, Func formatter)) in _playReportValues) - { - if (!TitleIDs.CurrentApplication.Value.Value.EqualsIgnoreCase(titleId)) - continue; - - if (!playReport.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject)) - return; + Optional details = _playReportAnalyzer.Run(TitleIDs.CurrentApplication.Value, playReport); - _discordPresencePlaying.Details = formatter(valuePackObject.ToObject()); - UpdatePlayingState(); - Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report."); - } + if (!details.HasValue) return; + + _discordPresencePlaying.Details = details; + UpdatePlayingState(); + Logger.Info?.Print(LogClass.UI, "Updated Discord RPC based on a supported play report."); } - // title ID -> Play Report key & value formatter - private static readonly ReadOnlyDictionary Formatter)> - _playReportValues = new(new Dictionary Formatter)> - { - { - // Breath of the Wild Master Mode display - "01007ef00011e000", - ("IsHardMode", val => val is 1 ? "Playing Master Mode" : "Playing Normal Mode") - } - }); - private static string TruncateToByteLength(string input) { if (Encoding.UTF8.GetByteCount(input) <= ApplicationByteLimit)