Преглед на файлове

Custom configuration for each game (#632)

![image](https://github.com/user-attachments/assets/5dd139b4-2004-4c13-85d1-fc3378382adf)

![image](https://github.com/user-attachments/assets/9bcb8524-a403-428f-9f98-e8c03c75f079)

Now you can make a separate configuration (independent file) for each
game. All emulator settings are available except for some UI functionality ones. 
The configuration file can be changed and deleted from a separate menu. The
user configuration menu is available through the context menu on a given application.

---------

Co-authored-by: Evan Husted <greem@greemdev.net>
Vladimir Sokolov преди 1 година
родител
ревизия
2e4de17472

+ 7 - 0
src/Ryujinx/AppHost.cs

@@ -1113,6 +1113,13 @@ namespace Ryujinx.Ava
             });
 
             (RendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(true);
+            
+            // Reload settings when the game is turned off
+            // (resets custom settings if there were any)
+            Program.ReloadConfig();
+
+            // Reload application list (changes the status of the user setting if it was added or removed during the game)
+            Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.LoadApplications());
         }
 
         public void InitStatus()

+ 31 - 2
src/Ryujinx/Assets/Styles/Styles.xaml

@@ -1,7 +1,8 @@
-<Styles
+<Styles
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+    xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
     xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
     <Design.PreviewWith>
         <Border Height="2000"
@@ -30,7 +31,8 @@
                         <Button
                             Name="btnRem"
                             HorizontalAlignment="Right"
-                            Content="Add" />
+                            Content="Add" 
+                            Classes="red"/>                
                         <TextBox
                             Width="100"
                             VerticalAlignment="Center"
@@ -41,7 +43,13 @@
                     </StackPanel>
                 </Grid>
                 <ui:NumberBox Value="1" />
+                <MenuItem
+                    Header="123 0000"
+                    ToolTip.Tip="What this"/>
+                <TextBlock
+                         Classes="globalConfigMarker"/>
             </StackPanel>
+            
         </Border>
     </Design.PreviewWith>
     <Style Selector="DropDownButton">
@@ -331,6 +339,14 @@
         <Setter Property="Margin"
                 Value="0,5,0,0" />
     </Style>
+    <Style Selector="TextBlock.globalConfigMarker" >
+        <Setter Property="Foreground" Value="SeaGreen"/>
+        <Setter Property="Margin" Value="5,0,0,0"/>
+        <Setter Property="VerticalAlignment" Value="Center"/>
+        <Setter Property="Text" Value="{ext:Locale GameSpecificConfigurationGlobal}"/>  
+    </Style>
+    <Style Selector="StackPanel.globalConfigMarker">
+    </Style>
     <Style Selector="ContextMenu">
         <Setter Property="BorderBrush"
                 Value="{DynamicResource MenuFlyoutPresenterBorderBrush}" />
@@ -373,6 +389,19 @@
         <Setter Property="Background"
                 Value="{DynamicResource AppListHoverBackgroundColor}" />
     </Style>
+    <Style Selector="Button.red /template/ ContentPresenter">
+        <Setter Property="CornerRadius" Value="4"/>
+            <Setter Property="Background" Value="red"/>
+            <Setter Property="Foreground" Value="White"/>
+        </Style>
+        <Style Selector="Button.red:pointerover /template/ ContentPresenter">
+            <Setter Property="CornerRadius" Value="4"/>
+            <Setter Property="Background" Value="{DynamicResource WarningBackgroundColor}" />
+            <Setter Property="Foreground" Value="White"/>
+    </Style>
+
+
+
     <Styles.Resources>
         <SolidColorBrush x:Key="ThemeAccentColorBrush"
                          Color="{DynamicResource SystemAccentColor}" />

+ 7 - 1
src/Ryujinx/Assets/Styles/Themes.xaml

@@ -1,4 +1,4 @@
-<ResourceDictionary xmlns="https://github.com/avaloniaui"
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <ResourceDictionary.ThemeDictionaries>
         <ResourceDictionary x:Key="Default">
@@ -12,11 +12,13 @@
             <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
             <Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
             <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
+            <Color x:Key="WarningBackgroundColor">#FF6347</Color>
             <Color x:Key="SecondaryTextColor">#A0000000</Color>
             <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
             <Color x:Key="Switch">#FF2EEAC9</Color>
             <Color x:Key="Unbounded">#FFFF4554</Color>
             <Color x:Key="Custom">#6483F5</Color>
+            <Color x:Key="Warning">#800080</Color>
         </ResourceDictionary>
         <ResourceDictionary x:Key="Light">
             <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -29,11 +31,13 @@
             <Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
             <Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
             <Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
+            <Color x:Key="WarningBackgroundColor">#FF6347</Color>
             <Color x:Key="SecondaryTextColor">#A0000000</Color>
             <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
             <Color x:Key="Switch">#13c3a4</Color>
             <Color x:Key="Unbounded">#FFFF4554</Color>
             <Color x:Key="Custom">#6483F5</Color>
+            <Color x:Key="Warning">#800080</Color>
         </ResourceDictionary>
         <ResourceDictionary x:Key="Dark">
             <SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -46,11 +50,13 @@
             <Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
             <Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
             <Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
+            <Color x:Key="WarningBackgroundColor">#FF6347</Color>
             <Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
             <Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
             <Color x:Key="Switch">#FF2EEAC9</Color>
             <Color x:Key="Unbounded">#FFFF4554</Color>
             <Color x:Key="Custom">#6483F5</Color>
+            <Color x:Key="Warning">#FFA500</Color>
         </ResourceDictionary>
     </ResourceDictionary.ThemeDictionaries>
 </ResourceDictionary>

+ 225 - 0
src/Ryujinx/Assets/locales.json

@@ -2747,6 +2747,56 @@
         "zh_TW": "建立桌面捷徑,啟動選取的應用程式"
       }
     },
+    {
+      "ID": "GameListContextMenuCreateCustomConfiguration",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "Create Custom Configuration",
+        "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": "GameListContextMenuEditCustomConfiguration",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "Edit Custom Configuration",
+        "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": "GameListContextMenuCreateShortcutToolTipMacOS",
       "Translations": {
@@ -2772,6 +2822,56 @@
         "zh_TW": "在 macOS 的應用程式資料夾中建立捷徑,啟動選取的應用程式"
       }
     },
+    {
+      "ID": "CreateCustomConfigurationToolTip",
+      "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 selected 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": "EditCustomConfigurationToolTip",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "Edit your existing independent configuration for the selected game",
+        "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": "GameListContextMenuShowCompatEntry",
       "Translations": {
@@ -3297,6 +3397,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": {
@@ -12647,6 +12772,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": {
@@ -19522,6 +19672,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": {
@@ -23997,6 +24172,56 @@
         "zh_TW": ""
       }
     },
+    {
+      "ID": "GameSpecificConfigurationHeader",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "Custom 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": "GameSpecificConfigurationGlobal",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "(Global)",
+        "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": {

+ 1 - 0
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]

+ 71 - 0
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);
@@ -156,11 +158,48 @@ 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)
+        {
+            if (string.IsNullOrEmpty(gameId))
+            {
+                return "";
+            }
+
+            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 +256,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 +276,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)

+ 76 - 0
src/Ryujinx/Rebooter.cs

@@ -0,0 +1,76 @@
+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<string> args)
+        {
+            _ = Reboot(gamePath, args);
+
+        }
+
+        private static async Task Reboot(string gamePath, List<string> 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<string> arguments = CommandLineState.Arguments.ToList();
+                string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
+
+                var dialogTask = taskDialog.ShowAsync(true);
+                await Task.Delay(500);
+
+                // Find the process name.
+                string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
+
+                // 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);
+            }
+        }
+    }
+}

+ 12 - 0
src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml

@@ -19,6 +19,18 @@
         Header="{ext:Locale GameListContextMenuCreateShortcut}"
         Icon="{ext:Icon fa-solid fa-bookmark}"
         ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
+    <MenuItem
+        Click="EditGameConfiguration_Click"
+        IsVisible="{Binding SelectedApplication.HasIndependentConfiguration}"
+        Header="{ext:Locale GameListContextMenuEditCustomConfiguration}"
+        Icon="{ext:Icon fa-solid fa-gear}"
+        ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
+    <MenuItem
+       Click="EditGameConfiguration_Click"
+       IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
+       Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
+       Icon="{ext:Icon fa-solid fa-gear}"
+       ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
     <MenuItem
         IsVisible="{Binding HasCompatibilityEntry}"
         Click="OpenApplicationCompatibility_Click"

+ 15 - 2
src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml.cs

@@ -386,13 +386,26 @@ namespace Ryujinx.Ava.UI.Controls
                     viewModel.SelectedApplication.Icon
                 );
         }
-        
+
+        public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
+        {
+            if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
+            {
+                await new GameSpecificSettingsWindow(viewModel).ShowDialog((Window)viewModel.TopLevel);
+
+                //just checking for file presence
+                viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
+
+                viewModel.RefreshView();
+            }
+        }
+
         public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
         {
             if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
                 await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
         }
-        
+               
         public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
         {
             if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

+ 32 - 7
src/Ryujinx/UI/Controls/ApplicationGridView.axaml

@@ -7,6 +7,7 @@
     xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+    xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
     d:DesignHeight="450"
     d:DesignWidth="800"
     Focusable="True"
@@ -73,12 +74,18 @@
                                     HorizontalAlignment="Stretch"
                                     VerticalAlignment="Stretch"
                                     IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
-                                    <TextBlock
-                                        HorizontalAlignment="Center"
-                                        VerticalAlignment="Center"
-                                        Text="{Binding Name}"
-                                        TextAlignment="Center"
-                                        TextWrapping="Wrap" />
+                                    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
+                                        <TextBlock
+                                            Text="{Binding Name}"
+                                            TextAlignment="Center"
+                                            TextWrapping="Wrap" />
+                                        <TextBlock
+                                            IsVisible="{Binding HasIndependentConfiguration}"
+                                            Text="{ext:Locale GameSpecificConfigurationHeader}"
+                                            TextAlignment="Center"
+                                            TextWrapping="Wrap"
+                                            Foreground="{DynamicResource Warning}" />
+                                    </StackPanel>
                                 </Panel>
                             </Grid>
                         </Border>
@@ -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" />
+                        <Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
+                            <Border
+                                Margin="15,35,5,15"
+                                HorizontalAlignment="Left"
+                                VerticalAlignment="Bottom"
+                                Width="90"
+                                Height="20"
+                                CornerRadius="4"
+                                IsVisible="{Binding HasIndependentConfiguration}"
+                                Background="{DynamicResource ThemeContentBackgroundColor}">
+                                <TextBlock
+                                    HorizontalAlignment="Center"
+                                    VerticalAlignment="Center"
+                                    Text="{ext:Locale GameSpecificConfigurationHeader}"
+                                    TextAlignment="Center"
+                                    TextWrapping="Wrap" />
+                            </Border>
+                        </Grid>
                     </Grid>
                 </DataTemplate>
             </ListBox.ItemTemplate>

+ 8 - 0
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"/>
+                                    <TextBlock
+                                        HorizontalAlignment="Stretch"
+                                        IsVisible="{Binding HasIndependentConfiguration}"
+                                        Text="{ext:Locale GameSpecificConfigurationHeader}"
+                                        TextAlignment="Start"
+                                        TextWrapping="Wrap"
+                                        Foreground="{DynamicResource Warning}" />
                                 </StackPanel>
                                 <StackPanel
                                     Grid.Column="4"

+ 50 - 2
src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs

@@ -356,6 +356,11 @@ namespace Ryujinx.Ava.UI.ViewModels
                     _ => null,
                 };
             }
+            set
+            {
+                ListSelectedApplication = value;
+                GridSelectedApplication = value;
+            }        
         }
 
         public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
@@ -1085,7 +1090,7 @@ namespace Ryujinx.Ava.UI.ViewModels
             _rendererWaitEvent.WaitOne();
 
             AppHost?.Start();
-
+            
             AppHost?.DisposeContext();
         }
 
@@ -1551,8 +1556,50 @@ namespace Ryujinx.Ava.UI.ViewModels
             }
         }
 
+        public bool InitializeUserConfig(ApplicationData application)
+        {
+            // Code where conditions will be met before loading the user configuration (Global Config)      
+            BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
+            string BackendThreadingInit = Program.BackendThreadingArg;
+
+            if (BackendThreadingInit is null)
+            {
+                BackendThreadingInit = ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
+            }
+            
+            // If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting. 
+            string idGame = application.IdBaseString;
+            if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
+            {
+                // Loads the user configuration, having previously changed the global configuration to the user configuration
+                ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
+            }
+
+            // Code where conditions will be executed after loading user configuration
+            if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
+            {
+
+                List<string> Arguments = new List<string>
+                {
+                    "--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<ApplicationControlProperty>? customNacpData = null)
         {
+
+            if (InitializeUserConfig(application))
+            {
+                return;
+            }
+
             if (AppHost != null)
             {
                 await ContentDialogHelper.CreateInfoDialog(
@@ -1568,7 +1615,7 @@ namespace Ryujinx.Ava.UI.ViewModels
 #if RELEASE
             await PerformanceCheck();
 #endif
-
+         
             Logger.RestartTime();
 
             SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
@@ -1613,6 +1660,7 @@ namespace Ryujinx.Ava.UI.ViewModels
 
             Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
             gameThread.Start();
+            
         }
 
         public void SwitchToRenderer(bool startFullscreen) =>

+ 94 - 9
src/Ryujinx/UI/ViewModels/SettingsViewModel.cs

@@ -1,5 +1,6 @@
 using Avalonia.Collections;
 using Avalonia.Controls;
+using Avalonia.Media.Imaging;
 using Avalonia.Threading;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
@@ -27,6 +28,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;
@@ -68,6 +70,19 @@ 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 bool IsGameTitleNotNull => !string.IsNullOrEmpty(GameTitle);
+        public double PanelOpacity => IsGameTitleNotNull ? 0.5 : 1;
+
         public int ResolutionScale
         {
             get => _resolutionScale;
@@ -335,7 +350,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;
@@ -348,7 +363,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 = [];
@@ -363,7 +422,9 @@ 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);
             }
@@ -592,8 +653,8 @@ namespace Ryujinx.Ava.UI.ViewModels
             config.HideCursor.Value = (HideCursorMode)HideCursor;
             config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
             config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
-            config.UI.GameDirs.Value = [..GameDirectories];
-            config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
+            config.UI.GameDirs.Value = [.. GameDirectories];
+            config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
 
             config.UI.BaseStyle.Value = BaseStyleIndex switch
             {
@@ -614,10 +675,10 @@ namespace Ryujinx.Ava.UI.ViewModels
 
             // System
             config.System.Region.Value = (Region)Region;
-            
+
             if (config.System.Language.Value != (Language)Language)
                 GameListNeedsRefresh = true;
-            
+
             config.System.Language.Value = (Language)Language;
             if (_validTzRegions.Contains(TimeZone))
             {
@@ -696,7 +757,7 @@ namespace Ryujinx.Ava.UI.ViewModels
             config.Multiplayer.DisableP2p.Value = DisableP2P;
             config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
             config.Multiplayer.LdnServer.Value = LdnServer;
-            
+
             // Dirty Hacks
             config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
 
@@ -712,7 +773,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()
@@ -720,6 +785,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();

+ 20 - 3
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();
 

+ 11 - 1
src/Ryujinx/UI/Views/Settings/SettingsSystemView.axaml

@@ -156,6 +156,8 @@
                             ValueMemberBinding="{Binding Mode=OneWay, Converter={x:Static helpers:TimeZoneConverter.Instance}}" />
                     </StackPanel>
                     <StackPanel
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
                         Margin="0,0,0,10"
                         Orientation="Horizontal">
                         <TextBlock
@@ -169,8 +171,11 @@
                             SelectedDate="{Binding CurrentDate}"
                             ToolTip.Tip="{ext:Locale TimeTooltip}"
                             Width="350" />
+                        <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
                     </StackPanel>
                     <StackPanel
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
                         Margin="250,0,0,10"
                         Orientation="Horizontal">
                         <TimePicker
@@ -180,8 +185,12 @@
                             SelectedTime="{Binding CurrentTime}"
                             Width="350"
                             ToolTip.Tip="{ext:Locale TimeTooltip}" />
+                        <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
                     </StackPanel>
-                    <StackPanel Orientation="Horizontal">
+                    <StackPanel
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
+                        Orientation="Horizontal">
                         <TextBlock
                             VerticalAlignment="Center"
                             Text="{ext:Locale SettingsTabSystemSystemTimeMatch}"
@@ -191,6 +200,7 @@
                             VerticalAlignment="Center"
                             IsChecked="{Binding MatchSystemTime}"
                             ToolTip.Tip="{ext:Locale MatchTimeTooltip}"/>
+                        <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
                     </StackPanel>
                     <Separator />
                     <StackPanel Margin="0,10,0,10"

+ 59 - 15
src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml

@@ -27,20 +27,42 @@
                 <TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGeneral}" />
                 <StackPanel Margin="10,0,0,0" Orientation="Vertical">
                     <CheckBox IsChecked="{Binding EnableDiscordIntegration}">
-                        <TextBlock VerticalAlignment="Center"
-                                   ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
-                                   Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock VerticalAlignment="Center"
+                                       ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
+                                       Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
+                        </StackPanel>
                     </CheckBox>
-                    <CheckBox IsChecked="{Binding ShowConfirmExit}">
-                        <TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
+                    <CheckBox 
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
+                        IsChecked="{Binding ShowConfirmExit}">
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
+                            <TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
+                        </StackPanel>
                     </CheckBox>
-                    <CheckBox IsChecked="{Binding RememberWindowState}">
-                        <TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
+                    <CheckBox 
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
+                        IsChecked="{Binding RememberWindowState}">
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
+                            <TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
+                            </StackPanel>
                     </CheckBox>
-                    <CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
-                        <TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
+                    <CheckBox
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
+                        IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
+                            <TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
+                        </StackPanel>
                     </CheckBox>
-                    <StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
+                    <StackPanel 
+                        Margin="0, 15, 0, 0" 
+                        Orientation="Horizontal">
                         <TextBlock VerticalAlignment="Center"
                                    Text="{ext:Locale SettingsTabGeneralFocusLossType}"
                                    Width="150" />
@@ -64,7 +86,11 @@
                             </ComboBoxItem>
                         </ComboBox>
                     </StackPanel>
-                    <StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
+                    <StackPanel 
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
+                        Margin="0, 15, 0, 0" 
+                        Orientation="Horizontal">
                         <TextBlock VerticalAlignment="Center"
                                    Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
                                    Width="150" />
@@ -81,8 +107,11 @@
                                 <TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
                             </ComboBoxItem>
                         </ComboBox>
+                        <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
                     </StackPanel>
-                    <StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
+                    <StackPanel 
+                        Margin="0, 15, 0, 0" 
+                        Orientation="Horizontal">
                         <TextBlock VerticalAlignment="Center"
                                    Text="{ext:Locale SettingsTabGeneralHideCursor}"
                                    Width="150" />
@@ -100,7 +129,11 @@
                             </ComboBoxItem>
                         </ComboBox>
                     </StackPanel>
-                    <StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
+                    <StackPanel 
+                        IsEnabled="{Binding !IsGameTitleNotNull}"
+                        Opacity="{Binding PanelOpacity}"
+                        Margin="0, 15, 0, 10" 
+                        Orientation="Horizontal">
                         <TextBlock
                             VerticalAlignment="Center"
                             Text="{ext:Locale SettingsTabGeneralTheme}"
@@ -118,11 +151,17 @@
                                 <TextBlock Text="{ext:Locale SettingsTabGeneralThemeDark}" />
                             </ComboBoxItem>
                         </ComboBox>
+                        <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
                     </StackPanel>
                 </StackPanel>
                 <Separator Height="1" />
-                <TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
+                <StackPanel Orientation="Horizontal">
+                    <TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGameDirectories}" />
+                    <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
+                </StackPanel>
                 <StackPanel
+                    IsEnabled="{Binding !IsGameTitleNotNull}"
+                    Opacity="{Binding PanelOpacity}"
                     Margin="10,0,0,0"
                     HorizontalAlignment="Stretch"
                     Orientation="Vertical"
@@ -172,10 +211,15 @@
                 </StackPanel>
                 <Separator Height="1" />
                <StackPanel Orientation="Vertical" Spacing="5">
-                    <TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
+                    <StackPanel Orientation="Horizontal">
+                        <TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralAutoloadDirectories}" />
+                        <TextBlock  Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}"/>
+                    </StackPanel>
                     <TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{ext:Locale SettingsTabGeneralAutoloadNote}" />
                 </StackPanel>
                 <StackPanel
+                    IsEnabled="{Binding !IsGameTitleNotNull}"
+                    Opacity="{Binding PanelOpacity}"
                     Margin="10,0,0,0"
                     HorizontalAlignment="Stretch"
                     Orientation="Vertical"

+ 155 - 0
src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml

@@ -0,0 +1,155 @@
+<window:StyleableAppWindow
+    x:Class="Ryujinx.Ava.UI.Windows.GameSpecificSettingsWindow"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
+    xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
+    xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
+    xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
+    xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
+    Width="1100"
+    Height="768"
+    MinWidth="800"
+    MinHeight="480"
+    WindowStartupLocation="CenterOwner"
+    x:DataType="viewModels:SettingsViewModel"
+    mc:Ignorable="d"
+    Focusable="True">
+    <Design.DataContext>
+        <viewModels:SettingsViewModel />
+    </Design.DataContext>
+    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
+        <ContentPresenter
+            x:Name="ContentPresenter"
+            Grid.Row="1"
+            IsVisible="False"
+            KeyboardNavigation.IsTabStop="False"/>
+        <Grid Name="Pages" IsVisible="False" Grid.Row="2">
+            <settings:SettingsUiView Name="UiPage" />
+            <settings:SettingsInputView Name="InputPage" />
+            <settings:SettingsSystemView Name="SystemPage" />
+            <settings:SettingsCPUView Name="CpuPage" />
+            <settings:SettingsGraphicsView Name="GraphicsPage" />
+            <settings:SettingsAudioView Name="AudioPage" />
+            <settings:SettingsNetworkView Name="NetworkPage" />
+            <settings:SettingsLoggingView Name="LoggingPage" />
+            <settings:SettingsHacksView Name="HacksPage" />
+        </Grid>
+        <ui:NavigationView
+            Grid.Row="1"
+            IsSettingsVisible="False"
+            Name="NavPanel"
+            IsBackEnabled="False"
+            PaneDisplayMode="Left"
+            Margin="2,10,10,0"
+            VerticalAlignment="Stretch"
+            HorizontalAlignment="Stretch"
+            OpenPaneLength="200"
+            IsPaneToggleButtonVisible="False">
+
+            <!-- For image -->
+            <ui:NavigationView.PaneHeader>
+                <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="5"/>
+                    </Grid.RowDefinitions>
+                    <TextBlock Text="{Binding GameId}"
+                        HorizontalAlignment="Center"
+                        VerticalAlignment="Center"
+                        Margin="0,0,0,10"
+                        TextAlignment="Center" Grid.Row="0" />
+                    <Image Source="{Binding GameIcon}" 
+                        Width="160" 
+                        Height="160" 
+                        Grid.Row="1" 
+                        Margin="0,0,0,10"
+                        HorizontalAlignment="Center" />
+                    <TextBlock Text="{Binding GameTitle}"
+                        HorizontalAlignment="Center"
+                        VerticalAlignment="Center"
+                        Margin="0,0,0,10"
+                        TextAlignment="Center" Grid.Row="2" />
+                    <Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
+                </Grid>
+            </ui:NavigationView.PaneHeader>
+                      
+            <ui:NavigationView.MenuItems>
+                <ui:NavigationViewItem
+                    IsSelected="True"
+                    Content="{ext:Locale SettingsTabGeneral}"
+                    Tag="UiPage"
+                    IconSource="New" />
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabInput}"
+                    Tag="InputPage"
+                    IconSource="Games" />
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabSystem}"
+                    Tag="SystemPage"
+                    IconSource="Settings" />
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabCpu}"
+                    Tag="CpuPage">
+                    <ui:NavigationViewItem.IconSource>
+                        <ui:FontIconSource
+                            FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
+                            Glyph="{helpers:GlyphValueConverter Chip}" />
+                    </ui:NavigationViewItem.IconSource>
+                </ui:NavigationViewItem>
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabGraphics}"
+                    Tag="GraphicsPage"
+                    IconSource="Image" />
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabAudio}"
+                    IconSource="Audio"
+                    Tag="AudioPage" />
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabNetwork}"
+                    Tag="NetworkPage"
+                    IconSource="Globe" />
+                <ui:NavigationViewItem
+                    Content="{ext:Locale SettingsTabLogging}"
+                    Tag="LoggingPage"
+                    IconSource="Document" />
+                <ui:NavigationViewItem
+                    IsVisible="{Binding ShowDirtyHacks}"
+                    Content="Dirty Hacks"
+                    Tag="HacksPage"
+                    IconSource="Code" />
+            </ui:NavigationView.MenuItems>
+        </ui:NavigationView>
+       
+        <ReversibleStackPanel
+            Grid.Row="2"
+            Margin="10"
+            Spacing="10"
+            Orientation="Horizontal"
+            HorizontalAlignment="Right"
+            ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
+            <Button
+                Content="{ext:Locale SettingsButtonSave}"
+                Command="{Binding SaveUserConfig}" />
+            <Button
+                HotKey="Escape"
+                Content="{ext:Locale SettingsButtonClose}"
+                Command="{Binding CancelButton}" />
+            <Button
+                IsVisible="{Binding IsGameRunning}"
+                Content="{ext:Locale SettingsButtonApply}"
+                Command="{Binding ApplyButton}" />
+            <Button
+                IsVisible="{Binding !IsGameRunning}"
+                Content="{ext:Locale UserProfilesDelete}"
+                Command="{Binding DeleteConfigGame}"
+                Classes="red"/>
+        </ReversibleStackPanel>
+    </Grid>
+</window:StyleableAppWindow>

+ 121 - 0
src/Ryujinx/UI/Windows/GameSpecificSettingsWindow.axaml.cs

@@ -0,0 +1,121 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
+using Avalonia.Media.Imaging;
+using FluentAvalonia.Core;
+using FluentAvalonia.UI.Controls;
+using Projektanker.Icons.Avalonia;
+using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Models;
+using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.UI.ViewModels.Input;
+using Ryujinx.Ava.Utilities;
+using Ryujinx.Ava.Utilities.Configuration;
+using Ryujinx.Common.Configuration;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS.SystemState;
+using Ryujinx.Input;
+using System;
+using System.IO;
+using System.Linq;
+using Key = Avalonia.Input.Key;
+
+
+namespace Ryujinx.Ava.UI.Windows
+{
+    public partial class GameSpecificSettingsWindow : StyleableAppWindow
+    {
+        internal readonly SettingsViewModel ViewModel;
+
+        public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true)
+        {
+            Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
+
+            DataContext = ViewModel = new SettingsViewModel(
+                viewModel.VirtualFileSystem, 
+                viewModel.ContentManager,
+                viewModel.IsGameRunning,
+                viewModel.SelectedApplication.Path,
+                viewModel.SelectedApplication.Name,
+                viewModel.SelectedApplication.IdString,
+                viewModel.SelectedApplication.Icon,
+                findUserConfigDir);
+
+            ViewModel.CloseWindow += Close;
+            ViewModel.SaveSettingsEvent += SaveSettings;
+
+            InitializeComponent();
+            Load();
+           
+#if DEBUG
+            this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
+#endif
+           
+        }
+
+        public void SaveSettings()
+        {
+            InputPage.InputView?.SaveCurrentProfile();
+        }
+
+
+        private void Load()
+        {
+            Pages.Children.Clear();
+            NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
+            NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
+            
+        }
+
+        private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
+        {
+            
+            if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
+            {
+                switch (navItem.Tag.ToString())
+                {
+                    case nameof(UiPage):
+                        UiPage.ViewModel = ViewModel;
+                        NavPanel.Content = UiPage;
+                        break;
+                    case nameof(InputPage):
+                        NavPanel.Content = InputPage;
+                        break;
+                    case nameof(SystemPage):
+                        SystemPage.ViewModel = ViewModel;
+                        NavPanel.Content = SystemPage;
+                        break;
+                    case nameof(CpuPage):
+                        NavPanel.Content = CpuPage;
+                        break;
+                    case nameof(GraphicsPage):
+                        NavPanel.Content = GraphicsPage;
+                        break;
+                    case nameof(AudioPage):
+                        NavPanel.Content = AudioPage;
+                        break;
+                    case nameof(NetworkPage):
+                        NetworkPage.ViewModel = ViewModel;
+                        NavPanel.Content = NetworkPage;
+                        break;
+                    case nameof(LoggingPage):
+                        NavPanel.Content = LoggingPage;
+                        break;
+                    case nameof(HacksPage):
+                        HacksPage.DataContext = ViewModel;
+                        NavPanel.Content = HacksPage;
+                        break;
+                    default:
+                        throw new NotImplementedException();
+                }
+            }        
+        }
+
+        protected override void OnClosing(WindowClosingEventArgs e)
+        {
+            InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
+            base.OnClosing(e);
+        }
+    }
+}

+ 3 - 1
src/Ryujinx/UI/Windows/MainWindow.axaml.cs

@@ -234,7 +234,7 @@ namespace Ryujinx.Ava.UI.Windows
             _deferLoad = true;
             _launchPath = launchPathArg;
             _launchApplicationId = launchApplicationId;
-            _startFullscreen = startFullscreenArg;
+            _startFullscreen = startFullscreenArg;          
         }
 
         public void SwitchToGameControl(bool startFullscreen = false)
@@ -385,6 +385,7 @@ namespace Ryujinx.Ava.UI.Windows
 
                             if (applicationData != null)
                             {
+                                ViewModel.SelectedApplication = applicationData;
                                 await ViewModel.LoadApplication(applicationData, _startFullscreen);
                             }
                             else
@@ -396,6 +397,7 @@ namespace Ryujinx.Ava.UI.Windows
                         else
                         {
                             applicationData = applications[0];
+                            ViewModel.SelectedApplication = applicationData;
                             await ViewModel.LoadApplication(applicationData, _startFullscreen);
                         }
                     }

+ 1 - 0
src/Ryujinx/Utilities/AppLibrary/ApplicationData.cs

@@ -23,6 +23,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
     public class ApplicationData
     {
         public bool Favorite { get; set; }
+        public bool HasIndependentConfiguration { get; set; }
         public byte[] Icon { get; set; }
         public string Name { get; set; } = "Unknown";
 

+ 3 - 2
src/Ryujinx/Utilities/AppLibrary/ApplicationLibrary.cs

@@ -504,7 +504,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
                 if (data.Id != 0)
                 {
                     ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
-                    {
+                    {                        
                         appMetadata.Title = data.Name;
 
                         // Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
@@ -528,10 +528,11 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
 
                         }
                     });
-
+            
                     data.Favorite = appMetadata.Favorite;
                     data.TimePlayed = appMetadata.TimePlayed;
                     data.LastPlayed = appMetadata.LastPlayed;
+                    data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
                 }
 
                 data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

+ 63 - 1
src/Ryujinx/Utilities/CommandLineState.cs

@@ -7,11 +7,16 @@ namespace Ryujinx.Ava.Utilities
     public static class CommandLineState
     {
         public static string[] Arguments { get; private set; }
-
+        public static int CountArguments { get; private set; }
         public static bool? OverrideDockedMode { get; private set; }
         public static bool? OverrideHardwareAcceleration { get; private set; }
         public static string OverrideGraphicsBackend { get; private set; }
         public static string OverrideBackendThreading { get; private set; }
+        public static string OverrideBackendThreadingAfterReboot { get; private set; }
+        public static string OverridePPTC { get; private set; }
+        public static string OverrideMemoryManagerMode { get; private set; } 
+        public static string OverrideSystemRegion { get; private set; } 
+        public static string OverrideSystemLanguage { get; private set; } 
         public static string OverrideHideCursor { get; private set; }
         public static string BaseDirPathArg { get; private set; }
         public static FilePath FirmwareToInstallPathArg { get; set; }
@@ -21,6 +26,7 @@ namespace Ryujinx.Ava.Utilities
         public static bool StartFullscreenArg { get; private set; }
         public static bool HideAvailableUpdates { get; private set; }
 
+
         public static void ParseArguments(string[] args)
         {
             List<string> arguments = [];
@@ -30,6 +36,11 @@ namespace Ryujinx.Ava.Utilities
             {
                 string arg = args[i];
 
+                if (arg.Contains("-") || arg.Contains("--"))
+                {
+                    CountArguments++; 
+                }
+
                 switch (arg)
                 {
                     case "-r":
@@ -100,6 +111,57 @@ namespace Ryujinx.Ava.Utilities
 
                         OverrideBackendThreading = args[++i];
                         break;
+                    case "--bt":
+                        if (i + 1 >= args.Length)
+                        {
+                            Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+                            continue;
+                        }
+
+                        OverrideBackendThreadingAfterReboot = args[++i];
+                        break;
+                    case "--pptc":
+                        if (i + 1 >= args.Length)
+                        {
+                            Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+                            continue;
+                        }
+
+                        OverridePPTC = args[++i];
+                        break;
+                    case "-m":
+                    case "--memory-manager-mode":
+                        if (i + 1 >= args.Length)
+                        {
+                            Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+                            continue;
+                        }
+
+                        OverrideMemoryManagerMode = args[++i];
+                        break;
+                    case "--system-region":
+                        if (i + 1 >= args.Length)
+                        {
+                            Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+                            continue;
+                        }
+
+                        OverrideSystemRegion = args[++i];
+                        break;
+                    case "--system-language":
+                        if (i + 1 >= args.Length)
+                        {
+                            Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
+
+                            continue;
+                        }
+
+                        OverrideSystemLanguage = args[++i];
+                        break;
                     case "-i":
                     case "--application-id":
                         LaunchApplicationId = args[++i];

+ 52 - 49
src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs

@@ -18,9 +18,10 @@ namespace Ryujinx.Ava.Utilities.Configuration
 {
     public partial class ConfigurationState
     {
-        public void Load(ConfigurationFileFormat cff, string configurationFilePath)
+        public void Load(ConfigurationFileFormat cff, string configurationFilePath, string titleId = "")
         {
             bool configurationFileUpdated = false;
+            bool shouldLoadFromFile = string.IsNullOrEmpty(titleId);
 
             if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
             {
@@ -43,16 +44,17 @@ namespace Ryujinx.Ava.Utilities.Configuration
                 configurationFileUpdated = true;
             }
             
+          
             EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
-            CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
-            UpdateCheckerType.Value = cff.UpdateCheckerType;
+            CheckUpdatesOnStart.Value = shouldLoadFromFile ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only
+            UpdateCheckerType.Value = shouldLoadFromFile ? cff.UpdateCheckerType : UpdateCheckerType.Value; // Get from global config only
             FocusLostActionType.Value = cff.FocusLostActionType;
-            ShowConfirmExit.Value = cff.ShowConfirmExit;
-            RememberWindowState.Value = cff.RememberWindowState;
-            ShowTitleBar.Value = cff.ShowTitleBar;
-            EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
+            ShowConfirmExit.Value = shouldLoadFromFile ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only
+            RememberWindowState.Value = shouldLoadFromFile ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only
+            ShowTitleBar.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only
+            EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
             HideCursor.Value = cff.HideCursor;
-            
+          
             Logger.EnableFileLog.Value = cff.EnableFileLog;
             Logger.EnableDebug.Value = cff.LoggingEnableDebug;
             Logger.EnableStub.Value = cff.LoggingEnableStub;
@@ -87,8 +89,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
             System.Language.Value = cff.SystemLanguage;
             System.Region.Value = cff.SystemRegion;
             System.TimeZone.Value = cff.SystemTimeZone;
-            System.SystemTimeOffset.Value = cff.SystemTimeOffset;
-            System.MatchSystemTime.Value = cff.MatchSystemTime;
+            System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
+            System.MatchSystemTime.Value = shouldLoadFromFile ? cff.MatchSystemTime : System.MatchSystemTime.Value; // Get from global config only
             System.EnableDockedMode.Value = cff.DockedMode;
             System.EnablePtc.Value = cff.EnablePtc;
             System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
@@ -102,47 +104,48 @@ namespace Ryujinx.Ava.Utilities.Configuration
             System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
             System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
             System.UseHypervisor.Value = cff.UseHypervisor;
-            
-            UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
-            UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn;
-            UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn;
-            UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn;
-            UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn;
-            UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn;
-            UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn;
-            UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn;
-            UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn;
-            UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn;
-            UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId;
-            UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending;
-            UI.GameDirs.Value = cff.GameDirs;
-            UI.AutoloadDirs.Value = cff.AutoloadDirs ?? [];
-            UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP;
-            UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0;
-            UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI;
-            UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA;
-            UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO;
-            UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO;
-            UI.LanguageCode.Value = cff.LanguageCode;
-            UI.BaseStyle.Value = cff.BaseStyle;
-            UI.GameListViewMode.Value = cff.GameListViewMode;
-            UI.ShowNames.Value = cff.ShowNames;
-            UI.IsAscendingOrder.Value = cff.IsAscendingOrder;
-            UI.GridSize.Value = cff.GridSize;
-            UI.ApplicationSort.Value = cff.ApplicationSort;
-            UI.StartFullscreen.Value = cff.StartFullscreen;
-            UI.StartNoUI.Value = cff.StartNoUI;
-            UI.ShowConsole.Value = cff.ShowConsole;
-            UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
-            UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
-            UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX;
-            UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY;
-            UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized;
-            
+
+            UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
+            UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
+            UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
+            UI.GuiColumns.DevColumn.Value = shouldLoadFromFile ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value;
+            UI.GuiColumns.VersionColumn.Value = shouldLoadFromFile ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value;
+            UI.GuiColumns.TimePlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value;
+            UI.GuiColumns.LastPlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value;
+            UI.GuiColumns.FileExtColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value;
+            UI.GuiColumns.FileSizeColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value;
+            UI.GuiColumns.PathColumn.Value = shouldLoadFromFile ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value;
+            UI.ColumnSort.SortColumnId.Value = shouldLoadFromFile ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value;
+            UI.ColumnSort.SortAscending.Value = shouldLoadFromFile ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value;
+            UI.GameDirs.Value = shouldLoadFromFile ? cff.GameDirs : UI.GameDirs.Value;
+            UI.AutoloadDirs.Value = shouldLoadFromFile ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value;
+            UI.ShownFileTypes.NSP.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value;
+            UI.ShownFileTypes.PFS0.Value = shouldLoadFromFile ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value;
+            UI.ShownFileTypes.XCI.Value = shouldLoadFromFile ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value;
+            UI.ShownFileTypes.NCA.Value = shouldLoadFromFile ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value;
+            UI.ShownFileTypes.NRO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value;
+            UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
+            UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value;
+            UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value;
+            UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value;
+            UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value;
+            UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;
+            UI.GridSize.Value = shouldLoadFromFile ? cff.GridSize : UI.GridSize.Value;
+            UI.ApplicationSort.Value = shouldLoadFromFile ? cff.ApplicationSort : UI.ApplicationSort.Value;
+            UI.StartFullscreen.Value = shouldLoadFromFile ? cff.StartFullscreen : UI.StartFullscreen.Value;
+            UI.StartNoUI.Value = shouldLoadFromFile ? cff.StartNoUI : UI.StartNoUI.Value;
+            UI.ShowConsole.Value = shouldLoadFromFile ? cff.ShowConsole : UI.ShowConsole.Value;
+            UI.WindowStartup.WindowSizeWidth.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value;
+            UI.WindowStartup.WindowSizeHeight.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value;
+            UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
+            UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
+            UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
+
+
             Hid.EnableKeyboard.Value = cff.EnableKeyboard;
             Hid.EnableMouse.Value = cff.EnableMouse;
-            Hid.DisableInputWhenOutOfFocus.Value = cff.DisableInputWhenOutOfFocus;
-            Hid.Hotkeys.Value = cff.Hotkeys;
+            Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus: Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
+            Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
             Hid.InputConfig.Value = cff.InputConfig ?? [];
             Hid.RainbowSpeed.Value = cff.RainbowSpeed;
 

+ 16 - 11
src/Ryujinx/Utilities/ShortcutHelper.cs

@@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Utilities
     public static class ShortcutHelper
     {
         [SupportedOSPlatform("windows")]
-        private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
+        private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath, string args = "")
         {
             string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
             iconPath += ".ico";
@@ -22,13 +22,13 @@ namespace Ryujinx.Ava.Utilities
             image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
             SaveBitmapAsIcon(image, iconPath);
 
-            Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
+            Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId, args), iconPath, 0);
             shortcut.StringData.NameString = cleanedAppName;
             shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
         }
 
         [SupportedOSPlatform("linux")]
-        private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
+        private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName, string args = "")
         {
             string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
             string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
@@ -40,11 +40,11 @@ namespace Ryujinx.Ava.Utilities
             data.SaveTo(file);
 
             using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
-            outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
+            outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId, args)}");
         }
 
         [SupportedOSPlatform("macos")]
-        private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
+        private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName, string args = "")
         {
             string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
             string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
@@ -63,7 +63,7 @@ namespace Ryujinx.Ava.Utilities
             string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
             using StreamWriter scriptFile = new(scriptPath);
 
-            scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
+            scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId, args));
 
             // Set execute permission
             FileInfo fileInfo = new(scriptPath);
@@ -87,7 +87,7 @@ namespace Ryujinx.Ava.Utilities
             outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
         }
 
-        public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
+        public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData, string args = "")
         {
             string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
             string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
@@ -96,7 +96,7 @@ namespace Ryujinx.Ava.Utilities
             {
                 string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
 
-                CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
+                CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath, args);
 
                 return;
             }
@@ -106,14 +106,14 @@ namespace Ryujinx.Ava.Utilities
                 string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
 
                 Directory.CreateDirectory(iconPath);
-                CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
+                CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName, args);
 
                 return;
             }
 
             if (OperatingSystem.IsMacOS())
             {
-                CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
+                CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName, args);
 
                 return;
             }
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.Utilities
             throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
         }
 
-        private static string GetArgsString(string appFilePath, string applicationId)
+        private static string GetArgsString(string appFilePath, string applicationId, string config = "")
         {
             // args are first defined as a list, for easier adjustments in the future
             List<string> argsList = [];
@@ -132,6 +132,11 @@ namespace Ryujinx.Ava.Utilities
                 argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
             }
 
+            if (!string.IsNullOrEmpty(config))
+            {
+                argsList.Add(config);
+            }
+
             if (appFilePath.ToLower().EndsWith(".xci"))
             {
                 argsList.Add("--application-id");