Merge branch 'master' into master

This commit is contained in:
FluffyOMC 2025-02-06 03:35:57 -05:00 committed by GitHub
commit cf1ad265f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 276 additions and 160 deletions

View File

@ -584,7 +584,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "UI를 숨긴 상태에서 게임 시작",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -2259,7 +2259,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "PPTC 캐시 제거",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -2284,7 +2284,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "앱의 모든 PPTC 캐시 파일 삭제",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -2609,7 +2609,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "선택한 DLC 파일에서 RomFS 추출",
"no_NO": "Pakk ut RomFS filene fra valgt DLC fil",
"pl_PL": "",
"pt_BR": "",
@ -2759,7 +2759,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "호환성 항목 표시",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -2784,7 +2784,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "일반적으로 도움말 메뉴를 통해 접근할 수 있는 호환성 목록에 선택한 게임을 표시합니다.",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -2809,7 +2809,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "게임 통계 표시",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -2834,7 +2834,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "그리드 보기 레이아웃에서 누락된 현재 선택된 게임에 대한 다양한 정보를 표시합니다.",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -3609,7 +3609,7 @@
"he_IL": "",
"it_IT": "Aggiornamenti e DLC che fanno riferimento a file mancanti verranno disabilitati automaticamente",
"ja_JP": "",
"ko_KR": "누락된 파일을 참조하는 DLC 및 업데이트가 자동으로 언로드",
"ko_KR": "누락된 파일을 참조하는 DLC 및 업데이트가 자동으로 불러오기 취소",
"no_NO": "DLC og oppdateringer som henviser til manglende filer, vil bli lastet ned automatisk",
"pl_PL": "",
"pt_BR": "DLCs e Atualizações que se referem a arquivos ausentes serão descarregadas automaticamente",
@ -4384,7 +4384,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "스웨덴어",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -4409,7 +4409,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "노르웨이어",
"no_NO": "Norsk",
"pl_PL": "",
"pt_BR": "",
@ -4484,7 +4484,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "매치 시스템 시간",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -8084,7 +8084,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "비활성화",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -8109,7 +8109,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "레인보우",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -8134,7 +8134,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "레인보우 속도",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -8159,7 +8159,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "색상",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -13409,7 +13409,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "다음에서 모든 PPTC 데이터를 제거하려고 합니다:\n\n{0}\n\n계속하시겠습니까?",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -19409,7 +19409,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "LED 설정",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
@ -22334,7 +22334,7 @@
"he_IL": "ממשק רשת",
"it_IT": "Interfaccia di rete:",
"ja_JP": "ネットワークインタフェース:",
"ko_KR": "네트워크 인터페이스:",
"ko_KR": "네트워크 인터페이스 :",
"no_NO": "Nettverksgrensesnitt",
"pl_PL": "Interfejs sieci:",
"pt_BR": "Interface de rede:",
@ -23309,7 +23309,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "최종 업데이트 : {0}",
"no_NO": "Sist oppdatert: {0}",
"pl_PL": "",
"pt_BR": "",
@ -23684,7 +23684,7 @@
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"ko_KR": "추출할 DLC 선택",
"no_NO": "Velg en DLC og hente ut",
"pl_PL": "",
"pt_BR": "",

View File

@ -4,16 +4,12 @@ using MsgPack;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Ava.Utilities.PlayReport;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.Loaders.Processes;
using Ryujinx.Horizon;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace Ryujinx.Ava
@ -130,8 +126,8 @@ namespace Ryujinx.Ava
if (!TitleIDs.CurrentApplication.Value.HasValue) return;
if (_discordPresencePlaying is null) return;
PlayReportAnalyzer.FormattedValue formattedValue =
PlayReport.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
Analyzer.FormattedValue formattedValue =
PlayReports.Analyzer.Format(TitleIDs.CurrentApplication.Value, _currentApp, playReport);
if (!formattedValue.Handled) return;

View File

@ -1,85 +0,0 @@
using PlayReportFormattedValue = Ryujinx.Ava.Utilities.PlayReportAnalyzer.FormattedValue;
namespace Ryujinx.Ava.Utilities
{
public static class PlayReport
{
public static PlayReportAnalyzer Analyzer { get; } = new PlayReportAnalyzer()
.AddSpec(
"01007ef00011e000",
spec => spec
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", PlayReportFormattedValue.AlwaysResets)
)
.AddSpec(
"0100f2c0115b6000",
spec => spec.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
.AddSpec(
"0100000000010000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
)
.AddSpec(
"010075000ecbe000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
)
.AddSpec(
"010028600ebda000",
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
)
.AddSpec( // Global & China IDs
["0100152000022000", "010075100e8ec000"],
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
);
private static PlayReportFormattedValue BreathOfTheWild_MasterMode(PlayReportValue value)
=> value.BoxedValue is 1 ? "Playing Master Mode" : PlayReportFormattedValue.ForceReset;
private static PlayReportFormattedValue TearsOfTheKingdom_CurrentField(PlayReportValue value) =>
value.DoubleValue switch
{
> 800d => "Exploring the Sky Islands",
< -201d => "Exploring the Depths",
_ => "Roaming Hyrule"
};
private static PlayReportFormattedValue SuperMarioOdyssey_AssistMode(PlayReportValue value)
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static PlayReportFormattedValue SuperMarioOdysseyChina_AssistMode(PlayReportValue value)
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static PlayReportFormattedValue SuperMario3DWorldOrBowsersFury(PlayReportValue value)
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static PlayReportFormattedValue MarioKart8Deluxe_Mode(PlayReportValue value)
=> value.StringValue switch
{
// Single Player
"Single" => "Single Player",
// Multiplayer
"Multi-2players" => "Multiplayer 2 Players",
"Multi-3players" => "Multiplayer 3 Players",
"Multi-4players" => "Multiplayer 4 Players",
// Wireless/LAN Play
"Local-Single" => "Wireless/LAN Play",
"Local-2players" => "Wireless/LAN Play 2 Players",
// CC Classes
"50cc" => "50cc",
"100cc" => "100cc",
"150cc" => "150cc",
"Mirror" => "Mirror (150cc)",
"200cc" => "200cc",
// Modes
"GrandPrix" => "Grand Prix",
"TimeAttack" => "Time Trials",
"VS" => "VS Races",
"Battle" => "Battle Mode",
"RaceStart" => "Selecting a Course",
"Race" => "Racing",
_ => PlayReportFormattedValue.ForceReset
};
}
}

View File

@ -6,27 +6,27 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Ryujinx.Ava.Utilities
namespace Ryujinx.Ava.Utilities.PlayReport
{
/// <summary>
/// The entrypoint for the Play Report analysis system.
/// </summary>
public class PlayReportAnalyzer
public class Analyzer
{
private readonly List<PlayReportGameSpec> _specs = [];
private readonly List<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="PlayReportAnalyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(string titleId, Func<PlayReportGameSpec, PlayReportGameSpec> transform)
/// <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(PlayReportGameSpec)}.");
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [titleId] }));
_specs.Add(transform(new GameSpec { TitleIds = [titleId] }));
return this;
}
@ -35,13 +35,13 @@ namespace Ryujinx.Ava.Utilities
/// </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="PlayReportAnalyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(string titleId, Action<PlayReportGameSpec> transform)
/// <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(PlayReportGameSpec)}.");
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(new PlayReportGameSpec { TitleIds = [titleId] }.Apply(transform));
_specs.Add(new GameSpec { TitleIds = [titleId] }.Apply(transform));
return this;
}
@ -50,15 +50,15 @@ namespace Ryujinx.Ava.Utilities
/// </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="PlayReportAnalyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds,
Func<PlayReportGameSpec, PlayReportGameSpec> transform)
/// <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(PlayReportGameSpec)}.");
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(transform(new PlayReportGameSpec { TitleIds = [..tids] }));
_specs.Add(transform(new GameSpec { TitleIds = [..tids] }));
return this;
}
@ -67,20 +67,20 @@ namespace Ryujinx.Ava.Utilities
/// </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="PlayReportAnalyzer"/>, for chaining convenience.</returns>
public PlayReportAnalyzer AddSpec(IEnumerable<string> titleIds, Action<PlayReportGameSpec> transform)
/// <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(PlayReportGameSpec)}.");
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
_specs.Add(new PlayReportGameSpec { TitleIds = [..tids] }.Apply(transform));
_specs.Add(new GameSpec { TitleIds = [..tids] }.Apply(transform));
return this;
}
/// <summary>
/// Runs the configured <see cref="PlayReportGameSpec.FormatterSpec"/> for the specified game title ID.
/// Runs the configured <see cref="GameSpec.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>
@ -95,25 +95,44 @@ namespace Ryujinx.Ava.Utilities
if (!playReport.IsDictionary)
return FormattedValue.Unhandled;
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out PlayReportGameSpec spec))
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
return FormattedValue.Unhandled;
foreach (PlayReportGameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
foreach (GameSpec.FormatterSpec formatSpec in spec.SimpleValueFormatters.OrderBy(x => x.Priority))
{
if (!playReport.AsDictionary().TryGetValue(formatSpec.ReportKey, out MessagePackObject valuePackObject))
continue;
return formatSpec.ValueFormatter(new PlayReportValue
return formatSpec.ValueFormatter(new Value
{
Application = appMeta, PackedValue = valuePackObject
});
}
foreach (GameSpec.MultiFormatterSpec formatSpec in spec.MultiValueFormatters.OrderBy(x => x.Priority))
{
List<MessagePackObject> 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
.Select(packObject => new Value { Application = appMeta, PackedValue = packObject })
.ToArray());
}
return FormattedValue.Unhandled;
}
/// <summary>
/// A potential formatted value returned by a <see cref="PlayReportValueFormatter"/>.
/// A potential formatted value returned by a <see cref="ValueFormatter"/>.
/// </summary>
public readonly struct FormattedValue
{
@ -123,7 +142,7 @@ namespace Ryujinx.Ava.Utilities
public bool Handled { get; private init; }
/// <summary>
/// Did the handler request the caller of the <see cref="PlayReportAnalyzer"/> to reset the existing value?
/// Did the handler request the caller of the <see cref="Analyzer"/> to reset the existing value?
/// </summary>
public bool Reset { get; private init; }
@ -151,42 +170,43 @@ namespace Ryujinx.Ava.Utilities
public static FormattedValue Unhandled => default;
/// <summary>
/// Return this to suggest the caller reset the value it's using the <see cref="PlayReportAnalyzer"/> for.
/// Return this to suggest the caller reset the value it's using the <see cref="Analyzer"/> for.
/// </summary>
public static FormattedValue ForceReset => new() { Handled = true, Reset = true };
/// <summary>
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="PlayReportValueFormatter"/>.
/// A delegate singleton you can use to always return <see cref="ForceReset"/> in a <see cref="ValueFormatter"/>.
/// </summary>
public static readonly PlayReportValueFormatter AlwaysResets = _ => ForceReset;
public static readonly ValueFormatter AlwaysResets = _ => ForceReset;
/// <summary>
/// A delegate factory you can use to always return the specified
/// <paramref name="formattedValue"/> in a <see cref="PlayReportValueFormatter"/>.
/// <paramref name="formattedValue"/> in a <see cref="ValueFormatter"/>.
/// </summary>
/// <param name="formattedValue">The string to always return for this delegate instance.</param>
public static PlayReportValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
public static ValueFormatter AlwaysReturns(string formattedValue) => _ => formattedValue;
}
}
/// <summary>
/// A mapping of title IDs to value formatter specs.
///
/// <remarks>Generally speaking, use the <see cref="PlayReportAnalyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
/// <remarks>Generally speaking, use the <see cref="Analyzer"/>.AddSpec(...) methods instead of creating this class yourself.</remarks>
/// </summary>
public class PlayReportGameSpec
public class GameSpec
{
public required string[] TitleIds { get; init; }
public List<FormatterSpec> SimpleValueFormatters { get; } = [];
public List<MultiFormatterSpec> MultiValueFormatters { get; } = [];
/// <summary>
/// Add a value formatter to the current <see cref="PlayReportGameSpec"/>
/// Add a value formatter to the current <see cref="GameSpec"/>
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
/// </summary>
/// <param name="reportKey">The key name to match.</param>
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
/// <returns>The current <see cref="PlayReportGameSpec"/>, for chaining convenience.</returns>
public PlayReportGameSpec AddValueFormatter(string reportKey, PlayReportValueFormatter valueFormatter)
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddValueFormatter(string reportKey, ValueFormatter valueFormatter)
{
SimpleValueFormatters.Add(new FormatterSpec
{
@ -196,15 +216,15 @@ namespace Ryujinx.Ava.Utilities
}
/// <summary>
/// Add a value formatter at a specific priority to the current <see cref="PlayReportGameSpec"/>
/// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
/// matching a specific key that could exist in a Play Report for the previously specified title IDs.
/// </summary>
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
/// <param name="reportKey">The key name to match.</param>
/// <param name="valueFormatter">The function which can return a potential formatted value.</param>
/// <returns>The current <see cref="PlayReportGameSpec"/>, for chaining convenience.</returns>
public PlayReportGameSpec AddValueFormatter(int priority, string reportKey,
PlayReportValueFormatter valueFormatter)
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddValueFormatter(int priority, string reportKey,
ValueFormatter valueFormatter)
{
SimpleValueFormatters.Add(new FormatterSpec
{
@ -212,6 +232,40 @@ namespace Ryujinx.Ava.Utilities
});
return this;
}
/// <summary>
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
/// </summary>
/// <param name="reportKeys">The key names to match.</param>
/// <param name="valueFormatter">The function which can format the values.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddMultiValueFormatter(string[] reportKeys, MultiValueFormatter valueFormatter)
{
MultiValueFormatters.Add(new MultiFormatterSpec
{
Priority = SimpleValueFormatters.Count, ReportKeys = reportKeys, ValueFormatter = valueFormatter
});
return this;
}
/// <summary>
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
/// matching a specific set of keys that could exist in a Play Report for the previously specified title IDs.
/// </summary>
/// <param name="priority">The resolution priority of this value formatter. Higher resolves sooner.</param>
/// <param name="reportKeys">The key names to match.</param>
/// <param name="valueFormatter">The function which can format the values.</param>
/// <returns>The current <see cref="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddMultiValueFormatter(int priority, string[] reportKeys,
MultiValueFormatter valueFormatter)
{
MultiValueFormatters.Add(new MultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, ValueFormatter = valueFormatter
});
return this;
}
/// <summary>
/// A struct containing the data for a mapping of a key in a Play Report to a formatter for its potential value.
@ -220,16 +274,26 @@ namespace Ryujinx.Ava.Utilities
{
public required int Priority { get; init; }
public required string ReportKey { get; init; }
public PlayReportValueFormatter ValueFormatter { get; init; }
public ValueFormatter ValueFormatter { get; init; }
}
/// <summary>
/// A struct containing the data for a mapping of an arbitrary key set in a Play Report to a formatter for their potential values.
/// </summary>
public struct MultiFormatterSpec
{
public required int Priority { get; init; }
public required string[] ReportKeys { get; init; }
public MultiValueFormatter ValueFormatter { get; init; }
}
}
/// <summary>
/// The input data to a <see cref="PlayReportValueFormatter"/>,
/// The input data to a <see cref="ValueFormatter"/>,
/// containing the currently running application's <see cref="ApplicationMetadata"/>,
/// and the matched <see cref="MessagePackObject"/> from the Play Report.
/// </summary>
public class PlayReportValue
public class Value
{
/// <summary>
/// The currently running application's <see cref="ApplicationMetadata"/>.
@ -245,7 +309,7 @@ namespace Ryujinx.Ava.Utilities
/// Access the <see cref="PackedValue"/> as its underlying .NET type.<br/>
///
/// Does not seem to work well with comparing numeric types,
/// so use <see cref="PackedValue"/> and the AsX (where X is a numerical type name i.e. Int32) methods for that.
/// so use XValue properties for that.
/// </summary>
public object BoxedValue => PackedValue.ToObject();
@ -269,14 +333,26 @@ namespace Ryujinx.Ava.Utilities
}
/// <summary>
/// The delegate type that powers the entire analysis system (as it currently is).<br/>
/// The delegate type that powers single value formatters.<br/>
/// Takes in the result value from the Play Report, and outputs:
/// <br/>
/// a formatted string,
/// <br/>
/// a signal that nothing was available to handle it,
/// <br/>
/// OR a signal to reset the value that the caller is using the <see cref="PlayReportAnalyzer"/> for.
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
/// </summary>
public delegate PlayReportAnalyzer.FormattedValue PlayReportValueFormatter(PlayReportValue value);
public delegate Analyzer.FormattedValue ValueFormatter(Value value);
/// <summary>
/// The delegate type that powers multiple value formatters.<br/>
/// Takes in the result value from the Play Report, and outputs:
/// <br/>
/// a formatted string,
/// <br/>
/// a signal that nothing was available to handle it,
/// <br/>
/// OR a signal to reset the value that the caller is using the <see cref="Analyzer"/> for.
/// </summary>
public delegate Analyzer.FormattedValue MultiValueFormatter(Value[] value);
}

View File

@ -0,0 +1,129 @@
using static Ryujinx.Ava.Utilities.PlayReport.Analyzer;
namespace Ryujinx.Ava.Utilities.PlayReport
{
public static class PlayReports
{
public static Analyzer Analyzer { get; } = new Analyzer()
.AddSpec(
"01007ef00011e000",
spec => spec
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
// reset to normal status when switching between normal & master mode in title screen
.AddValueFormatter("AoCVer", FormattedValue.AlwaysResets)
)
.AddSpec(
"0100f2c0115b6000",
spec => spec
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
.AddSpec(
"0100000000010000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
)
.AddSpec(
"010075000ecbe000",
spec =>
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
)
.AddSpec(
"010028600ebda000",
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
)
.AddSpec( // Global & China IDs
["0100152000022000", "010075100e8ec000"],
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
)
.AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec
.AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
);
private static FormattedValue BreathOfTheWild_MasterMode(Value value)
=> value.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
private static FormattedValue TearsOfTheKingdom_CurrentField(Value value) =>
value.DoubleValue switch
{
> 800d => "Exploring the Sky Islands",
< -201d => "Exploring the Depths",
_ => "Roaming Hyrule"
};
private static FormattedValue SuperMarioOdyssey_AssistMode(Value value)
=> value.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
private static FormattedValue SuperMarioOdysseyChina_AssistMode(Value value)
=> value.BoxedValue is 1 ? "Playing in 帮助模式" : "Playing in 普通模式";
private static FormattedValue SuperMario3DWorldOrBowsersFury(Value value)
=> value.BoxedValue is 0 ? "Playing Super Mario 3D World" : "Playing Bowser's Fury";
private static FormattedValue MarioKart8Deluxe_Mode(Value value)
=> value.StringValue switch
{
// Single Player
"Single" => "Single Player",
// Multiplayer
"Multi-2players" => "Multiplayer 2 Players",
"Multi-3players" => "Multiplayer 3 Players",
"Multi-4players" => "Multiplayer 4 Players",
// Wireless/LAN Play
"Local-Single" => "Wireless/LAN Play",
"Local-2players" => "Wireless/LAN Play 2 Players",
// CC Classes
"50cc" => "50cc",
"100cc" => "100cc",
"150cc" => "150cc",
"Mirror" => "Mirror (150cc)",
"200cc" => "200cc",
// Modes
"GrandPrix" => "Grand Prix",
"TimeAttack" => "Time Trials",
"VS" => "VS Races",
"Battle" => "Battle Mode",
"RaceStart" => "Selecting a Course",
"Race" => "Racing",
_ => FormattedValue.ForceReset
};
private static FormattedValue PokemonSVUnionCircle(Value value)
=> value.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
private static FormattedValue PokemonSVArea(Value value)
=> value.StringValue switch
{
// Base Game Locations
"a_w01" => "South Area One",
"a_w02" => "Mesagoza",
"a_w03" => "The Pokemon League",
"a_w04" => "South Area Two",
"a_w05" => "South Area Four",
"a_w06" => "South Area Six",
"a_w07" => "South Area Five",
"a_w08" => "South Area Three",
"a_w09" => "West Area One",
"a_w10" => "Asado Desert",
"a_w11" => "West Area Two",
"a_w12" => "Medali",
"a_w13" => "Tagtree Thicket",
"a_w14" => "East Area Three",
"a_w15" => "Artazon",
"a_w16" => "East Area Two",
"a_w18" => "Casseroya Lake",
"a_w19" => "Glaseado Mountain",
"a_w20" => "North Area Three",
"a_w21" => "North Area One",
"a_w22" => "North Area Two",
"a_w23" => "The Great Crater of Paldea",
"a_w24" => "South Paldean Sea",
"a_w25" => "West Paldean Sea",
"a_w26" => "East Paldean Sea",
"a_w27" => "Nouth Paldean Sea",
//TODO DLC Locations
_ => FormattedValue.ForceReset
};
}
}