using Gommon; using MsgPack; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Collections.Generic; using System.Collections.ObjectModel; 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 = []; 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. /// /// 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, Horizon.Prepo.Types.PlayReport playReport ) { if (!playReport.ReportData.IsDictionary) return FormattedValue.Unhandled; if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) return FormattedValue.Unhandled; foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority)) { if (!formatSpec.Format(appMeta, playReport, out FormattedValue value)) continue; return value; } return FormattedValue.Unhandled; } } }