using Gommon; using Ryujinx.Ava.Systems.AppLibrary; using Ryujinx.Common.Logging; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; namespace Ryujinx.Ava.Systems.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); 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. /// /// 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) { if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _)) return AddSpec(transform(GameSpec.Create(titleId))); Logger.Notice.PrintMsg(LogClass.Application, $"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{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) { if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _)) return AddSpec(GameSpec.Create(titleId).Apply(transform)); Logger.Notice.PrintMsg(LogClass.Application, $"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'"); 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(); if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x))) return AddSpec(transform(GameSpec.Create(tids))); Logger.Notice.PrintMsg(LogClass.Application, $"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{ tids.FormatCollection( x => x, separator: ", ", prefix: "[", suffix: "]" ) }'"); 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(); if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x))) return AddSpec(GameSpec.Create(tids).Apply(transform)); Logger.Notice.PrintMsg(LogClass.Application, $"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{ tids.FormatCollection( x => x, separator: ", ", prefix: "[", suffix: "]" ) }'"); return this; } /// /// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec. /// /// The to add. /// The current , for chaining convenience. public Analyzer AddSpec(GameSpec spec) { _specs.Add(spec); 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 (!TryGetSpec(runningGameId, out GameSpec spec)) return FormattedValue.Unhandled; foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority)) { if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value)) continue; return value; } return FormattedValue.Unhandled; } } }