From aa2178dbe5eee7079035e4098900b10f0e09bbe3 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 15 Feb 2025 20:25:17 -0600 Subject: [PATCH 1/3] UI: Button to open screenshots folder in File menu --- src/Ryujinx/Assets/locales.json | 50 +++++++++++++++++++ .../UI/ViewModels/MainWindowViewModel.cs | 19 +++++++ .../UI/Views/Main/MainMenuBarView.axaml | 4 ++ 3 files changed, 73 insertions(+) diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 555c6d3c3..3623a0073 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -447,6 +447,31 @@ "zh_TW": "開啟 Ryujinx 資料夾" } }, + { + "ID": "MenuBarFileOpenScreenshotsFolder", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Open Screenshots Folder", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "MenuBarFileOpenLogsFolder", "Translations": { @@ -17197,6 +17222,31 @@ "zh_TW": "開啟 Ryujinx 檔案系統資料夾" } }, + { + "ID": "OpenScreenshotFolderTooltip", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Open Ryujinx screenshots folder", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "OpenRyujinxLogsTooltip", "Translations": { diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index bbfe80570..7d7674c4e 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1347,6 +1347,25 @@ namespace Ryujinx.Ava.UI.ViewModels OpenHelper.OpenFolder(AppDataManager.BaseDirPath); } + public void OpenScreenshotsFolder() + { + string screenshotsDir = Path.Combine(AppDataManager.BaseDirPath, "screenshots"); + + try + { + if (!Directory.Exists(screenshotsDir)) + Directory.CreateDirectory(screenshotsDir); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + OpenHelper.OpenFolder(screenshotsDir); + } + public void OpenLogsFolder() { string logPath = AppDataManager.GetOrCreateLogsDir(); diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index cacb4b130..1da91c388 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -66,6 +66,10 @@ Command="{Binding OpenRyujinxFolder}" Header="{ext:Locale MenuBarFileOpenEmuFolder}" ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" /> + Date: Sat, 15 Feb 2025 20:45:27 -0600 Subject: [PATCH 2/3] UI: Add descriptions of what each dynamic RPC formatter actually shows when hovering whether it has support in the game info popup --- .../UI/Controls/ApplicationDataView.axaml | 14 +++++--- .../UI/ViewModels/ApplicationDataViewModel.cs | 6 ++++ .../Utilities/AppLibrary/ApplicationData.cs | 10 ++++-- src/Ryujinx/Utilities/PlayReport/Analyzer.cs | 11 +++++-- .../Utilities/PlayReport/PlayReports.cs | 32 ++++++++++++++----- src/Ryujinx/Utilities/PlayReport/Specs.cs | 16 +++++++++- 6 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml index aee8f7b36..92e4d1ac3 100644 --- a/src/Ryujinx/UI/Controls/ApplicationDataView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationDataView.axaml @@ -119,17 +119,23 @@ TextWrapping="Wrap" > - - + + + TextWrapping="Wrap"> - + AppData = appData; + public string DynamicRichPresenceDescription => + AppData.HasDynamicRichPresenceSupport + ? AppData.RichPresenceSpec.Value.Description + : GameSpec.DefaultDescription; + public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version); public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer); public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension); diff --git a/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs b/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs index 747ead8ca..2658352d7 100644 --- a/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs +++ b/src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs @@ -10,6 +10,7 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Utilities.Compat; +using Ryujinx.Ava.Utilities.PlayReport; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.Loaders.Processes.Extensions; @@ -35,9 +36,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary { _id = value; - Compatibility = CompatibilityCsv.Find(Id); + Compatibility = CompatibilityCsv.Find(value); + RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec) + ? gameSpec + : default(Optional); } } + public Optional RichPresenceSpec { get; set; } + public string Developer { get; set; } = "Unknown"; public string Version { get; set; } = "0"; public int PlayerCount { get; set; } @@ -46,7 +52,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public bool HasLdnGames => PlayerCount != 0 && GameCount != 0; public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString); - public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString); + public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue; public TimeSpan TimePlayed { get; set; } public DateTime? LastPlayed { get; set; } diff --git a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs index cd4021bc4..8faf4fb31 100644 --- a/src/Ryujinx/Utilities/PlayReport/Analyzer.cs +++ b/src/Ryujinx/Utilities/PlayReport/Analyzer.cs @@ -20,6 +20,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport 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. /// @@ -128,13 +133,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport { if (!playReport.ReportData.IsDictionary) return FormattedValue.Unhandled; - - if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec)) + + if (!TryGetSpec(runningGameId, out GameSpec spec)) return FormattedValue.Unhandled; foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority)) { - if (!formatSpec.Format(appMeta, playReport, out FormattedValue value)) + if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value)) continue; return value; diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 27330c808..7602c1de8 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -17,6 +17,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport .AddSpec( "01007ef00011e000", spec => spec + .WithDescription("based on being in Master Mode.") .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) // reset to normal status when switching between normal & master mode in title screen .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) @@ -24,34 +25,48 @@ namespace Ryujinx.Ava.Utilities.PlayReport .AddSpec( "0100f2c0115b6000", spec => spec + .WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).") .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) + .AddSpec( + "01002da013484000", + spec => spec + .WithDescription("based on how many Rupees you have.") + .AddValueFormatter("rupees", SkywardSwordHD_Rupees)) .AddSpec( "0100000000010000", - spec => - spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) + spec => spec + .WithDescription("based on if you're playing with Assist Mode.") + .AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) ) .AddSpec( "010075000ecbe000", - spec => - spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) + spec => spec + .WithDescription("based on if you're playing with Assist Mode.") + .AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) ) .AddSpec( "010028600ebda000", - spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) + spec => spec + .WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.") + .AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) ) .AddSpec( // Global & China IDs ["0100152000022000", "010075100e8ec000"], - spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode) + spec => spec + .WithDescription("based on what modes you're selecting in the menu & whether or not you're in a race.") + .AddValueFormatter("To", MarioKart8Deluxe_Mode) ) .AddSpec( ["0100a3d008c5c000", "01008f6008c5e000"], spec => spec + .WithDescription("based on what area of Paldea you're exploring.") .AddValueFormatter("area_no", PokemonSVArea) .AddValueFormatter("team_circle", PokemonSVUnionCircle) ) .AddSpec( "01006a800016e000", spec => spec + .WithDescription("based on what mode you're playing, who won, and what characters were present.") .AddSparseMultiValueFormatter( [ // Metadata to figure out what PlayReport we have. @@ -72,9 +87,10 @@ namespace Ryujinx.Ava.Utilities.PlayReport "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000", "010012f017576000", "0100c62011050000", "0100b3c014bda000" ], - spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) + spec => spec + .WithDescription("based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.") + .AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) ) - .AddSpec("01002da013484000", spec => spec.AddValueFormatter("rupees", SkywardSwordHD_Rupees)) ); private static string Playing(string game) => $"Playing {game}"; diff --git a/src/Ryujinx/Utilities/PlayReport/Specs.cs b/src/Ryujinx/Utilities/PlayReport/Specs.cs index f1b94de68..c162d4c2c 100644 --- a/src/Ryujinx/Utilities/PlayReport/Specs.cs +++ b/src/Ryujinx/Utilities/PlayReport/Specs.cs @@ -23,6 +23,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport public required string[] TitleIds { get; init; } + public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game."; + + private string _valueDescription; + + public string Description => _valueDescription ?? DefaultDescription; + + public GameSpec WithDescription(string description) + { + _valueDescription = description != null + ? $"Formats the details on your Discord presence {description}" + : null; + return this; + } + public List ValueFormatters { get; } = []; @@ -197,7 +211,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport public string[] ReportKeys { get; init; } public Delegate Formatter { get; init; } - public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, + public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport, out FormattedValue formattedValue) { formattedValue = default; From b1f61e5143cf7c29cfa5f742d96d2cd5f4ee51f7 Mon Sep 17 00:00:00 2001 From: Evan Husted Date: Sat, 15 Feb 2025 20:52:03 -0600 Subject: [PATCH 3/3] misc: chore: [ci skip] Reformat PlayReports.cs --- .../Utilities/PlayReport/PlayReports.cs | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs index 7602c1de8..9feb888b3 100644 --- a/src/Ryujinx/Utilities/PlayReport/PlayReports.cs +++ b/src/Ryujinx/Utilities/PlayReport/PlayReports.cs @@ -9,88 +9,89 @@ namespace Ryujinx.Ava.Utilities.PlayReport // init lazy value _ = Analyzer; } - + public static Analyzer Analyzer => _analyzerLazy.Value; - private static readonly Lazy _analyzerLazy = new(() => - new Analyzer() - .AddSpec( - "01007ef00011e000", - spec => spec - .WithDescription("based on being in Master Mode.") - .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) - // reset to normal status when switching between normal & master mode in title screen - .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) - ) - .AddSpec( - "0100f2c0115b6000", - spec => spec - .WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).") - .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) - .AddSpec( - "01002da013484000", - spec => spec - .WithDescription("based on how many Rupees you have.") - .AddValueFormatter("rupees", SkywardSwordHD_Rupees)) - .AddSpec( - "0100000000010000", - spec => spec - .WithDescription("based on if you're playing with Assist Mode.") - .AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) - ) - .AddSpec( - "010075000ecbe000", - spec => spec - .WithDescription("based on if you're playing with Assist Mode.") - .AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) - ) - .AddSpec( - "010028600ebda000", - spec => spec - .WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.") - .AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) - ) - .AddSpec( // Global & China IDs - ["0100152000022000", "010075100e8ec000"], - spec => spec - .WithDescription("based on what modes you're selecting in the menu & whether or not you're in a race.") - .AddValueFormatter("To", MarioKart8Deluxe_Mode) - ) - .AddSpec( - ["0100a3d008c5c000", "01008f6008c5e000"], - spec => spec - .WithDescription("based on what area of Paldea you're exploring.") - .AddValueFormatter("area_no", PokemonSVArea) - .AddValueFormatter("team_circle", PokemonSVUnionCircle) - ) - .AddSpec( - "01006a800016e000", - spec => spec - .WithDescription("based on what mode you're playing, who won, and what characters were present.") - .AddSparseMultiValueFormatter( - [ - // Metadata to figure out what PlayReport we have. - "match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count", - "adv_slot", - // List of Fighters - "player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter", - "player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter", - // List of rankings/placements - "player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank", - "player_6_rank", "player_7_rank", "player_8_rank" - ], - SuperSmashBrosUltimate_Mode - ) - ) - .AddSpec( - [ - "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000", - "010012f017576000", "0100c62011050000", "0100b3c014bda000" - ], - spec => spec - .WithDescription("based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.") - .AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) - ) + private static readonly Lazy _analyzerLazy = new(() => new Analyzer() + .AddSpec( + "01007ef00011e000", + spec => spec + .WithDescription("based on being in Master Mode.") + .AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode) + // reset to normal status when switching between normal & master mode in title screen + .AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets) + ) + .AddSpec( + "0100f2c0115b6000", + spec => spec + .WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).") + .AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField)) + .AddSpec( + "01002da013484000", + spec => spec + .WithDescription("based on how many Rupees you have.") + .AddValueFormatter("rupees", SkywardSwordHD_Rupees)) + .AddSpec( + "0100000000010000", + spec => spec + .WithDescription("based on if you're playing with Assist Mode.") + .AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode) + ) + .AddSpec( + "010075000ecbe000", + spec => spec + .WithDescription("based on if you're playing with Assist Mode.") + .AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode) + ) + .AddSpec( + "010028600ebda000", + spec => spec + .WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.") + .AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury) + ) + .AddSpec( // Global & China IDs + ["0100152000022000", "010075100e8ec000"], + spec => spec + .WithDescription( + "based on what modes you're selecting in the menu & whether or not you're in a race.") + .AddValueFormatter("To", MarioKart8Deluxe_Mode) + ) + .AddSpec( + ["0100a3d008c5c000", "01008f6008c5e000"], + spec => spec + .WithDescription("based on what area of Paldea you're exploring.") + .AddValueFormatter("area_no", PokemonSVArea) + .AddValueFormatter("team_circle", PokemonSVUnionCircle) + ) + .AddSpec( + "01006a800016e000", + spec => spec + .WithDescription("based on what mode you're playing, who won, and what characters were present.") + .AddSparseMultiValueFormatter( + [ + // Metadata to figure out what PlayReport we have. + "match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count", + "adv_slot", + // List of Fighters + "player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter", + "player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter", + // List of rankings/placements + "player_1_rank", "player_2_rank", "player_3_rank", "player_4_rank", "player_5_rank", + "player_6_rank", "player_7_rank", "player_8_rank" + ], + SuperSmashBrosUltimate_Mode + ) + ) + .AddSpec( + [ + "0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000", + "010012f017576000", "0100c62011050000", "0100b3c014bda000" + ], + spec => spec + .WithDescription( + "based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.") + .AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame) + ) ); private static string Playing(string game) => $"Playing {game}";