diff --git a/src/Ryujinx/DiscordIntegrationModule.cs b/src/Ryujinx/DiscordIntegrationModule.cs
index d95bb80dd..abdd9fed1 100644
--- a/src/Ryujinx/DiscordIntegrationModule.cs
+++ b/src/Ryujinx/DiscordIntegrationModule.cs
@@ -126,14 +126,16 @@ namespace Ryujinx.Ava
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
- Analyzer.FormattedValue formattedValue =
+ FormattedValue formattedValue =
PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
if (!formattedValue.Handled) return;
- _discordPresencePlaying.Details = formattedValue.Reset
- ? $"Playing {_currentApp.Title}"
- : formattedValue.FormattedString;
+ _discordPresencePlaying.Details = TruncateToByteLength(
+ formattedValue.Reset
+ ? $"Playing {_currentApp.Title}"
+ : formattedValue.FormattedString
+ );
if (_discordClient.CurrentPresence.Details.Equals(_discordPresencePlaying.Details))
return; //don't trigger an update if the set presence Details are identical to current
diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs
index 84bdbf085..390e06d28 100644
--- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs
+++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs
@@ -78,7 +78,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
return this;
}
-
+
///
/// Runs the configured for the specified game title ID.
///
@@ -98,261 +98,48 @@ namespace Ryujinx.Ava.Utilities.PlayReport
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
return FormattedValue.Unhandled;
- foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
+ foreach (FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
- return formatSpec.ValueFormatter(new Value
- {
- Application = appMeta, PackedValue = valuePackObject
- });
+ return formatSpec.Formatter(new Value { Application = appMeta, PackedValue = valuePackObject });
}
-
- foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
+
+ 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.ValueFormatter(packedObjects
+
+ 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;
}
-
- ///
- /// A potential formatted value returned by a .
- ///
- public readonly struct FormattedValue
- {
- ///
- /// Was any handler able to match anything in the Play Report?
- ///
- public bool Handled { get; private init; }
-
- ///
- /// Did the handler request the caller of the to reset the existing value?
- ///
- public bool Reset { get; private init; }
-
- ///
- /// The formatted value, only present if is true, and is false.
- ///
- public string FormattedString { get; private init; }
-
- ///
- /// The intended path of execution for having a string to return: simply return the string.
- /// This implicit conversion will make the struct for you.
- ///
- /// If the input is null, is returned.
- ///
- /// The formatted string value.
- /// The automatically constructed struct.
- public static implicit operator FormattedValue(string formattedValue)
- => formattedValue is not null
- ? new FormattedValue { Handled = true, FormattedString = formattedValue }
- : Unhandled;
-
- ///
- /// Return this to tell the caller there is no value to return.
- ///
- public static FormattedValue Unhandled => default;
-
- ///
- /// Return this to suggest the caller reset the value it's using the for.
- ///
- public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
-
- ///
- /// A delegate singleton you can use to always return in a .
- ///
- public static readonly ValueFormatter AlwaysResets = _ => ForceReset;
-
- ///
- /// A delegate factory you can use to always return the specified
- /// in a .
- ///
- /// The string to always return for this delegate instance.
- public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
- }
}
-
- ///
- /// A mapping of title IDs to value formatter specs.
- ///
- /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself.
- ///
- public class GameSpec
- {
- public required string[] TitleIds { get; init; }
- public List SimpleValueFormatters { get; } = [];
- public List MultiValueFormatters { get; } = [];
-
- ///
- /// Add a value formatter to the current
- /// matching a specific key that could exist in a Play Report for the previously specified title IDs.
- ///
- /// The key name to match.
- /// The function which can return a potential formatted value.
- /// The current , for chaining convenience.
- public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
- {
- SimpleValueFormatters.Add(new FormatterSpec
- {
- Priority = SimpleValueFormatters.Count, ReportKey = reportKey, ValueFormatter = valueFormatter
- });
- return this;
- }
-
- ///
- /// Add a value formatter at a specific priority to the current
- /// matching a specific key that could exist in a Play Report for the previously specified title IDs.
- ///
- /// The resolution priority of this value formatter. Higher resolves sooner.
- /// The key name to match.
- /// The function which can return a potential formatted value.
- /// The current , for chaining convenience.
- public GameSpec AddValueFormatter(int priority, string reportKey,
- ValueFormatter valueFormatter)
- {
- SimpleValueFormatters.Add(new FormatterSpec
- {
- Priority = priority, ReportKey = reportKey, ValueFormatter = valueFormatter
- });
- return this;
- }
-
- ///
- /// Add a multi-value formatter to the current
- /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
- ///
- /// The key names to match.
- /// The function which can format the values.
- /// The current , for chaining convenience.
- public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
- {
- MultiValueFormatters.Add(new MultiFormatterSpec
- {
- Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter
- });
- return this;
- }
-
- ///
- /// Add a multi-value formatter at a specific priority to the current
- /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
- ///
- /// The resolution priority of this value formatter. Higher resolves sooner.
- /// The key names to match.
- /// The function which can format the values.
- /// The current , for chaining convenience.
- public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
- MultiValueFormatter valueFormatter)
- {
- MultiValueFormatters.Add(new MultiFormatterSpec
- {
- Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter
- });
- return this;
- }
-
- ///
- /// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
- ///
- public struct FormatterSpec
- {
- public required int Priority { get; init; }
- public required string ReportKey { get; init; }
- public ValueFormatter ValueFormatter { get; init; }
- }
-
- ///
- /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
- ///
- public struct MultiFormatterSpec
- {
- public required int Priority { get; init; }
- public required string[] ReportKeys { get; init; }
- public MultiValueFormatter ValueFormatter { get; init; }
- }
- }
-
- ///
- /// The input data to a ,
- /// containing the currently running application's ,
- /// and the matched from the Play Report.
- ///
- public class Value
- {
- ///
- /// The currently running application's .
- ///
- public ApplicationMetadata Application { get; init; }
-
- ///
- /// The matched value from the Play Report.
- ///
- public MessagePackObject PackedValue { get; init; }
-
- ///
- /// Access the as its underlying .NET type.
- ///
- /// Does not seem to work well with comparing numeric types,
- /// so use XValue properties for that.
- ///
- public object BoxedValue => PackedValue.ToObject();
-
- #region AsX accessors
-
- public bool BooleanValue => PackedValue.AsBoolean();
- public byte ByteValye => PackedValue.AsByte();
- public sbyte SByteValye => PackedValue.AsSByte();
- public short ShortValye => PackedValue.AsInt16();
- public ushort UShortValye => PackedValue.AsUInt16();
- public int IntValye => PackedValue.AsInt32();
- public uint UIntValye => PackedValue.AsUInt32();
- public long LongValye => PackedValue.AsInt64();
- public ulong ULongValye => PackedValue.AsUInt64();
- public float FloatValue => PackedValue.AsSingle();
- public double DoubleValue => PackedValue.AsDouble();
- public string StringValue => PackedValue.AsString();
- public Span BinaryValue => PackedValue.AsBinary();
-
- #endregion
- }
-
- ///
- /// The delegate type that powers single value formatters.
- /// Takes in the result value from the Play Report, and outputs:
- ///
- /// a formatted string,
- ///
- /// a signal that nothing was available to handle it,
- ///
- /// OR a signal to reset the value that the caller is using the for.
- ///
- public delegate Analyzer.FormattedValue ValueFormatter(Value value);
-
- ///
- /// The delegate type that powers multiple value formatters.
- /// Takes in the result value from the Play Report, and outputs:
- ///
- /// a formatted string,
- ///
- /// a signal that nothing was available to handle it,
- ///
- /// OR a signal to reset the value that the caller is using the for.
- ///
- public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value);
}
diff --git a/src/Ryujinx/Utilities/PlayReport/Delegates.cs b/src/Ryujinx/Utilities/PlayReport/Delegates.cs
new file mode 100644
index 000000000..7c8952e18
--- /dev/null
+++ b/src/Ryujinx/Utilities/PlayReport/Delegates.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Ava.Utilities.PlayReport
+{
+ ///
+ /// The delegate type that powers single value formatters.
+ /// Takes in the result value from the Play Report, and outputs:
+ ///
+ /// a formatted string,
+ ///
+ /// a signal that nothing was available to handle it,
+ ///
+ /// OR a signal to reset the value that the caller is using the for.
+ ///
+ public delegate FormattedValue ValueFormatter(Value value);
+
+ ///
+ /// The delegate type that powers multiple value formatters.
+ /// Takes in the result values from the Play Report, and outputs:
+ ///
+ /// a formatted string,
+ ///
+ /// a signal that nothing was available to handle it,
+ ///
+ /// OR a signal to reset the value that the caller is using the for.
+ ///
+ public delegate FormattedValue MultiValueFormatter(Value[] value);
+
+ ///
+ /// The delegate type that powers multiple value formatters.
+ /// The dictionary passed to this delegate is sparsely populated;
+ /// that is, not every key specified in the Play Report needs to match for this to be used.
+ /// Takes in the result values from the Play Report, and outputs:
+ ///
+ /// a formatted string,
+ ///
+ /// a signal that nothing was available to handle it,
+ ///
+ /// OR a signal to reset the value that the caller is using the for.
+ ///
+ public delegate FormattedValue SparseMultiValueFormatter(Dictionary values);
+}
diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs
index 25457744e..ae954c81c 100644
--- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs
+++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs
@@ -1,6 +1,4 @@
-using static Ryujinx.Ava.Utilities.PlayReport.Analyzer;
-
-namespace Ryujinx.Ava.Utilities.PlayReport
+namespace Ryujinx.Ava.Utilities.PlayReport
{
public static class PlayReports
{
@@ -10,7 +8,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
spec => spec
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
- .AddValueFormatter("AoCVer", FormattedValue.AlwaysResets)
+ .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
)
.AddSpec(
"0100f2c0115b6000",
diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs
new file mode 100644
index 000000000..649813b7a
--- /dev/null
+++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs
@@ -0,0 +1,140 @@
+using FluentAvalonia.Core;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Ryujinx.Ava.Utilities.PlayReport
+{
+ ///
+ /// A mapping of title IDs to value formatter specs.
+ ///
+ /// Generally speaking, use the .AddSpec(...) methods instead of creating this class yourself.
+ ///
+ public class GameSpec
+ {
+ public required string[] TitleIds { get; init; }
+ public List SimpleValueFormatters { get; } = [];
+ public List MultiValueFormatters { get; } = [];
+ public List SparseMultiValueFormatters { get; } = [];
+
+
+ ///
+ /// Add a value formatter to the current
+ /// matching a specific key that could exist in a Play Report for the previously specified title IDs.
+ ///
+ /// The key name to match.
+ /// The function which can return a potential formatted value.
+ /// The current , for chaining convenience.
+ public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
+ => AddValueFormatter(SimpleValueFormatters.Count, reportKey, valueFormatter);
+
+ ///
+ /// Add a value formatter at a specific priority to the current
+ /// matching a specific key that could exist in a Play Report for the previously specified title IDs.
+ ///
+ /// The resolution priority of this value formatter. Higher resolves sooner.
+ /// The key name to match.
+ /// The function which can return a potential formatted value.
+ /// The current , for chaining convenience.
+ public GameSpec AddValueFormatter(int priority, string reportKey,
+ ValueFormatter valueFormatter)
+ {
+ SimpleValueFormatters.Add(new FormatterSpec
+ {
+ Priority = priority, ReportKey = reportKey, Formatter = valueFormatter
+ });
+ return this;
+ }
+
+ ///
+ /// Add a multi-value formatter to the current
+ /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
+ ///
+ /// The key names to match.
+ /// The function which can format the values.
+ /// The current , for chaining convenience.
+ public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
+ => AddMultiValueFormatter(MultiValueFormatters.Count, reportKeys, valueFormatter);
+
+ ///
+ /// Add a multi-value formatter at a specific priority to the current
+ /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
+ ///
+ /// The resolution priority of this value formatter. Higher resolves sooner.
+ /// The key names to match.
+ /// The function which can format the values.
+ /// The current , for chaining convenience.
+ public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
+ MultiValueFormatter valueFormatter)
+ {
+ MultiValueFormatters.Add(new MultiFormatterSpec
+ {
+ Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
+ });
+ return this;
+ }
+
+ ///
+ /// Add a multi-value formatter to the current
+ /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
+ ///
+ /// The 'Sparse' multi-value formatters do not require every key to be present.
+ /// If you need this requirement, use .
+ ///
+ /// The key names to match.
+ /// The function which can format the values.
+ /// The current , for chaining convenience.
+ public GameSpec AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter)
+ => AddSparseMultiValueFormatter(SparseMultiValueFormatters.Count, reportKeys, valueFormatter);
+
+ ///
+ /// Add a multi-value formatter at a specific priority to the current
+ /// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
+ ///
+ /// The 'Sparse' multi-value formatters do not require every key to be present.
+ /// If you need this requirement, use .
+ ///
+ /// The resolution priority of this value formatter. Higher resolves sooner.
+ /// The key names to match.
+ /// The function which can format the values.
+ /// The current , for chaining convenience.
+ public GameSpec AddSparseMultiValueFormatter(int priority, string[] reportKeys,
+ SparseMultiValueFormatter valueFormatter)
+ {
+ SparseMultiValueFormatters.Add(new SparseMultiFormatterSpec
+ {
+ Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
+ });
+ return this;
+ }
+ }
+
+ ///
+ /// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
+ ///
+ public struct FormatterSpec
+ {
+ public required int Priority { get; init; }
+ public required string ReportKey { get; init; }
+ public ValueFormatter Formatter { get; init; }
+ }
+
+ ///
+ /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
+ ///
+ public struct MultiFormatterSpec
+ {
+ public required int Priority { get; init; }
+ public required string[] ReportKeys { get; init; }
+ public MultiValueFormatter Formatter { get; init; }
+ }
+
+ ///
+ /// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their sparsely populated potential values.
+ ///
+ public struct SparseMultiFormatterSpec
+ {
+ public required int Priority { get; init; }
+ public required string[] ReportKeys { get; init; }
+ public SparseMultiValueFormatter Formatter { get; init; }
+ }
+}
diff --git a/src/Ryujinx/Utilities/PlayReport/Value.cs b/src/Ryujinx/Utilities/PlayReport/Value.cs
new file mode 100644
index 000000000..46d47366d
--- /dev/null
+++ b/src/Ryujinx/Utilities/PlayReport/Value.cs
@@ -0,0 +1,130 @@
+using MsgPack;
+using Ryujinx.Ava.Utilities.AppLibrary;
+using System;
+
+namespace Ryujinx.Ava.Utilities.PlayReport
+{
+ ///
+ /// The input data to a ,
+ /// containing the currently running application's ,
+ /// and the matched from the Play Report.
+ ///
+ public class Value
+ {
+ ///
+ /// The currently running application's .
+ ///
+ public ApplicationMetadata Application { get; init; }
+
+ ///
+ /// The matched value from the Play Report.
+ ///
+ public MessagePackObject PackedValue { get; init; }
+
+ ///
+ /// Access the as its underlying .NET type.
+ ///
+ /// Does not seem to work well with comparing numeric types,
+ /// so use XValue properties for that.
+ ///
+ public object BoxedValue => PackedValue.ToObject();
+
+ public override string ToString()
+ {
+ object boxed = BoxedValue;
+ return boxed == null
+ ? "null"
+ : boxed.ToString();
+ }
+
+ #region AsX accessors
+
+ public bool BooleanValue => PackedValue.AsBoolean();
+ public byte ByteValue => PackedValue.AsByte();
+ public sbyte SByteValue => PackedValue.AsSByte();
+ public short ShortValue => PackedValue.AsInt16();
+ public ushort UShortValue => PackedValue.AsUInt16();
+ public int IntValue => PackedValue.AsInt32();
+ public uint UIntValue => PackedValue.AsUInt32();
+ public long LongValue => PackedValue.AsInt64();
+ public ulong ULongValue => PackedValue.AsUInt64();
+ public float FloatValue => PackedValue.AsSingle();
+ public double DoubleValue => PackedValue.AsDouble();
+ public string StringValue => PackedValue.AsString();
+ public Span BinaryValue => PackedValue.AsBinary();
+
+ #endregion
+ }
+
+ ///
+ /// A potential formatted value returned by a .
+ ///
+ public readonly struct FormattedValue
+ {
+ ///
+ /// Was any handler able to match anything in the Play Report?
+ ///
+ public bool Handled { get; private init; }
+
+ ///
+ /// Did the handler request the caller of the to reset the existing value?
+ ///
+ public bool Reset { get; private init; }
+
+ ///
+ /// The formatted value, only present if is true, and is false.
+ ///
+ public string FormattedString { get; private init; }
+
+ ///
+ /// The intended path of execution for having a string to return: simply return the string.
+ /// This implicit conversion will make the struct for you.
+ ///
+ /// If the input is null, is returned.
+ ///
+ /// The formatted string value.
+ /// The automatically constructed struct.
+ public static implicit operator FormattedValue(string formattedValue)
+ => formattedValue is not null
+ ? new FormattedValue { Handled = true, FormattedString = formattedValue }
+ : Unhandled;
+
+ public override string ToString()
+ {
+ if (!Handled)
+ return "";
+
+ if (Reset)
+ return "";
+
+ return FormattedString;
+ }
+
+ ///
+ /// Return this to tell the caller there is no value to return.
+ ///
+ public static FormattedValue Unhandled => default;
+
+ ///
+ /// Return this to suggest the caller reset the value it's using the for.
+ ///
+ public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
+
+ ///
+ /// A delegate singleton you can use to always return in a .
+ ///
+ public static readonly ValueFormatter SingleAlwaysResets = _ => ForceReset;
+
+ ///
+ /// A delegate singleton you can use to always return in a .
+ ///
+ public static readonly MultiValueFormatter MultiAlwaysResets = _ => ForceReset;
+
+ ///
+ /// A delegate factory you can use to always return the specified
+ /// in a .
+ ///
+ /// The string to always return for this delegate instance.
+ public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
+ }
+}