diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index f143d4e03..a3e765a2d 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -582,6 +582,13 @@ namespace Ryujinx.Ava Rainbow.Disable(); Rainbow.Reset(); + // Reload settings when the game is turned off + // (resets custom settings if there were any) + Program.ReloadConfig(); + + //Updates the gameList (changes the status of the user setting if it was added or removed during the game) + RyujinxApp.MainWindow.GameListUpdate(); + _isStopped = true; Stop(); } diff --git a/src/Ryujinx/Assets/Styles/Styles.xaml b/src/Ryujinx/Assets/Styles/Styles.xaml index 5523f551a..cf9f28f02 100644 --- a/src/Ryujinx/Assets/Styles/Styles.xaml +++ b/src/Ryujinx/Assets/Styles/Styles.xaml @@ -1,7 +1,8 @@ - + Content="Add" + Classes="red"/> + + + + diff --git a/src/Ryujinx/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml index 3a0bd4217..de7584240 100644 --- a/src/Ryujinx/Assets/Styles/Themes.xaml +++ b/src/Ryujinx/Assets/Styles/Themes.xaml @@ -1,4 +1,4 @@ - @@ -12,11 +12,13 @@ #C1C1C1 #b3ffffff #80cccccc + #FF6347 #A0000000 #fffcd12a #FF2EEAC9 #FFFF4554 #6483F5 + #800080 #C1C1C1 #b3ffffff #80cccccc + #FF6347 #A0000000 #fffcd12a #13c3a4 #FFFF4554 #6483F5 + #800080 #3D3D3D #0FFFFFFF #1EFFFFFF + #FF6347 #A0FFFFFF #fffcd12a #FF2EEAC9 #FFFF4554 #6483F5 + #FFA500 diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 27f2b59a5..aac8dc816 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -2722,6 +2722,31 @@ "zh_TW": "建立桌面捷徑,啟動選取的應用程式" } }, + { + "ID": "GameListContextMenuEditGameConfiguration", + "Translations": { + "ar_SA": "تعديل تكوين اللعبة", + "de_DE": "Spielkonfiguration bearbeiten", + "el_GR": "Επεξεργασία ρυθμίσεων παιχνιδιού", + "en_US": "Edit Game Configuration", + "es_ES": "Editar configuración del juego", + "fr_FR": "Modifier la configuration du jeu", + "he_IL": "ערוך את הגדרת המשחק", + "it_IT": "Modifica configurazione gioco", + "ja_JP": "ゲーム設定を編集", + "ko_KR": "게임 설정 편집", + "no_NO": "Rediger spillkonfig.", + "pl_PL": "Edytuj konfigurację gry", + "pt_BR": "Editar configuração do jogo", + "ru_RU": "Редактировать конфигурацию игры", + "sv_SE": "Redigera spelkonfiguration", + "th_TH": "แก้ไขการตั้งค่าเกม", + "tr_TR": "Oyunun yapılandırmasını düzenle", + "uk_UA": "Редагувати конфігурацію гри", + "zh_CN": "编辑游戏配置", + "zh_TW": "編輯遊戲設定" + } + }, { "ID": "GameListContextMenuCreateShortcutToolTipMacOS", "Translations": { @@ -2747,6 +2772,31 @@ "zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式" } }, + { + "ID": "EditGameConfigurationToolTip", + "Translations": { + "ar_SA": "ينشئ تكوينًا مستقلًا للعبة الحالية", + "de_DE": "Erstellt eine unabhängige Konfiguration für das aktuelle Spiel", + "el_GR": "Δημιουργεί μια ανεξάρτητη διαμόρφωση για το τρέχον παιχνίδι", + "en_US": "Creates an independent configuration for the current game", + "es_ES": "Crea una configuración independiente para el juego actual", + "fr_FR": "Crée une configuration indépendante pour le jeu en cours", + "he_IL": "יוצר תצורה עצמאית למשחק הנוכחי", + "it_IT": "Crea una configurazione indipendente per il gioco attuale", + "ja_JP": "現在のゲーム用の独立した設定を作成します", + "ko_KR": "현재 게임에 대한 독립적인 설정을 생성합니다", + "no_NO": "Oppretter en uavhengig konfigurasjon for det gjeldende spillet", + "pl_PL": "Tworzy niezależną konfigurację dla bieżącej gry", + "pt_BR": "Cria uma configuração independente para o jogo atual", + "ru_RU": "Создает независимую конфигурацию для текущей игры", + "sv_SE": "Skapar en oberoende konfiguration för det aktuella spelet", + "th_TH": "สร้างการกำหนดค่าที่เป็นอิสระสำหรับเกมปัจจุบัน", + "tr_TR": "Mevcut oyun için bağımsız bir yapılandırma oluşturur", + "uk_UA": "Створює незалежну конфігурацію для поточної гри", + "zh_CN": "为当前游戏创建独立的配置", + "zh_TW": "為當前遊戲創建獨立的配置" + } + }, { "ID": "GameListContextMenuShowCompatEntry", "Translations": { @@ -3272,6 +3322,31 @@ "zh_TW": "設定" } }, + { + "ID": "SettingsWithInfo", + "Translations": { + "ar_SA": "{0} - إعدادات", + "de_DE": "Einstellungen - {0}", + "el_GR": "Ρυθμίσεις - {0}", + "en_US": "Settings - {0}", + "es_ES": "Configuración - {0}", + "fr_FR": "Paramètres - {0}", + "he_IL": "{0} - הגדרות", + "it_IT": "Impostazioni - {0}", + "ja_JP": "設定 - {0}", + "ko_KR": "설정 - {0}", + "no_NO": "Innstillinger - {0}", + "pl_PL": "Ustawienia - {0}", + "pt_BR": "Configurações - {0}", + "ru_RU": "Параметры - {0}", + "sv_SE": "Inställningar - {0}", + "th_TH": "ตั้งค่า - {0}", + "tr_TR": "Ayarlar - {0}", + "uk_UA": "Налаштування - {0}", + "zh_CN": "设置 - {0}", + "zh_TW": "設定 - {0}" + } + }, { "ID": "SettingsTabGeneral", "Translations": { @@ -12522,6 +12597,31 @@ "zh_TW": "正在下載更新..." } }, + { + "ID": "DialogRebooterMessage", + "Translations": { + "ar_SA": "من فضلك انتظر، المحاكي في طور إعادة التشغيل", + "de_DE": "Bitte warten Sie, der Emulator wird neu gestartet", + "el_GR": "Παρακαλώ περιμένετε, ο εξομοιωτής επανεκκινείται", + "en_US": "Please wait, the emulator is restarting", + "es_ES": "Por favor, espere, el emulador se está reiniciando", + "fr_FR": "Veuillez patienter, l'émulateur est en train de redémarrer", + "he_IL": "אנא המתן, המחקה מתארגן מחדש", + "it_IT": "Attendere prego, l'emulatore si sta riavviando", + "ja_JP": "お待ちください、エミュレーターが再起動しています", + "ko_KR": "잠시만 기다려 주세요, 에뮬레이터가 재시작 중입니다", + "no_NO": "Vennligst vent, emulatoren starter på nytt", + "pl_PL": "Proszę czekać, emulator jest w trakcie ponownego uruchamiania", + "pt_BR": "Por favor, aguarde, o emulador está reiniciando", + "ru_RU": "Пожалуйста, подождите, эмулятор перезапускается", + "sv_SE": "Vänligen vänta, emulatorn startar om", + "th_TH": "กรุณารอสักครู่, ตัวจำลองกำลังเริ่มใหม่", + "tr_TR": "Lütfen bekleyin, emülatör yeniden başlatılıyor", + "uk_UA": "Будь ласка, зачекайте, емулятор перезавантажується", + "zh_CN": "请稍等,模拟器正在重新启动", + "zh_TW": "請稍候,模擬器正在重新啟動" + } + }, { "ID": "DialogUpdaterExtractionMessage", "Translations": { @@ -19372,6 +19472,31 @@ "zh_TW": "{0} 更新程式" } }, + { + "ID": "RyujinxRebooter", + "Translations": { + "ar_SA": "إعادة تشغيل {0}", + "de_DE": "Neustart von {0}", + "el_GR": "Επανεκκίνηση {0}", + "en_US": "{0} Reboot", + "es_ES": "Reinicio de {0}", + "fr_FR": "Redémarrage de {0}", + "he_IL": "אתחול {0}", + "it_IT": "Riavvio di {0}", + "ja_JP": "{0} 再起動", + "ko_KR": "{0} 재부팅", + "no_NO": "Omstart av {0}", + "pl_PL": "Ponowne uruchomienie {0}", + "pt_BR": "Reinício de {0}", + "ru_RU": "{0} Перезагрузка", + "sv_SE": "Ominläsning av {0}", + "th_TH": "เริ่มต้นใหม่ {0}", + "tr_TR": "{0} Yeniden Başlatma", + "uk_UA": "Перезавантаження {0}", + "zh_CN": "{0} 重启", + "zh_TW": "{0} 重新啟動" + } + }, { "ID": "SettingsTabHotkeys", "Translations": { @@ -23847,6 +23972,31 @@ "zh_TW": "" } }, + { + "ID": "UserConfigurationHeader", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "User Config", + "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": "ExtractAocListHeader", "Translations": { diff --git a/src/Ryujinx/Common/LocaleManager.cs b/src/Ryujinx/Common/LocaleManager.cs index 4c86a6177..f60cff49b 100644 --- a/src/Ryujinx/Common/LocaleManager.cs +++ b/src/Ryujinx/Common/LocaleManager.cs @@ -54,6 +54,7 @@ namespace Ryujinx.Ava.Common.Locale SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName); SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName); + SetDynamicValues(LocaleKeys.RyujinxRebooter, RyujinxApp.FullAppName); } public string this[LocaleKeys key] diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 831e4294c..cc748087e 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -32,8 +32,10 @@ namespace Ryujinx.Ava public static double DesktopScaleFactor { get; set; } = 1.0; public static string Version { get; private set; } public static string ConfigurationPath { get; private set; } + public static string GlobalConfigurationPath { get; private set; } public static bool PreviewerDetached { get; private set; } public static bool UseHardwareAcceleration { get; private set; } + public static string BackendThreadingArg { get; private set; } [LibraryImport("user32.dll", SetLastError = true)] public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type); @@ -155,11 +157,43 @@ namespace Ryujinx.Ava } } + public static bool FindGameConfig(string gameDir) + { + if (File.Exists(gameDir)) + { + return true; + } + + return false; + } + + public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false) + { + string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName); + + // Should load with the game if there is a custom setting for the game + if (rememberGlobalDir) + { + GlobalConfigurationPath = ConfigurationPath; + } + + if (changeFolderForGame) + { + ConfigurationPath = gameDir; + } + + return gameDir; + } + public static void ReloadConfig() { + //It is necessary that when a user setting appears, the global setting remains available + GlobalConfigurationPath = null; + string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); + // Now load the configuration as the other subsystems are now registered if (File.Exists(localConfigurationPath)) { @@ -217,6 +251,11 @@ namespace Ryujinx.Ava _ => ConfigurationState.Instance.Graphics.BackendThreading }; + if (CommandLineState.OverrideBackendThreadingAfterReboot is not null) + { + BackendThreadingArg = CommandLineState.OverrideBackendThreadingAfterReboot; + } + // Check if docked mode was overriden. if (CommandLineState.OverrideDockedMode.HasValue) ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; @@ -232,6 +271,33 @@ namespace Ryujinx.Ava _ => ConfigurationState.Instance.HideCursor, }; + // Check if memoryManagerMode was overridden. + if (CommandLineState.OverrideMemoryManagerMode is not null) + if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result)) + { + ConfigurationState.Instance.System.MemoryManagerMode.Value = result; + } + + // Check if PPTC was overridden. + if (CommandLineState.OverridePPTC is not null) + if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result)) + { + ConfigurationState.Instance.System.EnablePtc.Value = result; + } + + // Check if region was overridden. + if (CommandLineState.OverrideSystemRegion is not null) + if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Ryujinx.HLE.HOS.SystemState.RegionCode result)) + { + ConfigurationState.Instance.System.Region.Value = (Utilities.Configuration.System.Region)result; + } + + //Check if language was overridden. + if (CommandLineState.OverrideSystemLanguage is not null) + if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Ryujinx.HLE.HOS.SystemState.SystemLanguage result)) + { + ConfigurationState.Instance.System.Language.Value = (Utilities.Configuration.System.Language)result; + } // Check if hardware-acceleration was overridden. if (CommandLineState.OverrideHardwareAcceleration != null) diff --git a/src/Ryujinx/Rebooter.cs b/src/Ryujinx/Rebooter.cs new file mode 100644 index 000000000..21fa650bd --- /dev/null +++ b/src/Ryujinx/Rebooter.cs @@ -0,0 +1,95 @@ +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.Utilities; +using SkiaSharp; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Ryujinx.Ava +{ + internal static class Rebooter + { + + private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); + + + public static void RebootAppWithGame(string gamePath, List args) + { + _ = Reboot(gamePath, args); + + } + + private static async Task Reboot(string gamePath, List args) + { + + bool shouldRestart = true; + + TaskDialog taskDialog = new() + { + Header = LocaleManager.Instance[LocaleKeys.RyujinxRebooter], + SubHeader = LocaleManager.Instance[LocaleKeys.DialogRebooterMessage], + IconSource = new SymbolIconSource { Symbol = Symbol.Games }, + XamlRoot = RyujinxApp.MainWindow, + }; + + if (shouldRestart) + { + List arguments = CommandLineState.Arguments.ToList(); + string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; + + // On macOS we perform the update at relaunch. + if (OperatingSystem.IsMacOS()) + { + string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", "..")); + string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app"); + string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh"); + string currentPid = Environment.ProcessId.ToString(); + + arguments.InsertRange(0, new List { updaterScriptPath, baseBundlePath, newBundlePath, currentPid }); + Process.Start("/bin/bash", arguments); + } + else + { + var dialogTask = taskDialog.ShowAsync(true); + await Task.Delay(500); + + // Find the process name. + string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty; + + // Some operating systems can see the renamed executable, so strip off the .ryuold if found. + if (ryuName.EndsWith(".ryuold")) + { + ryuName = ryuName[..^7]; + } + + // Fallback if the executable could not be found. + if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName))) + { + ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx"; + } + + ProcessStartInfo processStart = new(ryuName) + { + UseShellExecute = true, + WorkingDirectory = executableDirectory, + }; + + foreach (var arg in args) + { + processStart.ArgumentList.Add(arg); + } + + processStart.ArgumentList.Add(gamePath); + + Process.Start(processStart); + } + Environment.Exit(0); + } + } + } +} diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml index 3e47a1910..0befd36af 100644 --- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml @@ -19,6 +19,11 @@ Header="{ext:Locale GameListContextMenuCreateShortcut}" Icon="{ext:Icon fa-solid fa-bookmark}" ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> + - + + + + @@ -86,10 +93,28 @@ Margin="5,5,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" - FontSize="16" + FontSize="18" Foreground="{DynamicResource FavoriteApplicationIconColor}" IsVisible="{Binding Favorite}" Symbol="StarFilled" /> + + + + + diff --git a/src/Ryujinx/UI/Controls/ApplicationListView.axaml b/src/Ryujinx/UI/Controls/ApplicationListView.axaml index c6b7268b9..62619913f 100644 --- a/src/Ryujinx/UI/Controls/ApplicationListView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationListView.axaml @@ -6,6 +6,7 @@ xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" d:DesignHeight="450" d:DesignWidth="800" Focusable="True" @@ -156,6 +157,13 @@ Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}" TextAlignment="Start" TextWrapping="Wrap"/> + Arguments = new List + { + "--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading + }; + + Rebooter.RebootAppWithGame(application.Path, Arguments); + + return true; + } + + return false; + } + public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct? customNacpData = null) { + + if (InitializeUserConfig(application)) + { + return; + } + if (AppHost != null) { await ContentDialogHelper.CreateInfoDialog( @@ -1548,7 +1590,7 @@ namespace Ryujinx.Ava.UI.ViewModels #if RELEASE await PerformanceCheck(); #endif - + Logger.RestartTime(); SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id); @@ -1593,6 +1635,7 @@ namespace Ryujinx.Ava.UI.ViewModels Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; gameThread.Start(); + } public void SwitchToRenderer(bool startFullscreen) => diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 017d68a28..e1387c6d4 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -1,8 +1,10 @@ using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Humanizer; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.SDL2; @@ -27,6 +29,7 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Net.NetworkInformation; using System.Threading.Tasks; @@ -69,6 +72,17 @@ namespace Ryujinx.Ava.UI.ViewModels public SettingsHacksViewModel DirtyHacks { get; } + private readonly bool _isGameRunning; + private Bitmap _gameIcon; + private string _gameTitle; + private string _gamePath; + private string _gameId; + public bool IsGameRunning => _isGameRunning; + public Bitmap GameIcon => _gameIcon; + public string GamePath => _gamePath; + public string GameTitle => _gameTitle; + public string GameId => _gameId; + public int ResolutionScale { get => _resolutionScale; @@ -334,7 +348,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsInvalidLdnPassphraseVisible { get; set; } - public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() + public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false) { _virtualFileSystem = virtualFileSystem; _contentManager = contentManager; @@ -347,7 +361,51 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public SettingsViewModel() + public SettingsViewModel( + VirtualFileSystem virtualFileSystem, + ContentManager contentManager, + bool gameRunning, + string gamePath, + string gameName, + string gameId, + byte[] gameIconData, + bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig) + { + _virtualFileSystem = virtualFileSystem; + _contentManager = contentManager; + + if (gameIconData != null && gameIconData.Length > 0) + { + using (var ms = new MemoryStream(gameIconData)) + { + _gameIcon = new Bitmap(ms); + } + } + + _isGameRunning = gameRunning; + _gamePath = gamePath; + _gameTitle = gameName; + _gameId = gameId; + + if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window + { + string gameDir = Program.GetDirGameUserConfig(gameId, false, true); + if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat)) + { + ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId); + } + + LoadCurrentConfiguration(); // Needed to load custom configuration + } + + if (Program.PreviewerDetached) + { + Task.Run(LoadTimeZones); + + } + } + + public SettingsViewModel(bool noLoadGlobalConfig = false) { GameDirectories = []; AutoloadDirectories = []; @@ -362,7 +420,11 @@ namespace Ryujinx.Ava.UI.ViewModels if (Program.PreviewerDetached) { Task.Run(LoadAvailableGpus); - LoadCurrentConfiguration(); + + if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call + { + LoadCurrentConfiguration(); + } DirtyHacks = new SettingsHacksViewModel(this); } @@ -473,28 +535,34 @@ namespace Ryujinx.Ava.UI.ViewModels { ConfigurationState config = ConfigurationState.Instance; - // User Interface - EnableDiscordIntegration = config.EnableDiscordIntegration; - CheckUpdatesOnStart = config.CheckUpdatesOnStart; - ShowConfirmExit = config.ShowConfirmExit; - RememberWindowState = config.RememberWindowState; - ShowTitleBar = config.ShowTitleBar; - HideCursor = (int)config.HideCursor.Value; - UpdateCheckerType = (int)config.UpdateCheckerType.Value; - GameDirectories.Clear(); - GameDirectories.AddRange(config.UI.GameDirs.Value); - - AutoloadDirectories.Clear(); - AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value); - - BaseStyleIndex = config.UI.BaseStyle.Value switch + //It is necessary that the data is used from the global configuration file + if (string.IsNullOrEmpty(GameId)) { - "Auto" => 0, - "Light" => 1, - "Dark" => 2, - _ => 0 - }; + // User Interface + EnableDiscordIntegration = config.EnableDiscordIntegration; + CheckUpdatesOnStart = config.CheckUpdatesOnStart; + ShowConfirmExit = config.ShowConfirmExit; + RememberWindowState = config.RememberWindowState; + ShowTitleBar = config.ShowTitleBar; + HideCursor = (int)config.HideCursor.Value; + UpdateCheckerType = (int)config.UpdateCheckerType.Value; + + GameDirectories.Clear(); + GameDirectories.AddRange(config.UI.GameDirs.Value); + + AutoloadDirectories.Clear(); + AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value); + + BaseStyleIndex = config.UI.BaseStyle.Value switch + { + "Auto" => 0, + "Light" => 1, + "Dark" => 2, + _ => 0 + }; + + } // Input EnableDockedMode = config.System.EnableDockedMode; @@ -515,7 +583,6 @@ namespace Ryujinx.Ava.UI.ViewModels DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset); CurrentDate = currentDateTime.Date; CurrentTime = currentDateTime.TimeOfDay; - MatchSystemTime = config.System.MatchSystemTime; EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval; @@ -576,38 +643,43 @@ namespace Ryujinx.Ava.UI.ViewModels LdnPassphrase = config.Multiplayer.LdnPassphrase; LdnServer = config.Multiplayer.LdnServer; } - + public void SaveSettings() { ConfigurationState config = ConfigurationState.Instance; + bool userConfigFile = string.IsNullOrEmpty(GameId); - // User Interface - config.EnableDiscordIntegration.Value = EnableDiscordIntegration; - config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; - config.ShowConfirmExit.Value = ShowConfirmExit; - config.RememberWindowState.Value = RememberWindowState; - config.ShowTitleBar.Value = ShowTitleBar; - config.HideCursor.Value = (HideCursorMode)HideCursor; - config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType; - if (GameDirectoryChanged) + if (userConfigFile) { - config.UI.GameDirs.Value = [..GameDirectories]; + // User Interface + config.EnableDiscordIntegration.Value = EnableDiscordIntegration; + config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; + config.ShowConfirmExit.Value = ShowConfirmExit; + config.RememberWindowState.Value = RememberWindowState; + config.ShowTitleBar.Value = ShowTitleBar; + config.HideCursor.Value = (HideCursorMode)HideCursor; + config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType; + + if (GameDirectoryChanged) + { + config.UI.GameDirs.Value = [.. GameDirectories]; + } + + if (AutoloadDirectoryChanged) + { + config.UI.AutoloadDirs.Value = [.. AutoloadDirectories]; + } + + config.UI.BaseStyle.Value = BaseStyleIndex switch + { + 0 => "Auto", + 1 => "Light", + 2 => "Dark", + _ => "Auto" + }; } - if (AutoloadDirectoryChanged) - { - config.UI.AutoloadDirs.Value = [..AutoloadDirectories]; - } - - config.UI.BaseStyle.Value = BaseStyleIndex switch - { - 0 => "Auto", - 1 => "Light", - 2 => "Dark", - _ => "Auto" - }; - // Input config.System.EnableDockedMode.Value = EnableDockedMode; config.Hid.EnableKeyboard.Value = EnableKeyboard; @@ -704,7 +776,9 @@ namespace Ryujinx.Ava.UI.ViewModels config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled; config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay; - config.ToFileFormat().SaveConfig(Program.ConfigurationPath); + + config.ToFileFormat().SaveConfig(Program.ConfigurationPath); + MainWindow.UpdateGraphicsConfig(); RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged(); @@ -717,7 +791,11 @@ namespace Ryujinx.Ava.UI.ViewModels private static void RevertIfNotSaved() { - Program.ReloadConfig(); + // maybe this is an unnecessary check(all options need to be tested) + if (string.IsNullOrEmpty(Program.GlobalConfigurationPath)) + { + Program.ReloadConfig(); + } } public void ApplyButton() @@ -725,6 +803,26 @@ namespace Ryujinx.Ava.UI.ViewModels SaveSettings(); } + public void DeleteConfigGame() + { + string gameDir = Program.GetDirGameUserConfig(GameId,false,false); + + if (File.Exists(gameDir)) + { + File.Delete(gameDir); + } + + RevertIfNotSaved(); + CloseWindow?.Invoke(); + } + + public void SaveUserConfig() + { + SaveSettings(); + RevertIfNotSaved(); // Revert global configuration after saving user configuration + CloseWindow?.Invoke(); + } + public void OkButton() { SaveSettings(); diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index d1931ae2f..f1bb2de55 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -130,9 +130,26 @@ namespace Ryujinx.Ava.UI.Views.Main Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager); Rainbow.Enable(); - - await Window.SettingsWindow.ShowDialog(Window); - + + if (ViewModel.SelectedApplication is null) // Checks if game data exists + { + await Window.SettingsWindow.ShowDialog(Window); + } + else + { + bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false)); + + if (!ViewModel.IsGameRunning || !userConfigExist) + { + await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist + } + else + { + // If there is a custom configuration in the folder + await new GameSpecificSettingsWindow(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel); + } + } + Rainbow.Disable(); Rainbow.Reset(); diff --git a/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml b/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml new file mode 100644 index 000000000..c7a5d1fa4 --- /dev/null +++ b/src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +