using Gommon; using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace Ryujinx.Ava.Utilities.PlayReport { /// /// The entrypoint for the Play Report analysis system. /// public class Analyzer { 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 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(GameSpec)}."); _specs.Add(transform(new GameSpec { TitleIds = [titleId] })); return this; } /// /// 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 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(GameSpec)}."); _specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform)); return this; } /// /// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration. /// /// The IDs of the games to listen to Play Reports in. /// The configuration function for the analysis spec. /// 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(GameSpec)}."); _specs.Add(transform(new GameSpec { TitleIds = [..tids] })); return this; } /// /// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration. /// /// The IDs of the games to listen to Play Reports in. /// The configuration function for the analysis spec. /// 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(GameSpec)}."); _specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform)); return this; } /// /// 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. /// The Play Report received from HLE. /// A struct representing a possible formatted value. public FormattedValue Format( string runningGameId, ApplicationMetadata appMeta, MessagePackObject playReport ) { if (!playReport.IsDictionary) return FormattedValue.Unhandled; if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) return FormattedValue.Unhandled; foreach (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority)) { if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject)) continue; return formatSpec.Formatter(new Value { Application = appMeta, PackedValue = valuePackObject }); } 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.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; } } }