Merge branch 'master' into master

This commit is contained in:
FluffyOMC 2025-02-10 11:32:13 -05:00 committed by GitHub
commit 4b947713a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 535 additions and 105 deletions

View File

@ -164,15 +164,16 @@ namespace Ryujinx.Common
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
//NSO Membership games
"0100ccf019c8c000", // F-ZERO 99
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100d870045b6000", // NES - Nintendo Switch Online
"0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100ccf019c8c000", // F-ZERO 99
"0100ad9012510000", // PAC-MAN 99
"010040600c5ce000", // Tetris 99
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100277011f1a000", // Super Mario Bros. 35
//Misc Nintendo 1st party games

View File

@ -1543,7 +1543,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "由 {0} 开发",
"zh_TW": ""
}
},
@ -1843,7 +1843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "兼容性:",
"zh_TW": ""
}
},
@ -1868,7 +1868,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "标题 ID:",
"zh_TW": ""
}
},
@ -1893,7 +1893,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "服务的游戏: {0}",
"zh_TW": ""
}
},
@ -1918,7 +1918,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "在线玩家: {0}",
"zh_TW": ""
}
},
@ -2268,7 +2268,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "清理 PPTC 缓存",
"zh_TW": ""
}
},
@ -2293,7 +2293,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "删除应用程序的所有 PPTC 缓存",
"zh_TW": ""
}
},
@ -2768,7 +2768,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "显示兼容性项目",
"zh_TW": ""
}
},
@ -2793,7 +2793,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "在兼容性列表中显示选定的游戏,您通常可以通过帮助菜单访问。",
"zh_TW": ""
}
},
@ -2818,7 +2818,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "显示游戏信息",
"zh_TW": ""
}
},
@ -2843,7 +2843,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "显示当前选定游戏的状态与详细信息。",
"zh_TW": ""
}
},
@ -4493,7 +4493,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "与系统时间同步",
"zh_TW": ""
}
},
@ -6143,7 +6143,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "重置设置",
"zh_TW": ""
}
},
@ -6168,7 +6168,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "我要重置我的设置。",
"zh_TW": ""
}
},
@ -8143,7 +8143,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "彩虹滚动速度",
"zh_TW": ""
}
},
@ -13418,7 +13418,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "您正要清理 PPTC 数据:\n\n{0}\n\n您确实要继续吗?",
"zh_TW": ""
}
},
@ -23568,7 +23568,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "启动和游戏时不会出现任何崩溃或任何类型的 GPU bug 且速度足够快可以在一般 PC 上尽情游玩。",
"zh_TW": ""
}
},
@ -23593,7 +23593,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可以成功启动并进入游戏但可能会遇到以下一种或多种问题: 崩溃、卡死、GPU bug、令人无法接受的音频,或者只是太慢。仍然可以继续进行游戏,但是可能无法达到预期。",
"zh_TW": ""
}
},
@ -23618,7 +23618,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可以启动并通过标题画面但是无法进入到主要的游戏流程。",
"zh_TW": ""
}
},
@ -23643,7 +23643,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "可以启动但是无法通过标题画面。",
"zh_TW": ""
}
},
@ -23668,7 +23668,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "无法启动或显示无任何动静。",
"zh_TW": ""
}
},
@ -23718,7 +23718,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "Rich Presence 图像",
"zh_TW": ""
}
},
@ -23743,7 +23743,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "动态 Rich Presence",
"zh_TW": ""
}
}

View File

@ -55,9 +55,21 @@
Tag="{Binding AppData.IdString}"
Text="{Binding AppData.LocalizedStatus}"
Foreground="{Binding AppData.PlayabilityStatus, Converter={x:Static helpers:PlayabilityStatusConverter.Shared}}"
ToolTip.Tip="{Binding AppData.LocalizedStatusTooltip}"
TextAlignment="Start"
TextWrapping="Wrap" />
TextWrapping="Wrap">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock
Text="{Binding AppData.LocalizedStatusTooltip}" />
<Separator
Margin="0, 10, 0, 10"
IsVisible="{Binding AppData.HasCompatibilityLabels}" />
<TextBlock
IsVisible="{Binding AppData.HasCompatibilityLabels}"
Text="{Binding AppData.FormattedCompatibilityLabels}" />
</StackPanel>
</ToolTip.Tip>
</TextBlock>
<Button.Styles>
<Style Selector="Button">
<Setter Property="MinWidth"

View File

@ -93,8 +93,19 @@
IsVisible="{Binding HasPlayabilityInfo}"
Background="{DynamicResource AppListBackgroundColor}"
Margin="-1, 0, 0, 0"
Padding="0"
ToolTip.Tip="{Binding LocalizedStatusTooltip}">
Padding="0">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock
Text="{Binding LocalizedStatusTooltip}" />
<Separator
Margin="0, 10, 0, 10"
IsVisible="{Binding HasCompatibilityLabels}" />
<TextBlock
IsVisible="{Binding HasCompatibilityLabels}"
Text="{Binding FormattedCompatibilityLabels}" />
</StackPanel>
</ToolTip.Tip>
<TextBlock
Margin="1.5"
Tag="{Binding IdString}"

View File

@ -1,3 +1,4 @@
using Gommon;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@ -14,6 +15,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.Loaders.Processes.Extensions;
using System;
using System.IO;
using System.Text;
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Utilities.AppLibrary
@ -32,33 +34,12 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
set
{
_id = value;
PlayabilityStatus = CompatibilityCsv.GetStatus(Id);
Compatibility = CompatibilityCsv.Find(Id);
}
}
public string Developer { get; set; } = "Unknown";
public string Version { get; set; } = "0";
public bool HasPlayabilityInfo => PlayabilityStatus != null;
public string LocalizedStatus =>
PlayabilityStatus.HasValue
? LocaleManager.Instance[PlayabilityStatus!.Value]
: string.Empty;
public LocaleKeys? PlayabilityStatus { get; set; }
public string LocalizedStatusTooltip =>
PlayabilityStatus.HasValue
#pragma warning disable CS8509 // It is exhaustive for any value this property can contain.
? LocaleManager.Instance[PlayabilityStatus!.Value switch
#pragma warning restore CS8509
{
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
}]
: string.Empty;
public int PlayerCount { get; set; }
public int GameCount { get; set; }
@ -83,6 +64,53 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
public Optional<CompatibilityEntry> Compatibility { get; private set; }
public bool HasPlayabilityInfo => Compatibility.HasValue;
public string LocalizedStatus => Compatibility.Convert(x => x.LocalizedStatus);
public bool HasCompatibilityLabels => !FormattedCompatibilityLabels.Equals(string.Empty);
public string FormattedCompatibilityLabels
=> Compatibility.Convert(x => x.FormattedIssueLabels).OrElse(string.Empty);
public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null);
public string CompatibilityToolTip
{
get
{
StringBuilder sb = new(LocalizedStatusTooltip);
string formattedCompatibilityLabels = FormattedCompatibilityLabels;
if (!string.IsNullOrEmpty(formattedCompatibilityLabels))
{
sb.AppendLine()
.AppendLine()
.Append(formattedCompatibilityLabels);
}
return sb.ToString();
}
}
public string LocalizedStatusTooltip =>
Compatibility.Convert(x =>
#pragma warning disable CS8509 It is exhaustive for all possible values this can contain.
LocaleManager.Instance[x.Status switch
#pragma warning restore CS8509
{
LocaleKeys.CompatibilityListPlayable => LocaleKeys.CompatibilityListPlayableTooltip,
LocaleKeys.CompatibilityListIngame => LocaleKeys.CompatibilityListIngameTooltip,
LocaleKeys.CompatibilityListMenus => LocaleKeys.CompatibilityListMenusTooltip,
LocaleKeys.CompatibilityListBoots => LocaleKeys.CompatibilityListBootsTooltip,
LocaleKeys.CompatibilityListNothing => LocaleKeys.CompatibilityListNothingTooltip,
}]
).OrElse(string.Empty);
[JsonIgnore] public string IdString => Id.ToString("x16");

View File

@ -60,10 +60,21 @@ namespace Ryujinx.Ava.Utilities.Compat
}
}
public static CompatibilityEntry Find(string titleId)
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId));
public static CompatibilityEntry Find(ulong titleId)
=> Find(titleId.ToString("X16"));
public static LocaleKeys? GetStatus(string titleId)
=> Entries.FirstOrDefault(x => x.TitleId.HasValue && x.TitleId.Value.EqualsIgnoreCase(titleId))?.Status;
=> Find(titleId)?.Status;
public static LocaleKeys? GetStatus(ulong titleId) => GetStatus(titleId.ToString("X16"));
public static string GetLabels(string titleId)
=> Find(titleId)?.FormattedIssueLabels;
public static string GetLabels(ulong titleId) => GetLabels(titleId.ToString("X16"));
}
public class CompatibilityEntry

View File

@ -31,8 +31,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
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;
return AddSpec(transform(GameSpec.Create(titleId)));
}
/// <summary>
@ -46,8 +45,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
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;
return AddSpec(GameSpec.Create(titleId).Apply(transform));
}
/// <summary>
@ -63,8 +61,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
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;
return AddSpec(transform(GameSpec.Create(tids)));
}
/// <summary>
@ -79,7 +76,17 @@ namespace Ryujinx.Ava.Utilities.PlayReport
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 AddSpec(GameSpec.Create(tids).Apply(transform));
}
/// <summary>
/// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec.
/// </summary>
/// <param name="spec">The <see cref="GameSpec"/> to add.</param>
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
public Analyzer AddSpec(GameSpec spec)
{
_specs.Add(spec);
return this;
}

View File

@ -7,7 +7,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
{
public partial class PlayReports
{
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
private static FormattedValue BreathOfTheWild_MasterMode(SingleValue value)
=> value.Matched.BoxedValue is 1 ? "Playing Master Mode" : FormattedValue.ForceReset;
private static FormattedValue TearsOfTheKingdom_CurrentField(SingleValue value) =>
@ -112,7 +112,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
if (values.Matched.ContainsKey("adv_slot"))
{
return "Playing Adventure Mode"; // Doing this as it can be a placeholder until we can grab the character.
return
"Playing Adventure Mode"; // Doing this as it can be a placeholder until we can grab the character.
}
// Check if we have a match_mode at this point, if not, go to default.
@ -267,7 +268,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
{
if (!int.TryParse(player.Key.Split('_')[1], out int playerNumber))
continue;
string character = SuperSmashBrosUltimate_Character(player.Value);
int? rank = values.Matched.TryGetValue($"player_{playerNumber}_rank", out Value rankValue)
? rankValue.IntValue
@ -278,12 +279,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
}
players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList();
return players.Count > 4
? $"{players.Count} Players - " + string.Join(", ",
players.Take(3).Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}"))
: string.Join(", ", players.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}"));
string RankMedal(int? rank) => rank switch
{
0 => "🥇",
@ -292,5 +293,334 @@ namespace Ryujinx.Ava.Utilities.PlayReport
_ => ""
};
}
private static FormattedValue NsoEmulator_LaunchedGame(SingleValue value) => value.Matched.StringValue switch
{
#region SEGA Genesis
"m_0054_e" => Playing("Alien Soldier"),
"m_3978_e" => Playing("Alien Storm"),
"m_5234_e" => Playing("ALISIA DRAGOON"),
"m_5003_e" => Playing("Streets of Rage 2"),
"m_4843_e" => Playing("Kid Chameleon"),
"m_2874_e" => Playing("Columns"),
"m_3167_e" => Playing("Comix Zone"),
"m_5007_e" => Playing("Contra: Hard Corps"),
"m_0865_e" => Playing("Ghouls 'n Ghosts"),
"m_0935_e" => Playing("Dynamite Headdy"),
"m_8314_e" => Playing("Earthworm Jim"),
"m_5012_e" => Playing("Ecco the Dolphin"),
"m_2207_e" => Playing("Flicky"),
"m_9432_e" => Playing("Golden Axe II"),
"m_5015_e" => Playing("Golden Axe"),
"m_5017_e" => Playing("Gunstar Heroes"),
"m_0732_e" => Playing("Altered Beast"),
"m_2245_e" or "m_2245_pd" or "m_2245_pf" => Playing("Landstalker"),
"m_1654_e" => Playing("Target Earth"),
"m_7050_e" => Playing("Light Crusader"),
"m_5027_e" => Playing("M.U.S.H.A."),
"m_5028_e" => Playing("Phantasy Star IV"),
"m_9155_e" => Playing("Pulseman"),
"m_5030_e" => Playing("Dr. Robotnik's Mean Bean Machine"),
"m_0098_e" => Playing("Crusader of Centy"),
"m_0098_k" => Playing("신창세기 라그나센티"),
"m_0098_pd" or "m_0098_pf" or "m_0098_ps" => Playing("Soleil"),
"m_5033_e" => Playing("Ristar"),
"m_1987_e" => Playing("MEGA MAN: THE WILY WARS"),
"m_2609_e" => Playing("WOLF OF THE BATTLEFIELD: MERCS"),
"m_3353_e" => Playing("Shining Force II"),
"m_5036_e" => Playing("Shining Force"),
"m_9866_e" => Playing("Sonic The Hedgehog Spinball"),
"m_5041_e" => Playing("Sonic The Hedgehog 2"),
"m_5523_e" => Playing("Space Harrier II"),
"m_0041_e" => Playing("STREET FIGHTER II' : SPECIAL CHAMPION EDITION"),
"m_5044_e" => Playing("STRIDER"),
"m_6353_e" => Playing("Super Fantasy Zone"),
"m_9569_e" => Playing("Beyond Oasis"),
"m_9569_k" => Playing("스토리 오브 도어"),
"m_9569_pd" or "m_9569_ps" => Playing("The Story of Thor"),
"m_9569_pf" => Playing("La Légende de Thor"),
"m_5049_e" => Playing("Shinobi III: Return of the Ninja Master"),
"m_6811_e" => Playing("The Revenge of Shinobi"),
"m_4372_e" => Playing("Thunder Force II"),
"m_1535_e" => Playing("ToeJam & Earl in Panic on Funkotron"),
"m_0432_e" => Playing("ToeJam & Earl"),
"m_5052_e" => Playing("Castlevania: BLOODLINES"),
"m_3626_e" => Playing("VectorMan"),
"m_7955_e" => Playing("Sword of Vermilion"),
"m_0394_e" => Playing("Virtua Fighter 2"),
"m_9417_e" => Playing("Zero Wing"),
#endregion
#region Nintendo 64
"n_1653_e" or "n_1653_p" => Playing("1080º ™ Snowboarding"),
"n_4868_e" or "n_4868_p" => Playing("Banjo Kazooie™"),
"n_1226_e" or "n_1226_p" => Playing("Banjo-Tooie™"),
"n_3083_e" or "n_3083_p" => Playing("Blast Corps"),
"n_3007_e" => Playing("Dr. Mario™ 64"),
"n_4238_e" => Playing("Excitebike™ 64"),
"n_1870_e" => Playing("Extreme G"),
"n_2456_e" => Playing("F-Zero™ X"),
"n_4631_e" => Playing("GoldenEye 007"),
"n_1635_e" => Playing("Harvest Moon 64"),
"n_2225_e" => Playing("Iggys Reckin Balls"),
"n_1625_e" or "n_1625_p" => Playing("JET FORCE GEMINI™"),
"n_3052_e" => Playing("Kirby 64™: The Crystal Shards"),
"n_4371_e" => Playing("Mario Golf™"),
"n_3013_e" => Playing("Mario Kart™ 64"),
"n_1053_e" or "n_1053_p" => Playing("Mario Party™ 2"),
"n_2965_e" or "n_2965_p" => Playing("Mario Party™ 3"),
"n_4737_e" or "n_4737_p" => Playing("Mario Party™"),
"n_3017_e" => Playing("Mario Tennis™"),
"n_2992_e" or "n_2992_p" => Playing("Paper Mario™"),
"n_3783_e" or "n_3783_p" => Playing("Pilotwings™ 64"),
"n_1848_e" or "n_1848_pd" or "n_1848_pf" => Playing("Pokémon™ Puzzle League"),
"n_3240_e" or "n_3240_pd" or "n_3240_pf" or "n_3240_pi" or "n_3240_ps" => Playing("Pokémon Snap™"),
"n_4590_e" or "n_4590_pd" or "n_4590_pf" or "n_4590_pi" or "n_4590_ps" => Playing("Pokémon Stadium™"),
"n_3309_e" or "n_3309_pd" or "n_3309_pf" or "n_3309_pi" or "n_3309_ps" => Playing("Pokémon Stadium 2™"),
"n_3029_e" => Playing("Sin & Punishment™"),
"n_3030_e" => Playing("Star Fox™ 64"),
"n_3030_p" => Playing("Lylat Wars™"),
"n_3031_e" or "n_3031_p" => Playing("Super Mario 64™"),
"n_4813_e" or "n_4813_p" => Playing("Wave Race™ 64"),
"n_3034_e" => Playing("WIN BACK: COVERT OPERATIONS"),
"n_3034_p" => Playing("OPERATION: WIN BACK"),
"n_3036_e" or "n_3036_p" => Playing("Yoshi's Story™"),
"n_1407_e" or "n_1407_p" => Playing("The Legend of Zelda™: Majora's Mask™"),
"n_3038_e" or "n_3038_p" => Playing("The Legend of Zelda™: Ocarina of Time™"),
#endregion
#region NES
"clv_p_naaae" => Playing("Super Mario Bros.™"),
"clv_p_naabe" => Playing("Super Mario Bros.™: The Lost Levels"),
"clv_p_naace" or "clv_p_naace_sp1" => Playing("Super Mario Bros.™ 3"),
"clv_p_naade" => Playing("Super Mario Bros.™ 2"),
"clv_p_naaee" => Playing("Donkey Kong™"),
"clv_p_naafe" => Playing("Donkey Kong Jr.™"),
"clv_p_naage" => Playing("Donkey Kong™ 3"),
"clv_p_naahe" => Playing("Excitebike™"),
"clv_p_naaje" => Playing("EarthBound Beginnings"),
"clv_p_naame" => Playing("NES™ Open Tournament Golf"),
"clv_p_naane" or "clv_p_naane_sp1" => Playing("The Legend of Zelda™"),
"clv_p_naape" or "clv_p_naape_sp1" => Playing("Kirby's Adventure™"),
"clv_p_naaqe" or "clv_p_naaqe_sp1" or "clv_p_naaqe_sp2" => Playing("Metroid™"),
"clv_p_naare" => Playing("Balloon Fight™"),
"clv_p_naase" or "clv_p_naase_sp1" => Playing("Zelda II - The Adventure of Link™"),
"clv_p_naate" => Playing("Punch-Out!!™ Featuring Mr. Dream"),
"clv_p_naaue" => Playing("Ice Climber™"),
"clv_p_naave" or "clv_p_naave_sp1" => Playing("Kid Icarus™"),
"clv_p_naawe" => Playing("Mario Bros.™"),
"clv_p_naaxe" or "clv_p_naaxe_sp1" => Playing("Dr. Mario™"),
"clv_p_naaye" => Playing("Yoshi™"),
"clv_p_naaze" => Playing("StarTropics™"),
"clv_p_nabce" or "clv_p_nabce_sp1" => Playing("Ghosts'n Goblins™"),
"clv_p_nabre" or "clv_p_nabre_sp1" or "clv_p_nabre_sp2" => Playing("Gradius"),
"clv_p_nacbe" or "clv_p_nacbe_sp1" => Playing("Ninja Gaiden"),
"clv_p_nacce" => Playing("Solomon's Key"),
"clv_p_nacde" => Playing("Tecmo Bowl"),
"clv_p_nacfe" => Playing("Double Dragon"),
"clv_p_nache" => Playing("Double Dragon II: The Revenge"),
"clv_p_nacje" => Playing("River City Ransom"),
"clv_p_nacke" => Playing("Super Dodge Ball"),
"clv_p_nacle" => Playing("Downtown Nekketsu March Super-Awesome Field Day!"),
"clv_p_nacpe" => Playing("The Mystery of Atlantis"),
"clv_p_nacre" => Playing("Soccer"),
"clv_p_nacse" or "clv_p_nacse_sp1" => Playing("Ninja JaJaMaru-kun"),
"clv_p_nacte" => Playing("Ice Hockey"),
"clv_p_nacue" or "clv_p_nacue_sp1" => Playing("Blaster Master"),
"clv_p_nacwe" => Playing("ADVENTURES OF LOLO"),
"clv_p_nacxe" => Playing("Wario's Woods™"),
"clv_p_nacye" => Playing("Tennis"),
"clv_p_nacze" => Playing("Wrecking Crew™"),
"clv_p_nadbe" => Playing("Joy Mech Fight™"),
"clv_p_nadde" or "clv_p_nadde_sp1" => Playing("Star Soldier"),
"clv_p_nadke" => Playing("Tetris®"),
"clv_p_nadle" => Playing("Pro Wrestling"),
"clv_p_nadpe" => Playing("Baseball"),
"clv_p_nadte" or "clv_p_nadte_sp1" => Playing("TwinBee"),
"clv_p_nadue" or "clv_p_nadue_sp1" => Playing("Mighty Bomb Jack"),
"clv_p_nadve" => Playing("Kung-Fu Heroes"),
"clv_p_nadxe" => Playing("City Connection"),
"clv_p_nadye" => Playing("Rygar"),
"clv_p_naeae" => Playing("Crystalis"),
"clv_p_naece" => Playing("Vice: Project Doom"),
"clv_p_naehe" => Playing("Clu Clu Land™"),
"clv_p_naeie" => Playing("VS. Excitebike™"),
"clv_p_naeje" => Playing("Volleyball™"),
"clv_p_naeke" => Playing("JOURNEY TO SILIUS"),
"clv_p_naele" => Playing("S.C.A.T.: Special Cybernetic Attack Team"),
"clv_p_naeme" => Playing("Shadow of the Ninja"),
"clv_p_naene" => Playing("Nightshade"),
"clv_p_naepe" => Playing("The Immortal"),
"clv_p_naeqe" => Playing("Eliminator Boat Duel"),
"clv_p_naere" => Playing("Fire 'n Ice"),
"clv_p_nafce" => Playing("XEVIOUS"),
"clv_p_nagpe" => Playing("DAIVA STORY 6 IMPERIAL OF NIRSARTIA"),
"clv_p_nagqe" => Playing("DIG DUGⅡ"),
"clv_p_nague" => Playing("MAPPY-LAND"),
"clv_p_nahhe" => Playing("Mach Rider™"),
"clv_p_nahje" => Playing("Pinball"),
"clv_p_nahre" => Playing("Mystery Tower"),
"clv_p_nahte" => Playing("Urban Champion™"),
"clv_p_nahue" => Playing("Donkey Kong Jr.™ Math"),
"clv_p_nahve" => Playing("The Mysterious Murasame Castle"),
"clv_p_najae" => Playing("DEVIL WORLD™"),
"clv_p_najbe" => Playing("Golf"),
"clv_p_najpe" => Playing("R.C. PRO-AM™"),
"clv_p_najre" => Playing("COBRA TRIANGLE™"),
"clv_p_najse" => Playing("SNAKE RATTLE N ROLL™"),
"clv_p_najte" => Playing("SOLAR® JETMAN"),
#endregion
#region SNES
"s_2180_e" => Playing("BATTLETOADS™ DOUBLE DRAGON™"),
"s_2179_e" => Playing("BATTLETOADS™ IN BATTLEMANIACS"),
"s_2182_e" => Playing("BIG RUN"),
"s_2156_e" => Playing("Bombuzal"),
"s_2002_e" => Playing("BRAWL BROTHERS"),
"s_2025_e" => Playing("Breath of Fire II"),
"s_2003_e" => Playing("Breath Of Fire"),
"s_2163_e" => Playing("Claymates"),
"s_2150_e" => Playing("Congo's Caper"),
"s_2171_e" => Playing("COSMO GANG THE PUZZLE"),
"s_2004_e" => Playing("Demon's Crest"),
"s_2026_e" => Playing("Kunio-kun no Dodgeball da yo Zen'in Shūgō!"),
"s_2060_e" => Playing("Donkey Kong Country 2: Diddy's Kong Quest"),
"s_2061_e" => Playing("Donkey Kong Country 3: Dixie Kong's Double Trouble!"),
"s_2055_e" => Playing("Donkey Kong Country"),
"s_2139_e" => Playing("DOOMSDAY WARRIOR"),
"s_2051_e" => Playing("EarthBound"),
"s_2162_e" => Playing("Earthworm Jim™ 2"),
"s_2005_e" => Playing("F-ZERO™"),
"s_2183_e" => Playing("FATAL FURY 2"),
"s_2174_e" => Playing("Fighter's History"),
"s_2037_e" => Playing("Harvest Moon"),
"s_2161_e" => Playing("Jelly Boy"),
"s_2006_e" => Playing("Joe & Mac 2: Lost in the Tropics"),
"s_2169_e" => Playing("Caveman Ninja"),
"s_2181_e" => Playing("KILLER INSTINCT™"),
"s_2029_e" or "s_2029_e_sp1" => Playing("Kirby Super Star™"),
"s_2121_e" => Playing("Kirby's Avalanche™"),
"s_2007_e" or "s_2007_e_sp1" => Playing("Kirby's Dream Course™"),
"s_2008_e" or "s_2008_e_sp1" => Playing("Kirby's Dream Land™ 3"),
"s_2172_e" => Playing("Kirbys Star Stacker™"),
"s_2151_e" => Playing("Magical Drop2"),
"s_2044_e" => Playing("Mario's Super Picross"),
"s_2038_e" => Playing("Natsume Championship Wrestling"),
"s_2140_e" => Playing("Operation Logic Bomb"),
"s_2034_e" => Playing("Panel de Pon"),
"s_2009_e" => Playing("Pilotwings™"),
"s_2010_e" => Playing("Pop'n TwinBee"),
"s_2157_e" => Playing("Prehistorik Man"),
"s_2145_e" => Playing("Psycho Dream"),
"s_2141_e" => Playing("Rival Turf!"),
"s_2152_e" => Playing("SIDE POCKET"),
"s_2158_e" => Playing("Spankys™ Quest"),
"s_2031_e" => Playing("Star Fox™ 2"),
"s_2011_e" => Playing("Star Fox™"),
"s_2012_e" => Playing("Stunt Race FX™"),
"s_2032_e" => Playing("Amazing Hebereke"),
"s_2159_e" => Playing("Super Baseball Simulator 1.000"),
"s_2013_e" => Playing("SUPER E.D.F. EARTH DEFENSE FORCE"),
"s_2014_e" => Playing("Smash Tennis"),
"s_2015_e" => Playing("Super Ghouls'n Ghosts™"),
"s_2033_e" => Playing("Super Mario All-Stars™"),
"s_2016_e" or "s_2016_e_sp1" => Playing("Super Mario Kart™"),
"s_2017_e" or "s_2017_e_sp1" => Playing("Super Mario World™"),
"s_2018_e" or "s_2018_e_sp1" => Playing("Super Metroid™"),
"s_2184_e" => Playing("Super Ninja Boy"),
"s_2019_e" or "s_2019_e_sp1" => Playing("Super Punch-Out!!™"),
"s_2020_e" => Playing("Super Puyo Puyo 2"),
"s_2133_e" => Playing("SUPER R-TYPE"),
"s_2021_e" => Playing("Super Soccer"),
"s_2022_e" => Playing("Super Tennis"),
"s_2136_e" => Playing("Sutte Hakkun"),
"s_2142_e" => Playing("The Ignition Factor"),
"s_2143_e" => Playing("The Peace Keepers"),
"s_2146_e" => Playing("Tuff E Nuff"),
"s_2144_e" => Playing("SUPER VALIS Ⅳ"),
"s_2049_e" => Playing("Wild Guns"),
"s_2096_e" => Playing("Wrecking Crew™ '98"),
"s_2023_e" => Playing("Super Mario World™ 2: Yoshi's Island™"),
"s_2024_e" => Playing("The Legend of Zelda™: A Link to the Past™"),
#endregion
#region GameBoy
"c_7224_e" or "c_7224_p" => Playing("Alone in the Dark: The New Nightmare"),
"c_5022_e" => Playing("Blaster Master: Enemy Below"),
"c_3381_e" => Playing("Game & Watch™ Gallery 3"),
"c_0282_e" => Playing("Kirby Tilt n Tumble™"),
"c_4471_e" or "c_4471_p" => Playing("Mario Golf™"),
"c_9947_e" => Playing("Mario Tennis™"),
"c_3191_e" or "c_3191_p" or "c_3191_x" => Playing("Pokémon™ Trading Card Game"),
"c_8914_e" or "c_8914_p" => Playing("Quest for Camelot™"),
"c_2648_e" => Playing("Tetris® DX"),
"c_5928_e" => Playing("Wario Land™ 3"),
"c_3996_e" or "c_3996_pd" or "c_3996_pf" => Playing("The Legend of Zelda™: Link's Awakening DX™"),
"c_8852_e" or "c_8852_p" => Playing("The Legend of Zelda™: Oracle of Ages™"),
"c_9130_e" or "c_9130_p" => Playing("The Legend of Zelda™: Oracle of Seasons™"),
"d_6879_e" => Playing("Alleyway™"),
"d_7618_e" => Playing("Baseball"),
"d_6005_e" => Playing("BurgerTime Deluxe"),
"d_7120_e" => Playing("Castlevania Legends"),
"d_2744_e" => Playing("Dr. Mario™"),
"d_1593_e" => Playing("Donkey Kong Land 2™"),
"d_7216_e" => Playing("Donkey Kong Land III™"),
"d_4971_e" => Playing("Donkey Kong Land™"),
"d_7984_e" => Playing("GARGOYLE'S QUEST"),
"d_8212_e" => Playing("Kirby's Dream Land™ 2"),
"d_5661_e" => Playing("Kirby's Dream Land™"),
"d_3837_e" => Playing("MEGA MAN II"),
"d_1965_e" => Playing("MEGA MAN III"),
"d_0194_e" => Playing("MEGA MAN IV"),
"d_1425_e" => Playing("MEGA MAN V"),
"d_9324_e" => Playing("MEGA MAN: DR. WILY'S REVENGE"),
"d_1577_e" => Playing("Metroid™ II - Return of Samus™"),
"d_5124_e" => Playing("Super Mario Land™ 2 - 6 Golden Coins™"),
"d_7970_e" => Playing("Super Mario Land™"),
"d_8484_e" => Playing("Tetris®"),
#endregion
#region GameBoy Advance
"a_9694_e" => Playing("Densetsu no Starfy 1"),
"a_5600_e" => Playing("Densetsu no Starfy 2"),
"a_7565_e" => Playing("Densetsu no Starfy 3"),
"a_6553_e" => Playing("F-ZERO CLIMAX"),
"a_7842_e" or "a_7842_p" => Playing("F-Zero™- GP Legend"),
"a_9283_e" => Playing("F-Zero™ Maximum Velocity"),
"a_3744_e" or "a_3744_x" or "a_3744_y" => Playing("Fire Emblem™"),
"a_8978_d" or "a_8978_e" or "a_8978_f" or "a_8978_i" or "a_8978_s" => Playing("Golden Sun™: The Lost Age"),
"a_3108_d" or "a_3108_e" or "a_3108_f" or "a_3108_i" or "a_3108_s" => Playing("Golden Sun™"),
"a_3654_e" or "a_3654_p" => Playing("Kirby™ & The Amazing Mirror"),
"a_7279_p" => Playing("Kuru Kuru Kururin™"),
"a_7311_e" or "a_7311_p" => Playing("Mario & Luigi™: Superstar Saga"),
"a_6845_e" => Playing("Mario Kart™: Super Circuit™"),
"a_4139_e" or "a_4139_p" => Playing("Metroid™ Fusion"),
"a_6834_e" or "a_6834_p" => Playing("Metroid™: Zero Mission"),
"a_8989_e" or "a_8989_p" => Playing("Pokémon™ Mystery Dungeon: Red Rescue Team"),
"a_9444_e" => Playing("Super Mario™ Advance"),
"a_9901_e" or "a_9901_p" => Playing("Super Mario™ Advance 4: Super Mario Bros.™ 3"),
"a_2939_e" => Playing("Super Mario World™: Super Mario Advance 2"),
"a_2939_p" => Playing("Super Mario World™: Super Mario Advance 2™"),
"a_1302_e" => Playing("WarioWare™, Inc.: Mega Microgame$!"),
"a_1302_p" => Playing("WarioWare™, Inc.: Minigame Mania."),
"a_6960_e" or "a_6960_p" => Playing("Yoshi's Island™: Super Mario™ Advance 3"),
"a_5190_e" or "a_5190_p" => Playing("The Legend of Zelda™: A Link to the Past™ Four Swords"),
"a_8665_e" or "a_8665_p" => Playing("The Legend of Zelda™: The Minish Cap"),
#endregion
_ => FormattedValue.ForceReset
};
}
}

View File

@ -42,7 +42,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
spec => spec
.AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
).AddSpec(
)
.AddSpec(
"01006a800016e000",
spec => spec
.AddSparseMultiValueFormatter(
@ -59,6 +60,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
],
SuperSmashBrosUltimate_Mode
)
)
.AddSpec(
[
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
"010012f017576000", "0100c62011050000", "0100b3c014bda000"],
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
);
private static string Playing(string game) => $"Playing {game}";
}
}

View File

@ -14,8 +14,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// </summary>
public class GameSpec
{
public static GameSpec Create(string requiredTitleId, params IEnumerable<string> otherTitleIds)
=> new() { TitleIds = otherTitleIds.Prepend(requiredTitleId).ToArray() };
public static GameSpec Create(IEnumerable<string> titleIds)
=> new() { TitleIds = titleIds.ToArray() };
private int _lastPriority;
public required string[] TitleIds { get; init; }
public List<FormatterSpecBase> ValueFormatters { get; } = [];
@ -28,8 +34,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <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="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddValueFormatter(string reportKey, SingleValueFormatter valueFormatter)
=> AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
public GameSpec AddValueFormatter(
string reportKey,
SingleValueFormatter valueFormatter
) => AddValueFormatter(_lastPriority++, reportKey, valueFormatter);
/// <summary>
/// Add a value formatter at a specific priority to the current <see cref="GameSpec"/>
@ -39,15 +47,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <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="GameSpec"/>, for chaining convenience.</returns>
public GameSpec AddValueFormatter(int priority, string reportKey,
SingleValueFormatter valueFormatter)
public GameSpec AddValueFormatter(
int priority,
string reportKey,
SingleValueFormatter valueFormatter
) => AddValueFormatter(new FormatterSpec
{
ValueFormatters.Add(new FormatterSpec
{
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
});
return this;
}
Priority = priority, ReportKeys = [reportKey], Formatter = valueFormatter
});
/// <summary>
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
@ -56,8 +63,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <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)
=> AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
public GameSpec AddMultiValueFormatter(
string[] reportKeys,
MultiValueFormatter valueFormatter
) => AddMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
/// <summary>
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
@ -67,15 +76,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <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)
public GameSpec AddMultiValueFormatter(
int priority,
string[] reportKeys,
MultiValueFormatter valueFormatter
) => AddValueFormatter(new MultiFormatterSpec
{
ValueFormatters.Add(new MultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
return this;
}
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
/// <summary>
/// Add a multi-value formatter to the current <see cref="GameSpec"/>
@ -87,8 +95,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <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 AddSparseMultiValueFormatter(string[] reportKeys, SparseMultiValueFormatter valueFormatter)
=> AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
public GameSpec AddSparseMultiValueFormatter(
string[] reportKeys,
SparseMultiValueFormatter valueFormatter
) => AddSparseMultiValueFormatter(_lastPriority++, reportKeys, valueFormatter);
/// <summary>
/// Add a multi-value formatter at a specific priority to the current <see cref="GameSpec"/>
@ -101,13 +111,18 @@ namespace Ryujinx.Ava.Utilities.PlayReport
/// <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 AddSparseMultiValueFormatter(int priority, string[] reportKeys,
SparseMultiValueFormatter valueFormatter)
public GameSpec AddSparseMultiValueFormatter(
int priority,
string[] reportKeys,
SparseMultiValueFormatter valueFormatter
) => AddValueFormatter(new SparseMultiFormatterSpec
{
ValueFormatters.Add(new SparseMultiFormatterSpec
{
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
Priority = priority, ReportKeys = reportKeys, Formatter = valueFormatter
});
private GameSpec AddValueFormatter<T>(T formatterSpec) where T : FormatterSpecBase
{
ValueFormatters.Add(formatterSpec);
return this;
}
}
@ -174,16 +189,17 @@ namespace Ryujinx.Ava.Utilities.PlayReport
return true;
}
}
public abstract class FormatterSpecBase
{
public abstract bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object data);
public int Priority { get; init; }
public string[] ReportKeys { get; init; }
public Delegate Formatter { get; init; }
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, out FormattedValue formattedValue)
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
out FormattedValue formattedValue)
{
formattedValue = default;
if (!GetData(playReport, out object data))
@ -197,15 +213,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport
switch (Formatter)
{
case SingleValueFormatter svf when data is MessagePackObject mpo:
formattedValue = svf(new SingleValue(mpo) { Application = appMeta, PlayReport = playReport });
case SingleValueFormatter svf when data is MessagePackObject match:
formattedValue = svf(
new SingleValue(match) { Application = appMeta, PlayReport = playReport }
);
return true;
case MultiValueFormatter mvf when data is List<MessagePackObject> messagePackObjects:
formattedValue = mvf(new MultiValue(messagePackObjects) { Application = appMeta, PlayReport = playReport });
case MultiValueFormatter mvf when data is List<MessagePackObject> matches:
formattedValue = mvf(
new MultiValue(matches) { Application = appMeta, PlayReport = playReport }
);
return true;
case SparseMultiValueFormatter smvf when
data is Dictionary<string, MessagePackObject> sparseMessagePackObjects:
formattedValue = smvf(new SparseMultiValue(sparseMessagePackObjects) { Application = appMeta, PlayReport = playReport });
case SparseMultiValueFormatter smvf when data is Dictionary<string, MessagePackObject> sparseMatches:
formattedValue = smvf(
new SparseMultiValue(sparseMatches) { Application = appMeta, PlayReport = playReport }
);
return true;
default:
throw new InvalidOperationException("Formatter delegate is not of a known type!");