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;
}
}
}