| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- 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
- {
- /// <summary>
- /// The entrypoint for the Play Report analysis system.
- /// </summary>
- public class Analyzer
- {
- private readonly List<GameSpec> _specs = [];
- public string[] TitleIds => Specs.SelectMany(x => x.TitleIds).ToArray();
- public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
- /// <summary>
- /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
- /// </summary>
- /// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
- /// <param name="transform">The configuration function for the analysis spec.</param>
- /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
- public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> 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;
- }
- /// <summary>
- /// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
- /// </summary>
- /// <param name="titleId">The ID of the game to listen to Play Reports in.</param>
- /// <param name="transform">The configuration function for the analysis spec.</param>
- /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
- public Analyzer AddSpec(string titleId, Action<GameSpec> 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;
- }
- /// <summary>
- /// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration.
- /// </summary>
- /// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
- /// <param name="transform">The configuration function for the analysis spec.</param>
- /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
- public Analyzer AddSpec(IEnumerable<string> titleIds,
- Func<GameSpec, GameSpec> 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;
- }
- /// <summary>
- /// Add an analysis spec matching a specific set of games by title IDs, with the provided spec configuration.
- /// </summary>
- /// <param name="titleIds">The IDs of the games to listen to Play Reports in.</param>
- /// <param name="transform">The configuration function for the analysis spec.</param>
- /// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
- public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> 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;
- }
- /// <summary>
- /// Runs the configured <see cref="FormatterSpec"/> for the specified game title ID.
- /// </summary>
- /// <param name="runningGameId">The game currently running.</param>
- /// <param name="appMeta">The Application metadata information, including localized game name and play time information.</param>
- /// <param name="playReport">The Play Report received from HLE.</param>
- /// <returns>A struct representing a possible formatted value.</returns>
- 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 (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
- {
- if (!playReport.ReportData.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
- continue;
- return formatSpec.Formatter(new SingleValue(valuePackObject)
- {
- Application = appMeta,
- PlayReport = playReport
- });
- }
- foreach (MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
- {
- List<MessagePackObject> packedObjects = [];
- foreach (var reportKey in formatSpec.ReportKeys)
- {
- if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
- continue;
- packedObjects.Add(valuePackObject);
- }
- if (packedObjects.Count != formatSpec.ReportKeys.Length)
- return FormattedValue.Unhandled;
- return formatSpec.Formatter(new MultiValue(packedObjects)
- {
- Application = appMeta,
- PlayReport = playReport
- });
- }
- foreach (SparseMultiFormatterSpec formatSpec in spec.SparseMultiValueFormatters.OrderBy(x => x.Priority))
- {
- Dictionary<string, MessagePackObject> packedObjects = [];
- foreach (var reportKey in formatSpec.ReportKeys)
- {
- if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
- continue;
- packedObjects.Add(reportKey, valuePackObject);
- }
- return formatSpec.Formatter(
- new SparseMultiValue(packedObjects)
- {
- Application = appMeta,
- PlayReport = playReport
- });
- }
- return FormattedValue.Unhandled;
- }
- }
- }
|